JNI on Android

Why JNI

Long time I wanted to write about it. One of the hot topic on the Android mailing lists is JNI. JNI is the technology that allows to bind Java code with native code. In our case, this is going to be C/C++.

On Android, the Java VM (Dalvik) is not what you would call a performance monster. Even official presentations by Google agree with that. But what they also say, it's that you can use JNI if you want performances.

That is, write the hot parts in C or C++, and the rest in Java. And then interface both parts with JNI.

How does it work

Well, first, you obviously have to learn JNI. It's a thing by itself, and there are plenty of tutorials available on the internet.

The basic idea is to write a C file that is going to export methods, and a Java file that is going to use those methods. Simple, but in order for the magic to work, you'll need to respect some conventions.

A good starting point is a sample project made by the Android team. The announcement appeared on the mailing lists in this post and the code is available here.

I have taken this code and simplified it (read : removed most lines). Here is what I end up with :

The code (I)

First, the Java code. A simple Activity that writes some text on the screen.

package com.Hello;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import android.util.Log;
 
 
public class Hello extends Activity {
 
    static {
       try {
            Log.i("JNI", "Trying to load libHello.so");
            System.loadLibrary("Hello");
        }
        catch (UnsatisfiedLinkError ule) {
            Log.e("JNI", "WARNING: Could not load libHello.so");
        }
    }
 
    // Important part : this method is native, as in imported from C++ 
    native private int add(int a, int b);
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        // Call simple native method
        int res = add(3,5);
 
        // Just print the result on screen
        TextView tv = new TextView(this);
        tv.setText("C++ tells you that 3+5 = " + res);
        setContentView(tv);
    }
}

Not much there. the only new things are the System.loadLibrary(“Hello”) statement, which loads the .so C++ library, and then the native keyword, that tells Java that the add method is to be found in some external native library.

Simple Android.mk that builds the apk associated with this Hello.java code.

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := samples

LOCAL_PACKAGE_NAME := Hello

LOCAL_SRC_FILES := Hello.java

LOCAL_SDK_VERSION := current

include $(BUILD_PACKAGE)

And the AndroidManifest.xml file :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.Hello">
    <application android:label="Hello">
        <activity android:name="Hello">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

If you put those 3 files in the same directory (I've chosen the external subdir of the Android SDK, so ~/mydroid/external/test/java), then source build/envsetup.sh to get helper functions and type mm in that dir, you'll end up with an Hello.apk file !

The code (II)

Next the C++ code. This is a bit trickier, because this is where things actually happen. Here it is :

 
/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Modified (Simplified) by fred (2009), same license applies.
 */
 
#define LOG_TAG "Hello"
#include "utils/Log.h"
 
 
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
 
#include "jni.h"
 
/*
 * Trivial method : add two numbers
 *
 */
static jint Hello_add(JNIEnv* env, jobject thiz, jint a, jint b) {
    return (jint)(a + b);
}
 
/*
 * Array of methods.
 *
 * Each entry has three fields: the name of the method, the method
 * signature, and a pointer to the native implementation.
 */
static const JNINativeMethod gMethods[] = {
    { "add", "(II)I", (void*)Hello_add }
};
 
 
/*
 * Explicitly register all methods for our class.
 *
 * Returns 0 on success.
 */
static int registerMethods(JNIEnv* env) {
    static const char* const kClassName = "com/Hello/Hello";
    jclass clazz;
 
    /* look up the class */
    clazz = env->FindClass(kClassName);
    if (clazz == NULL) {
        LOGE("Can't find class %s\n", kClassName);
        return -1;
    }
 
    /* register all the methods */
    if (env->RegisterNatives(clazz, gMethods,
            sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
    {
        LOGE("Failed registering methods for %s\n", kClassName);
        return -1;
    }
 
    return 0;
}
 
 
/*
 * This is called by the VM when the shared library is first loaded.
 */
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;
 
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);
 
    if (registerMethods(env) != 0) {
        LOGE("ERROR: native registration failed\n");
        goto bail;
    }
 
    /* success -- return valid version number */
    result = JNI_VERSION_1_4;
 
bail:
    return result;
}

Not much to be said about the add function. Just takes two numbers and adds them (hence its name!). JNI conventions mean that the first two arguments, environment and objects are implicitly passed to it from Java. The Java code will just call add(x,y).

Then you need to declare this method in an array. The hard part, I think, is the signature, which is quite strange. Some help can be found here.

This array is then use when you actually register the methods in the class, through RegisterNatives. The code first has to get the class by its name, so do not forget to put the complete Java name in kClassName.

Again, here is the Android.mk :

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
 
LOCAL_MODULE_TAGS := samples
 
LOCAL_MODULE:= libHello
 
LOCAL_SRC_FILES:= Hello.cpp
 
# Additional libraries, maybe more than actually needed
LOCAL_SHARED_LIBRARIES := \
        libandroid_runtime \
        libnativehelper \
        libcutils \
        libutils
 
# JNI headers
LOCAL_C_INCLUDES += $(JNI_H_INCLUDE)
 
LOCAL_PRELINK_MODULE := false
 
include $(BUILD_SHARED_LIBRARY)

If you put both files (Android.mk and Hello.cpp), in say ~/external/test/jni/ and run mm, you'll get libHello.so. Good.

How to use this stuff ?

Good question. So first, you have to install the Hello.apk. Not too hard.

Then, you need to put libHello.so into /system/lib. You need to be root to do that. In the Java code, you can also call System.load() instead of System.loadLibrary() to use full pathname and put the library wherever you want.

Then you just have to launch the app, and you'll get to know what 3+5 are equal to.

Discussion

Android, 2009/12/30 12:17

Maybe you should mention the NDK at http://developer.android.com/sdk/ndk/1.6_r1/index.html which provides the JNI interface, etc.

fred, 2010/01/15 07:23

Thanks for mentioning this. When that post was written, the NDK was still just an idea being discussed on the Android mailing lists.

Heck, when the majority of these posts were written, even the SDK was not released!

Hareton, 2010/08/09 08:26

Really, no one writes JNI code by hand these days. I can state that fater reading lots of articles and listening to lots of interviews found by means of http://www.mp3hunting.com SE. Use SWIG if you have to. Or libffi/JNA if you want something akin to C#'s DllImport. If JNA doesn't work on Android, that's more the fault of Google's custom VM and runtime than of Java as a language.JNI is indeed quite old, and has not evolved very much for the sake of compatibility…

Enter your comment (wiki syntax is allowed):
ONQGM
 
wiki/jni.txt · Last modified: 2009/02/23 11:37 by fred
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki