1. 程式人生 > >通過CMake來進行ndk開發之補充篇

通過CMake來進行ndk開發之補充篇

前言

 之前有一篇的文章介紹了通過CMake來進行Android NDK開發的入門文章。寫那篇文章的時候由於內容較多,  
 所以有的內容沒有細說,就一筆帶過了。這篇文章算是一個小小的補充。所以,建議看這篇文章的小夥伴,先閱讀之前的博文。  

內容

使用CMake來進行Android NDK 開發這篇博文說道,向現有的專案裡新增原生代
碼的步驟分為三大步
1. 建立新的原生原始檔。
2. 建立CMake構建指令碼。
3. 將Gradle關聯到你的原生庫。
第一步比較簡單就不說了,第二步對CMake構建指令碼即CMakeLists.txt檔案的構建
說明,先看一張圖:
這裡寫圖片描述

這張圖片是系統自動給我們生成的CMakeLists.txt內容,當時我們只說明瞭add_library命令的作用,
那現在我們看一下find_library 代表什麼,根據Google文件,find_library表示定位NDK庫,圖片上關於這個命令的描述說明還挺好理解的,
查詢一個特殊的預構建庫並且把其儲存路徑設定為一個變數。那這個命令裡面第一個值是路徑變數的名稱,
第二個就是你想要CMake去定位的庫的具體名字。我們為什麼要定位一個NDK庫呢?是這樣的,Android平臺上有一些系統的預構建庫,這些系統的庫不需要再構建,打包到apk包中的。直接指定其的路徑就可以使用了。那現在用了find_library就可以直接使用系統的預構建庫了麼?還不行,我們還差一個target_link_libraries命令,這個命令表示:關聯目標庫和其他庫。

find_library(...)

# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the log library to the target library.
                       ${log-lib} )

如圖這樣設定後,native-lib庫中就可以呼叫log庫的函數了。
其實在我們建立支援C/C++的新專案時,系統就自動的建立了CMakeLists.txt檔案,裡面有預設的定位 Android 特定的日誌支援庫

新增其他的預構建庫

剛剛說了是新增系統的預構建庫,使用了find_library命令,那新增其他的非系統的預構建庫,還是使用find_library 麼?不是的了,是使用add_library。咦?之前我們指定要構建的庫就是用這個命令啊,現在新增其他的預構建庫也用這個命令哇~~確實使用這個命令,不過呢,還是有區別的。在使用add_libraray 新增其他的預構建庫時需要,用IMPORTED這個作為標誌。格式如下:

add_library( imported-lib
             SHARED
             IMPORTED )

然後,我們需要使用 set_target_properties() 命令指定庫的路徑,如下所示。
某些庫為特定的 CPU 架構提供了單獨的軟體包,並將其組織到單獨的目錄中。此方法既有助於庫充分利用特定的 CPU 架構,又能讓你僅使用所需的庫版本。要向 CMake 構建指令碼中新增庫的多個 ABI 版本,而不必為庫的每個版本編寫多個命令,你可以使用 ANDROID_ABI 路徑變數。此變數使用 NDK 支援的一組預設 ABI,或者你手動配置 Gradle 而讓其使用的一組經過篩選的 ABI。例如:

add_library(...)
set_target_properties( # Specifies the target library.
                       imported-lib

                       # Specifies the parameter you want to define.
                       PROPERTIES IMPORTED_LOCATION

                       # Provides the path to the library you want to import.
                       imported-lib/src/${ANDROID_ABI}/libimported-lib.so )

為了確保 CMake 可以在編譯時定位您的標標頭檔案,您需要使用 include_directories() 命令,幷包含標標頭檔案的路徑:

include_directories( imported-lib/include/ )
常用的一些構建命令就介紹到這,有其他的需求,大家可以看官方CMake命令文件cmake命令文件

將Gradle關聯到原生倉庫的第二種方式

第二步說完,我們現在看第三步,之前的博文中介紹了,gradle關聯原生倉庫的第一種方式。是通過as的快捷鍵來實現的,右鍵點選你想要關聯到原生庫的模組(例如 app 模組),
並從選單中選擇 Link C++ Project with Gradle。
BuildSystem選擇CMake,projectpath就是CMakeLists.txt檔案的路徑。點選ok完成。 如下圖:
這裡寫圖片描述
這樣呢,就會在應用模組下的build.gradle檔案中,android閉包下出現:

externalNativeBuild {
        cmake {
            path 'CMakeLists.txt'
        }
    }

