在AndroidStudio
中進(jìn)行NDK
開發(fā)比起以往的Eclipse
要方便的多绑谣,下面來介紹下如何使用AndroidStudio
這個(gè) IDE
工具實(shí)現(xiàn)NDK
相關(guān)開發(fā)工作。
1. 準(zhǔn)備工作
在實(shí)際寫代碼之前,首先我們還是需要做一些準(zhǔn)備工作:
- 下載
NDK
開發(fā)包:Android 官方下載頁面 - 配置系統(tǒng)環(huán)境變量
下載好NDK
開發(fā)包之后夷野,直接解壓到任意目錄距芬,然后需要配置一下系統(tǒng)環(huán)境變量,之所以要配置環(huán)境變量赃磨,是為了方便使用命令ndk-build
腳本進(jìn)行NDK
編譯立由。配置參考如下:
# 在.bash_profile中配置如下代碼export ANDROID_NDK=/Users/you/android-ndkexport
PATH=$ANDROID_NDK:$PATH
# 然后執(zhí)行如下代碼轧钓,更新配置文件
source .bash_profile
其實(shí)編譯C/C++代碼不一定在AndroidStudio
中,如果配置好環(huán)境變量锐膜,直接使用進(jìn)入項(xiàng)目中的jni
目錄執(zhí)行ndk-build
命令即可在當(dāng)前目錄下生成一個(gè)libs
的目錄毕箍,里面存放了不同 平臺(tái)的.so
包,當(dāng)然運(yùn)行這個(gè)命令的前提是道盏,這個(gè)目錄下至少得有一個(gè)Android.mk
文件而柑,如果需要指定具體的編譯平臺(tái),那么還需要添加一個(gè)Application.mk
文件荷逞,當(dāng)然媒咳,如果命令行讓你頭疼,那么你可以采用gradle
的方式來解決這些問題种远,接下來我們將分別介紹這些使用方式涩澡。
2. 項(xiàng)目配置
使用AndroidStudio
開發(fā)前我們也要做點(diǎn)額外工作,我們需要在項(xiàng)目根目錄下local.properties 中添加編譯NDK 的路徑:
ndk.dir=/Users/you/android-ndk
如果這個(gè)文件不存在坠敷,你可以手動(dòng)生成一個(gè)妙同,然后再添加上述內(nèi)容即可。完成這個(gè)步驟之后膝迎,我們就可以正式開始著手NDK
相關(guān)的開發(fā)工作了粥帚。之所以要配置這個(gè)目錄,目的是讓我們開發(fā)的項(xiàng)目在使用 gradle 編譯時(shí)能夠找到NDK
相關(guān)編譯路徑
那么限次,接下來的工作也分為兩種情況:
沒有(C/C++)源碼芒涡,別人已經(jīng)提供好相應(yīng)的.so
文件,不需要編譯代碼
擁有(C/C++)源碼卖漫,需要自己編譯.so
文件
2.1 已有.SO文件费尽,不需要編譯源碼
這類情況是最簡單的,.so
文件以及被其他人員編譯好懊亡,或者是第三方庫來提供的依啰,那么我們只需要把相應(yīng).so
文件放到AndroidStudio
目錄src/main/jniLibs/ 下即可,當(dāng)然店枣,肯定需要按 CPU 架構(gòu)分不同的子目錄速警。
jniLibs
是AndroidStudio
默認(rèn)提供的ndk
目錄,用來存放已經(jīng)編譯好的.so
文件鸯两,當(dāng)然你也可以放在任意自定義目錄下闷旧,例如src/main/libs
,然后在build.gradle
中指定相應(yīng)的資源目錄位置即可:
android {
sourceSets.main {
// 你的.so庫的實(shí)際路徑
jniLibs.srcDir 'src/main/libs'
}
}
在導(dǎo)入.so
文件完成之后钧唐,那么你可以在相應(yīng)的java
類文件中忙灼,加載這個(gè)靜態(tài)庫,一般來說,.so
文件如果由第三方提供该园,他在提供.so
文件的同時(shí)也會(huì)提供相應(yīng)的java
調(diào)用類文件酸舍,或者按之前雙方定好的規(guī)則自己創(chuàng)建相應(yīng)類文件,并生成相應(yīng)的方法里初,之所以要約定好只因?yàn)椋?code>NDK 下的C/C++
函數(shù)和Java
橋接的函數(shù)命名是有約束的啃勉,規(guī)則如下:
Java_PackageName_ClassName_MethodName
雙方必須按這個(gè)規(guī)則來實(shí)現(xiàn)或者調(diào)用此函數(shù),否則不會(huì)成功双妨,例如淮阐,我們現(xiàn)在有一個(gè)函數(shù):String stringFromJNI()
的java
函數(shù),它在com.example.hellojni.HelloJni
這個(gè)文件下刁品,這個(gè)函數(shù)用來返回一個(gè)字符串泣特,功能由底層C
來實(shí)現(xiàn),那么相應(yīng)的C
語言jni
開發(fā)文件中就必須按上述規(guī)則命名一個(gè)Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
的函數(shù)挑随,并返回一個(gè)字符串結(jié)果:
#include
// 函數(shù)名格式必須按規(guī)矩來
jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
{
return (*env)->NewStringUTF(env, "Hello from JNI ! Compiled with ABI " ABI ".");
}
同樣對(duì)應(yīng)的java
文件也必須:
- 文件必須在
com.example.hellojni
包名下 - 類文件名必須是
HelloJni
- 方法名必須是
stringFromJNI
package com.example.hellojni;
class HelloJni {
public static native String stringFromJNI();
static {
// 加載 hellojni.so靜態(tài)塊
System.loadLibrary("hellojni");
}
}
2.2 有源碼状您,需要編譯.so
文件
如果有 C/C++ 源碼,沒有.so
文件镀裤,那么我們就得手動(dòng)把源碼文件編譯成.so
文件竞阐,編譯的方式也分為兩種:
手工執(zhí)行命令經(jīng)行編譯
使用gradle
腳本自動(dòng)實(shí)現(xiàn)編譯
AndroidStudio
默認(rèn)的源碼存放目錄是:
src/main/jni
如果你沒發(fā)現(xiàn)此目錄,那么你可以手動(dòng)創(chuàng)建一個(gè)暑劝,把所有的 C/C++
源碼放在此文件下骆莹,當(dāng)然并非必須要放在此目錄下,你可以自定義目錄担猛,然后在build.gradle
中做一個(gè)資源路徑指定即可:
// build.gradle
android {
sourceSets.main {
// 你的源碼目錄
jni.srcDir 'src/main/otherDir'
}
}
2.2.1 手工執(zhí)行命令經(jīng)行編譯
在使用手工編譯(C/C++
)文件之前幕垦,我們要回到文章開頭部分,我們需要配置好系統(tǒng)環(huán)境變量傅联,這樣我們才能在系統(tǒng)環(huán)境下執(zhí)行ndk
相關(guān)編譯命令先改,如果您的環(huán)境變量還沒有配置,那么可以參考下文章開頭部分蒸走,如果已經(jīng)做好這部分工作仇奶,那么咱們繼續(xù)。
接下來比驻,我們還要?jiǎng)?chuàng)建如下兩個(gè)文件:
- Android.mk
- Applicatoin.mk (非必要)
2.2.1.1 創(chuàng)建 Android.mk
Android.mk
文件用來指定源碼編譯的配置信息该溯,例如工作目錄,編譯模塊的名稱别惦,參與編譯的文件等狈茉,大致內(nèi)容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello_jni
LOCAL_SRC_FILES := hello_jni.c
include $(BUILD_SHARED_LIBRARY)
1. LOCAL_PATH:設(shè)置工作目錄,而 my-dir 則會(huì)返回 Android.mk 文件所在的目錄掸掸。
2. CLEAR——VARS:清除幾乎所有以 LOCAL——PATH 開頭的變量(不包括 LOCAL_PATH)氯庆。
3. LOCAL_MODULE:用來設(shè)置模塊的名稱蹭秋。
4. LOCAL_SRC_FILES:用來指定參與模塊編譯的 C/C++ 源文件名。
5. BUILD_SHARED_LIBRARY:作用是指定生成的靜態(tài)庫或者共享庫在運(yùn)行時(shí)依賴的共享庫模塊列表堤撵。
2.2.1.2 創(chuàng)建 Application.mk
這個(gè)文件用來配置編譯平臺(tái)相關(guān)內(nèi)容仁讨,我們最常用的估計(jì)只是APP_ABI
字段,它用來指定我們需要基于哪些 CPU
架構(gòu)的.so
文件粒督,當(dāng)然你可以配置多個(gè)平臺(tái):
APP_ABI := armeabi armeabi-v7a x86 mips
如果不創(chuàng)建Application.mk
文件陪竿,那么手動(dòng)編譯的.so
文件只有armeabi
平臺(tái)一個(gè)版本,其他平臺(tái)的不會(huì)被編譯屠橄。
假設(shè)我們配置好了Android.mk
文件,那么接下來我們就可以執(zhí)行如下命令來生成.so
文件了闰挡,我們假設(shè)開發(fā)NDK
的目錄為默認(rèn)目錄:
cd src/main/jni/
ndk-build
如果順利锐墙,那么你將會(huì)看到,在src/main/
目錄下會(huì)多了一個(gè)libs
目錄长酗,這是NDK
使用命令編譯.so
文件的生成的默認(rèn)目錄溪北,而AndroidSutdio
默認(rèn)加載NDK
的目錄是jniLibs
,那么你有兩種解決方式:
- 配置
build.gradle
資源目錄夺脾,參見文章 2.1 小節(jié) - 使用
ndk-build NDK_LIBS_OUT=../jniLibs
指定具體的輸出目錄
生成.so
后之拨,為了避免C/C++
文件被Java
編譯,可能需要使用如下gradle 配置:
sourceSets.main {
// default .so path
jniLibs.srcDir 'src/main/libs'
// disable automatic ndk-build
jni.srcDirs = []
}
當(dāng)你得到了.so
文件咧叭,那么接下來就是在java
文件中調(diào)用執(zhí)行即可蚀乔,如果想了解更多ndk-build
命令內(nèi)容,可參見:Android ndk-build 使用文檔
2.2.2 使用 gradle 腳本
當(dāng)然該機(jī)器做的事我們還是盡量讓機(jī)器來做菲茬,因此吉挣,接下來我打算使用build.gradle
來添加一些配置,讓Gradle
自動(dòng)幫我完成編譯工作婉弹,這簡直就是爽歪歪啦睬魂!
使用gradle
, 你再也不用手動(dòng)添加Android.mk
和Application.mk
文件,一切在build.gradle
文件中就都能搞定镀赌,在這里我們直接貼出build.gradle 中ndk 相關(guān)的配置:
android.ndk {
// 模塊名稱
moduleName = "hello-jni"
// 指定編譯平臺(tái)氯哮,更多平臺(tái)信息 參見https://developer.android.com/ndk/guides/abis.html#sa
abiFilters "armeabi", "armeabi-v7a"
/* * Other ndk flags configurable here are
* cppFlags.add("-fno-rtti")
* cppFlags.add("-fno-exceptions")
* ldLibs.addAll(["android", "log"])
* stl = "system"
*/
}
使用gradle
的好處是,自動(dòng)編譯生成apk
文件商佛,并且把相關(guān)的.so
文件打包到apk
安裝包中喉钢,一勞永逸。