Android NDK開發入門
接觸Android有一段時間了,NDK算是一道坎,因為對c語言不熟悉,所以也沒怎麼去接觸這一塊,但是最近的學習,越發的覺得,如果想讓app能有更高的效率,比如在影象的處理方面,遊戲開發方面,甚至於要做一些涉及系統層面的操作時,不可避免的要去用到NDK。通過利用c/c++的能力來彌補java的缺點。
NDK的好處:
1. 提高程式的執行效率
2. 程式碼保護,java層容易被反編譯,但是ndk層較難
3. 便於移植,而且也能方便使用c/c++開源庫
這個是Android Studio的ndk入門版,於eclipse的不同,使用的是gradle進行構建。
下面就從hello ndk開始進入ndk的世界。這個sample可以在下載了ndk之後在ndk資料夾中的sample資料夾下找到,匯入Android Stuido就可以開始學習了。
Android Studio中有兩種方法去使用c/c++的程式碼:
1. 使用現有的.so庫
2. 使用專案中當前的c/c++程式碼
使用現有的.so庫
這一步是比較簡單的,只要把所有的.so檔案拷貝到對應的目錄檔案下就可以了。要對應好不同的平臺。
接著,在要使用的地方加上載入庫的程式碼,就可以使用當前的.so庫了。
String libName = "helloNDK"; // 載入的module的名字,不用加.so
System.loadLibrary( libName );
庫的名字構成是由,lib + moduleName + .so,當去載入庫時,只需要使用moduleName不用加字首或字尾。
如果想改變使用的jnilibs的路徑,可以在gradle中定義如下的程式碼,既可以改變引用的.so的檔案路徑。
android {
sourceSets.main {
jniLibs.srcDir 'src/main/filePath' // 設定有so庫的位置
}
}
使用當前專案中的c/c++程式碼
這個是很方便的一個開發方式,而且通過gradle去配置,將原本eclipse的複雜度降低了。主要要做以下的事情
1. 配置ndk的路徑
2. 在gradle中配置ndk的module
3. 新增c/c++檔案
配置ndk的路徑
可以通過在專案的ModuleSetting中設定,右鍵專案,選擇ModuleSetting,在sdk location中可以選擇下載或者是配置路徑,下載的話去官網就有的下載了。
也可以在工程根目錄下的local.properties中增加ndk的配置。
ndk.dir=/Users/ndkPath
在gradle中配置ndk module
指明將要編譯的c/c++程式碼
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
...
ndk {
moduleName "modulename"
}
}
}
當然可以在增加一些指定的配置,類似於cFlags, stl, ldLibs.
ndk {
moduleName "myEpicGameCode"
cFlags "-DANDROID_NDK -D_DEBUG DNULL=0" // 定義巨集變數
ldLibs "EGL", "GLESv3", "dl", "log" // 連結這些庫
stl "stlport_shared" // Use shared stlport library
}
新增c/c++檔案
配置好上述步驟之後,我們就可以開始開發我門的c/c++程式碼了。c/c++程式碼的位置是在src/main/jni目錄下,檔案包括
1. Android.mk
2. Application.mk
3. c/c++檔案包括標頭檔案
當然和引入so庫相似,對於c/c++的程式碼的位置也是可以改變,通過在gradle中進行配置
android {
sourceSets.main {
jni.srcDirs 'src/main/path' // your path
}
}
可以通過productFlavors去設定對於不同平臺時候的不同的變數,比如通過設定全域性變數指定不同平臺去載入不同的標頭檔案。
又或者編譯製定平臺的so庫而不是所有的平臺。通過這個方法來得到我們想要的結果。
android {
productFlavors {
x86 {
ndk {
abiFilter "x86"
}
}
arm {
ndk {
abiFilter "armeabi-v7a"
}
}
}
}
c/c++ 檔案
這裡開始真正的關於Hello NDK的程式碼編寫。首先在MainActivity.java中先定義好我們的native的方法,使用的native的關鍵字。
private native String sayHello();
// 在btn的setListener中設定toast
Toast.makeText(MainActivity.this, sayHello(), Toast.LENGTH_SHORT).show();
在src/main/jni目錄下建立hello-jni.c檔案,方法名字的定義要注意,否則會引起方法找不到的錯誤,方法名稱為Java_+完整類路徑+方法。當然,為了防止手動的錯誤,可以使用javah的命令生成,具體請繼續往下看。
#include <string.h>
#include <jni.h>
jstring Java_com_yxp_hellondk_MainActivity_sayHello( JNIEnv* env, jobject thiz ) {
return (*env)->NewStringUTF(env, "Hello NDK!");
}
使用javah生成標頭檔案
# 進入src/main/java資料夾,-d指明輸出檔案路徑
javah -d ../jni com.example.MainActivity
在gradle指定ndk module
defaultConfig {
...
ndk {
moduleName "hello-jni"
}
}
回到MainActivity.java中,引入ndk library,library的名稱也就是module name無需加字首或者字尾。
static {
System.loadLibrary("hello-jni");
}
Application.mk(官網)
Application.mk檔案的路徑是在src/main/jni目錄下,用來描述工程中需要用到的native庫,包括靜態庫,共享庫和可執行檔案。在HelloNdk專案中的Application.mk內容如下:
APP_ABI := all
預設情況下,NDK的編譯系統根據 “armeabi” ABI生成機器程式碼。可以使用APP_ABI 來選擇一個不同的ABI。
比如:為了在ARMv7的裝置上支援硬體FPU指令。可以使用:
APP_ABI := armeabi-v7a
當然,當有多個定義時,以空格分開。
Android.mk(官網)
Application.mk檔案的路徑是在src/main/jni目錄下,Android.mk檔案是用來描述ndk相關原始檔,便於構建工程。在構建的過程中它會被解析多次,以完成構建。在Android.mk檔案中,可以定義一個到多個模組,模組之間可以共享檔案。在檔案當中,不用列出具體的標頭檔案或者依賴,這些ndk編譯器都會自動完成。
ndk編譯系統的系統的變數都有如下的定義:
- LOCAL_為字首的變數,比如LOCAL_MODULE
- 以PRIVATE_、NDK_、APP_為字首的變數
- 全小寫字母的變數
所以在Android.mk檔案中使用自定義變數的時候,建議使用MY_字首。
在這個HelloNdk工程中,Android.mk檔案定義如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
CLEAR_VARS變數,主要在構建的時候會清除所有的LOCAL_開頭的變數的定義,包括LOCAL_MODULE、LOCAL_SRC_FILES等,但是不包括LOCAL_PATH。由於在構建的時候這些變數都是全域性的變數,所以在一個module在構建的時候,應該不受其他module的變數影響。使用方式就是:
inclued $(CLEAR_VARS)
my-dir屬於一個方法(function)的全域性定義,返回當前Android.mk檔案的檔案路徑,值得注意的是,這個方法是包含最後一個構建檔案的路徑,所以這個定義要在開頭,尤其是包含其他的檔案的時候。使用的方法是
$(call my-dir)
LOCAL_PATH變數是指明當前檔案的位置,必須定義在Android.mk檔案的開頭,定義方式:
LOCAL_PATH := $(call my-dir)
BUILD_SHARED_LIBRARY變數,收集從最近的CLEAR_VARS之後的LOCAL_字首的變數,並以此生成so庫。使用方法:
include $(BUILD_SHARED_LIBRARY)