Native Activity

So far, we have seen how to mix Java and C/C++ in a single application. Android 2.3 goes one step further and defines the NativeActivity class, which allows you to write the whole application in C/C++ but does not force you to, as you still have access to the whole Android Java framework through JNI.

NOTE: You do not have to use NativeActivity for all activities in your application. For example, you could write an application with two activities: one NativeActivity and one ListActivity.

If you prefer to read source files or header files in lieu of more formal documentation, you are in for a treat. In fact, most of the documentation is contained in the header files, which you can find in the NDK's platforms/android-9/arch-arm/usr/include/android directory. The list of header files is shown in Table 2–7.

Image
Image

Creating a native activity is quite simple. The first step is to define your application's manifest file to let Android know you are going to use a native activity. For that you need to specify two things in your application's AndroidManifest.xml for each native activity:

  • The class to instantiate
  • The library to load and its entry point

The first item is actually no different from other non-native activities. When your activity is created, Android needs to instantiate the right class, and this is what android:name is for inside the <activity> tag. In most cases there is no need for your application to extend the NativeActivity class, so you will almost always use android.app.NativeActivity as the class to instantiate. Nothing prevents you from instantiating a class of your creation that extends NativeActivity though.

The second item is for Android to know what library contains your activity's native code so that it can be loaded automatically when the activity is created. That piece of information is given to Android as metadata as a name/value pair: the name has to be set to android.app.lib_name, and the value specifies the name of the library without the lib prefix or .so suffix. Optionally, you can also specify the library's entry point as name/value metadata, the name being set to android.app.func_name, and the value to the function name. By default, the function name is set to ANativeActivity_onCreate.

An example of a manifest file is shown in Listing 2–23. The minimum SDK version is set to 9 as NativeActivity was introduced in Android 2.3, and the activity's native code is in libmyapp.so.

Listing 2–23. The Native Application's AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.apress.proandroid"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="9" />

    <application android:icon="@drawable/icon"
                 android:label="@string/app_name"
                 android:hasCode="false">
        <activity android:name="android.app.NativeActivity"
                  android:label="@string/app_name">
            <meta-data android:name="android.app.lib_name"
                       android:value="myapp" />
            <meta-data android:name="android.app.func_name"
                        android:value="ANativeActivity_onCreate" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

NOTE: Optionally, if your application does not contain any Java code, you can set android:hasCode to false in the <application> tag.

Launching this application now would result in a runtime error as the libmyapp.so does not exist yet. Consequently, the next step is to build this missing library. This is done as usual using the NDK's ndk-build tool.

Building the Missing Library

You have to define your Application.mk file as well as your Android.mk file. When using native activities, the difference lies in Android.mk, as shown in Listing 2–24. You also need a file that contains the actual implementation of the application, myapp.c (shown in Listing 2–25).

Listing 2–24. The Native Application's Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := myapp
LOCAL_SRC_FILES := myapp.c
LOCAL_LDLIBS    := -llog -landroid
LOCAL_STATIC_LIBRARIES := android_native_app_glue

include $(BUILD_SHARED_LIBRARY)

$(call import-module,android/native_app_glue)

The differences between this Android.mk and the one we previously used are:

  • The shared libraries to link with
  • The static library to link with

Since your native activity is going to use the Android native application APIs, you need to add –landroid to LOCAL_LDLIBS. You may need to link with more libraries depending on what you are going to use. For example, –llog is for linking with the logging library to allow you to use the logcat debugging facility.

The Android NDK provides a simpler way to create a native application, which is implemented in the NDK's native_app_glue module. To use this module you need to not only add it to LOCAL_STATIC_LIBRARIES, but also import it into your project by using the import-module function macro, as indicated by the last line in Listing 2–24.

NOTE: The native_app_glue module is implemented in the NDK, and the source code is located in the android-ndk-r7/sources/android/native_app_glue directory. You are free to modify the implementation and compile your application using your own modified version as the library is linked statically.

Listing 2–25 shows an example of an application, implemented in a single file myapp.c, which listens to the following events:

  • Application events (similar to methods like onStart in Java)
  • Input events (key, motion)
  • Accelerometer
  • Gyroscope (callback-based)

This application does not do anything meaningful other than enabling sensors and showing you how to process sensor events. In this particular case, the sensor values are displayed with a call to __android_log_print. Use this application as the skeleton for your own needs.

Listing 2–25. Implementation of myapp.c

#include<android_native_app_glue.h>
#include<android/sensor.h>
#include<android/log.h>

#define TAG "myapp"

typedefstruct {
    // accelerometer
    const ASensor* accelerometer_sensor;
    ASensorEventQueue* accelerometer_event_queue;
    // gyroscope
    const ASensor* gyroscope_sensor;
    ASensorEventQueue* gyroscope_event_queue;
} my_user_data_t;

static int32_t on_key_event (struct android_app* app, AInputEvent* event)
{
    // use AKeyEvent_xxx APIs
    return 0; // or 1 if you have handled the event
}

