JNI入門了解

什么是JNI?

JNI Java Native Interface java本地接口

JNI 能夠解決什么問題灵寺?

在 java 面世之前,很多代碼都是使用 c/c++ 編寫的纷宇,在 java 面世后為了避免重復(fù)造輪子,并且可以讓 java 使用之前編寫的 c/c++ 代碼,這樣就出現(xiàn)了 JNI 這門技術(shù)火诸,它是一個(gè) java 和 native 進(jìn)行相互調(diào)用的中間人躲查。

因?yàn)橛行┕δ苁褂?java 代碼編寫可能會(huì)有性能問題它浅,并且反編譯代碼的安全性問題,因此在某些情況下就需要使用 c/c++ 來配合 java 來開發(fā)了镣煮。

Java層是如何使用JNI姐霍?

Java 層調(diào)用 JNI 層的方法需要做兩件事

  • 加載動(dòng)態(tài)庫(kù)。
  • 使用 native 關(guān)鍵字聲明與 JNI 層對(duì)應(yīng)的函數(shù)典唇。

如何加載動(dòng)態(tài)庫(kù)镊折?

理論上是在任何需要調(diào)用 native 代碼的地方之前就可以去加載動(dòng)態(tài)庫(kù),但是一般情況下都會(huì)在類的 static 代碼塊中去加載介衔。

System.loadLibrary("動(dòng)態(tài)庫(kù)的名稱")

動(dòng)態(tài)庫(kù)的名稱恨胚,例如 media_jni,系統(tǒng)會(huì)自動(dòng)根據(jù)不同的平臺(tái)拓展成真實(shí)的動(dòng)態(tài)庫(kù)文件名炎咖,例如在Linux系統(tǒng)上會(huì)拓展成libmedia_jni.so赃泡,而在Windows平臺(tái)上則會(huì)拓展成media_jni.dll。

Native 和 JNI 層的代碼怎么相互對(duì)應(yīng)呢乘盼?

  • MediaScanner 類定義的 native_init 方法 是在這個(gè)路徑下:android.media.MediaScanner.native_init

  • JNI 層的代碼 : android_media_MediaScanner_native_init

jni 方法的命名規(guī)則:Java_包名類名方法名

JNI函數(shù)的注冊(cè)問題

什么叫做注冊(cè)急迂?

就是將 Java 層的函數(shù)和 JNI 層的函數(shù)關(guān)聯(lián)起來。

函數(shù)注冊(cè)分為兩種方式:靜態(tài)注冊(cè)\動(dòng)態(tài)注冊(cè)

一蹦肴、靜態(tài)注冊(cè)

  • 在 Java 層使用 native 關(guān)鍵字描述函數(shù)

    public class Utils {
      public static native int add(int a, int b);
    }
    
  • 根據(jù)在 Utils 類編寫的 add 函數(shù)生成對(duì)應(yīng)的 native 函數(shù)僚碎。

      //javah 是 jdk 提供的一個(gè)工具
      //在終端使用 cd 切換到 Utils 編譯后的文件目錄下,具體位置看下面的截圖阴幌,不要搞錯(cuò)地方勺阐,否則會(huì)出現(xiàn)找不到Utils類
      //-o Utils.h 表示輸出的文件名為 Utils.h
      javah -o Utils.h com.lu.ndkdemo.Utils
    
      //生成文件的路徑:app/build/intermediates/classes/debug/com/lu/ndkdemo/Utils.class
    
    
  • 過程:編寫 Java 代碼 -> 編譯 -> 使用 javah 命令生成 packageName_class.h 頭文件

     javah -o output packgeName.className
    
  • 生成對(duì)應(yīng)的 Utils.h 文件

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_lu_ndkdemo_Utils */
    
    #ifndef _Included_com_lu_ndkdemo_Utils
    #define _Included_com_lu_ndkdemo_Utils
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     com_lu_ndkdemo_Utils
     * Method:    add
     * Signature: (II)I
     */
    JNIEXPORT jint JNICALL   Java_com_lu_ndkdemo_Utils_add
    (JNIEnv *, jclass, jint, jint);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    
    
  • native 函數(shù)是如何尋找 JNI 函數(shù)的卷中?
    當(dāng)Java層調(diào)用native_init函數(shù)時(shí),它會(huì)從對(duì)應(yīng)的JNI庫(kù)Java_android_media_MediaScanner_native_init渊抽,如果沒有蟆豫,就會(huì)報(bào)錯(cuò)。如果找到懒闷,則會(huì)為這個(gè)native_init和Java_android_media_MediaScanner_native_init 建立一個(gè)關(guān)聯(lián)關(guān)系十减,其實(shí)就是保存JNI層函數(shù)的函數(shù)指針。以后再調(diào)用native_init函數(shù)時(shí)愤估,直接使用這個(gè)函數(shù)指針就可以了帮辟,當(dāng)然這項(xiàng)工作是由虛擬機(jī)完成的。

  • 將 Utils.h 文件拷貝到存放 native 代碼的文件夾中