這樣的語塊。
那我們的第二種方式就是手動的來修改build.gradle資料夾,來實現關聯的目的。時光倒退到向現有的專案裡新增原生程式碼的步驟三大步中的第二步。我們在第二步完成之後,不通過as 的快捷鍵,我們直接在build.gradle的檔案寫入

externalNativeBuild {
        cmake {
            path 'CMakeLists.txt'
        }
    }

也是可以的。。。當然,可以是可以,我們這裡肯定不止這個。我們剛剛有到上面出現程式碼是在android閉包內的。事實上呢,在build.gradle檔案的defaultConfig塊中也可以設定externalNativeBuild {}塊,為 CMake 指定可選引數和標誌。先上程式碼:

android {
  ...
  defaultConfig {
    ...
    // This block is different from the one you use to link Gradle
    // to your CMake or ndk-build script.
    externalNativeBuild {

      // For ndk-build, instead use ndkBuild {}
      cmake {

        // Passes optional arguments to CMake.
        arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"

        // Sets optional flags for the C compiler.
        cFlags "-D_EXAMPLE_C_FLAG1", "-D_EXAMPLE_C_FLAG2"

        // Sets a flag to enable format macro constants for the C++ compiler.
        cppFlags "-D__STDC_FORMAT_MACROS"
      }
    }
  }

  buildTypes {...}

  productFlavors {
    ...
    demo {
      ...
      externalNativeBuild {
        cmake {
          ...
          // Specifies which native libraries to build and package for this
          // product flavor. If you don't configure this property, Gradle
          // builds and packages all shared object libraries that you define
          // in your CMake or ndk-build project.
          targets "native-lib-demo"
        }
      }
    }

    paid {
      ...
      externalNativeBuild {
        cmake {
          ...
          targets "native-lib-paid"
        }
      }
    }
  }

  // Use this block to link Gradle to your CMake or ndk-build script.
  externalNativeBuild {
    cmake {...}
    // or ndkBuild {...}
  }
}

這個程式碼是我從官網山得到sample,我們看到defaultConfig閉包內是存在了externalNativeBuild {},並且配置了一些屬性。arguments “-DANDROID_ARM_NEON=TRUE”, “-DANDROID_TOOLCHAIN=clang”

"-DANDROID_ARM_NEON=TRUE"表示:是否讓CMake構建原生庫時支援“NEON”,預設的是不支援。  
DANDROID_TOOLCHAIN=clang" 這個引數不知道大家還不記得這個圖  

   這是當時建立支援原生程式碼的新專案時,我們見到介面,C++ Standard這個複選框,我們選擇  
   Toolchain Default選項。這與DANDROID_TOOLCHAIN=clang意思一樣,支援CMake構建原生庫的意思。

cFlags “-D_EXAMPLE_C_FLAG1”, “-D_EXAMPLE_C_FLAG2”
cppFlags “-D__STDC_FORMAT_MACROS”
這兩個配置,雖說能根據英文翻譯出個大概,但是有點不確定含義,沒用過。從網上也沒搜到滿意的回答。如果知道的大佬,可以說明一下,(^__^) 嘻嘻……

接著看上面的程式碼,我們發現externalNativeBuild {}塊,不僅僅出現在defaultConfig閉包內,也出現在了,productFlavor塊中。這是什麼情況!!??仔細想想,其實,也好理解,productFlavor閉包中其他的渠道之前都是可以重寫defaultConfig內的屬性的。所以他自然也可以了區分各個渠道,重寫externalNativeBuild {}內的內容哇。這樣,每個渠道的包就有他自己的屬性配置。多和諧~~
上面的程式碼中我們發現了,targets屬性,這個是面對這種情況的。如果你的 CMake 或 ndk-build 專案定義多個原生庫,你可以使用 targets 屬性僅為給定渠道構建和打包這些庫中的一部分。

好了,補充內容補充的差不多了,在國慶放假前一天靜下心來補充這個,真是難熬/(ㄒoㄒ)/~~。。
最後再補充一個,之前博文在最後展示打包進入apk的so檔案的時候,給出了下面的一張圖:
這裡寫圖片描述

我們看到了x86,armeabi、armeabi-v7a,mips只有這四個ABI。你們跑的時候可能出現了:
這裡寫圖片描述

7個ABI,看一些size,apk大小的45.9%。多麼可怕,這是不允許的。而且也不需要適配這麼多的ABI。所以我們需要指定ABI。如何做呢?在build.gradle檔案的defaultConfig閉包下加入如下語句即可:

ndk {

      abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',

    }

上訴語句就表明會打出對應與’x86’, ‘x86_64’, ‘armeabi’, ‘armeabi-v7a’,這四個ABI的庫。

好了,補充篇到此結束,安心過節啦~