JNI 動態註冊和靜態註冊的詳解
1. 什麼是JNI?
JNI的英文縮寫是 java nativie interface ,按照字面解釋就是java 本地介面。什麼樣的接口才叫nativie interface ,用c/c++寫程式碼。所以JNI是用c++語言編寫的介面供java呼叫。
2.為什麼JNI用C++寫得程式碼可以供java使用,兩個是完全不同的語言,他們是如何轉換的
我們用java中寫native方法:
public native void native_init(); //關鍵字native
使用javah 可以生成類似這樣的標頭檔案
#include <jni.h>
/* Header for class aai_along_and_jni_test2_JNI_test1 */
#ifndef _Included_aai_along_and_jni_test2_JNI_test1
#define _Included_aai_along_and_jni_test2_JNI_test1
#ifdef __cplusplus
extern "C" {
#endif
裡面有個#include <jni.h> ,#include 就是c/c++引用標頭檔案的方式。所以答案也就在這個jni.h裡面。對jni有點了解的應該知道jni的幾個資料型別,c++的資料型別跟JNI的資料型別對應關係都定義在jni.h裡面,如下。
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 */
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;
jni.h檔案配合jvm,這樣就完成了c++層的資料與java層的資料轉換過程,因此native可以傳遞值到java ,java 也可以傳值到native實現資料的相互通訊關係。其實從中也是可以看出來JNI只是做了一個轉換的工作:資料轉換的過程。
3.靜態註冊
1)載入jni庫並且載入nativie方法,本文的JNI庫名為:jni-test
public class JNIStaticTest{
public String name;
public Context testContext;
private final String TAG="JNIStaticTest-java";
//載入的jni庫,庫名:libjni-test.so ,載入的時候省略lib和.so
static {
System.loadLibrary("jni-test");
}
public JNIStaticTest(){
native_init();
}
public JNIStaticTest(Context context){
Log.d(TAG,"初始化");
testContext=context;
native_init();
}
public void setName(String name1){
Log.i(TAG,"setName:"+name1);
name =name1;
}
public String getName(){
Log.i(TAG,"getName:"+name);
return name;
}
public String transimFromJNI(String from,ExternClass inner){
Log.i(TAG,"from="+from+",ExternClass name="+inner.className);
String returnString="Java have get information";
return returnString;
}
//以下內地方法實現在native層,這裡只是作為函式的呼叫介面。
public native String stringFromJNI();
public native void native_init();
public native void transmitToJNI(String from, ExternClass inner);
}
可能你會問,系統是怎麼識別到我的lib庫呢?系統會在三個資料夾去搜索system/lib ,vend/lib ,your app package/lib。/data/app/aai.along.and.jni/lib/arm/libjni-test.so 這是在我的app安裝目錄下找到的。如果這三個目錄都沒有找到lib那麼就會報UnsatisfiedLinkError異常。在libcore\ojluni\src\main\java\java\lang\Runtime.java 程式碼裡面有這樣的描述。
public void loadLibrary(String libname, ClassLoader classLoader) {
checkTargetSdkVersionForLoad("java.lang.Runtime#loadLibrary(String, ClassLoader)");
java.lang.System.logE("java.lang.Runtime#loadLibrary(String, ClassLoader)" +
" is private and will be removed in a future Android release");
loadLibrary0(classLoader, libname);
}
synchronized void loadLibrary0(ClassLoader loader, String libname) {
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
}
String libraryName = libname;
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
// It's not necessarily true that the ClassLoader used
// System.mapLibraryName, but the default setup does, and it's
// misleading to say we didn't find "libMyLibrary.so" when we
// actually searched for "liblibMyLibrary.so.so".
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\"");
}
String error = doLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
String lastError = null;
for (String directory : getLibPaths()) {
String candidate = directory + filename;
candidates.add(candidate);
if (IoUtils.canOpenReadOnly(candidate)) {
String error = doLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
}
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
private volatile String[] mLibPaths = null;
private String[] getLibPaths() {
if (mLibPaths == null) {
synchronized(this) {
if (mLibPaths == null) {
mLibPaths = initLibPaths();
}
}
}
return mLibPaths;
}
想要更詳細的瞭解載入過程,可以參考這篇博文https://my.oschina.net/wolfcs/blog/129696
2) java層的我們已經寫完,現在開始轉到native層。native 層的方法命名是有一定的規則的,簡單點就是:包名+類名+方法名 由於jni中的點有特殊用處,所以點用_替代 。我java層:包名:aai.along.and.jni 類名:JNIStaticTest 方法名:native_init() 使用javah 生產的nativie方法名是:JNIEXPORT void JNICALL Java_aai_along_and_jni_JNIStaticTest_native_1init (JNIEnv *, jobject); 不對啊,跟剛才說的規則不同,多了JNIEXPORT JNICALL 而且native_1init 比native_init 多了一個1.JNIEXPORT JNICALL 這兩個都是jni的標誌性,告訴系統這個是jni方法,沒什麼特殊含義。而native_1init 裡面多了一個1確實需要特別注意的,只有當你的方法名、類名、包名中有_後面都需要加1.這是jni的語言規則。很多文章都沒講這個特殊性,是個大坑貨。
3)android studio 如果生產native的標頭檔案呢?
1.選擇setings
2.選擇External Tools
3.選擇+ 建立新的工具。
.
4填寫新的工具引數
5.程式碼中使用,在使用這個工具前,先要保證有生成對應的.class檔案,執行make project
需要注意點是:引數的填寫。
program :$JDKPath$\bin\javah.exe
argument:-d $ModuleFileDir$/src/main/jni -classpath $ModuleSdkPath$\platforms\android-28\android.jar;$ModuleSdkPath$\extras/android/m2repository/com/android/support/appcompat-v7/25.3.1/appcompat-v7-25.3.1-sources.jar; $FileClass$
working directory :$ModuleFileDir$\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\
引數說明: $JDKPath$\bin\javah.exe javah的路徑 。-d $ModuleFileDir$/src/main/jni 生成的標頭檔案目錄, -classpath $ModuleSdkPath$\platforms\android-28\android.jar;$ModuleSdkPath$\extras/android/m2repository/com/android/support/appcompat-v7/25.3.1/appcompat-v7-25.3.1-sources.jar; 需要引用其他jar包路徑。$FileClass$ 需要生成的java檔案 $ModuleFileDir$\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\ 此處是表示你生成的class檔案所在的目錄。
這樣我們生成的.h檔案如下
#include <jni.h>
#include <android/log.h>
/* Header for class aai_along_and_jni_JNIStaticTest */
#ifndef _Included_aai_along_and_jni_JNIStaticTest
#define _Included_aai_along_and_jni_JNIStaticTest
#ifdef __cplusplus
extern "C" {
#endif
#define LOG_TAG "JNIStaticTest-jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
/*
* Class: aai_along_and_jni_JNIStaticTest
* Method: stringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_aai_along_and_jni_JNIStaticTest_stringFromJNI
(JNIEnv *, jobject);
/*
* Class: aai_along_and_jni_JNIStaticTest
* Method: native_init
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_aai_along_and_jni_JNIStaticTest_native_1init
(JNIEnv *, jobject);
/*
* Class: aai_along_and_jni_JNIStaticTest
* Method: transmitToJNI
* Signature: (Ljava/lang/String;Laai/along/and/jni/ExternClass;)V
*/
JNIEXPORT void JNICALL Java_aai_along_and_jni_JNIStaticTest_transmitToJNI
(JNIEnv *, jobject, jstring, jobject);
#ifdef __cplusplus
}
#endif
#endif
其中如下的程式碼增加是我為了除錯列印log用的。因為native的程式碼屬於c++,上層的logcat就無法抓取到對應的log,除錯非常不方面,加了如下的log我們可以比較清楚的看到native 與java層的聯動。
#include <android/log.h>
#define LOG_TAG "JNIStaticTest-jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
4)java層呼叫native層,實現剛才的.h檔案的方法即可。參考如下程式碼。
#include <string>
#include <iostream>
#include <stdio.h>
//#include "jniUtils.h"
#include "aai_along_and_jni_JNIStaticTest.h"
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
//extern "C" JNIEXPORT jstring JNICALL
//extern "C" JNIEXPORT JNICALL
//extern "C" JNIEXPORT JNICALL
int file_size2(const char* filename);
using namespace std;
static const char* const kClassJniTest =
"aai/along/and/jni/JNIStaticTest";
static const char* const kClassJniExtern =
"aai/along/and/jni/ExternClass";
//儲存java層的fieldID 和methodID 以方便後續使用
struct fields_t {
jfieldID context;
jfieldID name;
jclass clazz;
jobject obj;//obj的儲存一定要使用NewGlobalRef的方法
jmethodID setNameMethodID;
jmethodID getNameMethodID;
jmethodID transimFromJNIMethodID;
}fields;
struct fields_t *Pfield;
jstring JNICALL Java_aai_along_and_jni_JNIStaticTest_stringFromJNI(
JNIEnv* env,
jobject obj ) {
LOGI("JAVA 呼叫 JNI stringFromJNI");
string hello = "Hello from JNI";
return env->NewStringUTF(hello.c_str());
}
void JNICALL Java_aai_along_and_jni_JNIStaticTest_native_1init
(JNIEnv *env, jobject object){
//printf("Java_aai_along_and_jni_JNIStaticTest_native_1init");
LOGI("JAVA 呼叫 JNI native_1init");
//初始化 方法 field id
Pfield=&fields;
jclass clazz = env->FindClass(kClassJniTest);//關聯native 和java層 獲取java層的class
Pfield->clazz=clazz;
if (Pfield->clazz == NULL) {
LOGE("can not find class");
return;
}
//獲取java層的方法id 並且儲存起來
Pfield->name = env->GetFieldID(clazz, "name", "Ljava/lang/String;");
if (Pfield->name == NULL) {
LOGE("can not find name ID");
return;
}
Pfield->context = env->GetFieldID(clazz, "testContext", "Landroid/content/Context;");
if (Pfield->context == NULL) {
LOGE("can not find context ID");
return;
}
Pfield->setNameMethodID = env->GetMethodID(
clazz,
"setName",
"(Ljava/lang/String;)V");
if (Pfield->setNameMethodID == NULL) {
LOGE("can not find setNameMethodID");
return;
}
Pfield->getNameMethodID = env->GetMethodID(
clazz,
"getName",
"()Ljava/lang/String;");
if (Pfield->getNameMethodID == NULL) {
LOGE("can not find getNameMethodID");
return;
}
Pfield->transimFromJNIMethodID = env->GetMethodID(
clazz,
"transimFromJNI",
"(Ljava/lang/String;Laai/along/and/jni/ExternClass;)Ljava/lang/String;");
if (Pfield->transimFromJNIMethodID == NULL) {
LOGE("can not find transimFromJNIMethodID");
return;
}
}
void JNICALL Java_aai_along_and_jni_JNIStaticTest_transmitToJNI
(JNIEnv * env, jobject thizz,jstring information, jobject obj){
const char *transmitString = env->GetStringUTFChars(information, NULL);
LOGI("JAVA 呼叫 JNI transmitToJNI transmitString=%s",transmitString);
Pfield->obj=env->NewGlobalRef(thizz);//想持久儲存thizz的物件,一定要使用NewGlobalRef 後續也要手動刪除
//初始化 field id 獲取obj 的name
jclass clazz=env->FindClass(kClassJniExtern);
jfieldID fieldNameID=env->GetFieldID(clazz, "className", "Ljava/lang/String;");
if (fieldNameID == NULL) {
LOGE("can not find className ");
return;
}
jobject objName;
objName=env->GetObjectField(obj,fieldNameID);
jstring jstringName=(jstring)(objName);
const char *charName = env->GetStringUTFChars(jstringName, NULL);
LOGI("JNI 呼叫 JAVA ExternClass className=%s",charName);
//呼叫java 層的方法。CallVoidMethod CallObjectMethod
string message = "JNI 呼叫 JAVA ";
jstring jMessage=env->NewStringUTF(message.c_str());
//通過CallVoidMethod方法,傳入之前獲取的MethodID 呼叫java層方法
env->CallVoidMethod(Pfield->obj, Pfield->setNameMethodID, jMessage);
jobject callName= env->CallObjectMethod(Pfield->obj, Pfield->getNameMethodID);
const char *callNameChar = env->GetStringUTFChars((jstring)callName, NULL);
LOGI("JNI 呼叫 JAVA getName=%s",callNameChar);
jobject calltransim= env->CallObjectMethod(Pfield->obj, Pfield->transimFromJNIMethodID, jMessage,obj);
const char *calltransimChar = env->GetStringUTFChars((jstring)calltransim, NULL);
LOGI("JNI 呼叫 JAVA transimFromJNI calltransimChar=%s",calltransimChar);
//垃圾回收
env->DeleteLocalRef(callName);
env->DeleteLocalRef(calltransim);
env->DeleteLocalRef(objName);
env->DeleteGlobalRef(Pfield->obj);
}
5)native 如何呼叫java層呢?
1.java層與native層的關聯;
jclass clazz = env->FindClass(kClassJniTest);
2.獲取java層的方法ID
Pfield->setNameMethodID = env->GetMethodID(
clazz,
"setName",
"(Ljava/lang/String;)V");
if (Pfield->setNameMethodID == NULL) {
LOGE("can not find setNameMethodID");
return;
}
3.向CallVoidMethod 方法,傳入之前獲取的setNameMethodID ,以及需要傳遞的引數值,jMessage
string message = "JNI 呼叫 JAVA ";
jstring jMessage=env->NewStringUTF(message.c_str());
env->CallVoidMethod(Pfield->obj, Pfield->setNameMethodID, jMessage);
具體的方法傳遞引數,可以參考jni.h檔案。
在第二步中Pfield->setNameMethodID = env->GetMethodID( clazz, "setName", "(Ljava/lang/String;)V"); 各個引數的含義是什麼呢?
jni.h 中的函式原型是這樣的:
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
{ return functions->GetMethodID(this, clazz, name, sig); }
jclass clazz 很好理解,就是與native關聯的java層的類。
const char* name 就是需要呼叫的方法名
const char* sig sig又是什麼鬼?之前沒聽過,一臉懵逼。這個是jni特有的稱呼。它的語法規則是:"(引數型別)返回型別"
引數型別和返回型別的規則需要按jni的方式。
由於我們java層的方法是setName(String name1) string型別,依據規則任何java類的全名:Ljava/lang/String; ;分號不要忘記
這樣我們的java層與native層就能聯動了,程式碼上傳在最後面。靜態註冊的方法基本已經講解完。靜態註冊的方式有很大的弊端就是編寫方法名稱太長,太不美觀了。所以我覺得還是動態註冊最好,最方便。
4.動態註冊
java層的程式碼跟靜態方式相同,就不囉嗦了,參考靜態註冊的程式碼。直接講解native層 .cpp檔案。
#include <jni.h>
#include <string>
#include <iostream>
#include <stdio.h>
#include <android/log.h>
//#include "JNIHelp.h"
#include <stdlib.h>
//#include "android_runtime/AndroidRuntime.h"
#define LOG_TAG "JNISharedTest-jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
using namespace std;
static const char* const kClassJniTest =
"aai/along/and/jni/JNISharedTest";
static const char* const kClassJniExtern =
"aai/along/and/jni/ExternClass";
//儲存java層的fieldID 和methodID 以方便後續使用
struct fields_t {
jfieldID context;
jfieldID name;
jclass clazz;
jobject obj;//obj的儲存一定要使用NewGlobalRef的方法
jmethodID setNameMethodID;
jmethodID getNameMethodID;
jmethodID transimFromJNIMethodID;
}fields;
struct fields_t *Pfield;
jstring JNISharedTest_stringFromJNI(JNIEnv* env, jobject obj){
LOGI("JAVA 呼叫 JNI stringFromJNI");
string hello = "Hello from JNI";
return env->NewStringUTF(hello.c_str());
}
void JNISharedTest_transmitToJNI(JNIEnv * env, jobject thizz,jstring information, jobject obj){
const char *transmitString = env->GetStringUTFChars(information, NULL);
LOGI("JAVA 呼叫 JNI transmitToJNI transmitString=%s",transmitString);
Pfield->obj=env->NewGlobalRef(thizz);//想持久儲存thizz的物件,一定要使用NewGlobalRef 後續也要手動刪除
//初始化 field id 獲取obj 的name
jclass clazz=env->FindClass(kClassJniExtern);
jfieldID fieldNameID=env->GetFieldID(clazz, "className", "Ljava/lang/String;");
if (fieldNameID == NULL) {
LOGE("can not find className ");
return;
}
jobject objName;
objName=env->GetObjectField(obj,fieldNameID);
jstring jstringName=(jstring)(objName);
const char *charName = env->GetStringUTFChars(jstringName, NULL);
LOGI("JNI 呼叫 JAVA ExternClass className=%s",charName);
//呼叫java 層的方法。CallVoidMethod CallObjectMethod
string message = "JNI 呼叫 JAVA ";
jstring jMessage=env->NewStringUTF(message.c_str());
env->CallVoidMethod(Pfield->obj, Pfield->setNameMethodID, jMessage);
jobject callName= env->CallObjectMethod(Pfield->obj, Pfield->getNameMethodID);
const char *callNameChar = env->GetStringUTFChars((jstring)callName, NULL);
LOGI("JNI 呼叫 JAVA getName=%s",callNameChar);
jobject calltransim= env->CallObjectMethod(Pfield->obj, Pfield->transimFromJNIMethodID, jMessage,obj);
const char *calltransimChar = env->GetStringUTFChars((jstring)calltransim, NULL);
LOGI("JNI 呼叫 JAVA transimFromJNI calltransimChar=%s",calltransimChar);
//垃圾回收
env->DeleteLocalRef(callName);
env->DeleteLocalRef(calltransim);
env->DeleteLocalRef(objName);
env->DeleteGlobalRef(Pfield->obj);
}
void JNISharedTest_native_init(JNIEnv* env,jobject thizz){
LOGI("JAVA 呼叫 JNI native_1init");
//初始化 方法 field id
Pfield=&fields;
jclass clazz = env->FindClass(kClassJniTest);
Pfield->clazz=clazz;
if (Pfield->clazz == NULL) {
LOGE("can not find class");
return;
}
Pfield->name = env->GetFieldID(clazz, "name", "Ljava/lang/String;");
if (Pfield->name == NULL) {
LOGE("can not find name ID");
return;
}
Pfield->context = env->GetFieldID(clazz, "testContext", "Landroid/content/Context;");
if (Pfield->context == NULL) {
LOGE("can not find context ID");
return;
}
Pfield->setNameMethodID = env->GetMethodID(
clazz,
"setName",
"(Ljava/lang/String;)V");
if (Pfield->setNameMethodID == NULL) {
LOGE("can not find setNameMethodID");
return;
}
Pfield->getNameMethodID = env->GetMethodID(
clazz,
"getName",
"()Ljava/lang/String;");
if (Pfield->getNameMethodID == NULL) {
LOGE("can not find getNameMethodID");
return;
}
Pfield->transimFromJNIMethodID = env->GetMethodID(
clazz,
"transimFromJNI",
"(Ljava/lang/String;Laai/along/and/jni/ExternClass;)Ljava/lang/String;");
if (Pfield->transimFromJNIMethodID == NULL) {
LOGE("can not find transimFromJNIMethodID");
return;
}
}
static const JNINativeMethod gMethods[] = {
{
"native_init",
"()V",
(void *)JNISharedTest_native_init
},
{
"transmitToJNI",
"(Ljava/lang/String;Laai/along/and/jni/ExternClass;)V",
(void *)JNISharedTest_transmitToJNI
},
{
"stringFromJNI",
"()Ljava/lang/String;",
(void *)JNISharedTest_stringFromJNI
},
};
static int registerNativeMethods(JNIEnv* env
, const char* className
, const JNINativeMethod* gMethod, int numMethods) {
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
LOGI(" JNI 註冊 失敗,沒發現此類");
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethod, numMethods) < 0) {
LOGI(" JNI 註冊 失敗");
return JNI_FALSE;
}
LOGI(" JNI 註冊 成功");
return JNI_TRUE;
}
static int register_along_jni(JNIEnv *env)
{
LOGI(" JNI 註冊");
/// return AndroidRuntime::registerNativeMethods(env,
// kClassJniTest, gMethods, NELEM(gMethods));
return registerNativeMethods(env, kClassJniTest, gMethods,
NELEM(gMethods));
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
LOGI(" JNI 載入");
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGE("ERROR: JNI 版本錯誤");
return JNI_ERR;
}
if (register_along_jni(env) == -1) {
LOGE("ERROR: JNI_OnLoad 載入失敗");
return JNI_ERR;
}
result = JNI_VERSION_1_6;
return result;
}
1)靜態方法是根據方法名稱來關聯native與java的。那麼動態註冊方式又是通過什麼方式關聯native層和java層呢?
通過
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */)函式,在java中執行
static {
System.loadLibrary("jni-shared");
}系統會呼叫JNI_OnLoad方法,所以我們動態關聯java層,只需要重寫這個方法就可以。
這裡需要特別說明一下,如果是在linux 環境編譯的話,可以去掉JNIEXPORT JNICALL 這兩個關鍵字。
2) 註冊native方法
if (env->RegisterNatives(clazz, gMethod, numMethods) < 0) {
LOGI(" JNI 註冊 失敗");
return JNI_FALSE;
}
方法原型是:
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
jint nMethods)
{ return functions->RegisterNatives(this, clazz, methods, nMethods); }
jclass clazz java的類,跟之前靜態註冊方法一樣。
jclass clazz = env->FindClass(kClassJniTest); kClassJniTest:類名,static const char* const kClassJniTest = "aai/along/and/jni/JNISharedTest";
const JNINativeMethod* methods :
static const JNINativeMethod gMethods[] = {
{
"native_init", //java 層的方法名
"()V", //簽名 簽名的規則跟靜態註冊方法相同。
(void *)JNISharedTest_native_init //對應的native 層的方法。方法名稱你可以隨便取。
},
{
"transmitToJNI",
"(Ljava/lang/String;Laai/along/and/jni/ExternClass;)V",
(void *)JNISharedTest_transmitToJNI
},
{
"stringFromJNI",
"()Ljava/lang/String;",
(void *)JNISharedTest_stringFromJNI
},
};
jint nMethods :gMethods陣列有多少個JNINativeMethod 結構體,JNINativeMethod 結構體如下
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
為了比較方便的獲取gMethods中的JNINativeMethod個數,使用如下的方法
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) // x用gMethods替換。
這裡需要特別說明一下:如果是在linux環境
#include "android_runtime/AndroidRuntime.h"
#include "JNIHelp.h"
增加這兩個標頭檔案,可以使用如下方法註冊
AndroidRuntime::registerNativeMethods(env,kClassMediaScanner, gMethods, NELEM(gMethods));引數跟RegisterNatives方法一樣
至此動態註冊方法講解完畢,是不是覺得很簡單。只需要兩步就完成動態註冊,所以以後還是建議使用動態註冊。
靜態註冊程式碼連線:https://download.csdn.net/download/bill_xiao/11097666
---------------------
作者:Bill_xiao
來源:CSDN
原文:https://blog.csdn.net/Bill_xiao/article/details/89095020
版權宣告:本文為博主原創文章,轉載請