Android之JNI動態註冊native方法和JNI資料簡單使用
1、介紹JNI註冊方式
JVM 查詢 native 方法有兩種方式: 1)、按照 JNI 規範的命名規則(靜態註冊) 2) 、呼叫 JNI 提供的 RegisterNatives 函式,將本地函式註冊到 JVM 中(動態註冊)
2、動態註冊的步驟
先看有幾個檔案
1、MainActivity.java
package com.example.chenyu.test; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.widget.TextView; public class MainActivity extends AppCompatActivity { public static final String TAG = "TestJni"; public TextView mTv; public JniClient mJniClient; static { System.loadLibrary("FirstJni"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTv = (TextView) findViewById(R.id.text); mJniClient = new JniClient(); String result = mJniClient.getStr(); int sum = mJniClient.addInt(2, 3); Log.d(TAG, " mTv.setText before"); mTv.setText("string is" + result + " and 2 + 3 is " + sum); Log.d(TAG, " mTv.setText after"); } }
2、JniClient.java檔案
package com.example.chenyu.test;
public class JniClient {
public JniClient() {
}
public native String getStr();
public native int addInt(int a, int b);
}
3、JniClient.c檔案(新建一個資料夾jni,然後把這個檔案放在jni資料夾裡面)
// // Created by chenyu on 5/7/17. // #include <stdlib.h> #include <string.h> #include <stdio.h> #include <jni.h> #include <assert.h> #define JNIREG_CLASS "com/example/chenyu/test/JniClient"//指定要註冊的類 jstring get_strstr(JNIEnv* env, jobject thiz) { return (*env)->NewStringUTF(env, "I am chenyu, 動態註冊JNI"); } jint add_int(JNIEnv* env, jobject jobj, jint num1, jint num2){ return num1 + num2; } /** * 方法對應表 */ static JNINativeMethod gMethods[] = { {"getStr", "()Ljava/lang/String;", (void*)get_str}, {"addInt", "(II)I", (void*)add_int}, }; /* * 為某一個類註冊本地方法 */ static int registerNativeMethods(JNIEnv* env , const char* className , JNINativeMethod* gMethods, int numMethods) { jclass clazz; clazz = (*env)->FindClass(env, className); if (clazz == NULL) { return JNI_FALSE; } if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE; } /* * 為所有類註冊本地方法 */ static int registerNatives(JNIEnv* env) { return registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0])); } /* * System.loadLibrary("lib")時呼叫 * 如果成功返回JNI版本, 失敗返回-1 */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { return -1; } assert(env != NULL); if (!registerNatives(env)) {//註冊 return -1; } //成功 result = JNI_VERSION_1_4; return result; }
我們先說明JniClient.c檔案裡面內容
1)、JNIEXPORT和JNICALL含義
我們先看jni.h檔案,裡面包含了標頭檔案 jni_md.h檔案
我們再來看jni_md.h檔案
所以JNIEXPORT 和 JNICALL 是一個空定義
JNIEXPORT jint JNICALL Java_com_example_firstjni_JniClient_AddInt
(JNIEnv *, jclass, jint, jint);
第一個引數:JNIEnv* 是定義任意 native 函式的第一個引數(包括呼叫 JNI 的 RegisterNatives 函式註冊的函式),指向 JVM 函式表的指標,函式表中的每一個入口指向一個 JNI 函式,每個函式用於訪問 JVM 中特定的資料結構。 第二個引數:呼叫 Java 中 native 方法的例項或 Class 物件,如果這個 native 方法是例項方法,則該引數是 jobject,如果是靜態方法,則是 jclass。 第三個引數:Java 對應 JNI 中的資料型別,Java 中 int 型別對應 JNI 的 jint 型別。(後面會詳細介紹 JAVA 與 JNI 資料型別的對映關係)
函式返回值型別:夾在 JNIEXPORT 和 JNICALL 巨集中間的 jint,表示函式的返回值型別,對應 Java 的int 型別
我們用RegisterNatives動態獲取本地方法
我們先看JNINativeMethod 結構體的官方定義
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
第一個變數name是Java中函式的名字。 第二個變數signature,用字串是描述了Java中函式的引數和返回值 第三個變數fnPtr是函式指標,指向native函式。前面都要接 (void *)
所以JniClient.c檔案裡面有下面的程式碼
/**
* 方法對應表
*/
static JNINativeMethod gMethods[] = {
{"getStr", "()Ljava/lang/String;", (void*)get_str},
{"addInt", "(II)I", (void*)add_int},
};
第一個引數就是我們寫的方法,第三個就是.h檔案裡面的方法,主要是第二個引數比較複雜.括號裡面表示引數的型別,括號後面表示返回值。 “()” 中的字元表示引數,後面的則代表返回值。例如”()V” 就表示void * Fun(); “(II)V” 表示 void Fun(int a, int b); “(II)I” 表示 int addInt(int a, int b); "()Ljava/lang/String;" 表示String getStr(); 這些字元與函式的引數型別的對映表如下: 字元 J型別 java型別
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
陣列則以”[“開始,用兩個字元表示
[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
[Z jbooleanArray boolean[]
如圖:
物件型別:以”L”開頭,以”;”結尾,中間是用”/” 隔開。如上表第1個 陣列型別:以”[“開始。如上表第2個(n維陣列的話,則是前面多少個”[“而已,如”[[[D”表示“double[][][]”) 如果Java函式的引數是class,則以”L”開頭,以”;”結尾中間是用”/” 隔開的包及類名。而其對應的C函式名的引數則為jobject. 一個例外是String類,其對應的類為jstring
Ljava/lang/String; String jstring
Ljava/net/Socket; Socket jobject
如果JAVA函式位於一個嵌入類,則用作為類名間的分隔符。例如“(Ljava/lang/String;Landroid/os/FileUtilsFileStatus;)Z”
重寫JNI_OnLoad()方法這樣就會當呼叫 System.loadLibrary(“XXXX”)方法的時候直接來呼叫JNI_OnLoad(),這樣就達到了動態註冊實現native方法的作用。
/*
* System.loadLibrary("lib")時呼叫
* 如果成功返回JNI版本, 失敗返回-1
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
if (!registerNatives(env)) {//註冊
return -1;
}
//成功
result = JNI_VERSION_1_4;
return result;
}
為類註冊本地方法
/*
* 為所有類註冊本地方法
*/
static int registerNatives(JNIEnv* env) {
return registerNativeMethods(env, JNIREG_CLASS, gMethods,sizeof(gMethods) / sizeof(gMethods[0]));
}
我們執行程式碼的時候要記得在build.gradle檔案加上部分生成so檔案的程式碼
defaultConfig {
applicationId "com.example.chenyu.test"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
ndk{
moduleName "FirstJni"
ldLibs "log", "z", "m"
abiFilters "armeabi", "armeabi-v7a", "x86"
//用於指定應用應該使用哪個標準庫,此處新增c++庫支援
stl "stlport_static" // 支援stl
cFlags "-fexceptions" // 支援exception
}
sourceSets.main{
jniLibs.srcDirs = ['libs']
}
}
然後我們進入這個專案的jni目錄,然後執行命令
ndk-build
然後就會在libs資料夾下面的armeabi資料夾下面生成libFirstJni.so檔案
3、JNI資料型別及常用方法
基本型別和本地等效型別表:
介面函式表:
const struct JNINativeInterface ... = {
NULL,
NULL,
NULL,
NULL,
GetVersion,
DefineClass,
FindClass,
NULL,
NULL,
NULL,
GetSuperclass,
IsAssignableFrom,
NULL,
Throw,
ThrowNew,
ExceptionOccurred,
ExceptionDescribe,
ExceptionClear,
FatalError,
NULL,
NULL,
NewGlobalRef,
DeleteGlobalRef,
DeleteLocalRef,
IsSameObject,
NULL,
NULL,
AllocObject,
NewObject,
NewObjectV,
NewObjectA,
GetObjectClass,
IsInstanceOf,
GetMethodID,
CallObjectMethod,
CallObjectMethodV,
CallObjectMethodA,
CallBooleanMethod,
CallBooleanMethodV,
CallBooleanMethodA,
CallByteMethod,
CallByteMethodV,
CallByteMethodA,
CallCharMethod,
CallCharMethodV,
CallCharMethodA,
CallShortMethod,
CallShortMethodV,
CallShortMethodA,
CallIntMethod,
CallIntMethodV,
CallIntMethodA,
CallLongMethod,
CallLongMethodV,
CallLongMethodA,
CallFloatMethod,
CallFloatMethodV,
CallFloatMethodA,
CallDoubleMethod,
CallDoubleMethodV,
CallDoubleMethodA,
CallVoidMethod,
CallVoidMethodV,
CallVoidMethodA,
CallNonvirtualObjectMethod,
CallNonvirtualObjectMethodV,
CallNonvirtualObjectMethodA,
CallNonvirtualBooleanMethod,
CallNonvirtualBooleanMethodV,
CallNonvirtualBooleanMethodA,
CallNonvirtualByteMethod,
CallNonvirtualByteMethodV,
CallNonvirtualByteMethodA,
CallNonvirtualCharMethod,
CallNonvirtualCharMethodV,
CallNonvirtualCharMethodA,
CallNonvirtualShortMethod,
CallNonvirtualShortMethodV,
CallNonvirtualShortMethodA,
CallNonvirtualIntMethod,
CallNonvirtualIntMethodV,
CallNonvirtualIntMethodA,
CallNonvirtualLongMethod,
CallNonvirtualLongMethodV,
CallNonvirtualLongMethodA,
CallNonvirtualFloatMethod,
CallNonvirtualFloatMethodV,
CallNonvirtualFloatMethodA,
CallNonvirtualDoubleMethod,
CallNonvirtualDoubleMethodV,
CallNonvirtualDoubleMethodA,
CallNonvirtualVoidMethod,
CallNonvirtualVoidMethodV,
CallNonvirtualVoidMethodA,
GetFieldID,
GetObjectField,
GetBooleanField,
GetByteField,
GetCharField,
GetShortField,
GetIntField,
GetLongField,
GetFloatField,
GetDoubleField,
SetObjectField,
SetBooleanField,
SetByteField,
SetCharField,
SetShortField,
SetIntField,
SetLongField,
SetFloatField,
SetDoubleField,
GetStaticMethodID,
CallStaticObjectMethod,
CallStaticObjectMethodV,
CallStaticObjectMethodA,
CallStaticBooleanMethod,
CallStaticBooleanMethodV,
CallStaticBooleanMethodA,
CallStaticByteMethod,
CallStaticByteMethodV,
CallStaticByteMethodA,
CallStaticCharMethod,
CallStaticCharMethodV,
CallStaticCharMethodA,
CallStaticShortMethod,
CallStaticShortMethodV,
CallStaticShortMethodA,
CallStaticIntMethod,
CallStaticIntMethodV,
CallStaticIntMethodA,
CallStaticLongMethod,
CallStaticLongMethodV,
CallStaticLongMethodA,
CallStaticFloatMethod,
CallStaticFloatMethodV,
CallStaticFloatMethodA,
CallStaticDoubleMethod,
CallStaticDoubleMethodV,
CallStaticDoubleMethodA,
CallStaticVoidMethod,
CallStaticVoidMethodV,
CallStaticVoidMethodA,
GetStaticFieldID,
GetStaticObjectField,
GetStaticBooleanField,
GetStaticByteField,
GetStaticCharField,
GetStaticShortField,
GetStaticIntField,
GetStaticLongField,
GetStaticFloatField,
GetStaticDoubleField,
SetStaticObjectField,
SetStaticBooleanField,
SetStaticByteField,
SetStaticCharField,
SetStaticShortField,
SetStaticIntField,
SetStaticLongField,
SetStaticFloatField,
SetStaticDoubleField,
NewString,
GetStringLength,
GetStringChars,
ReleaseStringChars,
NewStringUTF,
GetStringUTFLength,
GetStringUTFChars,
ReleaseStringUTFChars,
GetArrayLength,
NewObjectArray,
GetObjectArrayElement,
SetObjectArrayElement,
NewBooleanArray,
NewByteArray,
NewCharArray,
NewShortArray,
NewIntArray,
NewLongArray,
NewFloatArray,
NewDoubleArray,
GetBooleanArrayElements,
GetByteArrayElements,
GetCharArrayElements,
GetShortArrayElements,
GetIntArrayElements,
GetLongArrayElements,
GetFloatArrayElements,
GetDoubleArrayElements,
ReleaseBooleanArrayElements,
ReleaseByteArrayElements,
ReleaseCharArrayElements,
ReleaseShortArrayElements,
ReleaseIntArrayElements,
ReleaseLongArrayElements,
ReleaseFloatArrayElements,
ReleaseDoubleArrayElements,
GetBooleanArrayRegion,
GetByteArrayRegion,
GetCharArrayRegion,
GetShortArrayRegion,
GetIntArrayRegion,
GetLongArrayRegion,
GetFloatArrayRegion,
GetDoubleArrayRegion,
SetBooleanArrayRegion,
SetByteArrayRegion,
SetCharArrayRegion,
SetShortArrayRegion,
SetIntArrayRegion,
SetLongArrayRegion,
SetFloatArrayRegion,
SetDoubleArrayRegion,
RegisterNatives,
UnregisterNatives,
MonitorEnter,
MonitorExit,
GetJavaVM,
};
JNI與C/C++資料型別的轉換(效率開發)字元陣列與jbyteArray
jbyteArray轉字元陣列
int byteSize = (int) env->GetArrayLength(jbyteArrayData); //jbyteArrayData是jbyteArray型別的資料
unsigned char* data = new unsigned char[byteSize + 1];
env->GetByteArrayRegion(jbyteArrayData, 0, byteSize, reinterpret_cast<jbyte*>(data));
data[byteSize] = '\0';
字元陣列轉jbyteArray
jbyte *jb = (jbyte*) data; //data是字元陣列型別
jbyteArray jarray = env->NewByteArray(byteSize); //byteSize是字元陣列大小
env->SetByteArrayRegion(jarray, 0, byteSize, jb);
字元陣列與jstring jstring轉字元陣列
char* JstringToChar(JNIEnv* env, jstring jstr) {
if(jstr == NULL) {
return NULL;
}
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes",
"(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
rtn = (char*) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
字元陣列轉jstring
jstring StrtoJstring(JNIEnv* env, const char* pat)
{
jclass strClass = env->FindClass("java/lang/String");
jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = env->NewByteArray(strlen(pat));
env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
jstring encoding = env->NewStringUTF("utf-8");
return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
}
最簡單的可以直接使用
jstring jstr = env->NewStringUTF(str);
jint與int的互轉都可以直接使用強轉,如:
jint i = (jint) 1024;