The previous recipe described how the interface defined in native_activity.h
allows us to create native activity. However, all the callbacks defined
are invoked with the main UI thread, which means we cannot do heavy processing in the callbacks.
Android SDK provides AsyncTask
, Handler
, Runnable
, Thread
, and so on, to help us handle things in the background and communicate with the main UI thread. Android NDK provides a static library named android_native_app_glue
to help us execute callback functions and handle user inputs in a separate thread. This recipe will discuss the android_native_app_glue
library in detail.
The android_native_app_glue
library is built on top of the native_activity.h
interface. Therefore, readers are recommended to read the Creating a native activity with the native_activity.h interface recipe before going through this one.
The following steps create a simple Android NDK application based on the android_native_app_glue
library:
NativeActivityTwo
. Set the package name as cookbook.chapter5.nativeactivitytwo
. Please refer to the Loading native libraries and registering native methods recipe of Chapter 2, Java Native Interface, if you want more detailed instructions.NativeActivityTwo
project, select Android Tools | Add Native Support.AndroidManifest.xml
file as follows:<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cookbook.chapter5.nativeactivitytwo" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="9"/> <application android:label="@string/app_name" android:icon="@drawable/ic_launcher" android:hasCode="true"> <activity android:name="android.app.NativeActivity" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden"> <meta-data android:name="android.app.lib_name" android:value="NativeActivityTwo" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
NativeActivityTwo.cpp
and mylog.h
under the jni
folder. NativeActivityTwo.cpp
is shown as follows:#include <jni.h> #include <android_native_app_glue.h> #include "mylog.h" void handle_activity_lifecycle_events(struct android_app* app, int32_t cmd) { LOGI(2, "%d: dummy data %d", cmd, *((int*)(app->userData))); } void android_main(struct android_app* app) { app_dummy(); // Make sure glue isn't stripped. int dummyData = 111; app->userData = &dummyData; app->onAppCmd = handle_activity_lifecycle_events; while (1) { int ident, events; struct android_poll_source* source; if ((ident=ALooper_pollAll(-1, NULL, &events, (void**)&source)) >= 0) { source->process(app, source); } } }
Android.mk
file under the jni
folder:LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := NativeActivityTwo LOCAL_SRC_FILES := NativeActivityTwo.cpp LOCAL_LDLIBS := -llog -landroid LOCAL_STATIC_LIBRARIES := android_native_app_glue include $(BUILD_SHARED_LIBRARY) $(call import-module,android/native_app_glue)
adb logcat -v time NativeActivityTwo:I *:S
When the application starts, you should be able to see the following logcat output and the device screen will shows a black screen:
On pressing the back button, the following output will be shown:
This recipe demonstrates how the android_native_app_glue
library is used to create a native activity.
The following steps should be followed to use the
android_native_app_glue
library:
android_main
. This function should implement an event loop, which will poll for events continuously. This method will run in the background thread created by the library.LOOPER_ID_MAIN
or LOOPER_ID_INPUT
). It is also possible to attach additional event queues to the background thread.android_poll_source
data structure. We can call the process function of this structure. The process is a function pointer, which points to android_app->onAppCmd
for activity lifecycle events, and android_app->onInputEvent
for input events. We can provide our own processing functions and direct the corresponding function pointers to these functions.In our example, we implement a simple function named handle_activity_lifecycle_events
and point the
android_app->onAppCmd
function pointer to it. This function simply prints the cmd
value and the user data passed along with the android_app
data structure. cmd
is defined in android_native_app_glue.h
as an enum
. For example, when the app starts, the cmd
values are 10
, 11
, 0
, 1
, and 6
, which correspond to APP_CMD_START
, APP_CMD_RESUME
, APP_CMD_INPUT_CHANGED
, APP_CMD_INIT_WINDOW
, and APP_CMD_GAINED_FOCUS
respectively.
android_native_app_glue Library Internals: The source code of the android_native_app_glue
library can be found under the sources/android/native_app_glue
folder of Android NDK. It only consists of two files, namely android_native_app_glue.c
and android_native_app_glue.h
. Let's first describe the flow of the code and then discuss some important aspects in detail.
android_native_app_glue
is built on top of the native_activity.h
interface. As shown in the following code (extracted from sources/android/native_app_glue/android_native_app_glue.c
). It implements the ANativeActivity_onCreate
function, where it
registers the callback functions and calls the android_app_create
function. Note that the returned android_app
instance is pointed by the instance
field of the native activity, which can be passed to various callback functions:
void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize) { LOGV("Creating: %p ", activity); activity->callbacks->onDestroy = onDestroy; activity->callbacks->onStart = onStart; activity->callbacks->onResume = onResume; … … activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; activity->callbacks->onInputQueueCreated = onInputQueueCreated; activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; activity->instance = android_app_create(activity, savedState, savedStateSize); }
The android_app_create
function (shown in the following code snippet) initializes an instance of the android_app
data structure, which is defined in android_native_app_glue.h
. This function creates a unidirectional pipe for inter-thread communication. After that, it spawns a new thread (let's call it background thread thereafter) to run the android_app_entry
function with the initialized android_app
data as the input argument. The main thread will wait for the background thread to start and then return:
static struct android_app* android_app_create(ANativeActivity* activity, void* savedState, size_t savedStateSize) { struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app)); memset(android_app, 0, sizeof(struct android_app)); android_app->activity = activity; pthread_mutex_init(&android_app->mutex, NULL); pthread_cond_init(&android_app->cond, NULL); …… int msgpipe[2]; if (pipe(msgpipe)) { LOGE("could not create pipe: %s", strerror(errno)); return NULL; } android_app->msgread = msgpipe[0]; android_app->msgwrite = msgpipe[1]; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&android_app->thread, &attr, android_app_entry, android_app); // Wait for thread to start. pthread_mutex_lock(&android_app->mutex); while (!android_app->running) { pthread_cond_wait(&android_app->cond, &android_app->mutex); } pthread_mutex_unlock(&android_app->mutex); return android_app; }
The background thread starts with the android_app_entry
function (as shown in the following code snippet), where a looper is created. Two event queues will be attached to the looper. The activity lifecycle events queue is attached to the android_app_entry
function. When the activity's input queue is created, the input queue is attached (to the android_app_pre_exec_cmd
function of android_native_app_glue.c
). After attaching the activity lifecycle event queue, the
background thread signals the main thread it is already running. It then calls a function named android_main
with the android_app
data. android_main
is the function we need to implement, as shown in our sample code. It must run in a loop until the activity exits:
static void* android_app_entry(void* param) { struct android_app* android_app = (struct android_app*)param; … … //Attach life cycle event queue with identifier LOOPER_ID_MAIN android_app->cmdPollSource.id = LOOPER_ID_MAIN; android_app->cmdPollSource.app = android_app; android_app->cmdPollSource.process = process_cmd; android_app->inputPollSource.id = LOOPER_ID_INPUT; android_app->inputPollSource.app = android_app; android_app->inputPollSource.process = process_input; ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL, &android_app->cmdPollSource); android_app->looper = looper; pthread_mutex_lock(&android_app->mutex); android_app->running = 1; pthread_cond_broadcast(&android_app->cond); pthread_mutex_unlock(&android_app->mutex); android_main(android_app); android_app_destroy(android_app); return NULL; }
The following diagram indicates how the main and background thread work together to create the multi-threaded native activity:
We use the activity lifecycle event queue as an example. The main thread invokes the callback functions, which simply writes to the write end of the pipe, while true loop implemented in the android_main
function will poll for events. Once an event is detected, the function calls the event handler, which reads the exact command from the read end of the pipe and handles it. The android_native_app_glue
library implements all the main thread
stuff and part of the background thread
stuff for us. We only need to supply the polling loop and the event handler as illustrated in our sample code.
Pipe: The main thread creates a unidirectional pipe in the android_app_create
function by calling the pipe
method. This method accepts an array of two integers. After the function is returned, the first integer will be set as the file descriptor referring to the read end of the pipe, while the second integer will be set
as the file descriptor referring to the write end of the pipe.
A pipe is usually used for
Inter-process Communication (IPC), but here it is used for communication between the main UI thread and the background thread created at android_app_entry
. When an activity lifecycle event occurs, the main thread will execute the corresponding callback function registered at ANativeActivity_onCreate
. The callback function simply writes a command to the write end of the pipe and then waits for a signal from the background thread. The background thread is supposed to poll for events continuously and once it detects a lifecycle event, it will read the exact event from the read end of the pipe, signal the main thread to unblock and handle the events. Because the signal is sent right after receiving the command and before actual processing of the events, the main thread can return from the callback function quickly without worrying about the possible long processing of the events.
Different operating systems have different implementations for the pipe. The pipe implemented by Android system is "half-duplex", where communication is unidirectional. That is, one file descriptor can only write, and the other file descriptor can only read. Pipes in some operating system is "full-duplex", where the two file descriptors can both read and write.
Looper is an event tracking facility, which allows us to attach one or more event queues for an event loop of a thread. Each event queue has an associated file descriptor. An event is data available on a file descriptor. In order to use a looper, we need to include the android/looper.h
header file.
The library attaches two event queues for the event loop to be created by us in the background thread, including the activity lifecycle event queue and the input event queue. The following steps should be performed in order to use a looper:
ALooper_prepare
function:ALooper* ALooper_prepare(int opts);
This function prepares a looper associated with the calling thread and returns it. If the looper doesn't exist, it creates one, associates it with the thread, and returns it.
ALooper_addFd
. The function has the following prototype:int ALooper_addFd(ALooper* looper, int fd, int ident, int events, ALooper_callbackFunc callback, void* data);
The function can be used in two ways. Firstly, if callback
is set to NULL
, the ident
set will be returned by ALooper_pollOnce
and ALooper_pollAll
. Secondly, if callback
is non-NULL, then the callback function will be executed and ident
is ignored. The android_native_app_glue
library uses the first approach to attach a new event queue to the looper. The input argument fd
indicates the file descriptor associated with the event queue. ident
is the identifier for the events from the event queue, which can be used to classify the event. The identifier must be bigger than zero when callback
is set to NULL
. callback
is set to NULL
in the library source code, and data
points to the private data that will be returned along with the identifier at polling.
In the library, this function is called to attach the activity lifecycle event queue to the background thread. The input event queue is attached using the input queue specific function AInputQueue_attachLooper
, which we will discuss in the Detecting and handling input events at NDK recipe.
int ALooper_pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData); int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData);
These two methods are equivalent when callback
is set to NULL
in ALooper_addFd
. They have the same input arguments. timeoutMillis
specifies the timeout for polling. If it is set to zero, then the functions return immediately; if it is set to negative, they will wait indefinitely until an event occurs. The functions return the identifier (greater than zero) when an event occurs from any input queues attached to the looper. In this case, outFd
, outEvents
, and outData
will be set to the file descriptor, poll events, and data
associated with the event. Otherwise, they will be set to NULL
.
int ALooper_removeFd(ALooper* looper, int fd);
It accepts the looper and file descriptor associated with the event queue, and detaches the queue from the looper.