一、JNI 與 NDK
1、JNI定義
JNI : Java Native Interface的縮寫
,從字面意思理解即Java的本地接口,Java 與 Native(即C/C++)層通信的橋樑。
2、NDK的定義
NDK:Native Development Kit的縮寫
,即Android的工具開發包。作用即將C/C++代碼編譯到原生庫中。
3、Java,JNI 與NDK三者之間的關係
1、NDK將C/C++編譯到原生庫,然後使用IDE的集成編譯系統將原生庫打包到APK中。
2、JNI編寫對應的接口函數,函數的內部實現就是由C/C++來完成的。
3、Java代碼通過調用JNI編寫好的接口函數,調用底層C/C++實現的功能函數。
關於編譯流程可參考 :https://developer.android.com/studio/build/
二、NDK下載
下載路徑:https://developer.android.com/ndk/downloads
當然也可使用Android Studio 進行下載,只需要通過SDK Manager進行下載
注意:在下載過程中,我們需要下載兩個構建工具:
- CMake:一個跨平臺的編譯構建工具
- LLDB:C/C++的調試工具
三、編寫JNI程序
1.AndroidStudio會自動生成一個native-lib.cpp以及CmakeLists.txt文件
2.MainActivity的分析
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
Java 調用本地方法,使用的是native
關鍵字,而這個本地方法的實現在native-lib這個動態庫中實現的,如果說使用動態庫,就必須先加載這個動態庫,那麼就有了這個System.loadLibrary(“native-lib”)方法,這個是Java規定的。
3、native-lib.cpp的具體實現
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_android_jni_demo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
那麼MainActivity中native方法的具體實現就是由native-lib.cpp的Java_com_android_jni_demo_MainActivity_stringFromJNI()來完成;
該方法使用的是靜態註冊的方式,即Java_包名_類名_方法名的形式。
4、調用流程解析
1、Gradle調用外部構建腳本CmakeLists.txt
2、CMake按照構建腳本中的命令將C++源文件native-lib.cpp編譯到共享庫中,並命名爲libnative-lib.so,Gradle編譯系統將該文件打包到APK中。
3、運行時,應用的MainActivity會調用靜態代碼塊System.loadLibrary()進行原生庫的加載,那麼應用就可以使用案例中原生庫的stringFromJNI()方法。
4、通過調用stringFromJNI() 返回"Hello from C++"字符串,也就完成了Java層調用原生庫中的函數返回的數據。
四、Gradle腳本的相關解析
要配置cmakelists.txt的路徑,gradle在進行編譯的時候,就會加載cmakelists.txt文件,通過cmake編譯工具,執行文件中所編寫的指令生成相應的動態庫。通過配置externalNativeBuild
,
並且需要使用cmake{} 進行配置CMakeLists.txt 文件的路徑,用於cmake構建構建工具執行相關編譯指令。
android {
.....
defaultConfig{...}
externalNativeBuild{
cmake{
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}
五、CmakeLists.txt的相關解析
1、CMakeLists.txt 內容如下:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib})
- 1、指定cmake的最小編譯版本 :cmake_minimum_required
- 2、添加需要生成的原生庫信息:add_library 其中包含原生庫的名稱,原生庫的源文件路徑,以及指定是生成共享庫(SHARED - .so文件)還是靜態庫(STATIC - .a文件)
- 3、查找原生庫所需要的第三方庫或者是系統庫:find_library 其中指定的庫大多是系統的公共庫,cmake默認搜索的路徑也就是公共庫的路徑,在這個find_library( var path name) 其中name是存在的公共庫的名稱,var path 是一個變量用來存儲name庫的path路徑。
- 4、最後一步就是將生成的原生庫和共享庫進行連接,即target_link_libraries(); 該指令裏面會指定3步中var path變量,通過${path)的方式找到這個公共庫,還有就是一個native-lib是該項目所要生成的原生庫的名稱。
可以參照我對於camkelists.txt文件的一些理解,如果有理解不對的地方,歡迎留言討論:
##指定cmake最小版本
cmake_minimum_required(VERSION 3.4.1)
## 指定需要生成庫的名稱,生成庫的源文件路徑,以及指定生成的庫是動態庫還是靜態庫
add_library(
##指定生成的庫的名稱比如名字叫做hash
hash
##指定是動態庫還是靜態庫 比如說生成一個動態庫(SHARED), 靜態庫則是(STATIC)
SHARED
##指定生成庫的源文件路徑比如說是工程下的src/main/cpp/hash.cpp
src/main/cpp/hash.cpp
)
## 查找生成庫可能要依賴的其他庫 比如NDK 的 log 庫
## 該命令是指的是查找指定的庫文件; find_library(VAR name path) 查找到指定的預編譯庫,並將它的路徑存儲在變量中。
## 默認的搜索路徑爲cmake包含的系統庫(解釋: 也就是說你你指定一個log庫,該庫是位於NDK的公共庫中,屬於系統庫,那麼這個時候指定
庫的名稱即可,這個時候只需要指定該庫的一個變量名稱,方便在調用target_link_libraries 鏈接該庫的時候使用其聲明的一個變量名即可,
該變量名存儲的是該系統庫的路徑。)
find_library(
## 首先指定該庫對應的變量名 比如說 log-lib
log-lib
##
log
)
## 關聯庫 原則 目標庫需要使用第三方庫或者系統庫,需要先進行find_library操作(1,找到這些庫的路徑,2,將這些庫的路徑保存到相應的
變量中,方便target_link_libraries調用)
target_link_libraries(
## 目標庫
hash
## 需要關聯的第三方或者系統庫
${log_lib}
)
此處也參考了許多前輩的總結經驗,如有侵犯,請告知。還有如果有需要轉載請註明。