在AndroidStudio
中進行NDK
開發比起以往的Eclipse
要方便的多,下面來介紹下如何使用AndroidStudio
這個 IDE
工具實現NDK
相關開發工作。
1. 準備工作
在實際寫代碼之前,首先我們還是需要做一些準備工作:
- 下載
NDK
開發包:Android 官方下載頁面 - 配置系統環境變量
下載好NDK
開發包之后,直接解壓到任意目錄,然后需要配置一下系統環境變量,之所以要配置環境變量,是為了方便使用命令ndk-build
腳本進行NDK
編譯。配置參考如下:
# 在.bash_profile中配置如下代碼export ANDROID_NDK=/Users/you/android-ndkexport
PATH=$ANDROID_NDK:$PATH
# 然后執行如下代碼,更新配置文件
source .bash_profile
其實編譯C/C++代碼不一定在AndroidStudio
中,如果配置好環境變量,直接使用進入項目中的jni
目錄執行ndk-build
命令即可在當前目錄下生成一個libs
的目錄,里面存放了不同 平臺的.so
包,當然運行這個命令的前提是,這個目錄下至少得有一個Android.mk
文件,如果需要指定具體的編譯平臺,那么還需要添加一個Application.mk
文件,當然,如果命令行讓你頭疼,那么你可以采用gradle
的方式來解決這些問題,接下來我們將分別介紹這些使用方式。
2. 項目配置
使用AndroidStudio
開發前我們也要做點額外工作,我們需要在項目根目錄下local.properties 中添加編譯NDK 的路徑:
ndk.dir=/Users/you/android-ndk
如果這個文件不存在,你可以手動生成一個,然后再添加上述內容即可。完成這個步驟之后,我們就可以正式開始著手NDK
相關的開發工作了。之所以要配置這個目錄,目的是讓我們開發的項目在使用 gradle 編譯時能夠找到NDK
相關編譯路徑
那么,接下來的工作也分為兩種情況:
沒有(C/C++)源碼,別人已經提供好相應的.so
文件,不需要編譯代碼
擁有(C/C++)源碼,需要自己編譯.so
文件
2.1 已有.SO文件,不需要編譯源碼
這類情況是最簡單的,.so
文件以及被其他人員編譯好,或者是第三方庫來提供的,那么我們只需要把相應.so
文件放到AndroidStudio
目錄src/main/jniLibs/ 下即可,當然,肯定需要按 CPU 架構分不同的子目錄。
jniLibs
是AndroidStudio
默認提供的ndk
目錄,用來存放已經編譯好的.so
文件,當然你也可以放在任意自定義目錄下,例如src/main/libs
,然后在build.gradle
中指定相應的資源目錄位置即可:
android {
sourceSets.main {
// 你的.so庫的實際路徑
jniLibs.srcDir 'src/main/libs'
}
}
在導入.so
文件完成之后,那么你可以在相應的java
類文件中,加載這個靜態庫,一般來說,.so
文件如果由第三方提供,他在提供.so
文件的同時也會提供相應的java
調用類文件,或者按之前雙方定好的規則自己創建相應類文件,并生成相應的方法,之所以要約定好只因為,NDK
下的C/C++
函數和Java
橋接的函數命名是有約束的,規則如下:
Java_PackageName_ClassName_MethodName
雙方必須按這個規則來實現或者調用此函數,否則不會成功,例如,我們現在有一個函數:String stringFromJNI()
的java
函數,它在com.example.hellojni.HelloJni
這個文件下,這個函數用來返回一個字符串,功能由底層C
來實現,那么相應的C
語言jni
開發文件中就必須按上述規則命名一個Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
的函數,并返回一個字符串結果:
#include
// 函數名格式必須按規矩來
jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
{
return (*env)->NewStringUTF(env, "Hello from JNI ! Compiled with ABI " ABI ".");
}
同樣對應的java
文件也必須:
- 文件必須在
com.example.hellojni
包名下 - 類文件名必須是
HelloJni
- 方法名必須是
stringFromJNI
package com.example.hellojni;
class HelloJni {
public static native String stringFromJNI();
static {
// 加載 hellojni.so靜態塊
System.loadLibrary("hellojni");
}
}
2.2 有源碼,需要編譯.so
文件
如果有 C/C++ 源碼,沒有.so
文件,那么我們就得手動把源碼文件編譯成.so
文件,編譯的方式也分為兩種:
手工執行命令經行編譯
使用gradle
腳本自動實現編譯
AndroidStudio
默認的源碼存放目錄是:
src/main/jni
如果你沒發現此目錄,那么你可以手動創建一個,把所有的 C/C++
源碼放在此文件下,當然并非必須要放在此目錄下,你可以自定義目錄,然后在build.gradle
中做一個資源路徑指定即可:
// build.gradle
android {
sourceSets.main {
// 你的源碼目錄
jni.srcDir 'src/main/otherDir'
}
}
2.2.1 手工執行命令經行編譯
在使用手工編譯(C/C++
)文件之前,我們要回到文章開頭部分,我們需要配置好系統環境變量,這樣我們才能在系統環境下執行ndk
相關編譯命令,如果您的環境變量還沒有配置,那么可以參考下文章開頭部分,如果已經做好這部分工作,那么咱們繼續。
接下來,我們還要創建如下兩個文件:
- Android.mk
- Applicatoin.mk (非必要)
2.2.1.1 創建 Android.mk
Android.mk
文件用來指定源碼編譯的配置信息,例如工作目錄,編譯模塊的名稱,參與編譯的文件等,大致內容如下:
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:設置工作目錄,而 my-dir 則會返回 Android.mk 文件所在的目錄。
2. CLEAR——VARS:清除幾乎所有以 LOCAL——PATH 開頭的變量(不包括 LOCAL_PATH)。
3. LOCAL_MODULE:用來設置模塊的名稱。
4. LOCAL_SRC_FILES:用來指定參與模塊編譯的 C/C++ 源文件名。
5. BUILD_SHARED_LIBRARY:作用是指定生成的靜態庫或者共享庫在運行時依賴的共享庫模塊列表。
2.2.1.2 創建 Application.mk
這個文件用來配置編譯平臺相關內容,我們最常用的估計只是APP_ABI
字段,它用來指定我們需要基于哪些 CPU
架構的.so
文件,當然你可以配置多個平臺:
APP_ABI := armeabi armeabi-v7a x86 mips
如果不創建Application.mk
文件,那么手動編譯的.so
文件只有armeabi
平臺一個版本,其他平臺的不會被編譯。
假設我們配置好了Android.mk
文件,那么接下來我們就可以執行如下命令來生成.so
文件了,我們假設開發NDK
的目錄為默認目錄:
cd src/main/jni/
ndk-build
如果順利,那么你將會看到,在src/main/
目錄下會多了一個libs
目錄,這是NDK
使用命令編譯.so
文件的生成的默認目錄,而AndroidSutdio
默認加載NDK
的目錄是jniLibs
,那么你有兩種解決方式:
- 配置
build.gradle
資源目錄,參見文章 2.1 小節 - 使用
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 = []
}
當你得到了.so
文件,那么接下來就是在java
文件中調用執行即可,如果想了解更多ndk-build
命令內容,可參見:Android ndk-build 使用文檔
2.2.2 使用 gradle 腳本
當然該機器做的事我們還是盡量讓機器來做,因此,接下來我打算使用build.gradle
來添加一些配置,讓Gradle
自動幫我完成編譯工作,這簡直就是爽歪歪啦!
使用gradle
, 你再也不用手動添加Android.mk
和Application.mk
文件,一切在build.gradle
文件中就都能搞定,在這里我們直接貼出build.gradle 中ndk 相關的配置:
android.ndk {
// 模塊名稱
moduleName = "hello-jni"
// 指定編譯平臺,更多平臺信息 參見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
的好處是,自動編譯生成apk
文件,并且把相關的.so
文件打包到apk
安裝包中,一勞永逸。