This is a continuation of Part I in which we talked about sharing a common code base written in C with Android and iPhone applications. In this part, we will discuss the changes required to get the application to get up and running on Android.

There are many things you can do to improve code reuse, and restructure your application to be more portable. For a code base where most of the logic is written in C, we are going to be using the Java Native Interface (JNI). The most important parts of using JNI is to call System.loadLibrary() to link the Java source code with the C source code, and declare the native methods that you are going to use. If you don’t do this, then several errors will get thrown.

package org.drosoft.sawbix;
// ...

public class CubeView
  extends GLSurfaceView
  implements GLSurfaceView.Renderer, OnTouchListener {

  static {
    System.loadLibrary("Cube");
  }

  public native void touchDown(int world, float x, float y);
  public native void touchMove(int world, float x, float y);
  public native void touchUp  (int world, float x, float y);
  // ...
}

The static code block tells the Android Java runtime to load libCube.so from the library load path, which includes .apk archive directory in which your application is located during deployment. The native declarations tells the Android Java runtime to bind methods to those found in libCube.so, since this is not done automatically. However, the JNI file in which these methods are defined can be generated using the javah utility. After the javah utility has generated the .c and .h files, then you must fill in the implementations manually.

Below are the implementations of the touch functions in JNI:

#include "org_drosoft_sawbix_CubeView.h"
#include "CubeWorld.h"
// ...

JNIEXPORT void JNICALL
Java_org_drosoft_sawbix_CubeView_touchDown(
  JNIEnv * env, jobject _this,
  jint world, jfloat x, jfloat y)
{
  CubeWorld_touch_down((CubeWorld *)world, x, y);
}

JNIEXPORT void JNICALL
Java_org_drosoft_sawbix_CubeView_touchMove(
  JNIEnv * env, jobject _this,
  jint world, jfloat x, jfloat y)
{
  CubeWorld_touch_move((CubeWorld *)world, x, y);
}

JNIEXPORT void JNICALL
Java_org_drosoft_sawbix_CubeView_touchUp(
  JNIEnv * env, jobject _this,
  jint world, jfloat x, jfloat y)
{
  CubeWorld_touch_up((CubeWorld *)world, x, y);
}

You can see that the internal CubeWorld object is represented in the Java source code by an int, even though it is a pointer in the C source code. The reason for this is that Java doesn’t support pointer types, so neither does JNI. However, Android currently runs primarily on 32-bit architectures, and so we can safely store a pointer in an int on these architectures. On 64-bit architectures we would use long instead.

The next step is to ensure that the C source code is built into the target library libCube.so. In order to do this, we are going to follow the guidelines set by the ndk-build tool that comes with the Android Native Development Kit (NDK). It comes with a large library of Makefiles that are fine-tuned for targeting the Android platform, and using those Makefiles is the recommended way to build native Android apps. Some of the Makefile templates of interest are the following:

  • BUILD_STATIC_LIBRARY (rarely used)
  • BUILD_SHARED_LIBRARY (most common)
  • BUILD_EXECUTABLE (useful for non-graphical tools)

Since JNI requires that we use a shared library, we will write the Makefile with that template in mind. The actual template can be found at {YourAndroidNDK}/build/core/build-shared-library.mk if you would like more information. The following is an example of {YourProject}/jni/Android.mk:

headers = 
-I$(LOCAL_PATH) 
-I$(LOCAL_PATH)/include 
-I$(platform)/usr/include
# ...

include $(CLEAR_VARS)

LOCAL_MODULE := libCube
LOCAL_CFLAGS := -Werror -std=c99 $(headers)
LOCAL_LDLIBS := 
	-L$(platform)/usr/lib 
	-nostdlib -lc -llog -lGLESv1_CM

LOCAL_SRC_FILES := 
	org_drosoft_sawbix_CubeView.c 
	CubeWorld.c
	# ...

include $(BUILD_SHARED_LIBRARY)

Now that we’ve got the sources in order, how do we build them? This is much easier than it used to be, now all you have to do is call ndk-build and ant.

  $ ndk-build
  Compile thumb  : Cube <= .../sawbix/jni/org_drosoft_sawbix_CubeView.c
  Compile thumb  : Cube <= .../sawbix/jni/CubeWorld.c
  # ...

  $ ant debug   # for development, or
  $ ant release # for production

There are other build styles as well. For example, you can run ndk-build from a terminal, then use Eclipse (which uses ant) to install to a testing device. Using a combination of the Android NDK and JNI we have abstracted most of our code base by writing it in C, and loading the shared library in Java to bridge between the application logic, and Android’s GUI and graphics system. This follows the Don’t Repeat Yourself (DRY) principle of never copying when you can reuse. It is important to realize that we did this for code reuse, not for performance. Google has made it clear that the NDK will not benefit most apps, because Dalvik’s JIT is getting better every day. So abstracting our code into C is not a performance demand, it’s simply an implementation choice.

Stay tuned for Part III, in which we discuss how to bind the common code base to Objective-C to get the iOS/iPhone application up and running.