Android Studio中的NDK開發

讀書筆記:《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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章