Thursday 10 September 2009

How create a new JNI / STL based project for Android

Now that I've been through the process of creating new JNI / STL-based projects for Android a few times, I thought I'd summarise the steps; as you can see, they are pretty complicated! The problem isn't down to STL; the problem is one of the basic complexity of setting up a JNI-based project (which is never easy to get started, but is fine once you're up and running!).

Note: the steps below assume you're using cygwin and Windows. You'd need to tweak them a little if you're using Mac or Linux.

1. Make sure Eclipse uses a sensible workspace folder

NOTE!! I modified my Eclipse preferences to put my files in
Documents and Settings/myname/My Documents/workspace
rather than the Eclipse default of
Documents and Settings/myname/workspace
which I find really difficult to track-down and/or backup!

2. Create a new Android Java project.

a) Create a package called com.mycompany.MyStl, with your activity class called AdaptorClass

Ensure the following code is in your ActivityClass source code...


// A native method that is implemented by the
// 'libMyStl' native library, which is packaged
public native String stringFromSTL();

// Note: remember to put-in some test code that calls the above native method!!

// this will load the 'libMyStl.so' library on application startup.
static {
System.loadLibrary("MyStl");
}


b) you must add a "libs" folder to your project: and then add an "armeabi" sub-folder in this folder.

c) Build the project (and fix any compilation errors) - but don't run it yet!

3. Create the JNI / STL project

a) Create a folder in for your project, e.g.:


C:\android-ndk-1.5_r1\apps\MyStl


b) Create a file in this folder called Application.mk, which contains the following:


APP_PROJECT_PATH := /cygdrive/c/DOCUME~1/MYNAME/MYDOCU~1/WORKSP~1/MyStl
APP_MODULES := MyStl


NOTE!! The path is a path with no spaces (as otherwise the Android NDK make system gets confused...!), and was generated using cygwin's cygpath command. You must generate and use one that suits your user, using a command similar to the following...


cygpath -dm /cygdrive/c/Documents\ and\ Settings/myname/My\ Documents/workspace


c) Make an empty folder in this folder called "project".

4. Create the JNI / STL sources, and integrate with your Android project:

a) Create a folder for your C++ source code, e.g.:


C:\android-ndk-1.5_r1\sources\MyStl


b) Create a file in this folder called Android.mk ...


LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

STLPORT_BASE := /cygdrive/c/android-ndk-1.5_r1/stlport

LOCAL_MODULE := MyStl
LOCAL_CPP_EXTENSION := .cc
LOCAL_CFLAGS += -I$(STLPORT_BASE)/stlport \
-D__NEW__ \
-D__SGI_STL_INTERNAL_PAIR_H \
-DANDROID \
-DOS_ANDROID
LOCAL_SRC_FILES := MyStl.cc

LOCAL_STATIC_LIBRARIES := /../../../../../stlport/build/lib/obj/gcc/ar/libstlport.5.1

include $(BUILD_SHARED_LIBRARY)


c) Create a file in this folder called MyStl.cc ... note the instructions that follow in d) on how to re-generate your own method prototypes...!


/*
* Class: com_mycompany_MySTL_AdaptorClass
* Method: stringFromSTL
* Signature: ()Ljava/lang/String;
*/

// Prototype - make sure it uses C-style linkage!
extern "C"
{
JNIEXPORT jstring JNICALL Java_com_mycompany_MyStl_AdaptorClass_stringFromSTL
(JNIEnv *, jobject);
};

// Implementation
JNIEXPORT jstring JNICALL Java_com_mycompany_MyStl_AdaptorClass_stringFromSTL
(JNIEnv *env, jobject)
{
std::vector lVector;
lVector.push_back("Hello from STL!");

std::string simple_string = lVector[0];

return env->NewStringUTF(simple_string.c_str());
}


d) Modify the MyStl.cc file...

The prototype and implementation shown are just examples. You MUST construct your own by doing this:


cd "/cygdrive/c/Documents and Settings/myname/My Documents/workspace/MySTL/bin"
javah -jni com.mycompany.MySTL.AdaptorClass_MySTL


Copy-out the generated prototype from the generated .h file that javah creates for your, and replace what I've shown above with the correct generated function prototype and function name.

e) Then, build the library:


cd $NDK_HOME
./env.sh
make APP=MySTL


f) The first time you've built the library, you must drag the newly constructed .so file from Windows Explorer to your armeabi folder in the project. You must then right-click on the armeabi folder in Eclipse, and select "Refresh". The library is then bundled-in to your project bundle when you next build your Android project.

5. You can now build and run your Android project!

6. If you ever modify your C++ source...

Simply re-build the library (step 4e) and then re-build and run your Android project (step 5).

7. If you need to add a new method...

You'll need to re-use javah to generate the correct prototype function signature to use. Add this to your STL project, re-make, re-build and you're done!

Tuesday 8 September 2009

Android Lists - using the ListView component

Whenever I write GUI code for a platform that is new to me, I always find myself stumped early-on figuring-out how to get simple list gadgets working. After all, most simple applications are based around buttons and lists of data. Buttons are always easy to use, but lists are always a pain.

Every platform seems to use a completely unique approach to creating lists; Android's approach is no exception in terms of how difficult it is to figure-out how to use its LiewView components. My opinion here is that the problem is down to the trend for using Model-View-Controller architectures, which actually seem to get in the way of deploying basic components like a list.

Anyways: you'll find a really useful guide to creating multi-column lists on Android here.

The steps you need to follow:
- create a new res/layout/mylayout.xml file, with the correct magic format and properties that you have to copy from what you can glean through internet searches
- use some magic code based around Adapter classes
- call the setAdapter method on your list

For those who are interested, I think the Windows API model is by *far* the easiest (using owner-draw lists); a piece of cake to use. The Apple APIs (carbon/cocoa) are hideous. The Android APIs are probably the worst.

I really wish that people who wrote APIs would concentrate on making the basic stuff easy, with lots of example code. Well done Microsoft for getting this stuff right in the first place. :)

The good news about Java development these days is support for Java Generics; I'm very happy to find that Android supports them!