JNI相關概念的理解

閱讀本文前,前先閱讀 JNI-NDK 在AndroidStudio3.2.1版本集成方法(ndk-build方式),瞭解jni在AndroidStudio裏的集成步驟

概念

Java 原生接口 (JNI):JNI 是 Java 和 C++ 組件用以互相通信的接口。

理解JNI

先說說JNIEnv

現在說的是C裏的JNIEnv,不是C++裏的JNIEnv,有點區別,但是理解了C裏的JNIEnv,就理解了C++裏的JNIEnv

# include "jni_study_com_jnibsetpractice_Jni.h"

JNIEXPORT jstring JNICALL Java_jni_study_com_jnibsetpractice_Jni_sayHello
        (JNIEnv *env, jobject instance) {
    return (*env)->NewStringUTF(env, "Hello from C");
}

上面的代碼裏第一個參數是JNIEnv,這是個什麼東西,顧名思義jni環境,點進去跳到了
這個文件裏

發現 JNIEnv是JNINativeInterface*的別名,JNINativeInterface是什麼,發現他是一個結構體,裏面列出了許多方法指針,相當於java裏的一個類,類裏定義了很多方法,這些方法在jni開發中非常重要

看看這個方法是不是很眼熟,NewStringUTF


(*env)->NewStringUTF(env, "Hello from C");調用了這個方法,製造了一個字符串返給了java,你會問不就是一個字符串嗎,我直接"Hello from C"返回不就行了嗎?不行的,你知道C裏是沒有過String類型的,C裏的"xxx",java並不認識,這就需要jni (java native interface),即【java 與本地語言(C/C++)接口】來解決這個問題

看看這個方法,傳入一個*env和char *,char*就是C裏的字符串類型,返回了jstring(jstring是java裏的String的等價物),這樣就把C裏的字符串轉換成java可以是別的字符串,你說這個方法重要嗎,他是結構體JNINativeInterface提供的。

除了這個方法,其他很多方法都很重要,他的作用基本上就是一些jni開發中,常用到的一些方法,爲我們在C和Java之間搭建了一座橋樑,讓彼此互相溝通

下圖是JNINativeInterface結構體的一個圖示

JNINativeInterface裏所有方法的說明看這裏:
官方文檔

所有方法分爲以下幾類

看看NewStringUTF的文檔

以後會用到很多方法,都可以在這裏查詢

JNI裏的數據類型

在上面的方法裏,看到了很多奇奇怪怪的數據類型 jboolean jstring…,他們與java、c是如何對應的

我們在jin.h裏還發現了這段代碼

查看數據類型的官方文檔
文檔截圖:

參考類型
JNI包含許多與不同類型的Java對象相對應的引用類型。JNI引用類型按層次結構組織,如圖3-1所示。

JNIENV在C與C++的區別

我直接複製了上篇博客的內容

  1. 創建實現頭文件的.cpp源文件
    接下來要寫個c++代碼,實現這個jni接口

˙注意這裏新建的是c++代碼,c++代碼對應下面的代碼

//引入剛纔生成的頭文件
#include "ndkold_study_com_ndkolddemo_Java2CJNI.h"

//複製頭文件裏的要實現的方法名及其參數
JNIEXPORT jstring JNICALL
Java_ndkold_study_com_ndkolddemo_Java2CJNI_java2C(JNIEnv *env, jobject instance) {
//    實現這個方法,返回一個字符串
    return env->NewStringUTF("Hello from C++");
}
  1. 你也可以寫個.c源文件,其對應代碼爲
//引入剛纔生成的頭文件
#include "ndkold_study_com_ndkolddemo_Java2CJNI.h"

//複製頭文件裏的要實現的方法名及其參數
JNIEXPORT jstring JNICALL
Java_ndkold_study_com_ndkolddemo_Java2CJNI_java2C(JNIEnv *env, jobject instance) {
//    實現這個方法,返回一個字符串
    return (*env)->NewStringUTF(env, "Hello from C");
    //注意這裏是(*env),而且需要傳遞一個參數(env)
}

