你們好!我又來了,此次準備着手寫一個JNI開發系列,畢竟,如今JNI開發也是在各個公司愈來愈重要了,若是項目畢竟大,可能涉及的模塊較多,好比你做爲應用層的開發,不免避免不了須要使用一些庫,一些加密操做等等,通常都會放在本地方法裏面,比較安全,人家丟給你so文件或者靜態a文件。。你不會用豈不是很尷尬。網上資料比較雜,並且很亂,大部分仍是在用.mk的方法。java
本系列就基於CMake形式,但願可以帶着一些但願學習JNI開發的小夥伴一塊兒學會JNI開發~比心❤android
#定義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++中的方法進行動態的綁定:
有些朋友可能對方法簽名不太明白,後續語法會詳細說明,這裏先簡單說下,方法簽名也就是一個方法惟一性的一個標準,上面的()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文件在哪:
OK!大功告成,咱們的第一步就完成了,成功的完成了Java調用C++的方法,但別高興的太早,這只是第一步,好了,看到這的獎勵本身跟辣條吧~,溜了溜了。。