我的工程存放 native 代碼的目錄是 main/cpp/
所以將生成 Utils.h 拷貝到 main/cpp 即可

  • 編寫 native 層的代碼玩焰,我將其命名為 test.c

    //引入剛才生成的 Utils.h 文件
    #include "Utils.h"
    #include <android/log.h> 
    //將 Utils.h 生成函數(shù)拷貝到test.c 中
    //注意拷貝過來的函數(shù)是不完整的由驹,需要手動(dòng)添加參數(shù)變量名
    JNIEXPORT jint JNICALL   Java_com_lu_ndkdemo_Utils_add
          (JNIEnv *env, jclass obj, jint a, jint b) {
     //返回計(jì)算結(jié)果
     return a + b;
    }
    
  • 接下來就在 java 層代碼加載 so 庫(kù),并且調(diào)用該方法即可昔园,這一步就忽略不寫蔓榄。

二、動(dòng)態(tài)注冊(cè)

為什么有了靜態(tài)注冊(cè)之后還要有一個(gè)動(dòng)態(tài)注冊(cè)的功能呢默刚?

我們看到了甥郑,在靜態(tài)注冊(cè)中,我們使用了 javah 生成對(duì)應(yīng) native 函數(shù)荤西,可以看出它的方法名是非常的長(zhǎng)的澜搅,因此為了簡(jiǎn)化這個(gè)方法的表示,就有了動(dòng)態(tài)注冊(cè)皂冰。
動(dòng)態(tài)注冊(cè)的原理是這樣的:JNI 允許我們提供一個(gè)函數(shù)映射表店展,注冊(cè)給 JVM,這樣 JVM 就可以用函數(shù)映射表來調(diào)用相應(yīng)的函數(shù)秃流,而不必通過函數(shù)名來查找相關(guān)函數(shù)(這個(gè)查找效率很低赂蕴,函數(shù)名超級(jí)長(zhǎng))。

  • 在 Java 層使用 native 關(guān)鍵字描述函數(shù)
public class Utils {
   public static native int add(int a, int b);
}
  • 在哪里告訴系統(tǒng)我要?jiǎng)討B(tài)注冊(cè)函數(shù)呢舶胀?

我們都知道 java 想要調(diào)用 c 層的代碼概说,主要分 2 步:

  1. System.loadLibrary("so name");

  2. 調(diào)用對(duì)應(yīng)的 native 代碼
    int result = Utlils.add(1,2);

現(xiàn)在我們需要找到一個(gè)時(shí)機(jī)告訴系統(tǒng),我要?jiǎng)討B(tài)注冊(cè)函數(shù)嚣伐,并且告訴系統(tǒng)怎么動(dòng)態(tài)注冊(cè)糖赔。

而系統(tǒng)在執(zhí)行完 System.loadLibrary("..")之后會(huì)執(zhí)行 native 層的 JNI_Onload 方法,因此我們只需要在該方法完成動(dòng)態(tài)注冊(cè)即可轩端。

  • 在 JNI_Onload 函數(shù)動(dòng)態(tài)注冊(cè)

想要進(jìn)行動(dòng)態(tài)注冊(cè)放典,就必須要有一個(gè) java 層函數(shù)和 native 函數(shù)的對(duì)應(yīng)關(guān)系表。

在 jni 中是使用 JNINativeMethod 來保存對(duì)應(yīng)關(guān)系

typedef struct {
  char *name;//
  char *signature;
  void *fnPtr;
} JNINativeMethod;

編寫對(duì)應(yīng)關(guān)系表
method_table 是一個(gè)數(shù)組,它存放就是 JNINativeMethod 定義的三個(gè)屬性參數(shù)奋构。

1. name java 層的方法名
2. signature 方法的簽名
3. fnPtr 對(duì)應(yīng)的 native 的方法名
//下面這段代碼的表示就是將 java 層的 add 方法映射
//到 native 層的 add 方法壳影,不再是靜態(tài)注冊(cè)那種很長(zhǎng)的方法名了。
static JNINativeMethod method_table[] = {
{
"add", "(II)I",(void *) add}
};

