文章目錄
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完成註冊。