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

前言

你們好!我又來了,此次準備着手寫一個JNI開發系列,畢竟,如今JNI開發也是在各個公司愈來愈重要了,若是項目畢竟大,可能涉及的模塊較多,好比你做爲應用層的開發,不免避免不了須要使用一些庫,一些加密操做等等,通常都會放在本地方法裏面,比較安全,人家丟給你so文件或者靜態a文件。。你不會用豈不是很尷尬。網上資料比較雜,並且很亂,大部分仍是在用.mk的方法。java

本系列就基於CMake形式,但願可以帶着一些但願學習JNI開發的小夥伴一塊兒學會JNI開發~比心❤android

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

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

從一個栗子提及

c++ support
注意:要支持CMake,此時咱們須要勾選 Include C++ support,而後點擊Next--->Finish,完成工程的建立。 建立完成後,咱們打開工程目錄,發現增長了幾個不同的地方:
image.png
發現AS已經幫咱們生產一個cpp目錄以及一個native-lib.cpp的c++文件,在根目錄下,也多了一個CMakeLists.txt文件,咱們主要來關注 CMakeLists.txt裏面的東東

#定義cmake支持的最小版本號
cmake_minimum_required(VERSION 3.4.1)


add_library( # 設置生成so庫的文件名稱,例如此處生成的so庫文件名稱應該爲:libnative-lib.so
             native-lib

             # 設置生成的so庫類型,類型只包含兩種:
             # STATIC:靜態庫,爲目標文件的歸檔文件,在連接其餘目標的時候使用
             # SHARED:動態庫,會被動態連接,在運行時被加載
             SHARED

             # 設置源文件的位置,能夠是不少個源文件,都要添加進來,注意相對位置
             src/main/cpp/native-lib.cpp )

# 從系統裏查找依賴庫,可添加多個
find_library( # 例如查找系統中的log庫liblog.so
              log-lib

              # liblog.so庫指定的名稱即爲log,如同上面指定生成的libnative-lib.so庫名稱爲native-lib同樣
              log )
# 配置目標庫的連接,即相互依賴關係
target_link_libraries( # 目標庫(最終生成的庫)
                       native-lib

                        # 依賴於log庫,通常狀況下,若是依賴的是系統中的庫,須要加 ${} 進行引用,
                        # 若是是第三方庫,能夠直接引用庫名,例如:
                        # 引用第三方庫libthird.a,引用時直接寫成third;注意,引用時,每一行只能引用一個庫
                       ${log-lib} )
複製代碼

這裏我把註釋進行了縮減,標註了中文註釋,比較詳細,不明白的能夠看下每一個的做用,固然還有不少API可使用,後續再詳細說明,也能夠看看官方文檔,[戳我戳我] (developer.android.google.cn/ndk/guides/…)bash

咱們新建一個Helper類來編寫native方法app

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

打開咱們的MainActivityide

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(stringFromJNI());
    }
}
複製代碼

能夠看到最上面,靜態代碼塊引用了native-lib這個庫,而後直接調用native本地方法,將C++中返回的字符串拿到進行顯示。而後看看C++具體是怎麼是實現的post

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

extern "C" JNIEXPORT jstring

JNICALL
Java_com_example_hik_cmake_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
複製代碼

代碼很簡單,引用兩個頭文件,而後定義了一個方法,返回「Hello from C++」這個字符串,那有的朋友要問了,爲何java層直接調用stringFromJNI()方法可以直接映射到C++裏面的方法呢,細心的小夥伴可能發現了,C++裏面的這個方法名很長並且很熟悉。。這不是Java包名加上方法名拼湊而成的字符串嗎,這種方式呢叫作靜態註冊,這樣就能經過這個映射方式找到C++中的方法。學習

有的朋友又要說了,這麼長方法名也太麻煩了,雖然能夠自動生成,可是多不美觀,多不優雅。。是滴!有靜態註冊,那固然就有動態註冊了~咱們來改一改代碼:ui

#include <jni.h>
#include <string>
#include <android/log.h>

#define TAG "JNI_"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)
JNICALL
jstring backStringToJava(JNIEnv *env, jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
//動態註冊
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}};
    return env->RegisterNatives(clz,jniNativeMethod, sizeof(jniNativeMethod)/ sizeof(jniNativeMethod[0]));
}
jint JNI_OnLoad(JavaVM *vm, void *reserved){
    JNIEnv * env = NULL;
    if (vm->GetEnv((void**)&env,JNI_VERSION_1_6)!=JNI_OK){
        return JNI_ERR;
    }
    jint result = registerMethod(env);
    LOGD("RegisterNatives result: %d", result);
    return JNI_VERSION_1_6;
}
複製代碼

這裏呢,爲了在C++中打印日誌,咱們須要引入log.h頭文件,而後咱們把以前的方法名改爲backStringToJava,而後由於沒有了靜態註冊的規則,Java層調用的使用固然就找不到咱們對應的方法了,咱們定義一個動態註冊的方法,將Java中的方法和C++中的方法進行動態的綁定:

  • 經過env指針,拿到MainActivity的class對象,具體env指針後續會詳細說明
  • 定義一系列的方法對象,每一個包含三個參數,第一個是java中的方法名,第二個是方法對應的簽名,第三個是C++中的方法名
  • 在JNI_OnLoad方法中,調用動態註冊綁定方法進行綁定

有些朋友可能對方法簽名不太明白,後續語法會詳細說明,這裏先簡單說下,方法簽名也就是一個方法惟一性的一個標準,上面的()Ljava/lang/String;就是stringFromJNI的簽名,前面的括號裏面是參數的簽名,由於這裏沒有參數,因此爲空,緊接着後面是返回值得簽名,規則是,若是是基本數據類型就是相對應的基本數據類型,若是不是基本數據類型,那麼就是L+對象包名+「;」,注意這裏的分號不可省略!!根據這個規則,下面那個方法的簽名就是(II)I,依次類推,沒明白的也不要緊,後面會詳細對JNI中的語法詳細解釋,先知道有這麼回事就行了。

public native String stringFromJNI(); 
//簽名:()Ljava/lang/String;
public native int add(int a int b) 
//簽名:"(II)I"
複製代碼

而後咱們直接運行APP,能夠發現頁面上顯示出來了「Hello from C++」字符串,而後看看咱們生成的so文件在哪:

image.png

OK!大功告成,咱們的第一步就完成了,成功的完成了Java調用C++的方法,但別高興的太早,這只是第一步,好了,看到這的獎勵本身跟辣條吧~,溜了溜了。。

相關文章
相關標籤/搜索