有了對(duì)應(yīng)關(guān)系表 method_table 之后弥臼,我們就要開始注冊(cè)了宴咧。下面的 JNI_Onload 方法就是對(duì) method_table 表進(jìn)行動(dòng)態(tài)注冊(cè)。因此只需要實(shí)現(xiàn)這個(gè)方法径缅,在方法內(nèi)部進(jìn)行動(dòng)態(tài)注冊(cè)即可掺栅。在動(dòng)態(tài)加載 so 庫(kù)之后,系統(tǒng)會(huì)調(diào)用 jint 纳猪。

/**
 * 動(dòng)態(tài)注冊(cè)
 * 在 native 代碼中重寫該 JNI_Onload 方法即可
 */
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    //OnLoad方法是沒有JNIEnv參數(shù)的氧卧,需要通過vm獲取。
    JNIEnv *env = NULL;
    jint result = -1;

    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    //獲取對(duì)應(yīng)聲明native方法的Java類
    jclass clazz = (*env)->FindClass(env, "com/lu/ndkdemo/Utils");
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    //注冊(cè)方法兆旬,成功返回正確的JNIVERSION假抄。
    if ((*env)->RegisterNatives(env, clazz, method_table,
                                sizeof(method_table) / sizeof(method_table[0])) == JNI_OK) {
        return JNI_VERSION_1_4;
    }
    return JNI_FALSE;
}

jni 的方法簽名

  • Java 層的 native 方法:
private native void processFile(String path, String mimeType, MediaScannerClient client);

格式:(參數(shù)類型...)函數(shù)返回值類型

(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V
  • 簽名的相關(guān)解釋
    • L 開頭表示該參數(shù)是引用數(shù)據(jù)類型
    • 參數(shù)類型使用包/類名表示
    • 每一個(gè)引用類型使用;結(jié)束
    • 最右邊表示該方法的返回值類型
      • V 表示沒有返回值 void類型

如何將 jni 層的函數(shù)和 native 函數(shù)一一對(duì)應(yīng)呢怎栽?

一丽猬、通過JNINativeMethod一一對(duì)應(yīng)
在 jni.h 文件中聲明了一個(gè)叫 JNINativeMethod 的結(jié)構(gòu)體,通過這個(gè)結(jié)構(gòu)體就可以將 native 函數(shù)和 jni 函數(shù)進(jìn)行一一對(duì)應(yīng)了熏瞄。

/*
 * used in RegisterNatives to describe native method name, signature,
* and function pointer.*/
typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
} JNINativeMethod;

二脚祟、如何使用 JNINativeMethod?

  • 1、定義一個(gè) nativeMethod 數(shù)組

    JNINativeMethod nativeMethod[] = {
        {"processFile", //native函數(shù)名稱
        "(//函數(shù)參數(shù)類型
              Ljava/lang/String;
              Ljava/lang/String;
              Landroid/mediaMediaScannerClient;
           )
           V//函數(shù)返回值類型
           ",      (void*)android_media_MediaScanner_processFile  // jni 層函數(shù)名稱
             }
            };
    
    static JNINativeMethod gMethods[] = {
      {"_start","()V",(void   *)android_media_MediaPlayer_start}
              ...
    }
    
  • 2强饮、動(dòng)態(tài)注冊(cè)數(shù)組 registerNativeMethods

    static int   register_android_media_MediaPlayer(JNIEnv *env){
      //向 VM 進(jìn)行注冊(cè)
      return   AndroidRuntime::registerNativeMethods(env,
                  "android/media/MediaPlayer", gMethods, NELEM(gMethods));
    }
    
     int   AndroidRuntime::registerNativeMethods(JNIEnv*env,
      constchar* className, const JNINativeMethod* gMethods, int numMethods){
      //調(diào)用jniRegisterNativeMethods函數(shù)完成注冊(cè)
      return jniRegisterNativeMethods(env, className, gMethods, numMethods);}   
    int jniRegisterNativeMethods(JNIEnv* env, const char* className,const JNINativeMethod* gMethods, int numMethods){
      jclassclazz;
      clazz= (*env)->FindClass(env, className);
      ...
      //實(shí)際上是調(diào)用JNIEnv的RegisterNatives函數(shù)完成注冊(cè)的
      //gMethods = sizeof(methods) / sizeof(methods[0]))
      if((*env)->RegisterNatives(env, clazz, gMethods) numMethods) < 0) {
         return -1;
    
      }
    
      return0;
    
    }    
    

