「這是我參與8月更文挑戰的第7天,活動詳情查看:8月更文挑戰」java
JNI是Java Native Interface
的簡寫,它可使Java與其餘語言(如C、C++)進行交互。android
它是Java調用Native語言的一種特性,屬於Java語言的範疇,與Android無關。程序員
因爲上述緣由,此時咱們就須要讓Java與Native語言交互。而因爲Java的特色,與Native語言的交互能力很弱。所以在此時,咱們就須要用到JNI特性加強Java與Native方法的交互能力。面試
Java
命令執行 Java
程序,最終實現Java
調用本地代碼(藉助so庫文件)Native是Native Development Kit
的簡寫,是Android的開發工具包,屬於Android,與Java無關係。數組
它能夠快速開發C/C++的動態庫,自動將.so和應用一塊兒打包爲APK。所以咱們能夠經過NDK來在Android開發中經過JNI與Native方法交互。markdown
在local.properties中
加入以下一行便可app
ndk.dir=<ndk路徑>
函數
在Gradle的 gradle.properties
中加入以下一行,目的是對舊版本的NDK支持工具
android.useDeprecatedNdk=true
oop
在build.gradle中的defaultConfig
和android
中加入以下的externalNativeBuild
節點
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.n0texpecterr0r.ndkdemo"
minSdkVersion 19
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test<span class="emoji emoji-sizer" style="background-image:url(/emoji-data/img-apple-64/1f3c3.png)" data-codepoints="1f3c3"></span>1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
複製代碼
咱們首先須要在Java代碼的類中經過static塊來加載咱們的Native庫。能夠經過以下代碼,其中loadLibrary的參數是在CMakeList.txt中定義的Native庫的名稱
static {
System.loadLibrary("native-lib");
}
複製代碼
以後,咱們即可以在這個類中聲明Native方法
public native String getStringFromJNI();
複製代碼
咱們還須要在src中建立一個CMakeList.txt文件,這個文件約束了Native語言源文件的編譯規則。好比下面
cmake_minimum_required(VERSION 3.4.1)
add_library(native-lib SHARED src/main/cpp/native-lib.cpp)
find_library(log-lib log)
target_link_libraries(native-lib ${log-lib})
複製代碼
add_library
方法中定義了一個so庫,它的名稱是native-lib,也就是咱們在Java文件中用到的字符串,然後面則跟着這個庫對應的Native文件的路徑
find_library
則是定義了一個路徑變量,通過了這個方法,log-lib這個變量中的值就是Android中log庫的路徑
target_link_libraries
則是將native-lib這個庫和log庫鏈接了起來,這樣咱們就能在native-lib中使用log庫的方法。
在前面的CMake文件中能夠看到,咱們把文件放在了src/main/cpp/,所以咱們建立cpp這個目錄,在裏面建立C++源文件native-lib.cpp。
而後, 咱們即可以開始編寫以下的代碼:
#include <jni.h>
#include <string>
extern "C"{
JNIEXPORT jstring JNICALL
Java_com_n0texpecterr0r_ndkdemo_MainActivity_getStringFromJNI(
JNIEnv* env,
jobject) {
std::string hello = "IG牛逼";
return env->NewStringUTF(hello.c_str());
}
}
複製代碼
此處咱們使用的是C++語言,讓咱們來看看具體的代碼。
首先咱們引入了jni須要的jni.h,這個頭文件中聲明瞭各個jni須要用到的函數。同時咱們引入了C++中的string.h。
而後咱們看到extern "C"。爲了瞭解這裏爲何使用了extern "C",咱們首先須要知道下面的知識:
在C中,編譯時的函數簽名僅僅是包含了函數的名稱,所以不一樣參數的函數都是一樣的簽名。這也就是爲何C不支持重載。
而C++爲了支持重載,在編譯的時候函數的簽名除了包含函數的名稱,還攜帶了函數的參數及返回類型等等。
試想此時咱們有個C的函數庫要給C++調用,會由於簽名的不一樣而找不到對應的函數。所以,咱們須要使用extern "C"
來告訴編譯器使用編譯C的方式來鏈接。
接下來咱們看看JNIEXPORT和JNICALL關鍵字,這兩個關鍵字是兩個宏定義,他主要的做用就是說明該函數爲JNI函數。
而jstring則對應了Java中的String類,JNI中有不少相似jstring的類來對應Java中的類,下面是Java中的類與JNI類型的對照表
咱們繼續看到函數名Java_com_n0texpecterr0r_ndkdemo_MainActivity_getStringFromJNI
。其實函數名中的_至關於Java中的 . 也就是這個函數名錶明瞭java.com.n0texpecterr0r.ndkdemo.MainActivity.java
中的getStringFromJNI
方法,也就是咱們以前定義的native方法。
格式大概以下:
Java_包名_類名_須要調用的方法名
其中,Java必須大寫,包名裏的.
要改爲_
,_
要改爲_1
接下來咱們看到這個函數的兩個參數:
而後能夠看到後面咱們建立了一個string hello,以後經過env->NewStringUTF(hello.c_str())
方法建立了一個jstring類型的變量並返回。
接着,咱們即可以在MainActivty中像調用Java方法同樣調用這個native方法
TextView tv = findViewById(R.id.sample_text);
tv.setText(getStringFromJNI());
複製代碼
咱們嘗試運行,能夠看到,咱們成功用C++構建了一個字符串並返回給Java調用:
咱們在NDK開發中使用CMake的語法來編寫簡單的代碼描述編譯的過程,因爲這篇文章是講NDK的,因此關於CMake的語法就再也不贅述了。。。若是想要了解CMake語法能夠學習這本書《CMake Practice》
在咱們JNI層調用一個方法時,須要傳遞一個參數——方法簽名。
爲何要使用方法簽名呢?由於在Java中的方法是能夠重載的,兩個方法可能名稱相同而參數不一樣。爲了區分調用的方法,就引入了方法簽名的概念。
對於基本類型的參數,每一個類型對應了一個不一樣的字母:
對於類,則使用 L+類名 的方式,其中(.)用(/)代替,最後加上分號
好比 java.lang.String就是 Ljava/lang/String;
對於數組,則在前面加 [ ,而後加類型的簽名,幾維數組就加幾個。
好比 int[]
對應的就是[I
, boolean[][]
對應的則是[[Z
,而java.lang.String[]
就是[Ljava/lang/String;
咱們能夠經過 javap -s
命令來打印方法的簽名。
好比下面的方法
public native String getMessage();
public native String getMessage(String id,long i);
複製代碼
對應的方法簽名分別爲:
()Ljava/lang/String;
(Ljava/long/String;J)Ljava/lang/String;
複製代碼
能夠看到,前面括號中表示的是方法的參數列表,後面表示的則是返回值。
今天就先肝這麼多,明天再說
公衆號:程序員喵大人(專一於Android各種學習筆記、面試題以及IT類資訊的分享。)