本篇文章已授權(quán)微信公眾號(hào) guolin_blog(郭霖)獨(dú)家發(fā)布
五、JNI Java 和 C++ 無(wú)縫對(duì)接
一窥妇、JNI 涉及的名詞概念
1.1、 JNI:Java Native Interface
- 它是
Java
平臺(tái)的一個(gè)特性(并不是Android
系統(tǒng)特有的)娩践。實(shí)現(xiàn)Java
代碼調(diào)用C/C++
的代碼活翩,C/C++
的代碼也可以調(diào)用Java
的代碼.
1.2、 二進(jìn)制庫(kù)分類 : 靜態(tài)庫(kù)翻伺,動(dòng)態(tài)庫(kù).
系統(tǒng) | 靜態(tài)庫(kù)文件 |
---|---|
Windows | .lib |
Linux | .a |
MacOS/IOS | .a |
系統(tǒng) | 動(dòng)態(tài)庫(kù)文件 |
---|---|
Windows | .dll |
Linux | .so |
MacOS/IOS | .dylib |
- 靜態(tài)庫(kù)
這么解釋:
.a
靜態(tài)庫(kù)就是好多個(gè).o
合并到一塊的集合材泄,經(jīng)常在編譯C
庫(kù)的時(shí)候會(huì)看到很多.o
,這個(gè).o
就是目標(biāo)文件 由.c + .h
編譯出來(lái)的。.c
相當(dāng)于.java
,.h
是C
庫(kù)對(duì)外開放的接口聲明吨岭。對(duì)外開放的接口.h
和.c
需要一一對(duì)應(yīng)拉宗,如果沒(méi)有一一對(duì)應(yīng),外部模塊調(diào)用了接口辣辫,編譯的時(shí)候會(huì)提示找不到方法旦事。.a
存在的意義可以看成Android aar
存在的意義,方便代碼不用重復(fù)編譯急灭, 最終為了生成.so (apk)
- 動(dòng)態(tài)庫(kù)姐浮,在
Android
環(huán)境下就是.so
,可以直接被java
代碼調(diào)用的庫(kù).
1.3葬馋、 CPU 架構(gòu)(ABI):armeabi卖鲤,armeabi-v7a肾扰,x86,mips蛋逾,arm64-v8a白对,mips64,x86_64
各個(gè)平臺(tái)架構(gòu)的區(qū)別就是指令集不一樣换怖,浮點(diǎn)運(yùn)算能力不一樣甩恼,按照上面排列的順序,浮點(diǎn)運(yùn)算能力運(yùn)行從低到高沉颂。
-
armeabi
:這是相當(dāng)老舊的一個(gè)版本条摸,缺少對(duì)浮點(diǎn)數(shù)計(jì)算的硬件支持,在需要大量計(jì)算時(shí)有性能瓶頸 (微信) -
armeabi-v7a
:ARM v7
目前主流版本铸屉,兼容armeabi (facebook app)
-
arm64-v8a
:64
位支持 兼容armeabi-v7a armeabi
-
mips/mips64
: 極少用于手機(jī)可以忽略 -
x86/x86_64
:x86
架構(gòu)一般用于TV
電視機(jī) 钉蒲,兼容armeabi
建議 android apk
為了減少包體大小只接入 armeabi-v7a
即可
1.4、 Android 特有的文件 :Android.mk Application.mk
Android.mk
:在Android
上編譯需要的配置文件彻坛,相當(dāng)于build.gradle
顷啼,詳細(xì)細(xì)節(jié)后面會(huì)講到。Application.mk
:上代碼
APP_PLATFORM := android-14 //指定 android 系統(tǒng)
APP_ABI := armeabi-v7a // 指定生成哪個(gè)架構(gòu)的 so
1.5昌屉、 NDK :Android 平臺(tái)上用來(lái)編譯 C/C++
庫(kù)的工具
二钙蒙、JNI 在 Android Studio 搭建
2.1、創(chuàng)建一個(gè)子module
间驮,創(chuàng)建 java
層代碼躬厌,新建一個(gè)HelloWorld
類準(zhǔn)備和 c
層對(duì)接,代碼如下:
public class HelloWorld {
static {
try {
System.loadLibrary("helloworld");
} catch (Exception e) {
}
}
private volatile static HelloWorld instance;
private HelloWorld() {
}
public static HelloWorld getInstance() {
if(instance == null) {
synchronized (HelloWorld.class) {
if(instance == null) {
instance = new HelloWorld();
}
}
}
return instance;
}
public native String nativeGetString();
}
很明顯上面類分成三部分:
- 有
static
代碼塊竞帽,調(diào)用了System.loadLibrary("helloworld");
這句代碼代表著扛施,使用這個(gè)類之前都會(huì)去加載libhelloworld.so
這個(gè)動(dòng)態(tài)庫(kù),注意.so
前面有lib
屹篓。那這個(gè)動(dòng)態(tài)庫(kù)如何生成疙渣,后面講。 - 這個(gè)類是一個(gè)單例
- 有一個(gè)
native
的方法public native String nativeGetString();
這個(gè)方法的實(shí)現(xiàn)在c
層堆巧。所以接下來(lái)我們要構(gòu)建c
層的代碼妄荔。
2.2、接著在子module
的目錄下建立一個(gè)叫做 jni
的文件夾恳邀。例如:
2.3懦冰、創(chuàng)建 c
代碼灶轰,和配置文件谣沸,看下圖的位置:
- 生成一個(gè)
helloworld_android.c
,代碼如下:對(duì)接java
層 笋颤,下面的方法
JNIEXPORT jstring JNICALL Java_com_tct_helloworld_HelloWorld_nativeGetString(JNIEnv *env, jobject obj)
是java
層public native String nativeGetString();
方法的 代碼實(shí)現(xiàn):
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
//public native String nativeGetString();
JNIEXPORT jstring JNICALL Java_com_tct_helloworld_HelloWorld_nativeGetString(JNIEnv *env, jobject obj) {
char *foo = malloc(sizeof(char) * 64); //申請(qǐng)內(nèi)存
// char *foo = "helloworld";
snprintf(foo, "%s", "helloworld"); //寫入字符串到foo指針
jstring jname = (*env)->NewStringUTF(env, foo);
free(foo); //釋放指針
foo = NULL;
return jname; //返回字符串
//return helloworld;
}
- 生成并編寫
Android.mk
,代碼如下
#獲取當(dāng)前目錄的相對(duì)路徑乳附,也就是當(dāng)前文件的父路徑
LOCAL_PATH := $(call my-dir)
#清除當(dāng)前的所有變量值
include $(CLEAR_VARS)
#本模塊需要調(diào)用到的接口内地,也就是.h 文件
#LOCAL_C_INCLUDES := XXX
#本模塊需要編譯到的 c 文件
LOCAL_SRC_FILES := helloworld_android.c
#加入第三方庫(kù)log庫(kù),NDK 自帶的
LOCAL_LDLIBS := -llog
#生成庫(kù)的名字赋除。最終生成 libhelloworld
LOCAL_MODULE := helloworld
#生成的是動(dòng)態(tài)庫(kù).so
include $(BUILD_SHARED_LIBRARY)
#生成的是動(dòng)態(tài)庫(kù).a
#include $(BUILD_STATIC_LIBRARY)
- 生成并編寫
Application.mk
APP_ABI := armeabi-v7a //生成 armeabi-v7a 的 so
APP_PLATFORM := android-21 //指定 tagerSDK
2.4阱缓、接下來(lái)配置子 module
的 build.gradle
和 NDK
apply plugin: 'com.android.library'
android {
compileSdkVersion 27
externalNativeBuild.ndkBuild {
path "src/main/jni/Android.mk" //指定c 層的 mk 文件的位置
}
defaultConfig {
versionCode 1
versionName "1.0"
sourceSets {
main {
jni.srcDirs = [] //run 的時(shí)候不會(huì)重新編譯 jni ,只有make 的時(shí)候生效
}
}
ndk {
abiFilters "armeabi-v7a"http://讓APK只包含指定的ABI
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
根目錄下的 local.properties
举农,配置自己的 NDK
路徑
ndk.dir=G\:\\AndroidNDK\\android-ndk-r16b
2.5荆针、把項(xiàng)目跑起來(lái)
-
make
一下子module
,把項(xiàng)目編譯一下颁糟。把.so
和.aar
一次編譯出來(lái)航背。
image
image - 觀察編譯完畢的目錄結(jié)構(gòu),
aar
是出來(lái)了棱貌,但是好像沒(méi)有發(fā)現(xiàn)so
的蹤影玖媚。
- 解壓
aar
(aar
其實(shí)就是zip
壓縮,只是谷歌把它換了個(gè)后綴名)婚脱。
image
2.6今魔、最后寫個(gè)MainActivity.java
調(diào)用一下接口
- 調(diào)用接口代碼
@OnClick(R.id.btnTestNDKCrash)
void testNDKCrash(){
String ret = HelloWorld.getInstance().nativeGetString();
System.out.println("test "+ret);
}
- 發(fā)現(xiàn)崩潰了,如何定位并且解決障贸?先看
log
很明顯這種異常错森,是崩潰在c層的log,并且 java 層是無(wú)法 try - catch 住的篮洁。只能在 c 層去解決這個(gè)問(wèn)題问词。我們?cè)陧?xiàng)目中有時(shí)候也會(huì)遇到這種異常,有的時(shí)候是系統(tǒng)庫(kù)奔潰了(無(wú)解嘀粱,只能從java 層檢查是否有 規(guī)范的調(diào)用接口)激挪,有時(shí)候是第三方的 so 庫(kù)奔潰了(找到j(luò)ni 的源碼才能解決)。
- 定位并解決問(wèn)題
命令行:
G:\AndroidNDK\android-ndk-r16b\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\arm-linux-androideabi-addr2line -e D:\StadyProject\OpenCode\breakpad-for-android-master\sample\helloworld\build\intermediates\ndkBuild\debug\obj\local\armeabi-v7a\libhelloworld.so 00000fb9
打開你的 Terminal
把上面的命令輸進(jìn)去锋叨,就可以看到閃退的代碼行了:
定位奔潰的代碼行:
G:\AndroidNDK\android-ndk-r16b\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\arm-linux-androideabi-addr2line -e
目標(biāo)文件 so
庫(kù)的位置垄分,so
一個(gè)存在 aar
,一個(gè)存在 build
目錄下面娃磺,位置比較深薄湿,但是都是固定目錄,可以找到:
D:\StadyProject\OpenCode\breakpad-for-android-master\sample\helloworld\build\intermediates\ndkBuild\debug\obj\local\armeabi-v7a\libhelloworld.so
奔潰的 內(nèi)存位置:
00000fb9
崩潰的代碼行:
image
這句代碼的意思是把 helloworld 字符串賦值到 foo 這個(gè)變量中去偷卧。但是少傳了一個(gè)參數(shù)導(dǎo)致崩潰豺瘤。 看下面兩個(gè)函數(shù)的不同處
snprintf(foo, LEN, "%s", "helloworld");//最多傳入 foo 能承載的字符數(shù),多了一個(gè)參數(shù)
sprintf(foo, "%s", "helloworld");//無(wú)指定寫入多少字符
那么改成以下代碼听诸,就可以了
#define LEN 64
snprintf(foo, LEN, "%s", "helloworld");
再回顧一下 java
層代碼:
@OnClick(R.id.btnTestNDKCrash)
void testNDKCrash(){
String ret = HelloWorld.getInstance().nativeGetString();
System.out.println("test "+ret);
}
跑起來(lái)logcat
:
三坐求、JNI 類型,方法對(duì)照表
3.1晌梨、基本類型對(duì)照表
Java類型 | 本地類型 | 描述 |
---|---|---|
boolean | jboolean | C/C++8位整型 |
byte | jbyte | C/C++帶符號(hào)的8位整型 |
char | jchar | C/C++無(wú)符號(hào)的16位整型 |
short | jshort | C/C++帶符號(hào)的16位整型 |
int | jint | C/C++帶符號(hào)的32位整型 |
long | jlong | C/C++帶符號(hào)的64位整型e |
float | jfloat | C/C++32位浮點(diǎn)型 |
double | jdouble | C/C++64位浮點(diǎn)型 |
Object | jobject | 任何Java對(duì)象桥嗤,或者沒(méi)有對(duì)應(yīng)java類型的對(duì)象 |
Class | jclass | Class對(duì)象 |
String | jstring | 字符串對(duì)象 |
Object[] | jobjectArray | 任何對(duì)象的數(shù)組 |
boolean[] | jbooleanArray | 布爾型數(shù)組 |
byte[] | jbyteArray | 比特型數(shù)組 |
char[] | jcharArray | 字符型數(shù)組 |
short[] | jshortArray | 短整型數(shù)組 |
int[] | jintArray | 整型數(shù)組 |
long[] | jlongArray | 長(zhǎng)整型數(shù)組 |
float[] | jfloatArray | 浮點(diǎn)型數(shù)組 |
double[] | jdoubleArray | 雙浮點(diǎn)型數(shù)組 |
3.2须妻、jni 層使用 java 類方法名稱
Java類型 | 本地類型 | 描述
函數(shù) | Java數(shù)組類型 | 本地類型 |
---|---|---|
GetBooleanArrayElements | jbooleanArray | jboolean |
GetByteArrayElements | jbyteArray | jbyte |
GetCharArrayElements | jcharArray | jchar |
GetShortArrayElements | jshortArray | jshort |
GetIntArrayElements | jintArray | jint |
GetLongArrayElements | jlongArray | jlong |
GetFloatArrayElements | jfloatArray | jfloat |
GetDoubleArrayElements | jdoubleArray | jdouble |
函數(shù) | 描述 |
---|---|
GetFieldID | 得到一個(gè)實(shí)例的域的ID |
GetStaticFieldID | 得到一個(gè)靜態(tài)的域的ID |
GetMethodID | 得到一個(gè)實(shí)例的方法的ID |
GetStaticMethodID | 得到一個(gè)靜態(tài)方法的ID |
Java 類型 | 符號(hào) |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
float | F |
double | D |
void | V |
objects對(duì)象 | Lfully-qualified-class-name;L類名 |
Arrays數(shù)組 | [array-type [數(shù)組類型 |
methods方法 | (argument-types)return-type(參數(shù)類型)返回類型 |
四、JNI 場(chǎng)景實(shí)踐
由于上面看了方法的對(duì)照表泛领,下面講解如何使用:
4.1荒吏、java
調(diào)用到 C
層
// JAVA 層方法
public native String nativeGetString(String tmp);
// 對(duì)應(yīng) JNI 層方法
JNIEXPORT jstring JNICALL
Java_com_tct_helloworld_HelloWorld_nativeGetString(JNIEnv *env, jobject obj,jstring jtmp) {
}
// JAVA 層方法
public native void nativeGetString(Model tmp);
// 對(duì)應(yīng) JNI 層方法
JNIEXPORT void JNICALL
Java_com_tct_helloworld_HelloWorld_nativeGetString(JNIEnv *env, jobject obj,jobject jmod) {
}
-
JNIEnv *env
是JNI
中java
線程的上下文,每一個(gè)線程都有一個(gè)env
渊鞋。 -
jobject obj
代表的java
的對(duì)象绰更,從java
哪個(gè)對(duì)象調(diào)用下來(lái)的,就是哪對(duì)象锡宋。
4.2动知、C
層解析 java
類中的屬性值,轉(zhuǎn)成 C
層可使用的類型
//java 類
public class Model {
public int code;
public String name;
public Model(int code, String name) {
this.code = code;
this.name = name;
}
}
// JAVA 層方法
public native void nativeGetString(Model tmp);
// 對(duì)應(yīng) JNI 層方法
JNIEXPORT void JNICALL
Java_com_tct_helloworld_HelloWorld_nativeGetString(JNIEnv *env, jobject obj,jobject jmodel) {
jclass jmodelClass = (*env)->GetObjectClass(env, jmodel);
if (jmodelClass == 0) {
return;
}
//獲取變量 code 的值
jfieldID fidCode = (*env)->GetFieldID(env, jmodelClass, "code", "I");
int code = (*env)->GetIntField(env, jmodel, fidCode);
//獲取變量 name 的值
jfieldID fidName = (*env)->GetFieldID(env, jmodelClass, "name",
"Ljava/lang/String;");
jstring jname = (jstring)(*env)->GetObjectField(env, jmodel, fidName);
char *name = (*env)->GetStringUTFChars(env, jname, 0);
// ..
//使用完畢员辩,char * 需要回收
(*env)->ReleaseStringUTFChars(env, jname, name);
// 自己生成的 jclass 需要回收盒粮,以及其他的引用也是需要的,局部變量不能超512 個(gè)奠滑,特別是在 for 循環(huán)體內(nèi)要及時(shí)回收
(*env)->DeleteLocalRef(env, jmodelClass);
}
4.3丹皱、C
層返回 java
對(duì)象
//java 層方法
private volatile static HelloWorld instance;
private HelloWorld() {
}
public static HelloWorld getInstance() {
if(instance == null) {
synchronized (HelloWorld.class) {
if(instance == null) {
instance = new HelloWorld();
}
}
}
return instance;
}
public native static HelloWorld nativeGetInstance();
//C層方法
JNIEXPORT jobject JNICALL Java_com_tct_helloworld_HelloWorld_nativeGetInstance
(JNIEnv *env, jclass cls) {
//找到class
jclass cls1 = (*env)->FindClass(env, "com/tct/helloworld/HelloWorld");
//找到構(gòu)造函數(shù)的方法ID
jmethodID cid = (*env)->GetMethodID(env, cls1, "<init>", "()V");
//生成一個(gè)對(duì)象返回
jobject jInstance = (*env)->NewObject(env, cls1, cid);
return jInstance;
}
// MainActivity.java 的調(diào)用方法
@OnClick(R.id.btnTestNDKCrash)
void testNDKCrash(){
if(HelloWorld.getInstance() == HelloWorld.nativeGetInstance()) {
System.out.println("HelloWorld instance true");
} else {
System.out.println("HelloWorld instance false");
}
}
得出 log
:
I/System.out: HelloWorld instance false
原來(lái)不僅僅反射機(jī)制能破解單例, JNI
也是可以破解單例宋税。
4.4摊崭、C
層返回 java
對(duì)象數(shù)組
//java 層代碼
public native static HelloWorld[] nativeGetInstanceArray();
// c 層代碼
JNIEXPORT jobjectArray JNICALL Java_com_tct_helloworld_HelloWorld_nativeGetInstanceArray
(JNIEnv *env, jclass cls) {
jclass cls1 = (*env)->FindClass(env, "com/tct/helloworld/HelloWorld");
jmethodID cid = (*env)->GetMethodID(env, cls1, "<init>", "()V");
jsize len = 10;
jobjectArray mjobjectArray;
//新建object數(shù)組
mjobjectArray = (*env)->NewObjectArray(env, len, cls1, 0);
for (int i = 0; i < len; ++i) {
jobject jInstance = (*env)->NewObject(env, cls1, cid);
(*env)->SetObjectArrayElement(env, mjobjectArray, i, jInstance);
//回收,局部引用不能超過(guò)512個(gè)
(*env)->DeleteLocalRef(env, jInstance);
}
(*env)->DeleteLocalRef(env, cls1);
return mjobjectArray;
}
//MainActivity.java 調(diào)用
@OnClick(R.id.btnTestNDKCrash)
void testNDKCrash(){
HelloWorld[] HelloWorlds = HelloWorld.getInstance().nativeGetInstanceArray();
System.out.println("HelloWorld arrays length:"+HelloWorlds.length);
}
log
:
I/System.out: HelloWorld arrays length:10
4.5杰赛、C
層回調(diào)到 java
層
//java 層方法
public class TestBean {
public int code;
public String name;
public TestBean(int code, String name) {
this.code = code;
this.name = name;
}
@Override
public String toString() {
return "TestBean{" +
"code=" + code +
", name='" + name + '\'' +
'}';
}
}
public interface HelloWorldListener {
public void onLinstener(TestBean testBean);
}
public native void nativeGetInstanceByThread(HelloWorldListener listener);
//c 層方法
//jni 當(dāng)前上下文呢簸,可用于當(dāng)前 native 線程加入java 線程,用于回調(diào)乏屯,或者是獲取 jvm 線程 上下文
JavaVM *g_VM;
//用來(lái) findClass
jobject gClassLoader;
jmethodID gFindClassMethod;
//獲取jvm 上下文
JNIEnv *getEnv() {
JNIEnv *env;
int status = (*g_VM)->GetEnv(g_VM, (void **) &env, JNI_VERSION_1_6);
if (status < 0) {
status = (*g_VM)->AttachCurrentThread(g_VM, &env, NULL);
if (status < 0) {
return NULL;
}
}
return env;
}
/**
* java 層調(diào)用 System.loadLibrary(); 的時(shí)候就會(huì)調(diào)用這個(gè)方法根时,此方法的目的是 找到classloader的對(duì)象,還有類加載的方法ID
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
g_VM = pjvm; // cache the JavaVM pointer
JNIEnv *env = getEnv();
//replace with one of your classes in the line below
jclass randomClass = (*env)->FindClass(env, "com/tct/helloworld/HelloWorld");
jclass classClass = (*env)->GetObjectClass(env, randomClass);
jclass classLoaderClass = (*env)->FindClass(env, "java/lang/ClassLoader");
jclass getClassLoaderMethod = (*env)->GetMethodID(env, classClass, "getClassLoader",
"()Ljava/lang/ClassLoader;");
gClassLoader = (*env)->NewGlobalRef(env, (*env)->CallObjectMethod(env, randomClass,
getClassLoaderMethod));
gFindClassMethod = (*env)->GetMethodID(env, classLoaderClass, "findClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
return JNI_VERSION_1_6;
}
//調(diào)用ClassLoder 去找到對(duì)應(yīng)的類辰晕,在linux 線程是獨(dú)立于JVM 蛤迎,所以一般的 findClass 是找不到j(luò)vm中的類。只能使用八大基本類型含友。
jclass GlobalFindClass(const char* name) {
JNIEnv* env = getEnv();
return (jclass)((*env)->CallObjectMethod(env,gClassLoader, gFindClassMethod, (*env)->NewStringUTF(env,name)));
}
void test_process(void *p) {
jobject callBack = (jobject)p;
JNIEnv *env;
jboolean mNeedDetach;
//獲取當(dāng)前native線程是否有沒(méi)有被附加到j(luò)vm環(huán)境中
int getEnvStat = (*g_VM)->GetEnv(g_VM, (void **) &env, JNI_VERSION_1_6);
if (getEnvStat == JNI_EDETACHED) {
//如果沒(méi)有替裆, 主動(dòng)附加到j(luò)vm環(huán)境中,獲取到env
if ((*g_VM)->AttachCurrentThread(g_VM, &env, NULL) != 0) {
return;
}
mNeedDetach = JNI_TRUE;
}
jclass cls = GlobalFindClass( "com/tct/helloworld/TestBean");
if (cls == 0) {
LOGI("native cls= %ld", cls);
return;
}
jmethodID cid = (*env)->GetMethodID(env, cls, "<init>", "(ILjava/lang/String;)V");
jstring name = (*env)->NewStringUTF(env,"helloworld");
jobject jInstance = (*env)->NewObject(env, cls, cid,(jint)1, name);
//獲取回調(diào)的類
jclass jcallBackClass = (*env)->GetObjectClass(env,callBack);
//通過(guò)回調(diào)的類找到回調(diào)的方法
jmethodID callbackid = (*getEnv())->GetMethodID(env, jcallBackClass, "onLinstener", "(Lcom/tct/helloworld/TestBean;)V");
if(callbackid ==0) {
return;
}
//調(diào)用回調(diào)的方法
(*env)->CallVoidMethod(env,callBack,callbackid,jInstance);
(*env)->DeleteGlobalRef(env, callBack);
(*env)->DeleteLocalRef(env, jcallBackClass);
(*env)->DeleteLocalRef(env, jInstance);
(*env)->DeleteLocalRef(env, cls);
(*env)->DeleteLocalRef(env, name);
//釋放當(dāng)前線程
if (mNeedDetach) {
(*g_VM)->DetachCurrentThread(g_VM);
}
}
int start_test_thread(jobject listener) {
pthread_t tid;
if (0 != (pthread_create(&tid, NULL, test_process, listener))) {
return -1;
} else {
pthread_detach(tid); //設(shè)置成 分離線程窘问,線程跑完自己回收內(nèi)存
}
return 0;
}
JNIEXPORT void JNICALL Java_com_tct_helloworld_HelloWorld_nativeGetInstanceByThread
(JNIEnv *env, jobject obj,jobject jListener) {
// 這里的內(nèi)存區(qū)域?qū)儆?native 棧中辆童。跑完這個(gè)方法,局部變量都會(huì)被回收惠赫。所以需要使用 NewGlobalRef 對(duì) jListener 生成一個(gè)全局引用(linux 堆中)
jobject callback = (*env)->NewGlobalRef(env, jListener);
//開啟線程
start_test_thread(callback);
}
C 層回調(diào) Java 層 方法把鉴,更多的解決方案,詳細(xì)查看我的另一篇博客
五汉形、JNI Java 和 C++ 無(wú)縫對(duì)接
5.1纸镊、 以上實(shí)踐都是 java
和 C
的對(duì)接。然而 java
是面向?qū)ο螅?C
是面向過(guò)程沒(méi)有對(duì)象的概念概疆。
- 舉個(gè)場(chǎng)景例子:
如果
java
層需要發(fā)起A
逗威、B
個(gè)線程 到C
層去請(qǐng)求數(shù)據(jù),并且需要各自提供 請(qǐng)求岔冀、取消的接口凯旭。要實(shí)現(xiàn)多線程的取消接口,如果使用C
封裝JNI
使套,就需要提供鏈表(或者其他集合的數(shù)據(jù)結(jié)構(gòu))把每一個(gè)線程的Tid(java)
罐呼,和請(qǐng)求綁定起來(lái),取消的時(shí)候通過(guò)鏈表找到該線程的請(qǐng)求把柄侦高,通過(guò)把柄取消嫉柴。期間你會(huì)遇到鏈表插入刪除,多線程鎖奉呛,還得多個(gè)鏈表的全局引用计螺。非常麻煩。
- 然而
java
就是為了避免這種麻煩瞧壮,實(shí)現(xiàn)高效率編程登馒。面向?qū)ο笳Q生了。 - 那么如何從
java
->C++
->C
進(jìn)行調(diào)用咆槽。上流程圖陈轿,上代碼:
java
層對(duì)接類 HelloWorld.java
public class HelloWorld {
//加入一個(gè)變量 long 型保存 C++ 對(duì)象的地址
public long mNativeContext = 0L;
//類被創(chuàng)建,相對(duì)應(yīng)的 JNI 也創(chuàng)建一個(gè)類
public HelloWorld() {
init();
}
public native void init();
//..
}
JNI
層新建兩個(gè)文件:HelloWorld.cpp
秦忿、HelloWorld.h
麦射。
HelloWorld.cpp
代碼:
#include "HelloWorld.h"
extern "C" {
}
HelloWorld::HelloWorld() {
}
HelloWorld::~HelloWorld() {
}
char * HelloWorld::getString() {
return "HelloWorld";
}
HelloWorld.h
代碼
#ifndef HelloWorld_H
#define HelloWorld_H
class HelloWorld
{
public:
HelloWorld();
~HelloWorld();
char * getString();
};
#endif
JNI
層接口 helloworld_android.c
代碼:
//創(chuàng)建一個(gè)結(jié)構(gòu)體存放對(duì)象地址
typedef struct {
jfieldID context;
} fields_t;
static fields_t fields;
// System.loadLibrary("helloworld");觸發(fā)被調(diào)用的方法
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
JNIEnv *env = getEnv();
//獲取 java 層 mNativeContext 變量的 ID ,并賦值到 fields.context 這個(gè)全局變量灯谣。
fields.context = env->GetFieldID(randomClass, "mNativeContext", "J");
// ...
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL Java_com_tct_helloworld_HelloWorld_init
(JNIEnv *env, jobject obj) {
//初始化法褥,HelloWorld 指針對(duì)象,并且強(qiáng)轉(zhuǎn)指針為 long 型酬屉,賦值到 對(duì)應(yīng)的java 對(duì)象中 mNativeContext 的變量中去
HelloWorld *mHelloWorld = new HelloWorld();
env->SetLongField(obj, fields.context, (long)mHelloWorld);
}
最后驗(yàn)證一下:
//java 層代碼
//MainActivity.java
System.out.println("test1" +new HelloWorld().nativeGetStringByObject());
System.out.println("test2" +new HelloWorld().nativeGetStringByObject());
//HelloWorld.java
public native String nativeGetStringByObject();
//C 層代碼
static HelloWorld *getObject(JNIEnv *env, jobject thiz) {
// No lock is needed, since it is called internally by other methods that are protected
HelloWorld *retriever = (HelloWorld *) env->GetLongField(thiz,fields.context);
return retriever;
}
JNIEXPORT jstring JNICALL Java_com_tct_helloworld_HelloWorld_nativeGetStringByObject(JNIEnv *env, jobject obj) {
char * p = getObject(env,obj)->getString();
return env->NewStringUTF(p);
}
log
:
I/System.out: test1HelloWorld
I/System.out: test2HelloWorld
六半等、JNI 開源實(shí)戰(zhàn)
對(duì)于 JNI
的一些的基本知識(shí)基本就講完了。JNI
的用途為 java
開辟了另一扇大門呐萨,所有能在C
上面實(shí)現(xiàn)的杀饵。都能拿過(guò)來(lái)給Android
平臺(tái)上使用。
譬如以下一些 C
庫(kù):
- 音視頻播放庫(kù)
- 高斯模糊庫(kù)
-
openCV
人臉識(shí)別谬擦,車牌號(hào)碼識(shí)別 - 蘋果的
AirPlay
協(xié)議 藍(lán)牙耳機(jī)
更多 C
庫(kù)詳情地址
接下來(lái)實(shí)戰(zhàn)一個(gè) bilibili/ijkPlayer
音視頻解碼庫(kù)的開源代碼切距。
傳送門
注: 感謝 http://www.cnblogs.com/daniel-shen/archive/2006/10/16/530587.html 提供表格