讀書筆記:《Android 高級進階》
NDK簡介
NDK 是 Native Developmentit的縮寫,是Google在Android開發中提供的一套用於快速創建native工程的一個工具。
使用這個工具可以很方便的編寫和調試JNI的代碼。
- NDK是一系列工具的集合
NDK提供了一系列的工具,幫助開發者快速開發C(或C++)的動態庫,並能自動將so和java應用一起打包成apk。這些工具對開發者的幫助是巨大的.
- NDK集成了交叉編譯器,並提供了相應的mk文件隔離CPU、平臺、ABI等差異,開發人員只需要簡單修改mk文件(指出“哪些文件需要編譯”、“編譯特性要求”等),就可以創建出so。
NDK可以自動地將so和Java應用一起打包,極大地減輕了開發人員的打包工作。
爲什麼使用NDK,優點是什麼?
- 代碼的保護。由於apk的java層代碼很容易被反編譯,而C/C++庫反編譯難度較大。
- 可以方便地使用現存的開源庫。大部分現存的開源庫都是用C/C++代碼編寫的。
- 提高程序的執行效率。將要求高性能的應用邏輯使用C開發,從而提高應用程序的執行效率。
- 便於移植。用C/C++寫得庫可以方便在其他的嵌入式平臺上再次使用。
ABI的基本概念
早起的Android系統幾乎只支持ARMv5的CPU架構,而發展到現在,Android系統目前支持一下7種不同的CPU架構
- ARMv5
- ARMv7(從2010年起)
- x86(從2011年起)
- MIPS(從2012年起)
- ARMv8
- MIPS6
- x86_64(從2014年起)
每一種架構關聯着一種ABI,那麼什麼是ABI呢?ABI是Application Binary Interface的縮寫,即應用程序二進制接口,它定義了二進制文件(Android平臺上專指.so文件)如何運行在相應的系統平臺上,包括使用的指令集、內存對齊到可用的系統函數庫。在Android系統上,每一個CPU架構對應一個ABI,如前所述,總共有7種,對應到Android Studio中的目錄結構如下
[module_name]
[src]
[main]
[jniLibs]
[armeabi]
[armeabi-v7a]
[x86]
[mips]
注意jniLibs目錄是放在module下面,在Android Studio中效果如下:
在Android Studio中爲工程添加C++代碼的方式有兩種。
- 引入預編譯的二進制(自己編譯好或者第三方提供的)
- 在Android Studio中直接從C/C++源碼編譯
引入預編譯的二進制C/C++函數庫
添加預編譯的二進制庫很簡單,默認情況下,Android Studio會到jniLibs目錄中查找並拷貝所有的二進制庫。假設我們在上面每個ABI目錄中都有一個名爲libhellondk.so的二進制庫,在Android中要使用這個庫很簡單。
String libName = "helloNDK"; // 庫名, 注意沒有前綴lib和後綴.so
System.loadLibrary( libName );
直接從C/C++源碼編譯
Android Studio中對C/C++源碼編譯成.so文件的步驟主要如下:
- 配置ndk.dir變量
- 在Gradle中配置NDK模塊
- 添加C/C++文件到指定目錄
配置ndk.dir變量
進行NDK開發第一件要做的事情就是打開工程根目錄的local.properties文件,並在其中配置ndk的目錄,以便讓Android Studio能夠做到NDK的可執行文件等。
sdk.dir= /Users/guhaoxin/Library/Android/sdk
ndk.dir= /Users/guhaoxin/Library/Android/ndk
在Gradle中配置NDK模塊
爲了讓Gradle對C/C++代碼進行編譯,需要配置module的build.gradle文件,打開build.gradle,在其中defaultConfig段落中添加如下代碼:
ndk{
moduleName "moduleName"
}
其中,moduleName要替換成我們自己的C/C++模塊名,例如某個NDK項目中配置如下
android {
compileSdkVersion 20
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "com.example.ndksample"
minSdkVersion 9
targetSdkVersion 20
versionCode 1
versionName "1.0"
ndk {
moduleName "helloNDK" // <-- This is the name of my C++ module!
}
}
// ... more gradle stuff here ...
} // end of android section
ndk還可以配置更多選項,如下:
ndk {
moduleName "myEpicGameCode"
cFlags "-DANDROID_NDK -D_DEBUG DNULL=0" // Define some macros
ldLibs "EGL", "GLESv3", "dl", "log" // Link with these libraries!
stl "stlport_shared" // Use shared stlport library
}
添加C/C++文件到指定的目錄
默認情況下,Gradle會到:
[module]/src/main/jni/
效果如下圖:
目錄中查找C/C++文件進行編譯,我們只需要把C/C++文件放到這個目錄中即可。當然我們也可以修改jni的目錄,例如希望把C/C++文件放到source目錄中,可以修改gradle文件如下:
android {
// .. android settings ..
sourceSets.main {
jni.srcDirs 'src/main/source'
}
}
分平臺配置編譯(可選)
這一步不是必須的,你可以根據需要,對各個平臺進行不同的編譯配置,可以設置覆蓋前面的編譯選項(例如cFlags)。例如你只想編譯指定平臺的.so,而不是所有的平臺。如下:
android {
// .. android settings ..
productFlavors {
x86 {
ndk {
abiFilter "x86"
}
}
arm {
ndk {
abiFilter "armeabi-v7a"
}
}
mips {
ndk {
abiFilter "mips"
}
}
}
} // android
使用.so文件的注意事項
處理.so文件時有一條簡單卻不知名的重要法則:你應該儘可能地提供專爲每個ABI優化過的.so文件,要麼全部支持,要麼都不支持。我們不應該混合着使用,而應該爲每個ABI目錄提供對應的.so文件。
使用高平臺版本編譯的.so文件運行在低版本的設備上
這是大家習以爲常的做法,但是這是錯誤的。因爲NDK平臺不是向後兼容的,而是向前兼容的。推薦使用App的minSdkVersion對應的編譯平臺,因此,當我們引入一個預編譯好的.so文件時,首先需要檢查它被編譯所用的平臺版本。
混合使用不同的C++運行時編譯的.so文件
.so文件可以依賴於不同的C++運行時,靜態編譯或者動態加載。混合使用不同版本的C++運行時可能導致很多奇怪的Crash。作爲一個經驗法則,當只有一個.so文件時,靜態編譯C++運行時是沒有問題的,但存在多個.so文件時,應該讓所有的.so文件都動態鏈接相同的C++運行時。
沒有爲每個支持的CPU架構提供對應的.so文件
這一點前文已經說到,但應該特別注意它,它可能發生在根本沒有注意的情況下。例如:你的APP支持armeabi-v7a和x86架構,然後使用Android Studio新增一個函數庫依賴,這個函數庫包含.so文件並支持更多的CPU架構,例如新增android-gif-drawable函數庫。
compile 'pl.droidsonroids.gif:android-gif-drawable:1.1.+'
發佈我們的APP後,發現在某些設備上會發生Crash。解決辦法是重新編譯我們的.so文件使其支持確實的ABIs,或者設置
ndk.abiFilters
顯示地指定支持的ABIs。
將.so文件放在錯誤的地方
- Android Studio工程放在jniLibs/ABI目錄中(當然也可以通過在build.gradle文件中的設置jniLibs.srcDir屬性自己指定)。
- Eclipse工程放在libs/ABI目錄中(這也是ndk-build命令默認生成.so文件的目錄)。
- AAR壓縮包中位於jni/ABI目錄中(.so文件會自動包含到引用AAR壓縮包的APK中)。
- 最終APK文件中的lib/ABI目錄中
- 通過PackageManager安裝後,在小於Android 5.0的系統中,.so文件位於APP的nativeLibraryPath目錄中;在大於等於Android 5.0的系統中,.so文件位於APP的nativeLibraryRootDir/CPU_ARCH目錄中。
只提供armeabi架構的.so文件而忽略其他ABIs的
所有的x86/x86_64/armeabi-v7a/arm64-v8a設備都支持armeabi架構的.so文件,因此,似乎移除其他ABIs的.so文件時一個減少APK大小的好技巧。但事實上並不是:這將影響到函數庫的性能和兼容性。
以減少APK包大小爲由是一個錯誤的藉口,因爲你可以選擇在應用市場上傳指定ABI版本的APK,生成不同ABI版本的APK。