之前寫過一篇比較水的文章Android手機(jī)控制電腦擼出HelloWorld
里面用到了JNI/NDK技術(shù)。
這篇文章給大家介紹下JNI/NDK開發(fā)哮笆。采用的是Android Studio2.2開發(fā)環(huán)境叹誉,使用CMake方式進(jìn)行開發(fā)弱贼。
JNI(Java Native Interface)是java與C/C++進(jìn)行通信的一種技術(shù)融求,使用JNI技術(shù),可以java調(diào)用C/C++的函數(shù)對(duì)象等等沮峡,Android中的Framework層與Native層就是采用的JNI技術(shù)疚脐。
我們知道,Android系統(tǒng)是基于linux開發(fā)邢疙,采用的是linux內(nèi)核 棍弄,Android APP開發(fā)大部分也要和系統(tǒng)打交道,只是Android FrameWork 幫我們處理了和系統(tǒng)相關(guān)的操作疟游, 我們從Android 系統(tǒng)的分成結(jié)構(gòu)可以看出呼畸,Android FrameWork是通過JNI與底層的C/C++庫交互,例如:FreeType颁虐,OpenGL蛮原,SQLite,音視頻等等聪廉。
如果我們程序也需要調(diào)用自己的C/C++函數(shù)庫瞬痘,就必須用到JNI/NDK開發(fā)。
NDK配置(最新的CMake方式)
Android Studio2.2版本已經(jīng)完全支持ndk開發(fā)了板熊。而且默認(rèn)采用CMake方式框全。(傳統(tǒng)方式不過多介紹了)
CMake的優(yōu)勢(shì)
- 可以直接的在C/C++代碼中加入斷點(diǎn),進(jìn)行調(diào)試
- java引用的C/C++中的方法干签,可以直接ctrl+左鍵進(jìn)入
- 對(duì)于include的頭文件津辩,或者庫,也可以直接的進(jìn)入
- 不需要配置命令行操作,手動(dòng)的生成頭文件,不需要配置
android.useDeprecatedNdk=true
屬性
下載
首先需要下載NDK容劳,來到設(shè)置界面點(diǎn)擊下載NDK
安裝完NDK喘沿,還可以選擇配置一些工具。
- CMake: 外部構(gòu)建工具竭贩。如果你準(zhǔn)備只使用 ndk-build 的話蚜印,可以不使用它。(Android Studio2.2默認(rèn)采用CMake)
- LLDB: Android Studio上面調(diào)試本地代碼的工具留量。
創(chuàng)建項(xiàng)目
Android Studio升級(jí)到2.2版本之后窄赋,在創(chuàng)建新的project時(shí),界面上多了一個(gè)Include C++ Support的選項(xiàng)楼熄。勾選它之后將會(huì)創(chuàng)建一個(gè)默認(rèn)的C++與JAVA混編的Demo程序忆绰。
然后一路 Next,直到 Finish 為止即可可岂。
上面圖的這三個(gè)文件都是默認(rèn)生成的NDK項(xiàng)目的一部分:
- .externalNativeBuild文件夾:cmake編譯好的文件, 顯示支持的各種硬件等信息错敢。系統(tǒng)生成。
- cpp文件夾:存放C/C++代碼文件缕粹,native-lib.cpp文件是默認(rèn)生成的稚茅,可更改纸淮。需要自己編寫。
- CMakeLists.txt文件:CMake腳本配置的文件峰锁。需要自己配置編寫萎馅。
app/build.gradle也有所不同
如果你在創(chuàng)建工程選擇C++11的標(biāo)準(zhǔn),則使用cppFlags "-std=c++11"
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
}
來看一下双戳,CMakeLists.txt文件中的具體配置
這個(gè)文件#開頭的全是注釋虹蒋,里面不是注釋的只有下面的內(nèi)容。
cmake_minimum_required(VERSION 3.4.1) #指定cmake版本
add_library( #生成函數(shù)庫的名字
native-lib
SHARED #生成動(dòng)態(tài)函數(shù)看
src/main/cpp/native-lib.cpp ) #依賴的cpp文件
find_library( #設(shè)置path變量的名稱
log-lib
#指定要查詢庫的名字
log ) #在ndk開發(fā)包中查詢liblog.so函數(shù)庫(默認(rèn)省略lib和.so),路徑賦值給log-lib
target_link_libraries( #目標(biāo)庫,和上面生成的函數(shù)庫名字一至
native-lib
#連接的庫,根據(jù)log-lib變量對(duì)應(yīng)liblog.so函數(shù)庫
${log-lib} )
java代碼
public class MainActivity extends AppCompatActivity {
// 加載函數(shù)庫
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 = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**本地方法, 當(dāng)前方法是通過C/C++代碼實(shí)現(xiàn)*/
public native String stringFromJNI();
}
上面java代碼中的 stringFromJNI()方法用native關(guān)鍵字修飾飒货,這個(gè)方法是通過C/C++代碼實(shí)現(xiàn)的魄衅。
native-lib.cpp 代碼
#include <jni.h>
#include <string>
extern "C"
jstring
Java_com_a520wcf_jniproject_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
上面的C++代碼,定義的函數(shù)名是固定寫法塘辅,Java_包名_類名_Java中方法名 晃虫,通過這種命名方式就可以唯一對(duì)應(yīng)到j(luò)ava中具體的方法,從而具體實(shí)現(xiàn)java中的native方法扣墩。
運(yùn)行項(xiàng)目
修改完C/C++代碼需要點(diǎn)擊“錘子”圖標(biāo)進(jìn)行編譯哲银,然后運(yùn)行項(xiàng)目。
運(yùn)行代碼呻惕,就能看到效果荆责,調(diào)用了C++方法在界面上顯示了Hello from C++字符串。
如果你不是使用CMake而是使用傳統(tǒng)方式進(jìn)行開發(fā)亚脆,這時(shí)候就會(huì)使用了ndk -build來編譯C/C++文件為so文件做院。
那么,我們安裝運(yùn)行的apk中濒持,有對(duì)應(yīng)的so文件嗎键耕?
如果想驗(yàn)證一下apk是否有so文件,我們可以使用 APK Analyzer查看柑营。
選擇 Build > Analyze APK屈雄。
選擇 apk,并點(diǎn)擊 OK官套。
當(dāng)前項(xiàng)目debug階段的apk默認(rèn)路徑為 app/build/outputs/apk/app-debug.apk
如下圖酒奶,在 APK Analyzer 窗口中,選擇 lib/x86/虏杰,可以看見 libnative-lib.so 讥蟆。
.so文件是動(dòng)態(tài)函數(shù)庫,寫好的c/c++代碼默認(rèn)打包成函數(shù)庫纺阔,就沒法看到代碼瘸彤,只能使用了。
如果我們想在工程中使用其他人編譯好的函數(shù)庫笛钝,只需要根據(jù)不同的cpu架構(gòu)把函數(shù)庫在src/main/jniLibs目錄下质况。
在java代碼中也需要引入相應(yīng)的函數(shù)庫愕宋,編寫一樣的native方法。
手動(dòng)添加native方法
上面我們主要介紹程序自動(dòng)生成的代碼结榄,接下來我們自己動(dòng)手寫寫中贝。
我們也可以在MainActivity中寫一個(gè)native方法。
有紅色警告臼朗,因?yàn)楫?dāng)前方法并沒有找到對(duì)應(yīng)的底層代碼的實(shí)現(xiàn)邻寿。我們可以在報(bào)錯(cuò)的地方按下萬能的快捷鍵alt+回車。
選擇第一項(xiàng)视哑,就會(huì)自動(dòng)生成對(duì)應(yīng)的底層方法绣否。
參考之前的方法,照著葫蘆畫瓢挡毅,把錯(cuò)誤先修復(fù)下蒜撮。
修改MainActivity代碼,調(diào)用我們寫的native方法跪呈。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI2());//調(diào)用新寫的native方法
}
編譯運(yùn)行當(dāng)前程序段磨。
運(yùn)行結(jié)果:
可以看到我們成功調(diào)用了我們自己創(chuàng)建的native方法。