static int32_t on_motion_event (struct android_app* app, AInputEvent* event)
{
    // use AMotionEvent_xxx APIs
    return 0; // or 1 if you have handled the event
}

// this simply checks the event type and calls the appropriate function
static int32_t on_input_event (struct android_app* app, AInputEvent* event)
{
    int32_t type = AInputEvent_getType(event);
    int32_t handled = 0;

    switch (type) {
        case AINPUT_EVENT_TYPE_KEY:
            handled = on_key_event(app, event);
            break;

        case AINPUT_EVENT_TYPE_MOTION:
            handled = on_motion_event(app, event);
            break;
}

    return handled;
}

// some functions not yet implemented
staticvoidon_input_changed (struct android_app* app) {}
staticvoidon_init_window (struct android_app* app) {}
staticvoidon_term_window (struct android_app* app) {}
staticvoidon_window_resized (struct android_app* app) {}
staticvoidon_window_redraw_needed (struct android_app* app) {}
staticvoidon_content_rect_changed (struct android_app* app) {}

// we enable the sensors here
staticvoidon_gained_focus (struct android_app* app)
{
    my_user_data_t* user_data = app->userData;
    if (user_data->accelerometer_sensor != NULL) {
        ASensorEventQueue_enableSensor(
            user_data->accelerometer_event_queue,
            user_data->accelerometer_sensor);
        ASensorEventQueue_setEventRate(
            user_data->accelerometer_event_queue,
            user_data->accelerometer_sensor, 1000000L/60);
    }
    if (user_data->gyroscope_sensor != NULL) {
        ASensorEventQueue_enableSensor(
            user_data->gyroscope_event_queue,
            user_data->gyroscope_sensor);
        ASensorEventQueue_setEventRate(
            user_data->gyroscope_event_queue,
            user_data->gyroscope_sensor, 1000000L/60);
    }
}

// we disable the sensors when focus is lost
staticvoidon_lost_focus (struct android_app* app)
{
    my_user_data_t* user_data = app->userData;
    if (user_data->accelerometer_sensor != NULL) {
        ASensorEventQueue_disableSensor(
            user_data->accelerometer_event_queue,
            user_data->accelerometer_sensor);
    }
    if (user_data->gyroscope_sensor != NULL) {
        ASensorEventQueue_disableSensor(
            user_data->gyroscope_event_queue,
            user_data->gyroscope_sensor);
    }
}

// more functions to implement here…
staticvoidon_config_changed (struct android_app* app) {}
staticvoidon_low_memory (struct android_app* app) {}
staticvoidon_start (struct android_app* app) {}
staticvoidon_resume (struct android_app* app) {}
staticvoidon_save_state (struct android_app* app) {}
staticvoidon_pause (struct android_app* app) {}
staticvoidon_stop (struct android_app* app) {}
staticvoidon_destroy (struct android_app* app) {}

// this simply checks the command and calls the right function
staticvoidon_app_command (struct android_app* app, int32_t cmd) {
    switch (cmd) {
        case APP_CMD_INPUT_CHANGED:
            on_input_changed(app);
            break;

        case APP_CMD_INIT_WINDOW:
            on_init_window(app);
            break;

        case APP_CMD_TERM_WINDOW:
            on_term_window(app);
            break;

        case APP_CMD_WINDOW_RESIZED:
            on_window_resized(app);
            break;

        case APP_CMD_WINDOW_REDRAW_NEEDED:
            on_window_redraw_needed(app);
            break;

        case APP_CMD_CONTENT_RECT_CHANGED:
            on_content_rect_changed(app);
            break;

        case APP_CMD_GAINED_FOCUS:
            on_gained_focus(app);
            break;
        case APP_CMD_LOST_FOCUS:
            on_lost_focus(app);
            break;
        case APP_CMD_CONFIG_CHANGED:
            on_config_changed(app);
            break;

        case APP_CMD_LOW_MEMORY:
            on_low_memory(app);
            break;

        case APP_CMD_START:
            on_start(app);
            break;

        case APP_CMD_RESUME:
            on_resume(app);
            break;

        case APP_CMD_SAVE_STATE:
            on_save_state(app);
            break;

        case APP_CMD_PAUSE:
            on_pause(app);
            break;

        case APP_CMD_STOP:
            on_stop(app);
            break;

        case APP_CMD_DESTROY:
            on_destroy(app);
            break;
    }
}

// user-defined looper ids
#define LOOPER_ID_USER_ACCELEROMETER   (LOOPER_ID_USER + 0)
#define LOOPER_ID_USER_GYROSCOPE      (LOOPER_ID_USER + 1)

// we'll be able to retrieve up to 8 events at a time
#define NB_SENSOR_EVENTS 8

staticintgyroscope_callback (int fd, int events, void* data)
{
    // not really a good idea to log anything here as you may get more than you wished for…
    __android_log_write(ANDROID_LOG_INFO, TAG, "gyroscope_callback");
    return 1;
}

