NDK(入门)

1. 使用目的

  • 进一步提升设备性能,以降低延迟或运行游戏或物理模拟等计算密集型应用
  • 重复使用您自己或其他开发者的 C 或 C++ 库

Android Studio原生库编译默认使用CMake,同时也支持ndk-build

2. 准备工作

  • NDK:允许在Android中使用C/C++
  • CMake:外部构建工具,如果使用ndk-build,可以不需要
  • LLDB:原生代码调试工具,默认AS自带

先进到SDK Manager准备一下对应的工具


3. 添加原生代码

3.1. 创建原生源代码文件

首先,创建项目时选择Native C++类型

选择默认的工具链,这样可以使用默认的CMake配置

新的项目创建完成后,使用Android视图查看一下项目文件结构,会发现出现了一个cpp目录,CMakeLists.txt代表CMake的构建脚本,而另一个.cpp文件是示例代码

简要分析示例

1
2
3
4
5
6
7
8
9
10
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_minos_nativedemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}

上面是示例.cpp的代码,可以看到顶部包含了jni框架的头文件,下面声明的就是stringFromJNI()函数,大致的意思就是将该函数对外暴露,提供给Java调用


再看下CMakeLists.txt中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
nativedemo

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
native-lib.cpp)

就像注释里描述的那样,根据源文件的路径生成库,Gradle自动打包时会一同放入apk中


然后来看看使用的地方,在MainActivity中会使用到里面的方法

1
2
3
4
5
6
companion object {
// Used to load the 'nativedemo' library on application startup.
init {
System.loadLibrary("nativedemo")
}
}

这里是初始化,放在了Activity的初始化块中,这样在应用启动时就可以调用System.loadLibrary()方法根据库的名称将库加载进来

1
2
3
4
5
/**
* A native method that is implemented by the 'nativedemo' native library,
* which is packaged with this application.
*/
external fun stringFromJNI(): String

这里是对于JNI方法的声明,在加载完库之后,就可以从中取它了

1
2
3
4
5
6
7
8
9
10
11
private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

// Example of a call to a native method
binding.sampleText.text = stringFromJNI()
}

这里使用ViewBinding进行布局元素的操作,调用stringFromJNI()返回字符串,然后将内容显示到布局上


整体流程大致是:

  • Gradle调用CMakeLists.txt
  • CMake按照构建脚本中的命令将.cpp编译到共享对象库中,并命名为xxx.so,Gradle之后会将其一同打包到apk中(包分析器可以看到)
  • 运行时,MainActivity调用System.loadLibrary()加载原生库
  • onCreate()中MainActivity调用stringFromJNI()

开始自定义

首先需要在当前模块下有一个专门的cpp目录

大概是这样的层次结构,模块 > src > main > cpp


然后在该目录下放置C++的源代码文件

接下来就是代码逻辑的编写,去实现我们需要的功能


3.2. 配置CMake

CMake的构建脚本文件CMakeLists.txt是一个纯文本文件,并且使用它必须指定文件的名称为CMakeLists.txt


然后需要向其中添加一些需要用到的命令

1
cmake_minimum_required(VERSION 3.10.2)

这个命令是设置了构建的最小版本,也就是下限


1
2
3
4
add_library(
nativedemo
SHARED
native-lib.cpp)

这里给出库的名称、类型、对应的源文件路径

注意

so库的名称总是以lib<CMake中定义的库名称>.so出现,但是使用System.loadLibrary()时还是使用CMake中定义的名称


3.3. 提供CMake脚本文件的路径配置Gradle

实现Gradle关联CMake打包,需要在当前模块的build.gradle中进行一些配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
··android {
...

defaultConfig {

...

externalNativeBuild {
cmake {
cppFlags ''
}
}
}

...

externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.10.2'
}
}
}

可以看到需要externalNativeBuild块进行相关配置


外层的主要用于与Gradle建立连接,而defaultConfog块内层的则进行一些可选的配置项