Android架構(gòu)師之路:JNI與NDK編程-函數(shù)注冊(cè)與C++調(diào)用Java詳解(c++音視頻編碼基礎(chǔ))
前言小計(jì)
1、jni與ndk的基本知識(shí)點(diǎn)前面文章都講過了,不懂的,可以在公眾號(hào)首頁看;
2、jni中常用的方法比如:類、方法、數(shù)組、字符串等前面也講解過了;
3、這篇文章講解jni中函數(shù)的注冊(cè)和c++調(diào)用java的知識(shí)點(diǎn);
一. JNI函數(shù)注冊(cè)

1、jni函數(shù)注解知識(shí)點(diǎn)介紹
- JNI技術(shù)是Java世界與Native世界的通信橋梁,具體到代碼,Java層的代碼如何同Native層的代碼進(jìn)行調(diào)用的呢?我們都知道,在調(diào)用native方法之前,首先要調(diào)用System.loadLibrary接口加載一個(gè)實(shí)現(xiàn)了native方法的動(dòng)態(tài)庫才能正常訪問,否則就會(huì)拋出java.lang.UnsatisfiedLinkError異常 。那么,在Java中調(diào)用某個(gè)native方法時(shí),JVM是通過什么方式,能正確的找到動(dòng)態(tài)庫中C/C++實(shí)現(xiàn)的那個(gè)native函數(shù)呢?
- JVM查找native方法有兩種方式;
- 按照J(rèn)NI規(guī)范的命名規(guī)則,調(diào)用JNI提供的RegisterNatives函數(shù),將本地函數(shù)注冊(cè)到JVM中;
- 第一種方式,可用使用javah工具按照J(rèn)ava類中定義的native方法,按照J(rèn)NI規(guī)范的命名規(guī)則的方式自動(dòng)生成Jni本地C/C++頭文件;
- 第二種方式則需要在本地庫的JNI_OnLoad函數(shù)中調(diào)用RegisterNatives來動(dòng)態(tài)注冊(cè);
- JNI函數(shù)注冊(cè)是將Java層聲明的Native方法同實(shí)際的Native函數(shù)綁定起來的實(shí)現(xiàn)方式,也就是說,只要通過JNI函數(shù)注冊(cè)機(jī)制注冊(cè)了本地方,Java層就可以直接調(diào)用定義的這些本地方法了。對(duì)應(yīng)上述JVM查找native方法的兩種方式,JNI函數(shù)注冊(cè)方式一般分為靜態(tài)注冊(cè)和動(dòng)態(tài)注冊(cè)兩種方式。
2、靜態(tài)注冊(cè)
原理:根據(jù)函數(shù)名來建立 java 方法與 JNI 函數(shù)的一一對(duì)應(yīng)關(guān)系;
實(shí)現(xiàn)流程:
- 編寫 java 代碼;
- 利用 javah 指令生成對(duì)應(yīng)的 .h 文件;
- 對(duì) .h 中的聲明進(jìn)行實(shí)現(xiàn);
弊端:
編寫不方便,JNI 方法名字必須遵循規(guī)則且名字很長(zhǎng);
編寫過程步驟多,不方便;
程序運(yùn)行效率低,因?yàn)槌醮握{(diào)用native函數(shù)時(shí)需要根據(jù)根據(jù)函數(shù)名在JNI層中搜索對(duì)應(yīng)的本地函數(shù),然后建立對(duì)應(yīng)關(guān)系,這個(gè)過程比較耗時(shí);
- public class Test {
- static {
- System.loadLibrary("native-lib");
- }
- public native String textFromJni();
- }
- 使用javah生成對(duì)應(yīng)的本地方法頭文件。
- #include <jni.h>
- #ifndef _Included_test
- #define _Included_test
- #ifdef __cplusplus
- extern "C" {
- #endif
- JNIEXPORT jstring JNICALL Java_test_Test_textFromJni
- (JNIEnv *, jobject);
- #ifdef __cplusplus
- }
- #endif
3、動(dòng)態(tài)注冊(cè)
對(duì)應(yīng)與上面的靜態(tài)注冊(cè)方法,還有一種動(dòng)態(tài)注冊(cè)JNI函數(shù)的方式,即動(dòng)態(tài)注冊(cè)。動(dòng)態(tài)注冊(cè)是當(dāng)Java層調(diào)用System.loadLibrary方法加載so庫后,本地庫的JNI_OnLoad函數(shù)會(huì)被調(diào)用,在JNI_OnLoad函數(shù)中通過調(diào)用RegisterNatives函數(shù)來完成本地方法的注冊(cè)。
原理:利用 RegisterNatives 方法來注冊(cè) java 方法與 JNI 函數(shù)的一一對(duì)應(yīng)關(guān)系;
實(shí)現(xiàn)流程:
- 利用結(jié)構(gòu)體 JNINativeMethod 數(shù)組記錄 java 方法與 JNI 函數(shù)的對(duì)應(yīng)關(guān)系;
- 實(shí)現(xiàn) JNI_OnLoad 方法,在加載動(dòng)態(tài)庫后,執(zhí)行動(dòng)態(tài)注冊(cè);
- 調(diào)用 FindClass 方法,獲取 java 對(duì)象;
- 調(diào)用 RegisterNatives 方法,傳入 java 對(duì)象,以及 JNINativeMethod 數(shù)組,以及注冊(cè)數(shù)目完成注冊(cè);
優(yōu)點(diǎn):
流程更加清晰可控;
效率更高;
其中JNINativeMethod結(jié)構(gòu)體用來描述本地方法結(jié)構(gòu),其定義如下:
- typedef struct {
- const char* name; // Java方法名
- const char* signature; // Java方法簽名
- void* fnPtr; // jni本地方法對(duì)應(yīng)的函數(shù)指針
- } JNINativeMethod;
結(jié)構(gòu)體的第一個(gè)參數(shù) name 是java 方法名;
第二個(gè)參數(shù) signature 用于描述方法的參數(shù)與返回值;
第三個(gè)參數(shù) fnPtr 是函數(shù)指針,指向 jni 函數(shù);
其中,第二個(gè)參數(shù) signature 使用字符串記錄方法的參數(shù)與返回值,具體格式形如“()V”、“(II)V”,其中分為兩部分,括號(hào)內(nèi)表示的是參數(shù),括號(hào)右側(cè)表示的是返回值;
①、數(shù)據(jù)類型映射
基本數(shù)據(jù)類型
②. 數(shù)組引用類型
如果是一維數(shù)組則遵循下表,如果是二維數(shù)組或更高維數(shù)組則對(duì)應(yīng)的 native 類型為 jobjectArray,域描述符中使用 ‘[’ 的個(gè)數(shù)表示維數(shù)
③. 對(duì)象引用類型
對(duì)于其它引用類型,即 java 中的對(duì)象,其映射規(guī)則為
④. 對(duì)象數(shù)組引用類型
如果是一維數(shù)組則遵循下表,如果是二維數(shù)組或更高維數(shù)組則對(duì)應(yīng)的 native 類型為 jobjectArray,域描述符中使用 ‘[’ 的個(gè)數(shù)表示維數(shù)
在Java文件中定義本地方法,加載本地so庫
- package test.jnitest;
- public class Test {
- static {
- System.loadLibrary("native-lib");
- }
- public native String textFromJni();
- }
在JNI_OnLoad函數(shù)中注冊(cè)本地方法
- jstring textFromJni(JNIEnv* env, jobject thiz) {
- return env->NewStringUTF("text from jni");
- }
- static JNINativeMethod gMethods[] = {
- {"textFromJni", "()Ljava/lang/String;", (void*)textFromJni}
- };
- int registerMethod(JNIEnv *env) {
- jclass test = env->FindClass("cc/ccbu/jnitest/Test");
- return env->RegisterNatives(test, gMethods, sizeof(gMethods)/ sizeof(gMethods[0]));
- }
- JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
- JNIEnv* env = NULL;
- if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
- return JNI_ERR;
- }
- if (registerMethod(env) != JNI_OK) {
- return JNI_ERR;
- }
- return JNI_VERSION_1_6;
- }
注意:
在JNI_OnLoad函數(shù)的結(jié)尾處,我們一定要有返回值,而且必須是JNI_VERSION_1_4 或 JNI_VERSION_1_6,也就是JNI的版本號(hào),我們一定要返回正確的版本號(hào),否則系統(tǒng)也是無法加載的;
4、c++調(diào)用java詳解
(1) 找到j(luò)ava對(duì)應(yīng)的Class
(2) 找到要調(diào)用的方法的methodID
(3) 在C語言中調(diào)用相應(yīng)方法
①.通過JAVA層的本地方法創(chuàng)建同類對(duì)象
步驟:
I.通過對(duì)象獲取類
II.通過類獲取類的構(gòu)造方法的ID
III.基于方法ID和類,創(chuàng)建新對(duì)象
- JNIEXPORT void JNICALL JAVA_nativeMethod
- (JNIEnv *env, jobject thiz,jint i){
- ...
- jclass clazz = (*env).GetObjectClass(thiz);
- jmethodID mid = (*env).GetMethodID(clazz,"<init>","()V");
- jobject obj = (*env).NewObject(clazz,mid);
- ...
- return;
- }
②.通過C/C++創(chuàng)建不同類對(duì)象
步驟:
I.通過FindClass方法獲取需要的類
II.通過類獲取類的構(gòu)造方法的ID
III.基于方法ID和類,創(chuàng)建新對(duì)象
- JNIEXPORT void JNICALL JAVA_nativeMethod
- (JNIEnv *env, jobject thiz,jint i){
- ...
- jclass clazz = (*env).FindClass("com/x/test/Test");//參數(shù)為類路徑
- jmethodID mid = (*env).GetMethodID(clazz,"<init>","()V");
- jobject obj = (*env).NewObject(clazz,mid);
- ...
- return;
- }
③獲取上下文環(huán)境JNIEnv
如果找不到上下文JNIEnv就要獲取
- bool AttachCurrentThread(JavaVM* vm, JNIEnv** p_env)
- {
- bool bAttached = false;
- switch(vm->GetEnv((void**)p_env, JNI_VERSION_1_4))
- {
- case JNI_OK:
- break;
- case JNI_EDETACHED:
- if (vm->AttachCurrentThread(p_env, 0) < 0)
- {
- LOGD("%s :test failed!",__func__);
- return false;
- }
- else
- {
- bAttached = true;
- }
- break;
- case JNI_EVERSION:
- LOGE("Invalid java version");
- break;
- }
- return bAttached;
- }
總結(jié)
以上總結(jié)了JNI中函數(shù)注冊(cè)的兩種方法,在實(shí)際應(yīng)用中都很常見都用得到的,要理解到位才可以;
下次文章會(huì)繼續(xù)講解關(guān)于JNI的知識(shí)點(diǎn)和高級(jí)應(yīng)用
本文轉(zhuǎn)載自微信公眾號(hào)「Android開發(fā)編程」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系A(chǔ)ndroid開發(fā)編程公眾號(hào)。