一、簡述
JNI(Java Native Interface)Java本地介面,是為方便java呼叫C或者C++等本地代碼所封裝的一層介面,由于Java的跨平臺性導致本地互動能力不好,一些和作業系統相關的特性Java無法完成,于是Java提供了JNI專門用于和本地代碼互動,
NDK(Native Development Kit)本地開發工具鏈,是android提供的一個工具合集,幫助開發者快速開發C(或C++)的動態庫,并能自動將.so和java應用一起打包成apk,NDK集成了交叉編譯器(交叉編譯器需要UNIX或LINUX系統環境),并提供了相應的mk檔案隔離CPU、平臺、ABI等差異,開發人員只需要簡單修改mk檔案(指出“哪些檔案需要編譯”、“編譯特性要求”等),就可以創建出.so,
ABI(Application binary interface)應用程式二進制介面,不同的CPU 與指令集的每種組合都有定義的 ABI (應用程式二進制介面),一段程式只有遵循這個介面規范才能在該 CPU 上運行,所以同樣的程式代碼為了兼容多個不同的CPU,需要為不同的 ABI 構建不同的庫檔案,當然對于CPU來說,不同的架構并不意味著一定互不兼容,
armeabi設備只兼容armeabi;
armeabi-v7a設備兼容armeabi-v7a、armeabi;
arm64-v8a設備兼容arm64-v8a、armeabi-v7a、armeabi;
X86設備兼容X86、armeabi;
X86_64設備兼容X86_64、X86、armeabi;
mips64設備兼容mips64、mips;
mips只兼容mips;
Android的應用層的類都是以Java寫的,這些Java類編譯為Dex型式的位元組碼之后,必須依靠Dalvik虛擬機來運行,在Android中Dalvik虛擬機扮演很重要的角色.而Android中間件是由C/C++寫的,這些C/C++寫的組件并不是在Dalvik虛擬機上運行的,
一旦使用 JNI,JAVA 程式就喪失了 JAVA 平臺的兩個優點:
1、 程式不再跨平臺,要想跨平臺,必須在不同的系統環境下重新編譯本地語言部分,
2、 程式不再是絕對安全的,本地代碼的不當使用可能導致整個程式崩潰, 一個通用規則是,你應該讓本地方法集中在少數幾個類當中,這樣就降低了 JAVA 和 C 之間的耦合性,
二、java 與c/c++的互動
在java代碼中,可以通過loadLibrary要求VM裝載so檔案,java代碼一般如下形式:
public class jnitest {
static {
System.loadLibrary("jnitest");
}
}
注:在代碼運行時將會在/system/lib/目錄下查找libjnitest.so檔案,將載入VM中進行呼叫c庫代碼,
實作本地函式注冊方法有兩種:
1、靜態方法
通過javah 生成.h 檔案,接著實作.cpp檔案的方式,再通過ndk-build方式生成so庫,最后在java代碼上宣告物件進行呼叫類中的方法,通過javah 生成的方法名稱 格式為
JNIEXPORT <回傳型別> JNICALL Java_<包名>_<類名>_<方法名>(JNIEnv *, jobject,<引數>);
包名中的(.)用下劃線(_)代替
2、動態方法
JNI_OnLoad方式 當Android的VM執行到C組件(*so)里的System.loadLibrary()函式時,會產生一個Load事件,接著會去執行C組件里的JNI_OnLoad()函式,
JNI_OnLoad 函式中會執行兩個操作:
1)告訴java VM此C組件使用哪一個JNI版本,
2)JNI_OnLoad()來獲取JNIEnv,JNIEnv代表java環境,通過JNIEnv指標就可以對java端的代碼進行操作,
整個的流程:
1、首先進行重新 JNI_OnLoad函式,在函式中進行獲取vm的JNIEnv屬性資訊
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
2、其次 通過FindClas 找到對應的本地類
clazz = env->FindClass(className);
3、再接下來通過RegisterNatives 來注冊類中的方法
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
三、常用的jni開發知識
1、資料型別轉換
1.1、UTF-8 strings的轉換方法
// UTF-8 String (encoded to 1-3 byte, backward compatible with 7-bit ASCII)
// Can be mapped to null-terminated char-array C-string
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
// Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding.
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
// Informs the VM that the native code no longer needs access to utf.
jstring NewStringUTF(JNIEnv *env, const char *bytes);
// Constructs a new java.lang.String object from an array of characters in modified UTF-8 encoding.
jsize GetStringUTFLength(JNIEnv *env, jstring string);
// Returns the length in bytes of the modified UTF-8 representation of a string.
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);
// Translates len number of Unicode characters beginning at offset start into modified UTF-8 encoding
// and place the result in the given buffer buf.
注:
1)JNI 中定義了 jstring 型別代替Java中的String 型別,String 型別的傳遞相比基本型別要復雜, 因為在Java中String 是個物件, 而在c中, string 是個 char型別陣列,所以在傳遞String型別的時候, 在String(被jstring替換) 型別和 (char*)型別之間做轉化,
2)第三個引數isCopy 如果設為JNI_TRUE , 則回傳結果是原始Java String 的拷貝, 如果設為JNI_FALSE, 則直接回傳 Java String 的地址, 然而在這種情況下, 無法對string內容進行修改,JNI在運行時會試圖回傳指標, 如果可以的話,否則會回傳一個拷貝, 通常情況下, 我們并不關心底層string 的內容呢, 所以通常都設為 NULL,
1.2、unicode String型別
// Unicode Strings (16-bit character)
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
// Returns a pointer to the array of Unicode characters
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
// Informs the VM that the native code no longer needs access to chars.
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize length);
// Constructs a new java.lang.String object from an array of Unicode characters.
jsize GetStringLength(JNIEnv *env, jstring string);
// Returns the length (the count of Unicode characters) of a Java string.
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize length, jchar *buf);
// Copies len number of Unicode characters beginning at offset start to the given buffer buf
注:NewStringUTF 為根據傳入的UTF-8字串創建一個Java String物件(即是要回傳java層的),而GetStringUTFChars 是根據java層的資料來獲得jni層(c底層)的資料,
jstring NewString (JNIEnv *env, const jchar *unicodeChars, jsize len);
功能:利用 Unicode 字符陣列構造新的 java.lang.String 物件,
引數: env:JNI 介面指標,
unicodeChars:指向 Unicode 字串的指標,
len:Unicode 字串的長度,
回傳值: Java 字串物件,如果無法構造該字串,則為NULL,
拋出: OutOfMemoryError:如果系統記憶體不足,
jsize GetStringLength (JNIEnv *env, jstring string);
功能:回傳 Java 字串的長度(Unicode 字符數),
引數: env:JNI 介面指標,
string:Java 字串物件,
回傳值: Java 字串的長度,
const jchar * GetStringChars (JNIEnv*env, jstring string, jboolean *isCopy);
功能:回傳指向字串的 Unicode 字符陣列的指標,該指標在呼叫 ReleaseStringchars() 前一直有效,如果 isCopy 非空,則在復制完成后將 *isCopy 設為 JNI_TRUE,如果沒有復制,則設為JNI_FALSE,
引數: env:JNI 介面指標,
string:Java 字串物件,
isCopy:指向布林值的指標,
回傳值: 指向 Unicode 字串的指標,如果操作失敗,則回傳NULL,
void ReleaseStringChars (JNIEnv *env, jstring string, const jchar *chars);
功能:通知虛擬機平臺相關代碼無需再訪問 chars,引數chars 是一個指標,可通過 GetStringChars() 從 string 獲得,
引數: env:JNI 介面指標,
string:Java 字串物件,
chars:指向 Unicode 字串的指標,
jstring NewStringUTF (JNIEnv *env, const char *bytes);
功能:利用 UTF-8 字符陣列構造新 java.lang.String 物件,
引數: env:JNI 介面指標,如果無法構造該字串,則為 NULL,
bytes:指向 UTF-8 字串的指標,
回傳值:Java 字串物件,如果無法構造該字串,則為NULL,
拋出: OutOfMemoryError:如果系統記憶體不足,
jsize GetStringUTFLength (JNIEnv *env, jstring string);
功能:以位元組為單位回傳字串的 UTF-8 長度,
引數: env:JNI 介面指標,
string:Java 字串物件,
回傳值: 回傳字串的 UTF-8
const char* GetStringUTFChars (JNIEnv*env, jstring string, jboolean *isCopy);
功能:回傳指向字串的 UTF-8 字符陣列的指標,該陣列在被ReleaseStringUTFChars() 釋放前將一直有效.如果 isCopy 不是 NULL,*isCopy 在復制完成后即被設為 JNI_TRUE,如果未復制,則設為 JNI_FALSE,
引數: env:JNI 介面指標,
string:Java 字串物件,
isCopy:指向布林值的指標,
回傳值: 指向 UTF-8 字串的指標,如果操作失敗,則為 NULL,
void ReleaseStringUTFChars (JNIEnv *env, jstring string, const char *utf);
功能:通知虛擬機平臺相關代碼無需再訪問 utf,utf 引數是一個指標,可利用 GetStringUTFChars() 獲得,
引數: env:JNI 介面指標,
string:Java 字串物件,
utf:指向 UTF-8 字串的指標,
1.3、陣列型別
在JNI 中定義了 8種基本型別的陣列對應Java 的8種基本型別陣列,jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray , 和一種物件陣列jobjectArray對應Java中的物件陣列,
陣列傳遞是處理JNI 陣列和Native陣列之間的轉換,例如:
jintArray <-> jint[], jdoubleArray <-> jdouble[]
jintArray(JNI) --> (Native)jint[] : jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)
jint[] --> jintArray : 呼叫 jintArray NewIntArray(JNIEnv *env, jsize len)分配記憶體,
然后呼叫 SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf) 將jin[] 拷貝到 jintArray
// ArrayType: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray
// PrimitiveType: int, byte, short, long, float, double, char, boolean
// NativeType: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean
NativeType * Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, NativeType *buffer);
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, const NativeType *buffer);
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);
GET|ReleaseArrayElements() 用于根據jxxxArray創建 jxxx[]
GET|SetArrayRegion() 可以用于拷貝一個jxxxArray(或者其中一部分)到一個 預分配(pre-allocated)存盤的 jxxx[]
NewArray() 用于為jxxxArray分配記憶體, 然后呼叫 SetArrayRegion() 方法 將jxxx[] 設值,
Get|ReleasePrimitiveArrayCritical() 則是在get 和 release周期之間, 不允許阻塞呼叫(blocking calls),
1.4、訪問物件的屬性和方法
jclass GetObjectClass(JNIEnv *env, jobject obj);
// Returns the class of an object.
jfieldID GetFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
// Returns the field ID for an instance variable of a class.
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
// Get/Set the value of an instance variable of an object
// <type> includes each of the eight primitive types plus Object.
通過以上方法,我們就可以實作在Native代碼中訪問Java物件的成員變數了, 具體實作為:
首先 通過GetObjectClass() 方法獲取該物件的類的參考;
其次 通過GetFieldID() 方法從類參考中(Step1得到該參考)獲取FieldID; 呼叫該方法需要傳入成員變數的名稱和對應field的描述(descriptor)(或者簽名(signature)),
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
功能:回傳類或介面實體(非靜態)方法的方法 ID,方法可在某個 clazz 的超類中定義,也可從 clazz 繼承,該方法由其名稱和簽名決定, GetMethodID() 可使未初始化的類初始化,要獲得建構式的方法 ID,應將 <init> 作為方法名,同時將 void (V) 作為回傳型別,
引數: env:JNI 介面指標,
clazz:Java 類物件,
name:方法名,
sig:方法的簽名,
回傳值: 方法 ID,如果找不到指定的方法,則為 NULL,
拋出: NoSuchMethodError:如果找不到指定方法,
ExceptionInInitializerError:如果由于例外而導致類初始化程式失敗,
OutOfMemoryError:如果系統記憶體不足,
Call<type>Method 例程 、Call<type>MethodA 例程 、Call<type>MethodV 例程
NativeType Call<type>Method (JNIEnv*en v, jobject obj , jmethodID methodID, ...); //引數附加在函式后面,
NativeType Call<type>MethodA (JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args); //引數以指標形式附加
NativeType Call<type>MethodV (JNIEnv *env, jobject obj,jmethodID methodID, va_list args); //引數以"鏈表"形式附加
注:
說明:這三個操作的方法用于從本地方法呼叫Java 實體方法,它們的差別僅在于向其所呼叫的方法傳遞引數時所用的機制,
這三個操作將根據所指定的方法 ID 呼叫 Java 物件的實體(非靜態)方法,引數 methodID 必須通過呼叫 GetMethodID() 來獲得,當這些函式用于呼叫私有方法和建構式時,方法 ID 必須從obj 的真實類派生而來,而不應從其某個超類派生,當然,附加引數可以為空 ,
引數: env:JNI 介面指標,
obj:Java 物件,
methodID:方法 ID,
回傳值: 回傳呼叫 Java 方法的結果,
拋出: 執行 Java 方法時拋出的例外,
用戶應將CallMethod 中的 type 替換為所呼叫方法的Java 型別(或使用表中的實際方法名),同時將 NativeType 替換為該方法相應的本地型別,
Java層回傳值 方法族 本地回傳型別NativeType
回傳值為void: CallVoidMethod( )A / V (無)
回傳值為參考型別: CallObjectMethod( ) jobect
回傳值為boolean : CallBooleanMethod ( ) jboolean
回傳值為byte : CallByteMethod( ) jbyte
回傳值為char : CallCharMethod( ) jchar
回傳值為short CallShortMethod( ) jshort
回傳值為int : CallIntMethod( ) jint
回傳值為long: CallLongMethod() jlong
回傳值為float : CallFloatMethod() jfloat
回傳值為double: CallDoubleMethod() jdouble
呼叫靜態方法:也存在如下方法:
jfieldID GetStaticMethodID (JNIEnv *env,jclass clazz, const char *name, const char *sig);
NativeType Call<type>Method (JNIEnv*env,jclass classzz , jfieldID fieldID);
它們與于實體方法的唯一區別在于第二個引數jclass classzz代表的是類參考,而不是類實體,
1.5、注冊本地方法
jint RegisterNatives (JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);
功能:向 clazz 引數指定的類注冊本地方法,methods 引數將指定 JNINativeMethod 結構的陣列,其中包含本地方法的名稱、
簽名和函式指標,nMethods 引數將指定陣列中的本地方法數,JNINativeMethod 結構定義如下所示:
typedef struct {
char *name;
char *signature;
void *fnPtr;
} JNINativeMethod;
函式指標通常必須有下列簽名:
ReturnType (*fnPtr)(JNIEnv *env, jobject objectOrClass, ...);
引數: env:JNI 介面指標,
clazz:Java 類物件,
methods:類中本地方法和具體實作方法的映射指標,
nMethods:類中的本地方法數,
回傳值: 成功時回傳 "0";失敗時回傳負數,
拋出: NoSuchMethodError:如果找不到指定的方法或方法不是本地方法,
jint UnregisterNatives (JNIEnv *env, jclass clazz);
功能: 取消注冊類的本地方法,類將回傳到鏈接或注冊了本地方法函式前的狀態, 該函式不應在常規平臺相關代碼中使用,相反,它可以為某些程式提供一種重新加載和重新鏈接本地庫的途徑,
引數: env:JNI 介面指標,
clazz:Java 類物件,
回傳值: 成功時回傳“0”;失敗時回傳負數,
1.6、類操作
jclass DefineClass (JNIEnv *env, jobject loader, const jbyte *buf , jsize bufLen);
功能:從原始類資料的緩沖區中加載類,
引數: env JNI 介面指標,
loader 分派給所定義的類的類加載器,
buf 包含 .class 檔案資料的緩沖區,
bufLen 緩沖區長度,
回傳值:回傳 Java 類物件,如果出錯則回傳NULL,
拋出: ClassFormatError 如果類資料指定的類無效,
ClassCircularityError 如果類或介面是自身的超類或超介面,
OutOfMemoryError 如果系統記憶體不足,
jclass FindClass (JNIEnv *env, const char *name);
功能: 該函式用于加載本地定義的類,它將搜索由CLASSPATH 環境變數為具有指定名稱的類所指定的目錄和 zip檔案,
引數:env JNI 介面指標,
name 類全名(即包名后跟類名,之間由"/"分隔).如果該名稱以“[(陣列簽名字符)打頭,則回傳一個陣列類,
回傳值:回傳類物件全名,如果找不到該類,則回傳 NULL,
拋出: ClassFormatError 如果類資料指定的類無效,
ClassCircularityError 如果類或介面是自身的超類或超介面,
NoClassDefFoundError 如果找不到所請求的類或介面的定義,
OutOfMemoryError 如果系統記憶體不足,
jclass GetObjectClass (JNIEnv *env, jobject obj);
功能:通過物件獲取這個類,該函式比較簡單,唯一注意的是物件不能為NULL,否則獲取的class肯定回傳也為NULL,
引數: env JNI 介面指標,
obj Java 類物件實體,
jclass GetSuperclass (JNIEnv *env, jclass clazz);
功能:獲取父類或者說超類 , 如果 clazz 代表類class而非類 object,則該函式回傳由 clazz 所指定的類的超類, 如果 clazz
指定類 object 或代表某個介面,則該函式回傳NULL,
引數: env JNI 介面指標,
clazz Java 類物件,
回傳值: 由 clazz 所代表的類的超類或 NULL,
jboolean IsAssignableFrom (JNIEnv *env, jclass clazz1, jclass clazz2);
功能:確定 clazz1 的物件是否可安全地強制轉換為clazz2,
引數: env JNI 介面指標,
clazz1 第一個類引數,
clazz2 第二個類引數,
回傳值: 下列某個情況為真時回傳 JNI_TRUE:
1、 第一及第二個類引數參考同一個 Java 類,
2、 第一個類是第二個類的子類,
3、 第二個類是第一個類的某個介面,
2、方法簽名
“()” 中的字符表示引數,后面的則代表回傳值,例如"()V" 就表示void Func();
“(II)V” 表示 void Func(int, int);
1)常用型別簽名
具體的每一個字符的對應關系如下:
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
2)陣列簽名
陣列則以"["開始,用兩個字符表示:
[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
[Z jbooleanArray boolean[]
String[] [Ljava/lang/String;
Object[] [Ljava/lang/Object;
int[][] [[I
3)類簽名
采用L+包名+型別+;的形式,只需要將其中的.替換為/即可, 例如java.lang.String,它的簽名為Ljava/lang/String;,末尾的;也是一部分
Ljava/lang/String; String jstring
Ljava/net/Socket; Socket jobject
jthrowable --> java.lang.Throwable
注:
物件的簽名就是物件所屬的類簽名,
4)方法簽名
采用L+包名+型別+;的形式,只需要將其中的.替換為/即可, 例如java.lang.String,它的簽名為Ljava/lang/String;,末尾的;也是一部分
boolean fun(int a, double b, int[] c); --> (ID[I)Z
void fun(int a, String s, int[] c); --> (ILjava/lang/String;[I)V
int fun(); --> ()I
int fun(float f); --> (F)I
注:
可以使用javap 生成方法簽名
3、JNIEnv 介紹
1)JNIEnv概念
JNIEnv是一個執行緒相關的結構體, 該結構體代表了 Java 在本執行緒的運行環境
2)JNIEnv與JavaVM
JavaVM : JavaVM 是 Java虛擬機在 JNI 層的代表, JNI 全域只有一個;
JNIEnv : JavaVM 在執行緒中的代表, 每個執行緒都有一個, JNI 中可能有很多個 JNIEnv;
3)作用
呼叫 Java 函式 : JNIEnv 代表 Java 運行環境, 可以使用 JNIEnv 呼叫 Java 中的代碼;
操作 Java 物件 : Java 物件傳入 JNI 層就是 Jobject 物件, 需要使用 JNIEnv 來操作這個 Java 物件;
4)JNIEnv 在c和c++的區別
c風格:
(*env)->FindClass(env,"com/example/jnisample/Prompt");
c++風格:
env->FindClass("com/example/jnisample/Prompt");
JNIEnv 在jni.h中定義
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
struct JNINativeInterface {
......很多方法
jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
};
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
#if defined(__cplusplus)
jint GetVersion()
{ return functions->GetVersion(this); }
.....其他方法
};
可以看到JNINativeInterface 其實定義了很多方法,都是對Java的資料進行操作,而_JNIEnv則封裝了一個JNINativeInterface的指標,并且宣告與JNINativeInterface中一模一樣的方法,并且都是通過JNINativeInterface的指標來調方法,其實就是對JNINativeInterface做了一層封裝,
JNIEnv,指代了Java本地介面環境,是一個JNI介面指標,指向了本地方法的一個函式表, jobject與jclass通常作為JNI函式的第二個引數,當所宣告Native方法是靜態方法時,對應引數jclass,因為靜態方法不依賴物件實體,而依賴于類,所以引數中傳遞的是一個jclass型別,相反,如果宣告的Native方法時非靜態方法時,那么對應引數是jobject,
四、使用
1、新建javahello類
javahello.java
package com.testjnionload;
public class javahello {
public static native String getJniHello();
static {
System.loadLibrary("testttJni");
}
}
2、實作jni層程式 hello.cpp
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>
JNIEXPORT jstring JNICALL native_getJniHello(JNIEnv *env, jclass clazz)
{
return env->NewStringUTF("hello!!! this is jni layer");
}
#define JNIREG_CLASS "com/testjnionload/javahello"//指定要注冊的類
static JNINativeMethod gMethods[] = {
{"getJniHello", "()Ljava/lang/String;", (void*)native_getJniHello },//系結
};
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,
sizeof(gMethods) / sizeof(gMethods[0])))
return JNI_FALSE;
return JNI_TRUE;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
if (!registerNatives(env)) {//注冊
return -1;
}
return JNI_VERSION_1_4;
}
3、撰寫Android.mk檔案
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := testttJni
LOCAL_SRC_FILES := hello.cpp
include $(BUILD_SHARED_LIBRARY)
4、配置
4.1 app build.gradle
ndk{
abiFilters "armeabi-v7a", "x86", "armeabi"
//abiFilters 'armeabi-v7a' //, 'armeabi','x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
sourceSets.main {
jniLibs.srcDir 'src/main/libs'
// jni.srcDirs = [] //disable automatic ndk-build call
}
5、在jni目錄下使用ndk-build編譯 生成so庫
6、在MainActivity.java中呼叫
package com.testjnionload;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
javahello jh;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
jh = new javahello();
Log.i("MainActivity-------",jh.getJniHello()+"");
}
}
注:
1、FindClass呼叫時傳入的java層類的路徑并不需要相對jni中cpp檔案的路徑來配置 ,是直接在java檔案夾路徑下 即上面的com/testjnionload/javahello (類對應的包名),否則會出現 java.lang.UnsatisfiedLinkError: JNI_ERR returned from JNI_OnLoad in "/data/app/com.testjni_onload-NZTsWCAr2eyG1BYwpngVQA==/lib/arm/libtestJni.so"錯誤

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/241367.html
標籤:其他
