14 min read

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:

  • Creating a fully native activity
  • Handling main activity events
  • Accessing display window natively
  • Retrieving time and calculating delays

Creating a native Activity

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.

Time for action – creating a basic native Activity

We are now going to see how to create a minimal native activity that runs an event loop.

  1. Create a new hybrid Java/C++ project:
    •      Name it DroidBlaster.
    •      Turn the project into a native project. Name the native module droidblaster.
    •      Remove the native source and header files that have been created by ADT.
    •      Remove the reference to the Java src directory in Project Properties | Java Build Path | Source. Then, remove the directory itself on disk.
    •      Get rid of all layouts in the res/layout directory.
    •      Get rid of jni/droidblaster.cpp if it has been created.
  2. In AndroidManifest.xml, use Theme.NoTitleBar.Fullscreen as the application theme.

    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>
  3. Create the file jni/Types.hpp. This header will contain common types and the header cstdint:
    #ifndef _PACKT_TYPES_HPP_
    #define _PACKT_TYPES_HPP_
     
    #include <cstdint>
     
    #endif
  4. Let’s write a logging class to get some feedback in the Logcat.
    •      Create jni/Log.hpp and declare a new class Log.
    •      Define the packt_Log_debug macro to allow the activating or deactivating of debug messages with a simple compile flag:
    #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
  5. Implement the jni/Log.cpp file and implement the info() method. To write messages to Android logs, the NDK provides a dedicated logging API in the android/log.h header, which can be used similarly as printf() or vprintf() (with varArgs) in C:
    #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.

  6. Application events in NativeActivity can be processed with an event loop. So, create jni/EventLoop.hpp to define a class with a unique method run().

    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
  7. Create jni/EventLoop.cpp and implement the activity event loop in the run() method. Include a few log events to get some feedback in Android logs.

    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;
               }
           }
       }
    }
  8. Finally, create jni/Main.cpp to define the program entry point android_main(), which runs the event loop in a new file Main.cpp:
    #include "EventLoop.hpp"
    #include "Log.hpp"
     
    void android_main(android_app* pApplication) {
       EventLoop(pApplication).run();
    }
  9. Edit the jni/Android.mk file to define the droidblaster module (the LOCAL_MODULE directive).

    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)
     
  10. Create jni/Application.mk to compile the native module for multiple ABIs. We will use the most basic ones, as shown in the following code:
    APP_ABI := armeabi armeabi-v7a x86

What just happened?

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:

  • Launching the native thread, which runs our own native code
  • Receiving events from NativeActivity
  • Routing these events to the native thread event loop for further processing

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.

More about the Native App Glue

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.

Summary

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.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here