staticvoidlist_all_sensors (ASensorManager* sm)
{
    ASensorList list;
    int i, n;
    n = ASensorManager_getSensorList(sm, & list);
    for (i = 0; i < n; i++) {
        const ASensor* sensor = list[i];
        constchar* name = ASensor_getName(sensor);
        constchar* vendor = ASensor_getVendor(sensor);
        int type = ASensor_getType(sensor);
        int min_delay = ASensor_getMinDelay(sensor);
        float resolution = ASensor_getResolution(sensor);

        __android_log_print(
            ANDROID_LOG_INFO, TAG, "%s (%s) %d %d %f",name, vendor, type, min_delay,
resolution);
    }
}

// where things start…
voidandroid_main (struct android_app* state)
{
    my_user_data_t user_data;
    ASensorManager* sm = ASensorManager_getInstance();
    app_dummy(); // don't forget that call

    // we simply list all the sensors on the device
    list_all_sensors(sm);

    state->userData = & user_data;
    state->onAppCmd = on_app_command;
    state->onInputEvent = on_input_event;

    // accelerometer
    user_data.accelerometer_sensor =
        ASensorManager_getDefaultSensor(sm, ASENSOR_TYPE_ACCELEROMETER);
    user_data.accelerometer_event_queue = ASensorManager_createEventQueue(
        sm, state->looper, LOOPER_ID_USER_ACCELEROMETER, NULL, NULL);

    // gyroscope (callback-based)
    user_data.gyroscope_sensor =
        ASensorManager_getDefaultSensor(sm, ASENSOR_TYPE_GYROSCOPE);
    user_data.gyroscope_event_queue = ASensorManager_createEventQueue(
        sm, state->looper, LOOPER_ID_USER_GYROSCOPE, gyroscope_callback, NULL);

    while (1) {
        int ident;
        int events;
        struct android_poll_source* source;

        while ((ident = ALooper_pollAll(-1, NULL, &events, (void**)&source)) >= 0) {

            // “standard” events first
            if ((ident == LOOPER_ID_MAIN) || (ident == LOOPER_ID_INPUT)) {
                // source should not be NULL but we check anyway
                if (source != NULL) {
                    // this will call on_app_command or on_input_event
                    source->process(source->app, source);
        }
    }

    // accelerometer events
    if (ident == LOOPER_ID_USER_ACCELEROMETER) {
            ASensorEvent sensor_events[NB_SENSOR_EVENTS];
            int i, n;
            while ((n = ASensorEventQueue_getEvents(
                user_data.accelerometer_event_queue,sensor_events,
NB_SENSOR_EVENTS)) > 0) {

                for (i = 0; i < n; i++) {
                    ASensorVector* vector = & sensor_events[i].vector;
                    __android_log_print(
                        ANDROID_LOG_INFO, TAG,
                        "%d accelerometer x=%f y=%f z=%f",i, vector->x, vector->y, vector->z);
                }
            }
        }

        // process other events here

            // don't forget to check whether it's time to return
            if (state->destroyRequested != 0) {
                ASensorManager_destroyEventQueue(sm,
user_data.accelerometer_event_queue);
                ASensorManager_destroyEventQueue(sm, user_data.gyroscope_event_queue);
                return;
            }
        }

        // do your rendering here when all the events have been processed
    }
}

Alternative

Another way to create a native application is to implement your native version of onCreate in which you not only initialize your application but also define all your other callbacks (that is, the equivalent of onStart, onResume, to name just a few). This is exactly what the native_app_glue module implements for you to simplify your own development. Also, the native_app_glue module guarantees certain events are handled in a separate thread, allowing your application to remain responsive. Should you decide to define your own onCreate implementation, you would not need to link with the native_app_glue library, and instead of implementing android_main, you would implement ANativeActivity_onCreate, as shown in Listing 2.26.

Listing 2–26. Implementation of ANativeActivity_onCreate

#include<android/native_activity.h>

void ANativeActivity_onCreate (ANativeActivity* activity, void* savedState, size_t
savedStateSize)
{
    // set all callbacks here
    activity->callbacks->onStart = my_on_start;
    activity->callbacks->onResume = my_on_resume;
    …
    // set activity->instance to some instance-specific data
    activity->instance = my_own_instance; // similar to userData

    // no event loop here, it simply returns and NativeActivity will then call the
callbacks you defined
}

While this may appear simpler, it becomes more complicated when you start listening to some other events (such as sensors) and draw things on the screen.

TIP: Do not change the name of the library's entry point in your manifest file if you are using the native_app_glue module as it implements ANativeActivity_onCreate.

The new NativeActivity class in itself does not improve performance. It is simply a mechanism to make native application development easier. In fact, you could implement that same mechanism in your own application to write native applications on older Android versions. Despite the fact that your application is, or can be, fully written in C/C++, it still runs in the Dalvik virtual machine, and it still relies on the NativeActivity Java class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.3.136