1. 程式人生 > >Android NDK開發——CMake的編譯使用

Android NDK開發——CMake的編譯使用

mark:
1 NDK 簡介

在介紹 NDK 之前還是首推 Android 官方 NDK 文件。傳送門

官方文件分別從以下幾個方面介紹了 NDK

NDK 的基礎概念
如何編譯 NDK 專案
ABI 是什麼以及不同 CPU 指令集支援哪些 ABI
如何使用您自己及其他預建的庫
本節將會對文件進行總結和補充。所以建議先瀏覽一遍文件,或者看完本篇文章再回頭看一遍文件。

1.1 NDK 基礎概念

首先先用簡單的話分別解釋下 JNI、NDK, 以及分別和 Android 開發、c/c++ 開發的配合。在解釋過程中會對 Android.mk、Application.mk、ndk-build、CMake、CMakeList 這些常見名詞進行掃盲。

JNI(Java Native Interface):Java本地介面。是為了方便Java呼叫c、c++等原生代碼所封裝的一層介面(也是一個標準)。大家都知道,Java的優點是跨平臺,但是作為優點的同時,其在本地互動的時候就程式設計了缺點。Java的跨平臺特性導致其本地互動的能力不夠強大,一些和作業系統相關的特性Java無法完成,於是Java提供了jni專門用於和原生代碼互動,這樣就增強了Java語言的本地互動能力。上述部分文字摘自任玉剛的 Java JNI 介紹

NDK(Native Development Kit) : 原生開發工具包,即幫助開發原生程式碼的一系列工具,包括但不限於編譯工具、一些公共庫、開發IDE等。

NDK 工具包中提供了完整的一套將 c/c++ 程式碼編譯成靜態/動態庫的工具,而 Android.mk 和 Application.mk 你可以認為是描述編譯引數和一些配置的檔案。比如指定使用c++11還是c++14編譯,會引用哪些共享庫,並描述關係等,還會指定編譯的 abi。只有有了這些 NDK 中的編譯工具才能準確的編譯 c/c++ 程式碼。

ndk-build 檔案是 Android NDK r4 中引入的一個 shell 指令碼。其用途是呼叫正確的 NDK 構建指令碼。其實最終還是會去呼叫 NDK 自己的編譯工具。

那 CMake 又是什麼呢。脫離 Android 開發來看,c/c++ 的編譯檔案在不同平臺是不一樣的。Unix 下會使用 makefile 檔案編譯,Windows 下會使用 project 檔案編譯。而 CMake 則是一個跨平臺的編譯工具,它並不會直接編譯出物件,而是根據自定義的語言規則(CMakeLists.txt)生成 對應 makefile 或 project 檔案,然後再呼叫底層的編譯。

在Android Studio 2.2 之後,工具中增加了 CMake 的支援,你可以這麼認為,在 Android Studio 2.2 之後你有2種選擇來編譯你寫的 c/c++ 程式碼。一個是 ndk-build + Android.mk + Application.mk 組合,另一個是 CMake + CMakeLists.txt 組合。這2個組合與Android程式碼和c/c++程式碼無關,只是不同的構建指令碼和構建命令。本篇文章主要會描述後者的組合。(也是Android現在主推的)

1.2 ABI 是什麼

ABI(Application binary interface)應用程式二進位制介面。不同的CPU 與指令集的每種組合都有定義的 ABI (應用程式二進位制介面),一段程式只有遵循這個介面規範才能在該 CPU 上執行,所以同樣的程式程式碼為了相容多個不同的CPU,需要為不同的 ABI 構建不同的庫檔案。當然對於CPU來說,不同的架構並不意味著一定互不相容。

armeabi裝置只相容armeabi;
armeabi-v7a裝置相容armeabi-v7a、armeabi;
arm64-v8a裝置相容arm64-v8a、armeabi-v7a、armeabi;
X86裝置相容X86、armeabi;
X86_64裝置相容X86_64、X86、armeabi;
mips64裝置相容mips64、mips;
mips只相容mips;
具體的相容問題可以參見這篇文章。Android SO檔案的相容和適配