其實(shí)最終都是調(diào)用 RegisterNatives 方法完成注冊(cè)功能的由桌。

數(shù)據(jù)類型轉(zhuǎn)化

1、基本數(shù)據(jù)類型轉(zhuǎn)化
2邮丰、引用數(shù)據(jù)類型轉(zhuǎn)化

除了Java中基本數(shù)據(jù)類型的數(shù)組行您、Class、String和Throwable外剪廉,其余所有Java對(duì)象的數(shù)據(jù)類型在JNI中都用jobject表示娃循。

processFile(String path, String mimeType,MediaScannerClient client);

android_media_MediaScanner_processFile(JNIEnv*env, jobject thiz,jstring path, jstring mimeType, jobject client)

在 JNI 層的 processFile 的第二參數(shù)的說明:表示的是調(diào)用 processFile 方法的 MediaScanner 對(duì)象,如果該方法是 static 方式的話斗蒋,那么該參數(shù)就是 jclass 類型的捌斧,表示的是調(diào)用哪個(gè)類調(diào)用靜態(tài)函數(shù) processFile 。

JNIEnv 的介紹

在 JNI 層的 processFile 函數(shù)聲明中可以看到第一個(gè)參數(shù)就是 JNIEnv* 類型的泉沾,那么這個(gè)類型表示的是什么意思呢捞蚂?

JNIEnv*是定義任意native函數(shù)的第一個(gè)參數(shù)(包括調(diào)用JNI的RegisterNatives函數(shù)注冊(cè)的函數(shù)),用于訪問JVM數(shù)據(jù)結(jié)構(gòu)的JNI函數(shù)表指針跷究。

通過 JNIEnv 操作 jobject

能通過 JNIEnv 操作 jobject 就實(shí)際上就是通過 JNIEnv 操作 jobject 中的屬性和方法姓迅。

  • JNIEnv 操作 jobject 的屬性
  • JNIEnv 操作 jobject 的方法

JNI 是如何定義 jobject 的屬性和方法的?

jfieldID GetFieldID(jclass clazz,const char*name, const char *sig);

jmethodID GetMethodID(jclass clazz, const char*name,const char *sig);
//得到j(luò)ava對(duì)象
jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient");

//找到該對(duì)象的方法id
mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile","(Ljava/lang/String;JJ)V");
// env
//jobject 要操作的java對(duì)象,可以通過 FindClass 獲取
//methodID 表示該方法的id丁存,可以通過 GetMethodID 獲取
NativeType Call<type>Method(JNIEnv *env,jobject obj,jmethodID methodID, ...)色冀。
jboolean CallBooleanMethod(jobject obj,jmethodID methodID, ...) 

mEnv->CallBooleanMethod
//得到對(duì)象的屬性值
// env
//jobject 要操作的java對(duì)象,可以通過 FindClass 獲取
//fieldID 表示該屬性的id柱嫌,可以通過 GetFieldID 獲取
jboolean(jni對(duì)應(yīng)的類型) GetBooleanField(Get<type>Field)(jobject obj, jfieldID fieldID) {
        return functions->GetBooleanField(this,obj,fieldID);
}
//設(shè)置對(duì)象的屬性值
 void Set<type>Field(jobject obj, jfieldID fieldID,jboolean val) {
        //const struct JNINativeInterface_ *functions
        functions->SetBooleanField(this,obj,fieldID,val);
}

jstring

jstring NewString(const jchar *unicode, jsize len) {
        return functions->NewString(this,unicode,len);
}

GetStringChars得到一個(gè)Unicode字符串

const jchar *GetStringChars(jstring str, jboolean *isCopy) {
        return functions->GetStringChars(this,str,isCopy);
    }
jstring NewStringUTF(const char *utf) {
        return functions->NewStringUTF(this,utf);
}

GetStringUTFChars得到一個(gè)UTF-8字符串锋恬。

const char* GetStringUTFChars(jstring str, jboolean *isCopy) {
        return functions->GetStringUTFChars(this,str,isCopy);
}
void ReleaseStringChars(jstring str, const jchar *chars) {
        functions->ReleaseStringChars(this,str,chars);
}
void ReleaseStringUTFChars(jstring str, const char* chars) {
    functions->ReleaseStringUTFChars(this,str,chars);
}

使用 jstring

