JNI和NDK開發(1)_建立JNI程式
開始學習JNI開發技術,在網上看了很多文章,但講解的都是基礎或者過時的技術,沒有系統的關於JNI和NDK的學習教程,現在我寫《JNI和NDK開發》系列文章,主要是記錄自己從零開始學習遇到的一些問題和知識點,希望對大家也有些幫助。對於文章,本人也是邊學邊寫, 所以可能會更新的慢一點,大家有問題可以留言。
本系列文章主要解決的問題是:
- JNI和NDK開發常用API的使用
- JNI和NDK開發常見問題的解決
- JNI和NDK應用場景實踐
JNI和NDK的概念
JNI
全稱Java Native Interface
,意為java本地介面,它提供了若干的API實現了Java和其他語言的通訊(主要是C&C++)。從Java1.1開始,JNI標準成為java平臺的一部分,它允許Java程式碼和其他語言寫的程式碼進行互動。
NDK
全稱Native Development Kit
,是 Android的一個工具開發包 ,在Android開發中使用NDK編寫JNI程式就能實現對C和C++程式碼的互相呼叫。
使用JNI和NDK的好處
- 效率
大家都知道,Android的底層是使用C&C++實現的,所以使用C&C++編寫的程式,效率和效能上比java編寫的程式高。 - 安全
應用層編寫的java程式碼很容易被反編譯,而通過JNi編譯出的so庫是一個二進位制檔案,對於反編譯難度要大一點,如果在程式中再加上一些反除錯程式碼和簽名驗證,又更提高了反編譯的難度,所以一些核心加密的演算法會使用JNI來編寫。 - 呼叫Linux函式
眾所周知,Android的核心是Linux系統,通過java是無法呼叫Linux系統函式的,而使用JNI編寫C&C++程式碼卻可以呼叫Linux的系統函式,例如通過呼叫fork
函式建立子程序來實現關鍵程序的保活,使用mmap對映檔案來將日誌寫入到記憶體中等。 - 使用C&C++編譯的開源庫
使用C&C++編寫的著名開源庫有很多(例如音視訊處理庫FFmpeg
,圖形影象處理庫OpenCV
等),而Android應用層想要使用使用這些庫就必須通過JNI程式呼叫。
JNI和NDK應用場景
- 音視訊處理
像優酷、Bilibili、愛奇藝、抖音等這些視訊播放和直播軟體都是用的JNI程式呼叫FFmpeg
- 圖形影象處理
例如人臉識別、影象識別技術。 - 通訊協議加密SDK
封裝一個Http請求的通訊加密的SDK,加密演算法是在JNI層實現的。 - Android軟體呼叫硬體裝置
例如手機軟體
建立第一個JNI程式
網上很多文章講的都是Android Studio2.2之前通過Android.mk
來建立編譯JNI程式,因為時間有限,我也沒去學習和研究,所以直接使用Android Studio2.2之後通過CMake
來編譯JNI程式,我當前的Android環境配置是:Android studio版本是3.0.1,gradle外掛版本3.0.1,gradle版本是4.1,NDK版本是16。廢話不多說,下面開始建立第一個JNI程式。
1、 NDK環境配置
在Android Studio中檢視Android SDK的SDK Tools配置,如下圖所示,保證這三個SDK要下載。
2、Include C++ support
開啟Android Studio建立一個新工程,如下圖所示,紅框標註的意思是增加JNI的支援,這裡如果選中就會建立一個JNI程式,點選執行下一步。
配置C++編譯環境
C++ Standard
指定編譯庫的環境,其中Toolchain Default使用的是預設的CMake環境;C++ 11也就是C++環境。Exceptions Support
如果選中複選框,則表示當前專案支援C++異常處理,如果支援,在專案Module級別的build.gradle檔案中會增加一個標識 -fexceptions到cppFlags屬性中,並且在so庫構建時,gradle會把該屬性值傳遞給CMake進行構建。**Runtime Type Information Support
**
選中複選框,專案支援RTTI,屬性cppFlags增加標識-frtti。
建立完成後執行程式, 就會在手機上顯示Hello from C++
,從原始碼中可以看到這段文字是從native-lib.cpp
中返回的。效果看到了那就開始分析JNI的實現邏輯。
3、載入so庫
在MainActivity中可以看到這段程式碼
static {
System.loadLibrary("native-lib");
}
這段程式碼的意思是載入名為native-lib
的so檔案,那native-lib
這個名字是在哪定義的呢,其實是在CMakeLists.txt
中。
4、CMakeLists.txt
配置
開啟app目錄下的CMakeLists.txt
檔案,可以看到很大一堆註釋,讓人看著就不簡單,所以我把註釋去掉,只留下有用的部分,無非就四行。
// 要求使用的cmake最小版本
cmake_minimum_required(VERSION 3.4.1)
// 新增共享庫:命名為native-lib;設定為SHARED共享;檔案路徑是src/main/cpp/native-lib.cpp
add_library( native-lib SHARED src/main/cpp/native-lib.cpp )
// 找到Android自帶的日誌庫log,並賦值給log-lib變數
find_library( log-lib log )
// 將native-lib庫和log-lib庫
target_link_libraries( native-lib ${log-lib} )
簡單說一下CMake的這幾條指令的意思:
add_library
將指定的檔案生成與庫連結的目標檔案。
-find_library
查詢庫,並將庫賦值給變數。target_link_libraries
將目標檔案與生成的庫檔案連結。
5、build.gradle
中配置
在Android Studio中CMakeLists.txt
檔案是怎麼建立的呢?看一下app下的build.gradle檔案,在android
節點下配置
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
這段程式碼的作用就是配置CMakeLists.txt
檔案路徑,Android Studio會根據這個路徑找到並建立。
除了CMakeLists.txt
檔案路徑的配置,還有一段externalNativeBuild
的配置,在defaultConfig
節點下:
externalNativeBuild {
cmake {
cppFlags ""
}
}
這段意思是配置CMake編譯環境,在app目錄下可以看到.externalNativeBuild
編譯檔案。
6、C&C++
檔案
在app
目錄下,有一個cpp
目錄,裡面放的就是我們的JNI源程式,開啟native-lib.cpp
檔案,就可以看到JNI程式碼的語法結構。
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL
Java_com_rzr_jni_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
7、原生介面定義和呼叫
開啟MainActivity
找到程式碼如下:
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
這段程式碼定義了原生與JNI的介面函式,我們可以通過呼叫這個介面來獲取JNI返回的結果。
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
到此,我們就建立和分析了一個帶JNI的Android工程。