說明:c與c++就這點區別,查看jni.h文件,發現在c裏的JNIEnv是結構體指針JNINativeInterface*的別名,所以JNIEnv *env相當於二級指針,現在要調用JNINativeInterface*裏的方法,要用(*env)->xxx

在c++裏JNIEnv是_JNIEnv的別名,在_JNIEnv內部裏有個屬性爲結構體指針JNINativeInterface*,然後他把所有c裏的方法都重新定義了一下,定義方式就是通過JNINativeInterface*調了一遍所有c裏的方法,而且把JNINativeInterface*的對象以this方式傳遞進去了,可見這裏的JNIEnv *env是一個一級指針,所以通過env就可以直接調用對應的方法了(有點繞)

Android.mk文件

Android.mk是Android提供的一種makefile文件,用來指定諸如編譯生成so庫名、引用的頭文件目錄、需要編譯的.c/.cpp文件和.a靜態庫文件等。要掌握jni,就必須熟練掌握Android.mk的語法規範。

參考自Android.mk用法詳解

這個文件rebuild後 androidstudio會自動生成,看我的 上篇博客可以找到他的生成的路徑,有小坑請注意

Application.mk文件

摘自安卓Application.mk文件的屬性說明和基本寫法

使用androidstudio開發,不需要這個文件了,可以在gradle裏配置相關屬性,詳情見 此文

靜態鏈接庫(.a) 與 動態鏈接庫(.so)


我們編譯成功的文件是一個.so文件,我們的C代碼就打包在了這個文件中,類似於java的jar包。C編譯後的代碼有兩種格式文件靜態鏈接庫(.a) 與 動態鏈接庫(.so)
簡單說區別爲:

  1. 靜態鏈接庫文件大,裏面有你自己寫的邏輯,也有引用的其他庫函數,但是容易移植
  2. 動態鏈接庫文件小,公用的代碼,可以直接引用,確定是不容易移植,因爲so文件裏的代碼不是獨立可以運行的

下面的截圖來自C/C++ 靜態鏈接庫(.a) 與 動態鏈接庫(.so),想了解更多,請點擊查看

靜態鏈接庫(.a)

動態鏈接庫(.so)

ABI(應用二進制接口)

我們經常看到下面這個圖,會生成多個libxxx.so文件,他們分別在不同的armxxx下面

簡單來說,每個手機裏的cpu是不同的,不同的cpu對應支持不同的ABI,我們編譯出來的so文件,在不同ABI上是不能兼容的,所以我們要針對不同的ABI變移除不同的so文件,這樣不同cpu的手機都可以調用到適合自己的so文件。

不同的 Android 手機使用不同的 CPU,而不同的 CPU 支持不同的指令集。CPU 與指令集的每種組合都有專屬的應用二進制接口,即 ABI。ABI 可以非常精確地定義應用的機器代碼在運行時如何與系統交互。您必須爲應用要使用的每個 CPU 架構指定 ABI。—摘自android官網-ABI 管理

常見的有armeabi、armeabi-v7a、arm64-v8a、x86、x86_64,在abiFilters配置

defaultConfig {
        ...
        ndk {
            moduleName "Java2C"
            //so文件名,如果這裏配置了so文件名字,
            //記得更改Android.mk裏的
            //LOCAL_MODULE :字段爲 LOCAL_MODULE := Java2C
            abiFilters "armeabi", "armeabi-v7a", "x86" 
            //指定so文件所支持的CPU類型
            //如果不寫的話,會生成所有的CPU類型的so文件
        }
    }

再補充幾個概念

jni的類型簽名

jni的類型簽名表示了一個特定的Java類型,這個類型可以是方法和類,也可以是數據類型。

以上截圖均來自JNI開發最佳實踐,想了解更多,請點擊查看

小結

至此,關於jni裏的一些概念,都瞭解了一遍,接下來,要敲幾個demo,看看具體如何使用這些知識點

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章