android_media_MediaScanner_processFile(JNIEnv*env, jobject thiz, jstring path, jstring mimeType, jobject client) {
    //調(diào)用JNIEnv的GetStringUTFChars得到本地字符串pathStr
    const char *pathStr = env->GetStringUTFChars(path, NULL);

    //使用完后,必須調(diào)用ReleaseStringUTFChars釋放資源
    env->ReleaseStringUTFChars(path, pathStr);
}

JNI類型簽名

示例

"(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",   
  • (參數(shù)1類型標(biāo)示參數(shù)2類型標(biāo)示...參數(shù)n類型標(biāo)示)返回值類型標(biāo)示
  • L 表示該參數(shù)是引用類型的编丘;
  • 參數(shù)類型使用權(quán)限類名表示与学,不過.需要替換為/
  • 在引用類型后面需要使用;結(jié)尾
  • 最右邊V表示沒有返回值

工具的使用

雖然函數(shù)簽名信息很容易寫錯(cuò),但Java提供一個(gè)叫javap的工具能幫助生成函數(shù)或變量的簽名信息嘉抓,它的用法如下:

javap –s -p xxx索守。其中xxx為編譯后的class文件,s表示輸出內(nèi)部數(shù)據(jù)類型的簽名信息抑片,p表示打印所有函數(shù)和成員的簽名信息卵佛,而默認(rèn)只會(huì)打印public成員和函數(shù)的簽名信息。

垃圾回收

  • Local Reference

    LocalReference最大的特點(diǎn)就是敞斋,一旦JNI層函數(shù)返回截汪,這些jobject就可能被垃圾回收。

  • Global Reference

    全局引用植捎,這種對(duì)象如不主動(dòng)釋放衙解,就永遠(yuǎn)不會(huì)被垃圾回收。

  • WeakRe Global ference

  • 變量的內(nèi)存釋放

    void DeleteLocalRef(jobject obj) 
    mEnv->DeleteLocalRef(pathStr); 
    

JNI 異常處理

當(dāng)調(diào)用 JNIEnv 的某些函數(shù)出錯(cuò)后焰枢,不會(huì)向 Java 一樣會(huì)終止下一行代碼的執(zhí)行蚓峦,直到從 JNI 層返回到 Java 層后,虛擬機(jī)才會(huì)拋出這個(gè)異常济锄。

出現(xiàn)異常后暑椰,不能再調(diào)用 JNIEnv 的函數(shù)了,不然程序就會(huì)掛掉荐绝。

JNI層函數(shù)可以在代碼中截獲和修改這些異常一汽,JNIEnv提供了三個(gè)函數(shù)進(jìn)行幫助:

  • ExceptionOccured函數(shù),用來判斷是否發(fā)生異常很泊。

  • ExceptionClear函數(shù)角虫,用來清理當(dāng)前JNI層中發(fā)生的異常。

  • ThrowNew函數(shù)委造,用來向Java層拋出異常戳鹅。

在jni層打log日志

  • 引入 #include <android/log.h> 頭文件
  • 聲明
#define  LOG  "JNILOG" // 這個(gè)是自定義的LOG的TAG
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG,__VA_ARGS__) // 定義LOGD類型
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG,__VA_ARGS__) // 定義LOGI類型
#define  LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG,__VA_ARGS__) // 定義LOGW類型
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG,__VA_ARGS__) // 定義LOGE類型
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG,__VA_ARGS__) // 定義LOGF類型

  • 使用
jint i = 3;
LOGE("i = %d\n",i ));

總結(jié)

  1. JNI 函數(shù)的注冊(cè)
  2. JNI 和 Java 的類型轉(zhuǎn)換
  3. JNIEnv 的含義及其作用
  4. jstring 的使用
  5. JNI 的簽名
  6. JNI 的垃圾回收
  7. JNI 的異常處理

注意事項(xiàng)

在java類中編寫 native 方法。注意這時(shí)方法本身會(huì)報(bào)紅昏兆,只要按 command+enter 就可以直接生成一個(gè)與之對(duì)應(yīng)的 jni 方法枫虏。

package com.lu.ndkdemo2;
/**
 * Created by anlu on 2017/11/25.
 */
public class Utils {
    public native void call(String name,int age,String[] args);
}

編譯:build->rebuild

找到 /Users/lu/dev/as_code/NDKDemo2/app/build/intermediates/classes/debug

javah -jni com.lu.ndkdemo2.Utils 生成的文件名就是 com_lu_ndkdemo2_Utils.h
javah -o test.h com.lu.ndkdemo2.Utils 生成的文件名就是 test.h
即可生成對(duì)應(yīng)的頭文件

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_lu_ndkdemo2_Utils */

#ifndef _Included_com_lu_ndkdemo2_Utils
#define _Included_com_lu_ndkdemo2_Utils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_lu_ndkdemo2_Utils
 * Method:    call
 * Signature: (Ljava/lang/String;I[Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_lu_ndkdemo2_Utils_call
  (JNIEnv *, jobject, jstring, jint, jobjectArray);

#ifdef __cplusplus
}
#endif
#endif

路徑在:/Users/lu/dev/as_code/NDKDemo2/app/build/intermediates/classes/debug

#include <jni.h>
#include <string>

JNIEXPORT void JNICALL
Java_com_lu_ndkdemo2_Utils_call(JNIEnv *env, jobject instance, jstring name_, jint age,
                                  jobjectArray args) {
    const char *name = env->GetStringUTFChars(name_, 0);
    // TODO
    //釋放jstring
    env->ReleaseStringUTFChars(name_, name);
}

JNIEXPORT,JNICALL:是系統(tǒng)定義的宏;
JNIEXPORT后面寫函數(shù)的輸出類型隶债;
JNICALL后面寫函數(shù)名腾它,實(shí)測(cè)這兩個(gè)宏可寫可不寫。

No implementation found for int com.lu.ndkdemo.Utils.add(int, int) (tried Java_com_lu_ndkdemo_Utils_add and Java_com_lu_ndkdemo_Utils_add__II)

1.方法沒有使用extern C 表示死讹,會(huì)報(bào)不到異常瞒滴。
2.沒有寫system.loadLibrary("")。

couldn't find "libtest.so"赞警,沒有在 CMakeLists.txt 聲明妓忍。

一個(gè)so可以構(gòu)建多個(gè)源文件
add_library(<name> [STATIC | SHARED | MODULE][EXCLUDE_FROM_ALL] source1 [source2 ...])
name 就是打包的 so 庫(kù)的名稱。
so 庫(kù)一般會(huì)使用 SHARED愧旦。source1世剖、 source2 多個(gè)source直接用空格即可,不可以用,在 CMakeLists.txt 指定多個(gè)子CMakeLists.txt 構(gòu)建多個(gè) so 庫(kù)笤虫。

 #set(<variable> <value> [[CACHE <type> <docstring> [FORCE]] | PARENT_SCOPE])
 #add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

如果在c/c++代碼打log日志
異常情況:

11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 我是log3
11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 執(zhí)行了java層的callExceptionMethod方法
11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 沒有異常的情況...
11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 沒有異常的情況...
11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 沒有異常的情況...
11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 沒有異常的情況...
11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 沒有異常的情況5...
11-25 12:30:13.107 3165-3165/com.lu.ndkdemo E/zeal: java 層出現(xiàn)異常...JNI層拋出了異常...
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旁瘫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子琼蚯,更是在濱河造成了極大的恐慌酬凳,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凌停,死亡現(xiàn)場(chǎng)離奇詭異粱年,居然都是意外死亡售滤,警方通過查閱死者的電腦和手機(jī)罚拟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來完箩,“玉大人赐俗,你說我怎么就攤上這事”字” “怎么了阻逮?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)秩彤。 經(jīng)常有香客問我叔扼,道長(zhǎng),這世上最難降的妖魔是什么漫雷? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任瓜富,我火速辦了婚禮,結(jié)果婚禮上降盹,老公的妹妹穿的比我還像新娘与柑。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布价捧。 她就那樣靜靜地躺著丑念,像睡著了一般。 火紅的嫁衣襯著肌膚如雪结蟋。 梳的紋絲不亂的頭發(fā)上脯倚,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音嵌屎,去河邊找鬼挠将。 笑死,一個(gè)胖子當(dāng)著我的面吹牛编整,可吹牛的內(nèi)容都是我干的舔稀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼掌测,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼内贮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起汞斧,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤夜郁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后粘勒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竞端,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年庙睡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了事富。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乘陪,死狀恐怖统台,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情啡邑,我是刑警寧澤贱勃,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站谤逼,受9級(jí)特大地震影響贵扰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜流部,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一戚绕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贵涵,春花似錦列肢、人聲如沸恰画。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拴还。三九已至,卻和暖如春欧聘,著一層夾襖步出監(jiān)牢的瞬間片林,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工怀骤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留费封,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓蒋伦,卻偏偏與公主長(zhǎng)得像弓摘,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痕届,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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