在前一篇文章 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
的学习了。