這篇主要介紹CMake語法學(xué)習(xí)以及怎樣把上一篇文章中編譯生成的交叉編譯庫導(dǎo)入到Android項(xiàng)目中
由于其他原因耽誤導(dǎo)致這篇文章間隔這么久才寫好,導(dǎo)入的時(shí)候才發(fā)現(xiàn)上一篇文章中交叉編譯后的庫有點(diǎn)問題又進(jìn)行重新修改。這篇文章是NDK系列的最后一篇了,算是大概知道了NDK系列的入門知識。
NDK系列文章
- NDK(一):編寫第一個(gè)JNI項(xiàng)目
- NDK(二):JNI與Java回調(diào)以及靜動態(tài)注冊
- NDK(三):靜態(tài)庫和動態(tài)庫
- NDK(四):交叉編譯
mk
之前是使用,現(xiàn)在Google基本放棄了,都采用CMake
CMake
在學(xué)習(xí)CMake之前,我們或多或少了解過其他Make工具。其他的Make工具遵循不同的標(biāo)準(zhǔn)到時(shí)執(zhí)行的Makefile格式不同,比如要保證在不同平臺下編譯,就需要一個(gè)跨平臺的Make工具,CMake就是一個(gè)跨平臺的構(gòu)建工具。
Cmake并不直接構(gòu)建出最終的軟件,通過編寫CmakeList.txt文件,根據(jù)目標(biāo)用戶的平臺進(jìn)一步生成對應(yīng)的Makefile文件,從而達(dá)到跨平臺的目的,如Android Studio就是通過ninja。所以可以這么理解為,編寫Cmake語法,通過ninja,生成Makefile。
安裝CMake
brew install cmake
# 查看版本
cmake -version
cmake version 3.13.2
CMake語法入門
-
官網(wǎng)語法手冊(最好的資料)
學(xué)下CMake語法,可以參考官網(wǎng)的文檔,進(jìn)入官網(wǎng),然后Resource --> Documentation --> 選擇最新的Documentation --> Reference Manuals中的cmake-commands(7)。就可以看到所有的命令了。
實(shí)踐
Demo1 單文件
main.c文件
#include <stdio.h>
int main(){
printf("hello world\n");
return 0;
}
CMakeLists.txt文件
# CMake 最低版本號要求
cmake_minimum_required (VERSION 3.4.1)
# 項(xiàng)目信息(如果不設(shè)置該參數(shù)也可以正常運(yùn)行)
project (MyProject1)
# 指定生成目標(biāo)
add_executable(Demo1 main.c)
利用CMake命令編譯生成執(zhí)行文件
# 編譯生成Makefile文件
cmake .
# 執(zhí)行Makefile文件生成可執(zhí)行文件
make
# 執(zhí)行可執(zhí)行文件,輸出hello world
./Demo1
hello world
Demo2 多文件
如果源文件有很多個(gè),比如有main.c、hello.c、hello.h等,那么一個(gè)個(gè)寫進(jìn)去比較麻煩,如
main.c文件
#include "hello.h"
int main()
{
hello("world");
return 0;
}
hello.h文件
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
hello.c文件
#include <stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
CMakeLists.txt
# CMake 最低版本號要求
cmake_minimum_required (VERSION 3.4.1)
# 項(xiàng)目信息
project (MyProject2)
# 這樣多的話,寫進(jìn)去就比較麻煩,導(dǎo)入只需要寫源文件,.h可以不寫
add_executable(Demo2 main.c hello.c)
# 查找當(dāng)前目錄所有源文件 并將名稱保存到 DIR_SRCS 變量
# 注意這樣不能查找子目錄,也不會自動往子目錄找
aux_source_directory(. DIR_SRCS)
# 或者也可以使用如果通配符的方式,引入所有.c文件,同樣保存到 DIR_SRCS變量中
file(GLOB DIR_SRCS *.c)
# $() 為引用變量DIR_SRCS的值
add_executable(Demo2 ${DIR_SRCS})
Demo3 多文件多目錄
新建文件夾helloDir,把Demo2中的hello.c、hello.h和新建一個(gè)新的CMakeLists.txt文件放到該目錄下
main.c文件
// 記得修改頭文件的引用路徑
#include "helloDir/hello.h"
int main()
{
hello("world");
return 0;
}
CMakeLists.txt
# CMake 最低版本號要求
cmake_minimum_required (VERSION 3.4.1)
# 項(xiàng)目信息
project (MyProject3)
# 添加當(dāng)前目錄下的所有源文件
aux_source_directory(. DIR_SRCS)
# 添加 helloDir 子目錄下的cmakelist
add_subdirectory(helloDir)
# 指定生成目標(biāo)
add_executable(Demo3 ${DIR_SRCS})
# 添加鏈接庫,hello為helloDir子目錄的生成的鏈接庫名稱
target_link_libraries(Demo3 hello)
helloDir/CMakeLists.txt
# CMake 最低版本號要求
cmake_minimum_required (VERSION 3.4.1)
# 指定生成目標(biāo)
aux_source_directory(. DIR_LIB_SRCS)
# 生成鏈接庫,默認(rèn)編譯為靜態(tài)庫
# add_library (hello ${DIR_LIB_SRCS})
# 指定編譯為靜態(tài)庫,與上面的一樣
add_library (hello STATIC ${DIR_LIB_SRCS})
# 指定編譯為動態(tài)庫
add_library (hello SHARED ${DIR_LIB_SRCS})
add_library (hello STATIC ${DIR_LIB_SRCS})
NDK項(xiàng)目之CMake
通過Android Stuido創(chuàng)建支持NDK的項(xiàng)目,就可以看到,多了一個(gè)CMakeList.txt文件。在Android Studio 2.2及其以上,構(gòu)建原生庫的默認(rèn)工具就是CMake。
# 設(shè)置cmake最低支持版本
cmake_minimum_required(VERSION 3.4.1)
# 編譯library庫
add_library(
# 設(shè)置編譯后的library名稱
native-lib
# 設(shè)置library模式,STATIC為靜態(tài)庫,如果想編譯為動態(tài)庫,則修改為SHARED
STATIC
# 設(shè)置編譯使用到的源代碼,如果添加了源代碼,都需要修改此處添加源代碼的路徑
src/main/cpp/native-lib.cpp )
# 查找library,因?yàn)镹DK中已經(jīng)有一部分預(yù)構(gòu)建庫,已經(jīng)被配置為cmake搜索路徑的一部分,所以,可以直接添加庫的名稱。
find_library(
# 此處為查找名稱為log的庫,將絕對路徑賦值到變量log-lib
log-lib
log )
# 鏈接library
target_link_libraries(
# 鏈接自己編寫的native-lib庫和上面查找到的lib庫
# 位置不能更改,native-lib為目標(biāo)的庫,必須放在前面
native-lib
${log-lib} )
# 如果上面不預(yù)先使用find_library的話,也可以直接使用
target_link_libraries( native-lib
log)
同時(shí)build.gradle文件也多了一些配置
android {
compileSdkVersion 28
defaultConfig {
externalNativeBuild {
// 主要是配置了cmake的命令參數(shù)
cmake {
cppFlags ""
// 設(shè)置編譯c/c++ 源文件的cpu類型
// 如果不設(shè)置,連接不同的設(shè)備,gralde會自動根據(jù)連接的設(shè)備,如armeabi-v7a架構(gòu)的手機(jī),則編譯出armeabi-v7a,如模擬器,則編譯出x86。
// 由于考慮編譯出來后包的大小,一般實(shí)際開發(fā)中,都只是編譯兼容性最多的armeabi-v7a架構(gòu)
abiFilters 'armeabi-v7a'
}
}
}
externalNativeBuild {
// 主要定義了CMake的構(gòu)建腳本CMakeLists.txt的路徑
cmake {
// 當(dāng)前的CMakeLists.txt與build.gradle在同一個(gè)目錄下
// CMakeLists.txt也可以在其他路徑下,比如可以在cpp目錄下創(chuàng)建一個(gè)CMakeList.txt配置cpp目錄下的源文件
path "CMakeLists.txt"
}
}
}
添加預(yù)編譯庫
在上一篇文章中,分別對怎樣編譯Android平臺上的靜、動態(tài)庫進(jìn)行介紹,接下來,就把其編譯好的庫(即預(yù)編譯庫)放到Android Stuido項(xiàng)目中
引入靜態(tài)庫
引入libmain.a的靜態(tài)庫,在新建armeabi-v7a在cpp文件下,把libmain.a放入其中。
cmake_minimum_required(VERSION 3.6)
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp )
# hello-jni: 變量名 最終會生成的so名字
# SHARED: 動態(tài)庫 STATIC:靜態(tài)庫
add_library(hello-jni SHARED hello-jni.c)
# 引入預(yù)編譯庫
# Main:引入的庫的名字,可以隨便定義,但是要和下面的保存一致
# STATIC:聲明導(dǎo)入的是動態(tài)庫或者靜態(tài)庫,STATIC為靜態(tài)庫
# IMPORTED:表示這個(gè)庫是以導(dǎo)入的方式導(dǎo)入
add_library(Main STATIC IMPORTED)
# 設(shè)置導(dǎo)入的路徑
# Main:庫的名稱
# 設(shè)置目標(biāo)屬性 導(dǎo)入路徑 當(dāng)前庫的路徑
# ${ANDROID_ABI}引用的變量,在上面的build.grale中,我們設(shè)置abiFilters為armeabi-v7a
set_target_properties(Main PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/cpp/${ANDROID_ABI}/libmain.a)
find_library( log-lib
log )
target_link_libraries(hello-jni Main)
修改native-lib.cpp文件,修改后如下
#include <jni.h>
#include <string>
#include <android/log.h>
// 使用上面的log庫
// 該文件為c++,庫為c,因此需要添加extern "C"
extern "C" int main();
extern "C" JNIEXPORT jstring
JNICALL
Java_cn_guidongyuan_studyndk_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
// 如果輸出結(jié)果與庫中main定義的返回值一致,則表示調(diào)用成功
__android_log_print(ANDROID_LOG_ERROR,"Log","%d", main());
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
引入動態(tài)庫
引入libTest.so動態(tài)庫,動態(tài)庫必須放在src/jniLibs/armeabi-v7a(不同CPU架構(gòu)不同目錄)下,否則不會打包進(jìn)去
cmake_minimum_required(VERSION 3.6)
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp )
find_library( log-lib
log )
# 設(shè)置庫的查找路徑
# set方法 定義一個(gè)變量,此處為在原來的CMAKE_CXX_FLAGS變量上,添加-L加庫的查找路徑
# 因?yàn)轫?xiàng)目中的native-lib.cpp為C++,因此存在C++則使用CMAKE_CXX_FLAGS,完全沒有就使用CMAKE_C_FLAGS
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI} ")
# 鏈接動態(tài)庫的名字,庫名詞為libmain.so,這里必須使用main,就會結(jié)合上面設(shè)置的庫查找路徑以及庫名稱查找到對應(yīng)的庫
target_link_libraries( native-lib
${log-lib}
main)
message("ANDROID_ABI : ${ANDROID_ABI}")
# set 方法 定義一個(gè)變量
#CMAKE_C_FLAGS = "${CMAKE_C_FLAGS} XXXX"
# -L: 庫的查找路徑 libTest.so
# 如果庫用c寫的,則用CMAKE_C_FLAGS,如果使用c++或者c++和c混合使用,則用CMAKE_CXX_FLAGS
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI} ")
find_library(log-lib log)
# 此處的Test名字,必須為動態(tài)庫的名稱,查找路徑在上面的set設(shè)置了
target_link_libraries(hello-jni Test ${log-lib} )
修改native-lib.cpp文件,修改內(nèi)容與上面靜態(tài)庫一樣
參考資料
-
很詳細(xì)的一篇文章,從實(shí)例入手,講解 CMake 的常見用法,單獨(dú)作為一種語法來學(xué)習(xí),而不是在Android Studio項(xiàng)目中進(jìn)行介紹。
-
可以看一下其中的CMake常用命令章節(jié),基本列出了CMake語法中常見的語法
-
Android NDK開發(fā)(一) 使用CMake構(gòu)建工具進(jìn)行NDK開發(fā)
簡單介紹了CMake在Android Stuido中的配置,各個(gè)變量的作用
