【Android】Android JNI

1、簡介

JNI,Java Native Interface,用於Java與C/C++相互調用的接口,下面舉例說明其用法。

1)java調用native接口

第一步,將需要本地實現的Java方法加上native聲明,如下面例子中TestJNI類中的testJniAdd方法。

// TestJNI.java
class TestJNI
{
	private native int testJniAdd(int v1, int v2); // using native to declaration
}

第二步,使用javac命令編譯java類,如下面例子中編譯 TestJNI.java 後生成TestJNI.class文件。

javac TestJNI.java

第三步,使用javah生成.h頭文件,如下面例子中生成的TestJNI.h文件。

javah TestJNI
// TestJNI.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestJNI */

#ifndef _Included_TestJNI
#define _Included_TestJNI

#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     TestJNI
 * Method:    testJniAdd
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_TestJNI_testJniAdd(JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif

#endif // _Included_TestJNI

從上面自動生成的代碼可以看出,jni有一定的規則,一個是數據類型和宏定義,這個是C/C++跨平臺的慣例,再一個是函數簽名規則,下面會介紹。
第四步,在本地代碼中實現native方法,如下面例子中TestJNI.c的Java_TestJNI_testJniAdd。

// TestJNI.c
#include <stdio.h>
#include "TestJNI.h"

JNIEXPORT jint JNICALL Java_TestJNI_testJniAdd(JNIEnv *env, jobject obj, jint v1, jint v2)
{	
	int ret = v1 + v2;
	printf("%s called, %d + %d = %d\n", __func__, v1, v2, ret);

	return v1 + v2;
}

第五步,編譯上述的本地方法,生成動態鏈接庫,如下面例子中使用gcc命令編譯生成libjnitest.so
gcc -fpic -shared -o libjnitest.so TestJNI.c -I<jni.h所在目錄>
第六步,在Java類中加載這一動態鏈接庫,如下面例子使用System.loadLibrary(“jnitest”)。

// TestJNI.java
class TestJNI
{
	private native int testJniAdd(int v1, int v2); // using native to declaration

	static
	{
		System.loadLibrary("jnitest"); // load native lib with xxx of libxxx.so from the right path
	}
}

第七步,Java代碼中的其它地方可以正常調用這一native方法,完整代碼如下所示。

// TestJNI.java
class TestJNI
{
	private native int testJniAdd(int v1, int v2); // using native to declaration

	private void test()
	{
		System.out.println("called native from java: testJniAdd(10, 11) is " + testJniAdd(10, 11));
	}

	public static void main(String args[])
	{
		new TestJNI().test();
	}

	static
	{
		System.loadLibrary("jnitest"); // load native lib with xxx of libxxx.so from the right path
	}
}

第八步,運行,首先需要導出前面生成的native lib路徑,然後使用java命令運行,如下面例子中native lib庫和運行java命令在同一目錄。

export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
java TestJNI

最後,運行結果如下。

Java_TestJNI_testJniAdd called, 10 + 11 = 21
called native from java: testJniAdd(10, 11) is 21

2)native調用java接口

在上面介紹的java調用native接口的基礎上,如下面例子,native調用java接口(native的Java_TestJNI_testJniAdd中調用java的negate)時,首先通過JNIEnv FindClass找到java類(TestJNI),然後通過JNIEnv GetMethodID找到java方法(negate),最後通過JNIEnv CallIntMethod調用java方法。

// TestJNI.java
class TestJNI
{
	private native int testJniAdd(int v1, int v2); // using native to declaration

	private void test()
	{
		System.out.println("called native from java: testJniAdd(10, 11) is " + testJniAdd(10, 11));
	}

	public int negate(int v)
	{
		int ret = -v;
		System.out.println("negate called from native: " + ret);
		return ret;
	}

	public static void main(String args[])
	{
		new TestJNI().test();
	}

	static
	{
		System.loadLibrary("jnitest"); // load native lib with xxx of libxxx.so from the right path
	}
}
// TestJNI.c
#include <stdio.h>
#include <stdlib.h>
#include "TestJNI.h"

