在前一篇文章 NDK 知識梳理(1) - 使用 CMake 進行 NDK 開發之初體驗 中,咱們一塊兒學習瞭如何在Android Studio
中使用CMake
來進行NDK
開發,而編寫CMakeLists.txt
構建腳本是其中一個重要的環節,今天咱們就來一塊兒學習CMakeLists.txt
的一些應用,介紹它在下面三種場景的用法:html
Android NDK
的API
so
cmake_minimum_required
用於指定CMake
的最低版本信息,不加入會收到警告。java
cmake_minimum_required(VERSION 3.4.1)
複製代碼
add_library()
用於指示CMake
從原生代碼構建一個原生庫,通俗地說,就是從.cpp
通過編譯獲得.so
文件。正如咱們在 NDK 知識梳理(1) - 使用 CMake 進行 NDK 開發之初體驗 中看到的那樣,咱們經過add_library
讓CMake
根據native-lib.cpp
源文件構建一個名爲native-lib
的共享庫:android
# 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()
括號中的內容,能夠分爲三個部分:api
(1) 指定原生庫的名字bash
add_library
的第一個參數,決定了最終生成的共享庫的名字,例如咱們將共享庫的名字定義爲native-lib
,那麼最終生成的so
文件將在前面加上lib
前綴,也就是libnative-lib.so
,可是咱們在代碼中加載該共享庫的時候,仍然應當使用native-lib
,也就是像下面這樣:架構
static {
System.loadLibrary(「native-lib」);
}
複製代碼
(2) 靜態庫 or 共享庫app
經過第二個參數,咱們能夠指定根據源文件編譯出來的是靜態庫仍是共享庫,分別對應STATIC/SHARED
關鍵字,這裏簡單提一下二者的區別:ide
.a
結尾。靜態庫在程序連接的時候使用,連接器會將程序中使用到函數的代碼從庫文件中拷貝到應用程序中。一旦連接完成,在執行程序的時候就不須要靜態庫了。.so
結尾。在程序的連接時候並不像靜態庫那樣在拷貝使用函數的代碼,而只是做些標記。而後在程序開始啓動運行的時候,動態地加載所需模塊。(3) 指定源文件函數
指定編譯的源文件,這裏是一個和CMakeLists.txt
相關的相對路徑,若是咱們有多個源文件,那麼就在後面添加文件的路徑便可。學習
下面,咱們對 NDK 知識梳理(1) - 使用 CMake 進行 NDK 開發之初體驗 中的計算器的例子進行優化,把加法和減法的操做放在另外一個.cpp
文件中實現,以演示關聯多個.cpp
文件的例子,整個目錄的結構變爲:
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/)
複製代碼
在Android
系統當中,預製了一些標準的NDK
庫,這些庫函數的目的就是讓開發者可以在原生方法中實現以前在Java
層開發的一些功能,咱們能夠經過 NDK 庫 查找所須要的API
。
由於這些庫已經預製在系統當中了,因此若是咱們要調用這些庫中的函數,那麼不須要將其打包到APK
當中,所須要作的就是向CMake
提供但願使用的庫名稱,並將其關聯到本身的原生庫,最後在原生代碼中引入相應的頭文件,調用方法就能夠了。
下面,咱們就介紹一個調用Android Native API
的例子。咱們給Calculator
加上一個新的接口,而在其本地方法中調用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 關聯
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
的某個庫創建關聯關係。該函數的第二個參數爲Android NDK
中對應的庫名稱,而調用該方法以後,它就被和第一個參數所指定的變量關聯在一塊兒。 在這種關聯創建之後,咱們就可使用這個變量在構建腳本的其它部分引用該變量所關聯的NDK
庫。target_link_libraries
:把NDK
庫和咱們本身的原生庫calculator
進行關聯,這樣,咱們就能夠調用該NDK
庫中的函數了。(3) 在 Calculator.cpp 引入頭文件並調用打印 Log 的函數
#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>
複製代碼
NDK
庫中的方法__android_log_write(ANDROID_LOG_DEBUG, tag, log);
複製代碼
(4) 在 Java 中調用,觀察結果
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");
}
}
複製代碼
最終的打印結果爲:
.so
庫最後一部分,咱們舉一個經過第三方.so
庫來實現乘除法的例子,爲了獲得一個.so
庫,咱們經過新建一個工程,而後將它編譯出的.apk
文件解壓,取出其中的.so
文件。
這裏得到第三方so
的原理其實和咱們以前一直談到的實際上是同樣的,咱們只是藉助了Android Studio
來模擬了這個流程。
新建工程的目錄結構爲:
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
節點下,增長構建任務:
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
}
}
複製代碼
這個工程編譯完畢以後,去app/build/outputs/apk/
目錄下將編譯出來的APK
文件解壓,獲得libmultiplication_division.so
庫,咱們將它做爲第三方的so
庫導入到計算器的例子當中。
(1) 將 so 庫和頭文件拷貝到對應的目錄
(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
庫 這裏和以前在第二步中介紹的建立一個新的原生庫相似,區別在於最後一個參數,咱們經過IMPORTANT
標誌告知CMake
只但願將庫導入到項目中。
指定目標庫的路徑 這裏有幾點須要說明:
${CMAKE_SOURCE_DIR}
表示的是CMakeLists.txt
所在的路徑,咱們指定第三方so
所在路徑時,應當以這個常量爲起點。
按理來講,咱們應當爲每種ABI
接口提供單獨的軟件包,那麼,咱們就能夠在jinLibs
下創建多個文件夾,每一個文件夾對應一種ABI
接口類型,以後再經過${ANDROID_ABI}
來泛化這一層目錄的結構,這樣將有助於充分利用特定的CPU
架構。
將第三方的庫關聯到原生庫 這裏和將NDK
庫關聯到原生庫的原理是同樣的。
(3) 聲明新的接口,並在 Calculator.cpp 引入第三方的頭文件,調用函數
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
庫對應的頭文件,再調用它提供的方法就能夠了。
(4) 在 Java 中調用本地方法
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)));
}
}
複製代碼
運行程序,最終打印的結果爲:
這一篇文章,咱們簡要地總結了CMakeLists.txt
在幾種場景下應該如何編寫。在學習的過程當中,感受以前學的C/C++
都忘光了,頭文件、靜態庫/動態庫、extern
關鍵字,都不記得了,打算先好好複習一下相關的知識再繼續NDK
的學習了。