提供一種Java字節碼調用C/C++的解決方案,JNI描述的是一種技術 java
Android NDK 是一組容許您將 C 或 C++(「原生代碼」)嵌入到 Android 應用中的工具,NDK描述的是工具集。 可以在 Android 應用中使用原生代碼對於想執行如下一項或多項操做的開發者特別有用:android
這裏已有現成的文章就引用一下別人的了 ,講的很仔細 關於JNI環境配置c++
當Java層調用navtie函數時,會在JNI庫中根據函數名查找對應的JNI函數。若是沒找到,會報錯。若是找到了,則會在native函數與JNI函數之間創建關聯關係,其實就是保存JNI函數的函數指針。下次再調用native函數,就能夠直接使用這個函數指針。git
JNI函數名格式(包名裏面的」.」須要改成」_」): Java_ + 包名(com_example_auto_jnitest)+ 類名(_MainActivity) + 函數名(_stringFromJNI)github
靜態註冊缺點:數組
靜態註冊例子markdown
類 JNITest 包名:com.hqk.jnitestone架構
package com.hqk.jnitestone;
public class JNITest {
static {
System.loadLibrary("native-lib");
}
public static native String sayHello();
}
複製代碼
對應c++代碼 ,cpp 類名爲:native-lib.cpp函數
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL Java_com_hqk_jnitestone_JNITest_sayHello(JNIEnv *env, jclass clazz) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
複製代碼
注:工具
static {
System.loadLibrary("native-lib");
}
複製代碼
經過提供一個函數映射表,註冊給JVM虛擬機,這樣JVM就能夠用函數映射表來調用相應的函數,就沒必要經過函數名來查找須要調用的函數。
Java與JNI經過JNINativeMethod的結構來創建函數映射表,它在jni.h頭文件中定義,其結構內容以下:
typedef struct {
const char* name; // 對應交互java 類對應的方法名
const char* signature; //對應交互方法的函數簽名 (參考本文1.4.3)
void* fnPtr; //對應交互cpp方法的 指針函數 (指向對應函數)
} JNINativeMethod;
複製代碼
一、建立映射表後,調用RegisterNatives函數將映射表註冊給JVM; 二、當Java層經過System.loadLibrary加載JNI庫時,會在庫中查JNI_OnLoad函數。可將JNI_OnLoad視爲JNI庫的入口函數,須要在這裏完成全部函數映射和動態註冊工做,及其餘一些初始化工做。
概念說完了,實際操做一下
JAVA類 JNITest 包名:com.hqk.jnitestone
package com.hqk.jnitestone;
public class JNITest {
static {
System.loadLibrary("native-lib");
}
public static native String sayHello();
public static native String sayHello2();
}
複製代碼
注意:這裏多了一個 sayHello2 方法,方法返回值爲: String
CPP類名 native-lib.cpp 對應代碼
#include <jni.h>
#include <string>
#include <android/log.h>
extern "C"
JNIEXPORT jstring JNICALL Java_com_hqk_jnitestone_JNITest_sayHello(JNIEnv *env, jclass clazz) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
//cpp 交互方法
jstring sayHello2(JNIEnv *env, jobject thiz) {
std::string hello = "Hello,我是動態註冊成功的!";
return env->NewStringUTF(hello.c_str());
}
// 動態註冊 函數 結構數組
static const JNINativeMethod gMethods[] = {
{"sayHello2", //對應java交互類的方法名
"()Ljava/lang/String;", //對應方法名的函數簽名 (參考本文1.4.3)
(jstring *) sayHello2 //對應 cpp交互類的指針函數
}
};
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
__android_log_print(ANDROID_LOG_INFO, "native", "Jni_OnLoad");
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) //從JavaVM獲取JNIEnv,通常使用1.4的版本
return -1;
//注意:這裏 FindClass 必需要和交互類的 包名對應上,並換成[/]符號
jclass clazz = env->FindClass("com/hqk/jnitestone/JNITest");
if (!clazz) {
__android_log_print(ANDROID_LOG_INFO, "native",
"cannot get class: com/hqk/jnitestone/JNITest");
return -1;
}
if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) {
__android_log_print(ANDROID_LOG_INFO, "native", "register native method failed!\n");
return -1;
}
return JNI_VERSION_1_4;
}
複製代碼
從上面代碼能夠看出
一、CPP類裏面 建立了 JNI_OnLoad 方法--》 該方法是在 初始化的時候執行,相似於 activity 的onCreate, 核心代碼,就是經過Find找到對應Java類,最後經過RegisterNatives 實現動態註冊 二、CPP類裏面 新增了JNINativeMethod 類型的 結構數組 ; 三、CPP類裏面 新增了 sayHello2 交互方法 四、Java類裏面通用新增了 sayHello2方法
除了Class、String、Throwable和基本數據類型的數組外,其他全部Java對象的數據類型在JNI中都用jobject表示。Java中的String也是引用類型,可是因爲使用頻率較高,因此在JNI中單首創建了一個jstring類型。
例如,二維整型數組就是指向一位數組的數組,其聲明使用方式以下:
//得到一維數組的類引用,即jintArray類型
jclass intArrayClass = env->FindClass("[I");
//構造一個指向jintArray類一維數組的對象數組,該對象數組初始大小爲length,類型爲 jsize
jobjectArray obejctIntArray = env->NewObjectArray(length ,intArrayClass , NULL);
複製代碼
因爲Java支持函數重載,所以僅僅根據函數名是無法找到對應的JNI函數。爲了解決這個問題,JNI將參數類型和返回值類型做爲函數的簽名信息。
(參數1類型字符…)返回值類型字符
3.JNI經常使用的數據類型及對應字符:
注意: 在C++建立的子線程中獲取JNIEnv,要經過調用JavaVM的AttachCurrentThread函數得到。在子線程退出時,要調用JavaVM的DetachCurrentThread函數來釋放對應的資源,不然會出錯。
JNIEnv 做用:
CMake 則是一個跨平臺的編譯工具,它並不會直接編譯出對象,而是根據自定義的語言規則(CMakeLists.txt)生成 對應 makefile 或 project 文件,而後再調用底層的編譯, 在Android Studio 2.2 以後支持Cmake編譯。
cmake_minimum_required(VERSION 3.4.1)
add_library(
native-lib
SHARED
native-lib.cpp)
find_library(
log-lib
log)
target_link_libraries(
native-lib
${log-lib})
複製代碼
語法:add_library(libname SHARED | STATIC | MODULE [source])
將一組源文件 source 編譯出一個庫文件,並保存爲 libname.so (lib 前綴是生成文件時 CMake自動添加上去的)。其中有三種庫文件類型,不寫的話,默認爲 STATIC;
#將compress.c 編譯成 libcompress.so 的共享庫
add_library(compress SHARED compress.c)
複製代碼
語法:target_link_libraries(target library <debug | optimized> library2…)
這個指令能夠用來爲 target 添加須要的連接的共享庫,一樣也能夠用於爲本身編寫的共享庫添加共享庫連接。如:
#指定 compress 工程須要用到 libjpeg 庫和 log 庫
target_link_libraries(compress libjpeg ${log-lib})
複製代碼
語法:find_library( name1 path1 path2 ...)
VAR 變量表示找到的庫全路徑,包含庫文件名 。例如:
find_library(libX X11 /usr/lib)
find_library(log-lib log) #路徑爲空,應該是查找系統環境變量路徑
複製代碼
參考文獻:更多關於Cmake的詳細使用
ABI(Application binary interface)應用程序二進制接口。不一樣的CPU 與指令集的每種組合都有定義的 ABI (應用程序二進制接口),一段程序只有遵循這個接口規範才能在該 CPU 上運行,因此一樣的程序代碼爲了兼容多個不一樣的CPU,須要爲不一樣的 ABI 構建不一樣的庫文件。固然對於CPU來講,不一樣的架構並不意味着必定互不兼容。
根據以上的兼容總結,咱們還能夠獲得一些規律:
gitHub地址:點擊下載