Android JNI 篇 - 從入門(mén)到放棄

本篇文章已授權(quán)微信公眾號(hào) guolin_blog(郭霖)獨(dú)家發(fā)布

一、JNI 涉及的名詞概念

二、JNI 在 Android Studio 搭建

三、JNI 類(lèi)型,方法對(duì)照表

四、JNI 場(chǎng)景實(shí)踐

五、JNI Java 和 C++ 無(wú)縫對(duì)接

六、JNI 開(kāi)源實(shí)戰(zhàn)

一、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, .hC 庫(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 的文件夾。例如:
image
2.3、創(chuàng)建 c 代碼,和配置文件,看下圖的位置:
image
  • 生成一個(gè) helloworld_android.c,代碼如下:對(duì)接 java 層 ,下面的方法
    JNIEXPORT jstring JNICALL Java_com_tct_helloworld_HelloWorld_nativeGetString(JNIEnv *env, jobject obj)javapublic 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)配置子 modulebuild.gradleNDK
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 的蹤影。
image
  • 解壓 aaraar 其實(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
image

很明顯這種異常,是崩潰在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)去,就可以看到閃退的代碼行了:

image

定位奔潰的代碼行:

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

image

三、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 *envJNIjava 線程的上下文,每一個(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í)踐都是 javaC 的對(duì)接。然而 java 是面向?qū)ο螅?C 是面向過(guò)程沒(méi)有對(duì)象的概念。
  • 舉個(gè)場(chǎng)景例子:

如果 java 層需要發(fā)起AB個(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è)鏈表的全局引用。非常麻煩。

image
  • 然而 java 就是為了避免這種麻煩,實(shí)現(xiàn)高效率編程。面向?qū)ο笳Q生了。
  • 那么如何從 java -> C++->C 進(jìn)行調(diào)用。上流程圖,上代碼:
image

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 提供表格

推薦閱讀:Android 編譯速度優(yōu)化黑科技 - RocketX

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容