本篇文章已授權(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ù)分類(lèi) : 靜態(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ù)
這么解釋?zhuān)?/p>
.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ì)外開(kāi)放的接口聲明。對(duì)外開(kāi)放的接口.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 類(lèi)準(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();
}
很明顯上面類(lèi)分成三部分:
- 有
static代碼塊,調(diào)用了System.loadLibrary("helloworld");這句代碼代表著,使用這個(gè)類(lèi)之前都會(huì)去加載libhelloworld.so這個(gè)動(dòng)態(tài)庫(kù),注意.so前面有lib。那這個(gè)動(dòng)態(tài)庫(kù)如何生成,后面講。 - 這個(gè)類(lèi)是一個(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"); //寫(xiě)入字符串到foo指針
jstring jname = (*env)->NewStringUTF(env, foo);
free(foo); //釋放指針
foo = NULL;
return jname; //返回字符串
//return helloworld;
}
- 生成并編寫(xiě)
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)
- 生成并編寫(xiě)
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、最后寫(xiě)個(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
打開(kāi)你的 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ú)指定寫(xiě)入多少字符
那么改成以下代碼,就可以了
#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 類(lèi)型,方法對(duì)照表
3.1、基本類(lèi)型對(duì)照表
| Java類(lèi)型 | 本地類(lèi)型 | 描述 |
|---|---|---|
| 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類(lèi)型的對(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 類(lèi)方法名稱(chēng)
Java類(lèi)型 | 本地類(lèi)型 | 描述
| 函數(shù) | Java數(shù)組類(lèi)型 | 本地類(lèi)型 |
|---|---|---|
| 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 類(lèi)型 | 符號(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類(lèi)名 |
| Arrays數(shù)組 | [array-type [數(shù)組類(lèi)型 |
| methods方法 | (argument-types)return-type(參數(shù)類(lèi)型)返回類(lèi)型 |
四、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 類(lèi)中的屬性值,轉(zhuǎn)成 C 層可使用的類(lèi)型
//java 類(lèi)
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ì)象,還有類(lèi)加載的方法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)的類(lèi),在linux 線程是獨(dú)立于JVM ,所以一般的 findClass 是找不到j(luò)vm中的類(lèi)。只能使用八大基本類(lèi)型。
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)的類(lèi)
jclass jcallBackClass = (*env)->GetObjectClass(env,callBack);
//通過(guò)回調(diào)的類(lèi)找到回調(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);
//開(kāi)啟線程
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ì)接類(lèi) HelloWorld.java
public class HelloWorld {
//加入一個(gè)變量 long 型保存 C++ 對(duì)象的地址
public long mNativeContext = 0L;
//類(lèi)被創(chuàng)建,相對(duì)應(yīng)的 JNI 也創(chuàng)建一個(gè)類(lèi)
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 開(kāi)源實(shí)戰(zhàn)
對(duì)于 JNI 的一些的基本知識(shí)基本就講完了。JNI 的用途為 java 開(kāi)辟了另一扇大門(mén),所有能在C 上面實(shí)現(xiàn)的。都能拿過(guò)來(lái)給Android平臺(tái)上使用。
譬如以下一些 C庫(kù):
- 音視頻播放庫(kù)
- 高斯模糊庫(kù)
-
openCV人臉識(shí)別,車(chē)牌號(hào)碼識(shí)別 - 蘋(píng)果的
AirPlay協(xié)議 藍(lán)牙耳機(jī)
更多 C 庫(kù)詳情地址
接下來(lái)實(shí)戰(zhàn)一個(gè) bilibili/ijkPlayer音視頻解碼庫(kù)的開(kāi)源代碼。
傳送門(mén)
注: 感謝 http://www.cnblogs.com/daniel-shen/archive/2006/10/16/530587.html 提供表格



