一质涛、前言
在前一篇文章 NDK 知識梳理(1) - 使用 CMake 進行 NDK 開發(fā)之初體驗 中,我們一起學(xué)習(xí)了如何在Android Studio
中使用CMake
來進行NDK
開發(fā)坦敌,而編寫CMakeLists.txt
構(gòu)建腳本是其中一個重要的環(huán)節(jié)有咨,今天我們就來一起學(xué)習(xí)CMakeLists.txt
的一些應(yīng)用,介紹它在下面三種場景的用法:
- 從原生代碼構(gòu)建一個原生庫
- 添加
Android NDK
的API
- 引入第三方
so
二均函、從原生代碼構(gòu)建一個原生庫
2.1 指定 CMake 最低版本
cmake_minimum_required
用于指定CMake
的最低版本信息螟凭,不加入會收到警告虚青。
cmake_minimum_required(VERSION 3.4.1)
2.2 從原生代碼構(gòu)建一個原生庫
add_library()
用于指示CMake
從原生代碼構(gòu)建一個原生庫,通俗地說赂摆,就是從.cpp
經(jīng)過編譯得到.so
文件挟憔。正如我們在 NDK 知識梳理(1) - 使用 CMake 進行 NDK 開發(fā)之初體驗 中看到的那樣,我們通過add_library
讓CMake
根據(jù)native-lib.cpp
源文件構(gòu)建一個名為native-lib
的共享庫:
# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add.library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.
add_library( # Specifies 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 )
對于add_library()
括號中的內(nèi)容烟号,可以分為三個部分:
(1) 指定原生庫的名字
add_library
的第一個參數(shù),決定了最終生成的共享庫的名字政恍,例如我們將共享庫的名字定義為native-lib
汪拥,那么最終生成的so
文件將在前面加上lib
前綴,也就是libnative-lib.so
篙耗,但是我們在代碼中加載該共享庫的時候迫筑,仍然應(yīng)當使用native-lib
,也就是像下面這樣:
static {
System.loadLibrary(“native-lib”);
}
(2) 靜態(tài)庫 or 共享庫
通過第二個參數(shù)宗弯,我們可以指定根據(jù)源文件編譯出來的是靜態(tài)庫還是共享庫脯燃,分別對應(yīng)STATIC/SHARED
關(guān)鍵字,這里簡單提一下兩者的區(qū)別:
- 靜態(tài)庫:以
.a
結(jié)尾蒙保。靜態(tài)庫在程序鏈接的時候使用辕棚,鏈接器會將程序中使用到函數(shù)的代碼從庫文件中拷貝到應(yīng)用程序中。一旦鏈接完成邓厕,在執(zhí)行程序的時候就不需要靜態(tài)庫了逝嚎。 - 共享庫:以
.so
結(jié)尾。在程序的鏈接時候并不像靜態(tài)庫那樣在拷貝使用函數(shù)的代碼详恼,而只是作些標記补君。然后在程序開始啟動運行的時候,動態(tài)地加載所需模塊昧互。
(3) 指定源文件
指定編譯的源文件挽铁,這里是一個和CMakeLists.txt
相關(guān)的相對路徑,如果我們有多個源文件敞掘,那么就在后面添加文件的路徑即可叽掘。
2.3 關(guān)聯(lián)多個源文件的例子
下面,我們對 NDK 知識梳理(1) - 使用 CMake 進行 NDK 開發(fā)之初體驗 中的計算器的例子進行優(yōu)化渐逃,把加法和減法的操作放在另一個.cpp
文件中實現(xiàn)够掠,以演示關(guān)聯(lián)多個.cpp
文件的例子,整個目錄的結(jié)構(gòu)變?yōu)椋?br>
addition_subtraction.cpp
int addition(int a, int b) {
return a + b;
}
int subtraction(int a, int b) {
return a - b;
}
addition_subtraction.h
#ifndef CMAKEOLDDEMO_ADDITION_SUBTRACTION_H
#define CMAKEOLDDEMO_ADDITION_SUBTRACTION_H
//加法
int addition(int a, int b);
//減法
int subtraction(int a, int b);
#endif
calculator.cpp
#include <jni.h>
#include "../include/addition_subtraction.h"
extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_addition(JNIEnv *env, jobject instance, jint a, jint b) {
return addition(a, b);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_subtraction(JNIEnv *env, jobject instance, jint a, jint b) {
return subtraction(a, b);
}
那么我們需要將add_library
改寫為:
cmake_minimum_required(VERSION 3.4.1)
add_library(calculator SHARED src/main/cpp/calculator/calculator.cpp src/main/cpp/calculator/addition_subtraction.cpp)
include_directories(src/main/cpp/include/)
三茄菊、添加 NDK API
在Android
系統(tǒng)當中疯潭,預(yù)制了一些標準的NDK
庫赊堪,這些庫函數(shù)的目的就是讓開發(fā)者能夠在原生方法中實現(xiàn)之前在Java
層開發(fā)的一些功能,我們可以通過 NDK 庫 查找所需要的API
竖哩。
因為這些庫已經(jīng)預(yù)制在系統(tǒng)當中了哭廉,所以如果我們要調(diào)用這些庫中的函數(shù),那么不需要將其打包到APK
當中相叁,所需要做的就是向CMake
提供希望使用的庫名稱遵绰,并將其關(guān)聯(lián)到自己的原生庫,最后在原生代碼中引入相應(yīng)的頭文件增淹,調(diào)用方法就可以了椿访。
下面,我們就介紹一個調(diào)用Android Native API
的例子虑润。我們給Calculator
加上一個新的接口成玫,而在其本地方法中調(diào)用Android NDK
的方法來打印一串Java
層傳過來的字符。
(1) 增加接口
我們在NativeCalculator.java
中增加一個接口logByNative
:
public class NativeCalculator {
private static final String SELF_LIB_NAME = "calculator";
static {
System.loadLibrary(SELF_LIB_NAME);
}
public native int addition(int a, int b);
public native int subtraction(int a, int b);
public native void logByNative(String tag, String log);
}
(2) 在 CMakeLists.txt 引入 Android NDK 的 log 庫并把它和 calculator 關(guān)聯(lián)
cmake_minimum_required(VERSION 3.4.1)
add_library(calculator SHARED src/main/cpp/calculator/calculator.cpp src/main/cpp/calculator/addition_subtraction.cpp)
include_directories(src/main/cpp/include/)
find_library(log-lib log)
target_link_libraries(calculator ${log-lib})
對比于之前拳喻,我們增加了下面這兩句:
find_library(log-lib log)
target_link_libraries(calculator ${log-lib})
它們的作用分別是:
-
find_library
:將一個變量和Android NDK
的某個庫建立關(guān)聯(lián)關(guān)系哭当。該函數(shù)的第二個參數(shù)為Android NDK
中對應(yīng)的庫名稱,而調(diào)用該方法之后冗澈,它就被和第一個參數(shù)所指定的變量關(guān)聯(lián)在一起钦勘。
在這種關(guān)聯(lián)建立以后,我們就可以使用這個變量在構(gòu)建腳本的其它部分引用該變量所關(guān)聯(lián)的NDK
庫亚亲。 -
target_link_libraries
:把NDK
庫和我們自己的原生庫calculator
進行關(guān)聯(lián)彻采,這樣,我們就可以調(diào)用該NDK
庫中的函數(shù)了朵栖。
(3) 在 Calculator.cpp 引入頭文件并調(diào)用打印 Log 的函數(shù)
#include <jni.h>
#include "../include/addition_subtraction.h"
#include <android/log.h>
extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_addition(JNIEnv *env, jobject instance, jint a, jint b) {
return addition(a, b);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_subtraction(JNIEnv *env, jobject instance, jint a, jint b) {
return subtraction(a, b);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_logByNative(JNIEnv *env, jobject instance, jstring tag_, jstring log_) {
const char *tag = env->GetStringUTFChars(tag_, 0);
const char *log = env->GetStringUTFChars(log_, 0);
__android_log_write(ANDROID_LOG_DEBUG, tag, log);
env->ReleaseStringUTFChars(tag_, tag);
env->ReleaseStringUTFChars(log_, log);
}
這里需要做的就是兩步:
- 引入頭文件
#include <android/log.h>
- 調(diào)用
NDK
庫中的方法
__android_log_write(ANDROID_LOG_DEBUG, tag, log);
(4) 在 Java 中調(diào)用颊亮,觀察結(jié)果
public class MainActivity extends AppCompatActivity {
private NativeCalculator mNativeCalculator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNativeCalculator = new NativeCalculator();
Log.d("Calculator", "11 + 12 = " + (mNativeCalculator.addition(11,12)));
Log.d("Calculator", "11 - 12 = " + (mNativeCalculator.subtraction(11,12)));
mNativeCalculator.logByNative("Calculator", "Log By Native");
}
}
最終的打印結(jié)果為:
四、引入第三方的.so
庫
最后一部分陨溅,我們舉一個通過第三方.so
庫來實現(xiàn)乘除法的例子终惑,為了得到一個.so
庫,我們通過新建一個工程门扇,然后將它編譯出的.apk
文件解壓雹有,取出其中的.so
文件。
這里獲得第三方so
的原理其實和我們之前一直談到的其實是一樣的臼寄,我們只是借助了Android Studio
來模擬了這個流程霸奕。
3.1 獲得第三方 so 庫
新建工程的目錄結(jié)構(gòu)為:
multiplication_division.h
#ifndef SOMAKER_MULTIPLICATION_DIVISION_H
#define SOMAKER_MULTIPLICATION_DIVISION_H
int multiplication(int a, int b);
int division(int a, int b);
#endif
multiplication_division.cpp
int multiplication(int a, int b) {
return a * b;
}
int division(int a, int b) {
return a / b;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
add_library(multiplication_division SHARED src/main/cpp/multiplication_division.cpp)
include_directories(src/main/cpp/include/)
在build.gradle
的android
節(jié)點下,增加構(gòu)建任務(wù):
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
}
}
這個工程編譯完畢之后吉拳,去app/build/outputs/apk/
目錄下將編譯出來的APK
文件解壓质帅,得到libmultiplication_division.so
庫,我們將它作為第三方的so
庫導(dǎo)入到計算器的例子當中。
3.2 引入第三方 so 庫
(1) 將 so 庫和頭文件拷貝到對應(yīng)的目錄
(2) 修改 CMakeLists.txt 文件
cmake_minimum_required(VERSION 3.4.1)
add_library(calculator SHARED src/main/cpp/calculator/calculator.cpp src/main/cpp/calculator/addition_subtraction.cpp)
include_directories(src/main/cpp/include/)
add_library(multiplication_division SHARED IMPORTED)
set_target_properties(multiplication_division PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libmultiplication_division.so )
find_library(log-lib log)
target_link_libraries(calculator multiplication_division ${log-lib})
這里相比于之前煤惩,修改了以下三句:
add_library(multiplication_division SHARED IMPORTED)
set_target_properties(multiplication_division PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libmultiplication_division.so )
target_link_libraries(calculator multiplication_division ${log-lib})
這三句話的作用分別為:
添加第三方
so
庫
這里和之前在第二步中介紹的創(chuàng)建一個新的原生庫類似嫉嘀,區(qū)別在于最后一個參數(shù),我們通過IMPORTANT
標志告知CMake
只希望將庫導(dǎo)入到項目中魄揉。指定目標庫的路徑
這里有幾點需要說明:${CMAKE_SOURCE_DIR}
表示的是CMakeLists.txt
所在的路徑剪侮,我們指定第三方so
所在路徑時,應(yīng)當以這個常量為起點洛退。按理來說瓣俯,我們應(yīng)當為每種
ABI
接口提供單獨的軟件包,那么兵怯,我們就可以在jinLibs
下建立多個文件夾彩匕,每個文件夾對應(yīng)一種ABI
接口類型,之后再通過${ANDROID_ABI}
來泛化這一層目錄的結(jié)構(gòu)媒区,這樣將有助于充分利用特定的CPU
架構(gòu)推掸。將第三方的庫關(guān)聯(lián)到原生庫
這里和將NDK
庫關(guān)聯(lián)到原生庫的原理是一樣的。
(3) 聲明新的接口驻仅,并在 Calculator.cpp 引入第三方的頭文件,調(diào)用函數(shù)
package com.demo.lizejun.cmakeolddemo;
public class NativeCalculator {
private static final String SELF_LIB_NAME = "calculator";
static {
System.loadLibrary(SELF_LIB_NAME);
}
public native int addition(int a, int b);
public native int subtraction(int a, int b);
public native void logByNative(String tag, String log);
public native int multiplication(int a, int b);
public native int division(int a, int b);
}
#include <jni.h>
#include "../include/addition_subtraction.h"
#include <android/log.h>
#include "../include/multiplication_division.h"
extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_addition(JNIEnv *env, jobject instance, jint a, jint b) {
return addition(a, b);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_subtraction(JNIEnv *env, jobject instance, jint a, jint b) {
return subtraction(a, b);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_logByNative(JNIEnv *env, jobject instance, jstring tag_, jstring log_) {
const char *tag = env->GetStringUTFChars(tag_, 0);
const char *log = env->GetStringUTFChars(log_, 0);
__android_log_write(ANDROID_LOG_DEBUG, tag, log);
env->ReleaseStringUTFChars(tag_, tag);
env->ReleaseStringUTFChars(log_, log);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_multiplication(JNIEnv *env, jobject instance, jint a, jint b) {
return multiplication(a, b);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_division(JNIEnv *env, jobject instance, jint a, jint b) {
return division(a, b);
}
之前的步驟完成之后就很簡單了登渣,我們只需要引入該so
庫對應(yīng)的頭文件噪服,再調(diào)用它提供的方法就可以了。
(4) 在 Java 中調(diào)用本地方法
public class MainActivity extends AppCompatActivity {
private NativeCalculator mNativeCalculator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNativeCalculator = new NativeCalculator();
Log.d("Calculator", "11 + 12 = " + (mNativeCalculator.addition(11,12)));
Log.d("Calculator", "11 - 12 = " + (mNativeCalculator.subtraction(11,12)));
mNativeCalculator.logByNative("Calculator", "Log By Native");
Log.d("Calculator", "11 * 12 = " + (mNativeCalculator.multiplication(11,12)));
Log.d("Calculator", "11 / 12 = " + (mNativeCalculator.division(11,12)));
}
}
運行程序胜茧,最終打印的結(jié)果為:
四粘优、小結(jié)
這一篇文章,我們簡要地總結(jié)了CMakeLists.txt
在幾種場景下應(yīng)該如何編寫呻顽。在學(xué)習(xí)的過程中雹顺,感覺之前學(xué)的C/C++
都忘光了,頭文件廊遍、靜態(tài)庫/動態(tài)庫嬉愧、extern
關(guān)鍵字,都不記得了喉前,打算先好好復(fù)習(xí)一下相關(guān)的知識再繼續(xù)NDK
的學(xué)習(xí)了没酣。