JNIEXPORT jint JNICALL Java_TestJNI_testJniAdd(JNIEnv *env, jobject obj, jint v1, jint v2)
{
	printf("%s called\n", __func__);

	jclass cls = (*env)->FindClass(env, "TestJNI");
	jmethodID mid = (*env)->GetMethodID(env, cls, "negate", "(I)I");
	jint ret = (*env)->CallIntMethod(env, obj, mid, 12);
	printf("called java from native: negate(12) is %d\n", ret);

	return v1 + v2;
}

最後,運行結果如下。

Java_TestJNI_testJniAdd called
called java from native: negate(12) is -12
called native from java: testJniAdd(10, 11) is 21

2、jni.h

下面來看看jni.h中定義了哪些內容。

1)基本類型

/* Primitive types that match up with Java equivalents. */
typedef uint8_t  jboolean; /* unsigned 8 bits */
typedef int8_t   jbyte;    /* signed 8 bits */
typedef uint16_t jchar;    /* unsigned 16 bits */
typedef int16_t  jshort;   /* signed 16 bits */
typedef int32_t  jint;     /* signed 32 bits */
typedef int64_t  jlong;    /* signed 64 bits */
typedef float    jfloat;   /* 32-bit IEEE 754 */
typedef double   jdouble;  /* 64-bit IEEE 754 */

/* "cardinal indices and sizes" */
typedef jint     jsize;

2)C++中的非基本類型

/*
 * Reference types, in C++
 */
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};

typedef _jobject*       jobject;
typedef _jclass*        jclass;
typedef _jstring*       jstring;
typedef _jarray*        jarray;
typedef _jobjectArray*  jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray*    jbyteArray;
typedef _jcharArray*    jcharArray;
typedef _jshortArray*   jshortArray;
typedef _jintArray*     jintArray;
typedef _jlongArray*    jlongArray;
typedef _jfloatArray*   jfloatArray;
typedef _jdoubleArray*  jdoubleArray;
typedef _jthrowable*    jthrowable;
typedef _jobject*       jweak;

3)C中的非基本類型

/*
 * Reference types, in C.
 */
typedef void*           jobject;
typedef jobject         jclass;
typedef jobject         jstring;
typedef jobject         jarray;
typedef jarray          jobjectArray;
typedef jarray          jbooleanArray;
typedef jarray          jbyteArray;
typedef jarray          jcharArray;
typedef jarray          jshortArray;
typedef jarray          jintArray;
typedef jarray          jlongArray;
typedef jarray          jfloatArray;
typedef jarray          jdoubleArray;
typedef jobject         jthrowable;
typedef jobject         jweak;

4)變量field與函數method

struct _jfieldID;                       /* opaque structure */
typedef struct _jfieldID* jfieldID;     /* field IDs */

struct _jmethodID;                      /* opaque structure */
typedef struct _jmethodID* jmethodID;   /* method IDs */

5)函數簽名

typedef union jvalue {
    jboolean    z;
    jbyte       b;
    jchar       c;
    jshort      s;
    jint        i;
    jlong       j;
    jfloat      f;
    jdouble     d;
    jobject     l;
} jvalue;

6)引用類型

typedef enum jobjectRefType {
    JNIInvalidRefType = 0,
    JNILocalRefType = 1,
    JNIGlobalRefType = 2,
    JNIWeakGlobalRefType = 3
} jobjectRefType;

jni支持三種引用類型,Local、Global和Weak Global。引用有自己的的生命週期,Local是自釋放的,Global和Weak Global需要手動釋放。Local和Global引用的對象不會被gc回收,而Weak Global引用的對象可以被gc回收。Local是線程獨立的,只能在創建它的線程使用,而Global和Weak Global是支持跨線程使用的。

7)一個很重要的函數結構

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

8)兩個很重要的數據類型:JNIEnv和JavaVM,C和C++的實現不同。

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

JNIEnv是個Java虛擬機中的Runtime變量,如上面的FindClass、GetMethodID、CallIntMethod等,都在JNIEnv中定義;JavaVM則用於Java虛擬機管理相關,如獲取JNIEnv、線程管理等。

9)其它

最後就是一些jni相關的宏定義。

3、jni原理

