20090627

Hello, Native Android World!

I just thought I would post my experiences with creating an Android application that uses JNI to call native code.

First of all, create a regular Android project in Eclipse, following the Hello Android tutorial, except
  • change the project name to HelloNativeAndroid
  • change the class name to be HelloNativeAndroid
  • change the package name to be com.christopherfriedt.android, or something similar
Then change your HelloNativeAndroid.java to be something like this:

HelloNativeAndroid.java:
package com.christopherfriedt.android;

import java.io.*;
import java.util.zip.*;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class HelloNativeAndroid extends Activity {

private native String greet();

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
tv.setText( greet() );
setContentView(tv);
}

static {

String pkg = "com.christopherfriedt.android";
String cls = "HelloNativeAndroid";
String lib = "libHelloNativeAndroid.so";
String apkLocation = "/data/app/" + pkg + ".apk";
String libLocation = "/sdcard/" + lib;

File f = new File(libLocation);

if ( ! f.canRead() ) {

Log.d( cls, "'" + libLocation + "' not found, proceeding to installation" );

try {

ZipFile zip = new ZipFile(apkLocation);
Log.d( cls, "found ZipFile " + apkLocation );

ZipEntry zipen = zip.getEntry("assets/" + lib );

if ( zipen == null ) {
Log.d( cls, "unable to find ZipEntry '" + "assets/" + lib + "'" );
} else {
Log.d( cls, "found ZipEntry '" + "assets/" + lib + "'" );
}

InputStream is = zip.getInputStream(zipen);
Log.d( cls, "opened InputStream" );
OutputStream os = new FileOutputStream(libLocation);
Log.d( cls, "opened OutputStream " + libLocation );

byte[] buf = new byte[4096];
int n;
while ( (n = is.read(buf)) > 0 ) {
Log.d( cls, "read " + n + " bytes from InputStream" );
os.write(buf,0,n);
Log.d( cls, "wrote " + n + " bytes to OutputStream" );
}
os.close();
Log.d( cls, "closed OutputStream" );
is.close();
Log.d( cls, "closed InputStream" );
} catch( Exception e ) {
Log.e( cls, "failed to install native library: " + e );
}
}

try {
Log.d( cls, "attempting to load " + lib );
System.load( libLocation );
//System.loadLibrary( cls );
Log.d( cls, "loaded " + lib );
} catch(Exception e) {
Log.e( cls, "failed to load native library: " + e );
}
}
}
Open up a shell in the 'src' directory of your project and run
javac com.christopherfriedt.android.HelloNativeAndroid.java
javah com.christopherfriedt.android.HelloNativeAndroid

Make a subdirectory in your Android project, called for example 'C', and then copy the resulting com_christopherfriedt_android_HelloNativeAndroid.h (created with javah), to the 'C' folder.

Next, create the following files:

HelloAndroid.c:
#include "com_christopherfriedt_android_HelloNativeAndroid.h"

#define GREETING "Hello, Native Android!"

JNIEXPORT jstring JNICALL Java_com_christopherfriedt_android_HelloNativeAndroid_greet
(JNIEnv *e, jobject o) {
return (*e)->NewStringUTF(e,GREETING);
}

Makefile:
CROSS = arm-softfloat-linux-gnueabi- CC = $(CROSS)gcc STRIP = $(CROSS)strip JDK_HOME = /opt/sun-jdk-1.6.0.14 PKG = com_christopherfriedt_android CLASS = HelloNativeAndroid DEBUG = CFLAGS = -O2 -pipe -Wall -march=armv4t -mtune=arm920t \ -I$(JDK_HOME)/include -I$(JDK_HOME)/include/linux LDFLAGS = -fPIC -shared -nostdlib -Wl,-rpath,/system/lib ifneq ($(DEBUG),) CFLAGS += -g endif SOBJ = lib$(CLASS).so all: $(SOBJ) $(SOBJ): $(PKG)_$(CLASS).h $(CLASS).c $(CC) $(CFLAGS) $(LDFLAGS) -o $(SOBJ) $(CLASS).c ifeq ($(DEBUG),) $(STRIP) --strip-unneeded $(SOBJ) endif clean: rm -f $(SOBJ) install: all cp $(SOBJ) ../assets

Then run 'make', and 'make install'. Build your Android project, and it will now have libHelloNativeAndroid.so bundled in the .apk file, inside the 'assets' directory (you can easily view the contents of it with unzip -l, because it's just a zip file).

Then install the .apk file using the android debug bridge (adb), with 'adb install HelloNativeAndroid.apk'.

Open the application up on your mobile, or in the emulator, and you should see something resembling the screenshot below.

Some notes:
  • My target was a Neo FreeRunner from OpenMoko, hence the -march=armv4t -mtune=arm920t . Feel free to change that to something like -march=armv5te as appropriate
  • In the Makefile, adjust the path to your JDK as appropriate
  • The rpath argument is necessary so that the native Android linker will know to link the shared object to /system/lib/libc.so, which is from the Bionic C library
  • The -nostdlib option is required so that the shared library does not require libc.so.6, which would actually refer to glibc
  • The shared library will be extracted to /sdcard/libHelloNativeAndroid.so, so you'll need that folder to exist and be writable on your device

Acknowlegements:
  • http://android.wooyd.org/
  • http://honeypod.blogspot.com/2007/12/shared-library-hello-world-for-android.html

No comments: