零基礎帶你吃掉JNI全家桶(二)

散人丶發表於2019-02-17

##前言 上篇通過一個簡單的例子大概闡述了jni開發的基本流程,最後也編譯出了自己的so檔案,本篇主要介紹怎麼引入第三方的so檔案並進行呼叫

零基礎帶你吃掉JNI全家桶(一)

零基礎帶你吃掉JNI全家桶(三)

一、如何呼叫第三方so中的方法?

通過上篇我們知道,從Java層要呼叫native層的方法,要麼是靜態註冊,要麼是動態註冊,但是不管是哪一種,兩個方法之間必須需要建立一定的通道關係,靜態註冊需要對應好方法名,動態註冊需要對兩個方法進行繫結,都是需要知道包名的,但是問題來了,那麼如何在我的專案中呼叫第三方so中的方法呢?自己的專案包名都是不一樣的,實際上有兩種方式:

  • 如果第三方提供了so檔案,同時也提供了SDK jar包檔案,那實際上自己本身就不需要做太多的操作,直接呼叫API中的方法,sdk內部再去跟native方法進行對映,我們只要將so庫檔案匯入進來放在指定位置,一般是在jniLibs目錄下,這樣sdk裡面就可以跟native層通訊了。
  • 上面那種方式一般適用於整合第三方服務,比如高德、友盟等等,侷限性較大,只能在sdk限制下進行操作,假如是需要對其進行一定的擴充套件性或者沒有人給你提供SDK包(比如自己公司的一些內部庫經常會有這種情況),那麼這個時候就需要自己通過需要把so導進來之後,編寫本地方法來進行對映通訊

二、關聯第三方so庫

上面第一種方法就不說了,一般直接呼叫sdk即可,我們直接看第二種,大概分為以下幾個步驟:

  • 匯入第三方so檔案,放在指定目錄下,一般就放在jniLibs目錄下面
  • 編寫CMakeLists.txt檔案,引入so庫並進行關聯
  • 編寫native方法,包名一定要和so中對應的包名一樣

我們就使用上篇例子生成出來的so,我們來呼叫下,從build中把so拷出來,位置如下,這裡我們用arm架構就行了,一個是64位的,一個是32位的

image.png

然後放到我們新建的一個專案中,這裡把so名字重新命名下,防止混淆

image.png

然後我們需要在build.gradle中指定so檔案目錄

sourceSets {
        main {
            jni.srcDirs = []
            jniLibs.srcDirs = ['src\\main\\jniLibs']
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
複製程式碼

然後,因為本身自己是不需要生成so庫的,所以CMakeLists.txt中的native-lib可以刪掉,我們加入要引入的so庫,並與之關聯


#定義cmake支援的最小版本號
cmake_minimum_required(VERSION 3.4.1)
#加入lib2庫 ,定義為匯入形式
add_library(lib2 SHARED IMPORTED)

#關聯lib2庫為我們要匯入進來的libdata.so檔案,ANDROID_ABI會根據自身cpu架構選擇so檔案
set_target_properties( lib2
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libdata.so)
# 從系統裡查詢依賴庫,可新增多個
find_library( # 例如查詢系統中的log庫liblog.so
              log-lib

              # liblog.so庫指定的名稱即為log,如同上面指定生成的libnative-lib.so庫名稱為native-lib一樣
              log )
複製程式碼

這樣CMakeLists檔案在執行的時候就會把libdata.so檔案載入進來。

最後一步,編寫本地方法,新建一個類

package com.example.taolin.jni_project;

public class NativeHelper {
    static {
        System.loadLibrary("data");
    }
    public static native String stringFromJNI();
    public static  native int add(int a,int b);
}
複製程式碼

主要包名,要和so中的一直的,也是上篇文章中的動態註冊程式碼,這裡貼上部分

//動態註冊
jint registerMethod(JNIEnv *env) {
    jclass clz = env->FindClass("com/example/taolin/jni_project/NativeHelper");
    if (clz == NULL) {
        LOGD("con't find class: com/example/taolin/jni_project/NativeHelper");
    }
    JNINativeMethod jniNativeMethod[] = {{"stringFromJNI",    "()Ljava/lang/String;",                       (void *) backStringToJava},
                                         {"add",              "(II)I",                                      (void *) addNum},};
    return env->RegisterNatives(clz, jniNativeMethod,
                                sizeof(jniNativeMethod) / sizeof(jniNativeMethod[0]));
}
複製程式碼

這樣的話,就在呼叫的時候,就可以找到對應的類,將Java層方法和Native層方法進行關聯起來,進行通訊

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(NativeHelper.stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */

}
複製程式碼

具體圖我就不截了,也是能成功顯示出字串的,這樣就呼叫了外部so的方法,大功告成!

這裡只是最簡單的,從native層返回一個字串,比較淺顯侷限,下篇將接著介紹下,Jni的語法,以及java層和native間更為複雜的互動過程,物件怎麼傳輸?,native層怎麼操作java層的類?等等。

有新的想法和疑問的老哥可以留言一起討論哦,溜了溜了~

相關文章