In this article written by Sylvain Ratabouil, author of Android NDK Beginner`s Guide - Second Edition, we have breached Android NDK's surface using JNI. But there is much more to find inside! The NDK includes its own set of specific features, one of them being Native Activities. Native activities allow creating applications based only on native code, without a single line of Java. No more JNI! No more references! No more Java!
(For more resources related to this topic, see here.)
In addition to native activities, the NDK brings some APIs for native access to Android resources, such as display windows, assets, device configuration. These APIs help in getting rid of the tortuous JNI bridge often necessary to embed native code. Although there is a lot still missing, and not likely to be available (Java remains the main platform language for GUIs and most frameworks), multimedia applications are a perfect target to apply them.
Here we initiate a native C++ project developed progressively throughout this article: DroidBlaster. Based on a top-down viewpoint, this sample scrolling shooter will feature 2D graphics, and, later on, 3D graphics, sound, input, and sensor management. We will be creating its base structure and main game components.
Let's now enter the heart of the Android NDK by:
The NativeActivity class provides a facility to minimize the work necessary to create a native application. It lets the developer get rid of all the boilerplate code to initialize and communicate with native code and concentrate on core functionalities. This glue Activity is the simplest way to write applications, such as games without a line of Java code.
The resulting project is provided with this book under the name DroidBlaster_Part1.
We are now going to see how to create a minimal native activity that runs an event loop.
Declare a NativeActivity that refers to the native module named droidblaster (that is, the native library we will compile) using the meta-data property android.app.lib_name:
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.packtpub.droidblaster2d" android_versionCode="1"
android_versionName="1.0">
<uses-sdk
android_minSdkVersion="14"
android_targetSdkVersion="19"/>
<application android_icon="@drawable/ic_launcher"
android_label="@string/app_name"
android_allowBackup="false"
android:theme ="@android:style/Theme.NoTitleBar.Fullscreen">
<activity android_name="android.app.NativeActivity"
android_label="@string/app_name"
android_screenOrientation="portrait">
<meta-data android_name="android.app.lib_name"
android:value="droidblaster"/>
<intent-filter>
<action android:name ="android.intent.action.MAIN"/>
<category
android_name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
#ifndef _PACKT_TYPES_HPP_
#define _PACKT_TYPES_HPP_
#include <cstdint>
#endif
#ifndef _PACKT_LOG_HPP_
#define _PACKT_LOG_HPP_
class Log {
public:
static void error(const char* pMessage, ...);
static void warn(const char* pMessage, ...);
static void info(const char* pMessage, ...);
static void debug(const char* pMessage, ...);
};
#ifndef NDEBUG
#define packt_Log_debug(...) Log::debug(__VA_ARGS__)
#else
#define packt_Log_debug(...)
#endif
#endif
#include "Log.hpp"
#include <stdarg.h>
#include <android/log.h>
void Log::info(const char* pMessage, ...) {
va_list varArgs;
va_start(varArgs, pMessage);
__android_log_vprint(ANDROID_LOG_INFO, "PACKT", pMessage,
varArgs);
__android_log_print(ANDROID_LOG_INFO, "PACKT", "n");
va_end(varArgs);
}
...
Write other log methods, error(), warn(), and debug(), which are almost identical, except the level macro, which are respectively ANDROID_LOG_ERROR, ANDROID_LOG_WARN, and ANDROID_LOG_DEBUG instead.
Include the android_native_app_glue.h header, which defines the android_app structure. It represents what could be called an applicative context, where all the information is related to the native activity; its state, its window, its event queue, and so on:
#ifndef _PACKT_EVENTLOOP_HPP_
#define _PACKT_EVENTLOOP_HPP_
#include <android_native_app_glue.h>
class EventLoop {
public:
EventLoop(android_app* pApplication);
void run();
private:
android_app* mApplication;
};
#endif
During the whole activity lifetime, the run() method loops continuously over events until it is requested to terminate. When an activity is about to be destroyed, the destroyRequested value in the android_app structure is changed internally to indicate to the client code that it must exit.
Also, call app_dummy() to ensure the glue code that ties native code to NativeActivity is not stripped by the linker.
#include "EventLoop.hpp"
#include "Log.hpp"
EventLoop::EventLoop(android_app* pApplication):
mApplication(pApplication)
{}
void EventLoop::run() {
int32_t result; int32_t events;
android_poll_source* source;
// Makes sure native glue is not stripped by the linker.
app_dummy();
Log::info("Starting event loop");
while (true) {
// Event processing loop.
while ((result = ALooper_pollAll(-1, NULL, &events,
(void**) &source)) >= 0) {
// An event has to be processed.
if (source != NULL) {
source->process(mApplication, source);
}
// Application is getting destroyed.
if (mApplication->destroyRequested) {
Log::info("Exiting event loop");
return;
}
}
}
}
#include "EventLoop.hpp"
#include "Log.hpp"
void android_main(android_app* pApplication) {
EventLoop(pApplication).run();
}
Describe the C++ files to compile the LOCAL_SRC_FILES directive with the help of the LS_CPP macro.
Link droidblaster with the native_app_glue module (the LOCAL_STATIC_LIBRARIES directive) and android (required by the Native App Glue module), as well as the log libraries (the LOCAL_LDLIBS directive):
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LS_CPP=$(subst $(1)/,,$(wildcard $(1)/*.cpp))
LOCAL_MODULE := droidblaster
LOCAL_SRC_FILES := $(call LS_CPP,$(LOCAL_PATH))
LOCAL_LDLIBS := -landroid -llog
LOCAL_STATIC_LIBRARIES := android_native_app_glue
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/native_app_glue)
APP_ABI := armeabi armeabi-v7a x86
Build and run the application. Of course, you will not see anything tremendous when starting this application. Actually, you will just see a black screen! However, if you look carefully at the LogCat view in Eclipse (or the adb logcat command), you will discover a few interesting messages that have been emitted by your native application in reaction to activity events.
We initiated a Java Android project without a single line of Java code! Instead of referencing a child of Activity in AndroidManifest, we referenced the android.app.NativeActivity class provided by the Android framework.
NativeActivity is a Java class, launched like any other Android activity and interpreted by the Dalvik Virtual Machine like any other Java class. However, we never faced it directly. NativeActivity is in fact a helper class provided with Android SDK, which contains all the necessary glue code to handle application events (lifecycle, input, sensors, and so on) and broadcasts them transparently to native code. Thus, a native activity does not eliminate the need for JNI. It just hides it under the cover! However, the native C/C++ module run by NativeActivity is executed outside Dalvik boundaries in its own thread, entirely natively (using the Posix Thread API)!
NativeActivity and native code are connected together through the native_app_glue module. The Native App Glue has the responsibility of:
The Native glue module code is located in ${ANDROID_NDK}/sources/android/native_app_glue and can be analyzed, modified, or forked at will. The headers related to native APIs such as, looper.h, can be found in ${ANDROID_NDK}/platforms/<Target Platform>/<Target Architecture>/usr/include/android/. Let's see in more detail how it works.
Our own native code entry point is declared inside the android_main() method, which is similar to the main methods in desktop applications. It is called only once when NativeActivity is instantiated and launched. It loops over application events until NativeActivity is terminated by the user (for example, when pressing a device's back button) or until it exits by itself.
The android_main() method is not the real native application entry point. The real entry point is the ANativeActivity_onCreate() method hidden in the android_native_app_glue module. The event loop we implemented in android_main() is in fact a delegate event loop, launched in its own native thread by the glue module. This design decouples native code from the NativeActivity class, which is run on the UI thread on the Java side. Thus, even if your code takes a long time to handle an event, NativeActivity is not blocked and your Android device still remains responsive.
The delegate native event loop in android_main() is itself composed, in our example, of two nested while loops. The outer one is an infinite loop, terminated only when activity destruction is requested by the system (indicated by the destroyRequested flag). It executes an inner loop, which processes all pending application events.
...
int32_t result; int32_t events;
android_poll_source* source;
while (true) {
while ((result = ALooper_pollAll(-1, NULL, &events,
(void**) &source)) >= 0) {
if (source != NULL) {
source->process(mApplication, source);
}
if (mApplication->destroyRequested) {
return;
}
}
}
...
The inner For loop polls events by calling ALooper_pollAll(). This method is part of the Looper API, which can be described as a general-purpose event loop manager provided by Android. When timeout is set to -1, like in the preceding example, ALooper_pollAll() remains blocked while waiting for events. When at least one is received, ALooper_pollAll() returns and the code flow continues.
The android_poll_source structure describing the event is filled and is then used by client code for further processing. This structure looks as follows:
struct android_poll_source {
int32_t id; // Source identifier
struct android_app* app; // Global android application context
void (*process)(struct android_app* app,
struct android_poll_source* source); // Event processor
};
The process() function pointer can be customized to process application events manually.
As we saw in this part, the event loop receives an android_app structure in parameter. This structure, described in android_native_app_glue.h, contains some contextual information as shown in the following table:
void* userData
|
Pointer to any data you want. This is essential in giving some contextual information to the activity or input event callbacks.
|
void (*pnAppCmd)(…) and int32_t (*onInputEvent)(…)
|
These member variables represent the event callbacks triggered by the Native App Glue when an activity or an input event occurs.
|
ANativeActivity* activity
|
Describes the Java native activity (its class as a JNI object, its data directories, and so on) and gives the necessary information to retrieve a JNI context.
|
AConfiguration* config
|
Describes the current hardware and system state, such as the current language and country, the current screen orientation, density, size, and so on.
|
void* savedState size_t and savedStateSize
|
Used to save a buffer of data when an activity (and thus its native thread) is destroyed and later restored.
|
AInputQueue* inputQueue
|
Provides input events (used internally by the native glue).
|
ALooper* looper
|
Allows attaching and detaching event queues used internally by the native glue. Listeners poll and wait for events sent on a communication pipe.
|
ANativeWindow* window and ARect contentRect
|
Represents the "drawable" area on which graphics can be drawn. The ANativeWindow API, declared in native_window.h, allows retrieval of the window width, height, and pixel format, and the changing of these settings.
|
int activityState
|
Current activity state, that is, APP_CMD_START, APP_CMD_RESUME, APP_CMD_PAUSE, and so on.
|
int destroyRequested
|
When equal to 1, it indicates that the application is about to be destroyed and the native thread must be terminated immediately. This flag has to be checked in the event loop.
|
The android_app structure also contains some additional data for internal use only, which should not be changed.
Knowing all these details is not essential to program native programs but can help you understand what's going on behind your back. Let's now see how to handle these activity events.
The Android NDK allows us to write fully native applications without a line of Java code. NativeActivity provides a skeleton to implement an event loop that processes application events. Associated with the Posix time management API, the NDK provides the required base to build complex multimedia applications or games.
In summary, we created NativeActivity that polls activity events to start or stop native code accordingly. We accessed the display window natively, like a bitmap, to display raw graphics. Finally, we retrieved time to make the application adapt to device speed using a monotonic clock.
Further resources on this subject: