Android JNI 篇 - 從入門到放棄

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

一专缠、JNI 涉及的名詞概念

二方咆、JNI 在 Android Studio 搭建

三郊供、JNI 類型踪古,方法對(duì)照表

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

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

六窄刘、JNI 開源實(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ù)分類 : 靜態(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, .hC 庫(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 的文件夾恳邀。例如:
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"); //寫入字符串到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)配置子 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今魔、最后寫個(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

打開你的 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ú)指定寫入多少字符

那么改成以下代碼听诸,就可以了

#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 類型,方法對(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 *envJNIjava 線程的上下文,每一個(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í)踐都是 javaC 的對(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è)鏈表的全局引用计螺。非常麻煩。

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惨远,隨后出現(xiàn)的幾起案子谜悟,更是在濱河造成了極大的恐慌话肖,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件葡幸,死亡現(xiàn)場(chǎng)離奇詭異最筒,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蔚叨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門床蜘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蔑水,你說(shuō)我怎么就攤上這事邢锯。” “怎么了搀别?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵丹擎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我歇父,道長(zhǎng)鸥鹉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任庶骄,我火速辦了婚禮毁渗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘单刁。我一直安慰自己灸异,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布羔飞。 她就那樣靜靜地躺著肺樟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逻淌。 梳的紋絲不亂的頭發(fā)上么伯,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音卡儒,去河邊找鬼田柔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛骨望,可吹牛的內(nèi)容都是我干的硬爆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼擎鸠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缀磕!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤袜蚕,失蹤者是張志新(化名)和其女友劉穎糟把,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牲剃,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡遣疯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了颠黎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柑晒。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蒋情,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鹦马,到底是詐尸還是另有隱情文判,我是刑警寧澤过椎,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站戏仓,受9級(jí)特大地震影響疚宇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赏殃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一敷待、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仁热,春花似錦榜揖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至迅矛,卻和暖如春妨猩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背秽褒。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工壶硅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人销斟。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓森瘪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親票堵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扼睬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容