當我們開發 Android 應用的時候,由於 Java 程式碼執行在虛擬機器上,所以我們從來沒有關心過這方面的問題。但是當我們開發或者使用原生程式碼時就需要了解不同 ABI 以及為自己的程式選擇接入不同 ABI 的庫。(庫越多,包越大,所以要有選擇)

下面我們來看下一共有哪些 ABI 以及對應的指令集

ABI
2 CMake 的使用

這一節將重點介紹 CMake 的規則和使用,以及如何使用 CMake 編譯自己及其他預建的庫。

2.1 Hello world

我們通過一個Hello World專案來理解 CMake

首先建立一個新的包含原生程式碼的專案。在 New Project 時,勾選 Include C++ support

New Project
專案建立好以後我們可以看到和普通Android專案有以下4個不同。

main 下面增加了 cpp 目錄,即放置 c/c++ 程式碼的地方
module-level 的 build.gradle 有修改
增加了 CMakeLists.txt 檔案
多了一個 .externalNativeBuild 目錄

Difference
build.gradle

android {

defaultConfig {

externalNativeBuild {
cmake {
cppFlags “-frtti -fexceptions”
arguments “-DANDROID_ARM_NEON=TRUE”
}
}
}
buildTypes {

}
externalNativeBuild {
cmake {
path “CMakeLists.txt”
}
}
}

由於 CMake 的命令整合在了 gradle - externalNativeBuild 中,所以在 gradle 中有2個地方配置 CMake。

defaultConfig外面的 externalNativeBuild - cmake,指明瞭 CMakeList.txt 的路徑;
defaultConfig 裡面的 externalNativeBuild - cmake,主要填寫 CMake 的命令引數。即由 arguments 中的引數最後轉化成一個可執行的 CMake 的命令,可以在 .externalNativeBuild/cmake/debug/{abi}/cmake_build_command.txt 中查到。如下

cmake command
更多的可以填寫的命令引數和含義可以參見Android NDK-CMake文件

CMakeLists.txt

CMakeLists.txt 中主要定義了哪些檔案需要編譯,以及和其他庫的關係等。

看下新專案中的 CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

編譯出一個動態庫 native-lib,原始檔只有 src/main/cpp/native-lib.cpp

add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )

找到預編譯庫 log_lib 並link到我們的動態庫 native-lib中

find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
這其實是一個最基本的 CMakeLists.txt ,其實 CMakeLists.txt 裡面可以非常強大,比如自定義命令、查詢檔案、標頭檔案包含、設定變數等等。建議結合 CMake 的官方文件使用。同時在這推薦一箇中文翻譯的簡易的CMake手冊

2.2 CMake 使用自己及其他預建的庫

當你需要引入已有的靜態庫/動態庫(FFMpeg)或者自己編譯核心部分並提供出去時就需要考慮如何在 CMake 中使用自己及其他預建的庫。

Android NDK 官網的使用現有庫的文件中還是使用 ndk-build + Android.mk + Application.mk 組合的說明文件。(其實官方文件中大部分都是的,並沒有使用 CMake)

幸運的是, Github上的官方示例 裡面有個專案 hello-libs 實現瞭如何創建出靜態庫/動態庫,並引用它。現在我們把程式碼拉下來看下具體是如何實現的。

hello-libs
我們先看下Github上的README介紹:

app - 從 project/distribution/使genlibsproject/distribution/ 目錄,你不需要再編譯這個庫,二進位制檔案已經儲存在了專案中。當然,如果有需要你也可以編譯自己的原始碼,只需要去掉 setting.gradle 和 app/build.gradle 中的註釋,然後執行一次,接著註釋回去,防止在 build 的過程中不受影響。
我們採用自底向上的方式分析模組,先看下 gen-libs 模組。

gen-libs/build.gradle

android {

defaultConfig {

externalNativeBuild {
cmake {
arguments ‘-DANDROID_PLATFORM=android-9’,
‘-DANDROID_TOOLCHAIN=clang’
// explicitly build libs
targets ‘gmath’, ‘gperf’
}
}
}

}