Java運行時需要load本地jni庫,最終通過dlopen完成。
jni支持靜態註冊和動態註冊。上面的例子就是靜態註冊,可以看出,靜態註冊必須遵循一定的規則,函數名過長,格式爲Java_Java類名_Java函數名,前面有JNIEXPORT和JNICALL宏標記,函數參數的第一個參數爲JNIEnv*,第二個參數爲jobject,後面纔是Java中的函數參數,這裏的jobject就是對應的Java類,多個class需javah多遍,運行時查找效率不高。
關於動態註冊,java在load本地lib時,會調用JNI_OnLoad,對應的還有個JNI_OnUnload,我們可以提供一個java和native的函數映射表,並註冊給JVM,這樣,JVM就通過函數映射表來調用相關的函數。註冊函數爲RegisterNatives,對應的反註冊的函數爲UnregisterNatives。
動態註冊使用了上面提到的JNINativeMethod,name表示java中native關鍵字聲明的函數名,
signature表示函數簽名,包括參數類型和返回值類型,fnPtr表示對應的native的函數。

4、Android jni

Android jni代碼位置爲:frameworks/base/core/jni。從其中的Android.bp中可以看出,jni編譯後的結果爲libandroid_runtime動態庫。
加載libandroid_runtime的地方有兩處,一個在frameworks/native/services/surfaceflingerDdmConnection.cpp中的dlopen,另一個在frameworks/base/tools/preload/loadclass/LoadClass中的loadLibrary。
Android jni使用了動態加載,由AndroidRuntime統一註冊,註冊時使用了libnativehelper庫進行傳送,不過最終還是調用的jni本身的RegisterNatives函數完成註冊工作。

5、AndroidRuntime

在AndroidRuntime中定義了啓動的四種模式,分別是Zygote、SystemServer、Application和Tool,如下面的enum。

    enum StartMode {
        Zygote,
        SystemServer,
        Application,
        Tool,
    };

在AndroidRuntime有四個重要的回調,可以看出AndroidRuntime的四個階段,分別是onVmCreated、onStarted、onZygoteInit、onExit,代碼如下。

/**
     * This gets called after the VM has been created, but before we
     * run any code. Override it to make any FindClass calls that need
     * to use CLASSPATH.
     */
    virtual void onVmCreated(JNIEnv* env);

    /**
     * This gets called after the JavaVM has initialized.  Override it
     * with the system's native entry point.
     */
    virtual void onStarted() = 0;

    /**
     * This gets called after the JavaVM has initialized after a Zygote
     * fork. Override it to initialize threads, etc. Upon return, the
     * correct static main will be invoked.
     */
    virtual void onZygoteInit() { }

    /**
     * Called when the Java application exits to perform additional cleanup actions
     * before the process is terminated.
     */
    virtual void onExit(int /*code*/) { }

在AndroidRuntime的start函數中,通過startReg註冊jni,代碼如下。

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    /*
     * Register android functions.
     */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }
}

在startReg中調用register_jni_process進行註冊。

/*
 * Register android native functions with the VM.
 */
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
}

這裏有個關鍵的變量gRegJNI,定義如下。

static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_com_android_internal_os_RuntimeInit),
    REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),
    REG_JNI(register_android_os_SystemClock),
    REG_JNI(register_android_util_EventLog),
    REG_JNI(register_android_util_Log),
    // ...
};

REG_JNI是個宏,其中的參數就是個函數名,分佈在各個文件,在AndroidRuntime.cpp中使用extern進行了聲明,如下代碼。

extern int register_android_os_Binder(JNIEnv* env);
extern int register_android_os_Process(JNIEnv* env);
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
// ...

下面是REG_JNI宏的定義,其實就是個函數指針的類型定義。

    #define REG_JNI(name)      { name }
    struct RegJNIRec {
        int (*mProc)(JNIEnv*);
    };

然後在register_jni_procs中循環調用gRegJNI數組中各個mProc即對應的函數名,從而完成jni註冊,如下代碼。

static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
    for (size_t i = 0; i < count; i++) {
        if (array[i].mProc(env) < 0) {
#ifndef NDEBUG
            ALOGD("----------!!! %s failed to load\n", array[i].mName);
#endif
            return -1;
        }
    }
    return 0;
}

在各個文件的jni註冊函數中,調用了core_jni_helpers.h中的RegisterMethodsOrDie,接着調用了AndroidRuntime::registerNativeMethods,然後調用了libnativehelper的JNIHelp.cpp中的jniRegisterNativeMethods,最終調用jnit本身的RegisterNatives完成註冊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章