2012年9月22日土曜日

Getting Started with the NDK

Once you've installed the NDK successfully, take a few minutes to read the documentation included in the NDK. You can find the documentation in the <ndk>/docs/ directory. In particular, please read the OVERVIEW.HTML document completely, so that you understand the intent of the NDK and how to use it.

If you used a previous version of the NDK, take a moment to review the list of NDK changes in the CHANGES.HTML document.

Here's the general outline of how you work with the NDK tools:

  1. Place your native sources under <project>/jni/...
  2. Create <project>/jni/Android.mk to describe your native sources to the NDK build system
  3. Optional: Create <project>/jni/Application.mk.
  4. Build your native code by running the 'ndk-build' script from your project's directory. It is located in the top-level NDK directory:
    cd <project>  <ndk>/ndk-build  

    The build tools copy the stripped, shared libraries needed by your application to the proper location in the application's project directory.

  5. Finally, compile your application using the SDK tools in the usual way. The SDK build tools will package the shared libraries in the application's deployable .apk file.

2012年9月21日金曜日

JNI Examples for Android

[*]Java interface

We start by defining a Java class JNIExampleInterface, which will provide the interface to calling the native functions, defined in a native (C++) library. The native functions corresponding to Java functions will need to have matching call signatures (i.e. the count and types of the arguments, as well as return type). The easiest way to get the correct function signatures in the native library is to first write down their Java prototypes, and then use the javah tool to generate the native JNI header with native function prototypes. These can be cut and pasted into the C++ file for implementation.

The Java functions which are backed by the corresponding native functions are declared in a usual way, adding a native qualifier. We also want to demonstrate how we could do the callbacks, i.e. calling the Java code from native code. That leads to the following high-level view of our interface class:

<JNIExampleInterface.java>=  package org.wooyd.android.JNIExample;    import android.os.Handler;  import android.os.Bundle;  import android.os.Message;  import org.wooyd.android.JNIExample.Data;    public class JNIExampleInterface {      static Handler h;      <Example constructors>      <Example native functions>      <Example callback>  }  

One valid question about this definition is why we need a Handler class attribute. It turns out that it will come in handy in situations, when the native library wants to pass some information to the Java process through a callback. If the callback will be called by a native thread (for extended discussion see "Calling Java functions" section [->]), and then will try to modify the application's user interface (UI) in any way, an exception will be thrown, as Android only allows the thread which created the UI (the UI thread) to modify it. To overcome this problem we are going to use the message-passing interface provided by Handler to dispatch the data received by a callback to the UI thread, and allow it to do the UI modifications. In order for this to work, we are going to accept a Handler instance as an argument for non-trivial constructor (reasons for keeping trivial one will become apparent later), and save it in a class attribute, and that's pretty much the only task for the constructor:

<Example constructors>= (<-U)      public JNIExampleInterface() {}      public JNIExampleInterface(Handler h) {          this.h = h;      }  

To illustrate various argument-passing techniques, we define three native functions:

  • callVoid(): takes no arguments and returns nothing;
  • getNewData(): takes two arguments and constructs a new class instance using them;
  • getDataString(): extracts a value from an object, which is passed as an argument.

<Example native functions>= (<-U)      public static native void callVoid();      public static native Data getNewData(int i, String s);      public static native String getDataString(Data d);  

The callback will receive a string as an argument, and dispatch it to the Handler instance recorded in the constructor, after wrapping it in a Bundle:

<Example callback>= (<-U)      public static void callBack(String s) {          Bundle b = new Bundle();          b.putString("callback_string", s);          Message m = Message.obtain();          m.setData(b);          m.setTarget(h);          m.sendToTarget();      }  

We also need a definition of a dummy Data class, used purely for illustrative purposes:

<Data.java>=  package org.wooyd.android.JNIExample;    public class Data {      public int i;      public String s;      public Data() {}      public Data(int i, String s) {          this.i = i;          this.s = s;      }  }  

After the source files Data.java and JNIExampleInterface.java are compiled, we can generate the JNI header file, containing the prototypes of the native functions, corresponding to their Java counterparts:

$ javac -classpath /path/to/sdk/android.jar \          org/wooyd/android/JNIExample/*.java  $ javah -classpath . org.wooyd.android.JNIExample.JNIExampleInterface  

Native library implementation

At a high level, the Java library (consisting, in this case, of a single source file JNIExample.cpp) will look like that:

<JNIExample.cpp>=  <JNI includes>  <Miscellaneous includes>  <Global variables>  #ifdef __cplusplus  extern "C" {