查詢文件可以知道 arguments 中 -DANDROID_PLATFORM 代表編譯的 android 平臺,文件建議直接設定 minSdkVersion 就行了,所以這個引數可忽略。另一個引數 -DANDROID_TOOLCHAIN=clang,CMake 一共有2種編譯工具鏈 - clang 和 gcc,gcc 已經廢棄,clang 是預設的。

targets ‘gmath’, ‘gperf’ 代表編譯哪些專案。(不填就是都編譯)

cpp/CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

set(CMAKE_VERBOSE_MAKEFILE on)

set(lib_src_DIR ${CMAKE_CURRENT_SOURCE_DIR})

set(lib_build_DIR ENVHOME/tmp)file(MAKEDIRECTORY{lib_build_DIR})

add_subdirectory(libsrcDIR/gmath{lib_build_DIR}/gmath)
add_subdirectory(libsrcDIR/gperf{lib_build_DIR}/gperf)
外層的 CMakeLists 裡面核心就是 add_subdirectory,查詢CMake 官方文件 可以知道這條命令的作用是為構建新增一個子路徑。子路徑中的 CMakeLists.txt 也會被執行。即會去分別執行 gmath 和 gperf 中的 CMakeLists.txt

cpp/gmath/CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

set(CMAKE_VERBOSE_MAKEFILE on)

add_library(gmath STATIC src/gmath.c)

copy out the lib binary… need to leave the static lib around to pass gradle check

set(distribution_DIR CMAKECURRENTSOURCEDIR/../../../../../distribution)settargetproperties(gmathPROPERTIESARCHIVEOUTPUTDIRECTORY{distribution_DIR}/gmath/lib/${ANDROID_ABI}”)

copy out lib header file…

add_custom_command(TARGET gmath POST_BUILD
COMMAND “CMAKECOMMANDEcopy{CMAKE_CURRENT_SOURCE_DIR}/src/gmath.h”
“${distribution_DIR}/gmath/include/gmath.h”

* the following 2 lines are for potential future debug purpose *

COMMAND “${CMAKE_COMMAND}” -E

remove_directory “${CMAKE_CURRENT_BINARY_DIR}”

               COMMENT "Copying gmath to output directory")

這個是其中一個靜態庫的 CMakeLists.txt,另一個跟他很像。只是把 STATIC 改成了 SHARED (動態庫)。

add_library(gmath STATIC src/gmath.c) 之前用到過,編譯出一個靜態庫,原始檔是 src/gmath.c

set_target_properties 命令的意思是設定目標的一些屬性來改變它們構建的方式。這個命令中設定了 gmath 的 ARCHIVE_OUTPUT_DIRECTORY 屬性。也就是改變了輸出路徑。

add_custom_command 命令是自定義命令。命令中把標頭檔案也複製到了 distribution_DIR 中。

以上就是一個靜態庫/動態庫的編譯過程。總結以下3點

編譯靜態庫/動態庫
修改輸出路徑
複製暴露的標頭檔案
接著,我們看下 app 模組是如何使用預建好的靜態庫/動態庫的。

app/src/main/cpp/CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

configure import libs

set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../distribution)

建立一個靜態庫 lib_gmath 直接引用libgmath.a

add_library(lib_gmath STATIC IMPORTED)
set_target_properties(lib_gmath PROPERTIES IMPORTED_LOCATION
distributionDIR/gmath/lib/{ANDROID_ABI}/libgmath.a)

建立一個動態庫 lib_gperf 直接引用libgperf.so

add_library(lib_gperf SHARED IMPORTED)
set_target_properties(lib_gperf PROPERTIES IMPORTED_LOCATION
distributionDIR/gperf/lib/{ANDROID_ABI}/libgperf.so)

build application’s shared lib

set(CMAKE_CXX_FLAGS “${CMAKE_CXX_FLAGS} -std=gnu++11”)

建立庫 hello-libs

add_library(hello-libs SHARED
hello-libs.cpp)

加入標頭檔案

target_include_directories(hello-libs PRIVATE
distributionDIR/gmath/include{distribution_DIR}/gperf/include)

hello-libs庫連結上 lib_gmath 和 lib_gperf

target_link_libraries(hello-libs
android
lib_gmath
lib_gperf
log)
我將解釋放在了註釋中。可以看下基本上分成了4個步驟引入:

分別建立靜態庫/動態庫,直接引用已經有的 .a 檔案 或者 .so 檔案
建立自己應用的庫 hello-libs
加入之前暴露標頭檔案
連結上靜態庫/動態庫
還是很好理解的。編輯好並 Sync 後,你就可以發現 hello-libs 中的c/c++程式碼可以引用暴露的標頭檔案呼叫內部方法了。

3 資料文獻

首推 Android NDK 官方文件,雖然很多都不完整,但是絕對是必須看一遍的東西。

當初次接觸 NDK 開發又覺得新建的 Hello World 專案過於簡單時。建議把 googlesamples - android-ndk 專案拉下來。裡面有多個例項參考,比官方文件完整很多。

Google Samples
當你發現示例裡的一些NDK配置滿足不了你的需求後,你就需要到 CMake 官方文件 去查詢完整的支援的函式,同時這裡也提供一箇中文翻譯的簡易的CMake手冊。

以上文件資料僅為了解決 NDK 開發過程中編譯配置問題,具體 c/c++ 的邏輯編寫、jni等不在此範疇。

彩蛋

文末獻上一組彩蛋,將 CMake 或者 NDK 開發過程中遇到的坑和小技巧以 Q&A 的方式列出。持續更新

Q1:怎麼指定 C++標準?

A:在 build_gradle 中,配置 cppFlags -std

externalNativeBuild {
cmake {
cppFlags “-frtti -fexceptions -std=c++14”
arguments ‘-DANDROID_STL=c++_shared’
}
}
Q2:add_library 如何編譯一個目錄中所有原始檔?

A: 使用 aux_source_directory 方法將路徑列表全部放到一個變數中。

查詢所有原始碼 並拼接到路徑列表

aux_source_directory(CMAKEH

相關推薦

Android NDK開發——CMake編譯使用

mark: 1 NDK 簡介 在介紹 NDK 之前還是首推 Android 官方 NDK 文件。傳送門 官方文件分別從以下幾個方面介紹了 NDK NDK 的基礎概念 如何編譯 NDK 專案 ABI 是什麼以及不同 CPU 指令集支援哪些 ABI

android ndk開發之一 編譯ffmpeg

之所以要在linux下編譯ffmpeg,是因為在windows下的坑很多,而且在linux下編譯方便快捷。 安裝 現在大部分的電腦都是windows,所以我們可以先裝個虛擬機器,裝個linux系統,這裡不詳細說了,網上教程很多,這裡我用的虛擬機器是Ora

Android NDK開發掃盲及最新CMake編譯使用

本篇文章旨在簡介 Android 中 NDK 是什麼以及重點講解最新 Android Studio 編譯工具 CMake 的使用 1 NDK 簡介 在介紹 NDK 之前還是首推 Android 官方 NDK 文件。傳送門 官方文件分別從以下幾個方面介紹了 NDK ND

Android NDK開發(一)CMake構建工具使用

  一、Android studio中需要的外掛:     CMake     LLDB     NDK   二、專案配置      ①build.gardle的配置  :多了兩個externalNativeBuild :def

Android NDK/JNI cmake開發入門教程

本人使用Android Studio3.0進行JNI開發,在Android Studio2.2之前都是使用Android.mk進行開發,Android Studio2.2及以上採用更加簡便的NDK + Cmake + Cmakelist.txt開發。 一、首先新建Android Studio專案

Android NDK開發CMake

知之為知之,不知為不知 哇!(先來個王者之哇助助興),最近的專案一直用到Android NDK,簡直頭皮發麻,每次底層出現問題,都要找同事幫忙,甚是尷尬,於是看一些帖子,稍微整理了一下,做個小筆記,同時也分享一下前人之經驗.不說了,開始進入正題. Android開發環境 工具:And

Android逆向基礎筆記—Android NDK開發2之Windows下的gcc手動編譯(交叉連編譯)和利Linux Ubuntu系統下的交叉工具鏈手動編譯

一、交叉工具鏈 這些工具都在NDK的路徑下:E:\Android\android-ndk-r13\toolchains\arm-linux-androideabi-4.9\prebuilt\windo

NDK開發_編譯的cpp引用到 其它so, Android.mk 的寫法

    如果我們編譯的so檔案需要引用到其它的so檔案,那我們來看下這時候的Android.mk 檔案如何寫。    1>【不需要ndk編譯 .cpp,直接是 so檔案】切到 Project 檢視,在java同級目錄下 新建  jn

Android NDK 開發CMake 使用

1. 前言 當在做 Android NDK 開發時,如果不熟悉用 CMake 來構建,讀不懂 CMakeLists.txt 的配置指令碼,很容易就會踩坑,遇到編譯失敗,一個很小的配置問題都會浪費很多時間。所謂工欲善其事必先利其器,學習 NDK 開發還是要大致瞭

Android逆向基礎筆記—Android NDK開發4之Android studio NDK自動編譯

這部分就是最後的部分了,為什麼要寫Android studio呢。大家知道,eclipse 到了現在,已經不被Google支援了,所以現在最好的開發就是利用Android studio。雖然說,網上有

Android NDK 開發總結

設置 .text nbsp def runt 編寫 abi 文件的 targe 一.安裝配置環境 1.安裝Android Studio,下載路徑https://developer.android.com/studio/index.html?hl=zh-cn。我下載的是Win

android NDK開發中,用Cygwin調試本地代碼時報錯“Another debug session running,Use --force to kill it”原因及解決的方法

能夠 att cati kill 時報 andro 使用 deb gdb調試 在使用ndk-gdb調試的時候。運行$NDK/ndk-gdb --verbose報錯“Another debug session running,Use --force to kil

Android NDK開發及OpenCV初步學習筆記

-a cep cto strip 鏈接 jni 加載 idt jniexport https://www.jianshu.com/p/c29bb20908da Android NDK開發及OpenCV初步學習筆記 Super_聖代 關註 2017.08.19 00:

Android NDK開發 Android JNI專案建立

本篇文章只介紹android ndk在windows系統的編譯環境配置方法 更新於2015年1月11日 將更加詳細的介紹一個基本的Android Jni專案的建立。 步驟一:下圖是必須的,配置好這一步驟就可以進行Android JNI專案的建立了。 步驟二:新建一個Andr

android-studio使用cmake編譯ffmpeg實踐

本例使用的是合併的libffmpeg庫,可參考之前的實踐操作 android全平臺編譯ffmpeg合併為單個庫實踐 目錄 配置環境 新建hello工程 配置環境 作業系統: ubuntu 16.05 注意: ffmpeg庫的編譯使用的是androi

Android NDK 開發教程三 Hello JNI 示例

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Ubuntu14.04+android-ndk-r10b+arm編譯libcurl-7.61.1,支援openssl-1.1.1(https)

相關依賴: android-ndk-r10b:ubuntu14.04搭建Android-NDK開發環境 android-arm:ubuntu14.04搭建Android-arm交叉編譯環境 openssl-1.1.1: ubuntu14.04+android-ndk-r10b+arm

ubuntu14.04搭建Android-NDK開發環境

1.建立Android平臺工作空間 mkdir AndroidWorkSpace 2.進入Android平臺工作空間,建立NDK工具目錄 cd AndroidWorkSpace mkdir NDK_Tools 3.獲取android-ndk-r10b 下載:wget ht

Android NDK開發之引入第三方庫

在Android開發中我們經常要把一些比較看重安全或者計算效率的東西通過JNI呼叫C/C++程式碼來實現,如果需要實現的功能簡單或者你的C/C++程式碼能力比較強,但是目前還是有很多功能強大的第三方庫的,比如openssl、FFmpeg等,呼叫這些第三方實現顯然比重複造輪子實際的多。 本教程適合將原始的動態

android ndk開發crash崩潰定位:

android使用ndk開發crash崩潰定位: 1、法一:使用ndk-stack輸出呼叫堆疊    cd /home/hk/Android-Develop/android-ndk-r12b 將log.txt放在這個目錄    ./ndk-stack -sym