返回正文
简体中文
Android Native 集成
Android 主流开发语言是 Kotlin,但很多场景你绕不开 C/C++:
- 图像处理、音视频编解码。
- 加密算法、安全模块。
- 复用已有的 C++ 库(如 OpenCV、FFmpeg、游戏引擎)。
- 对性能极度敏感的计算。
这篇会讲 Android Native 开发的核心:JNI、NDK、CMake,以及 Kotlin 和 C++ 怎么协作。
什么时候该用 Native?
不是性能瓶颈就一定要上 Native。Native 代码会带来:
- 包体积增大。
- 构建复杂度增加。
- 崩溃更难定位(栈信息、符号表)。
- 平台差异(ARM64 / x86_64 / ABI)。
适合用 Native 的场景:
- 已有 C/C++ 资产需要复用。
- 计算密集且 Kotlin 无法优化到目标性能。
- 需要绕过 JVM 的内存管理。
- 安全敏感逻辑需要隐藏实现。
不适合的场景:
- 普通 UI 逻辑。
- 简单的网络请求。
- 能用 Kotlin 协程 + 算法优化解决的问题。
JNI 基础:Kotlin 调用 C++
声明 Native 方法
java
class NativeLib {
external fun stringFromJNI(): String
external fun add(a: Int, b: Int): Int
companion object {
init {
System.loadLibrary("native-lib")
}
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
生成头文件
用 javac -h 生成 JNI 头文件:
bash
javac -h jni src/main/java/com/example/NativeLib.kt1
C++ 实现
cpp
// native-lib.cpp
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_NativeLib_stringFromJNI(JNIEnv* env, jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jint JNICALL
Java_com_example_NativeLib_add(JNIEnv* env, jobject /* this */, jint a, jint b) {
return a + b;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
方法签名规则
JNI 方法名格式:
txt
Java_包名_类名_方法名1
包名里的点换成下划线,下划线换成 _1。
例如 com.example.NativeLib.add → Java_com_example_NativeLib_add。
NDK 与 CMake 构建
build.gradle 配置
txt
android {
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
defaultConfig {
externalNativeBuild {
cmake {
cppFlags += "-std=c++17"
abiFilters += listOf("arm64-v8a", "x86_64")
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CMakeLists.txt
txt
cmake_minimum_required(VERSION 3.22.1)
project("native-lib")
add_library(
native-lib
SHARED
native-lib.cpp
)
find_library(
log-lib
log
)
target_link_libraries(
native-lib
${log-lib}
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ABI 选择
| ABI | 设备类型 |
|---|---|
armeabi-v7a | 32 位 ARM,老设备 |
arm64-v8a | 64 位 ARM,主流 |
x86 / x86_64 | 模拟器、少数设备 |
建议:只保留
arm64-v8a和x86_64,包体积更小。如果必须支持 32 位,再加armeabi-v7a。
JNI 引用类型与内存管理
局部引用 vs 全局引用
cpp
jstring local = env->NewStringUTF("local");
// 局部引用在函数返回后自动释放
// 如果需要长期保存,转成全局引用
jstring global = (jstring) env->NewGlobalRef(local);
// 用完必须手动释放
env->DeleteGlobalRef(global);1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
不要阻塞 JNI 调用
cpp
// 错误:长时间运行会阻塞 JVM 线程
JNIEXPORT void JNICALL
Java_com_example_NativeLib_heavyWork(JNIEnv* env, jobject thiz) {
for (int i = 0; i < 1e9; i++) { ... }
}1
2
3
4
5
2
3
4
5
如果 Native 任务耗时,应该在 C++ 层自己开线程,或者用 Kotlin 协程 + Dispatchers.Default 调用。
数组传递
cpp
JNIEXPORT void JNICALL
Java_com_example_NativeLib_processArray(JNIEnv* env, jobject thiz, jintArray arr) {
jint* elements = env->GetIntArrayElements(arr, nullptr);
jsize len = env->GetArrayLength(arr);
for (int i = 0; i < len; i++) {
elements[i] *= 2;
}
env->ReleaseIntArrayElements(arr, elements, 0);
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
注意 Get 和 Release 必须配对,否则内存泄漏。
Kotlin 与 C++ 协作模式
数据模型下沉
不要频繁跨 JNI 边界传复杂对象。正确做法是:
- Kotlin 把输入序列化成 ByteArray 或 FloatArray。
- 一次性传给 Native。
- Native 处理完,返回数组或句柄。
java
val input = FloatArray(1000)
val output = nativeLib.processAudio(input)1
2
2
对象生命周期管理
如果 Native 层持有资源,建议封装成 Closeable:
java
class NativeProcessor : AutoCloseable {
private var handle: Long = nativeCreate()
fun process(data: ByteArray): ByteArray = nativeProcess(handle, data)
override fun close() {
nativeDestroy(handle)
handle = 0
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
配合 use { } 自动释放。
Native 调试与崩溃定位
符号表
发布 Native 库时保留符号表:
bash
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g")1
或者用 ndk-stack 还原崩溃栈:
bash
adb logcat | ndk-stack -sym obj/arm64-v8a/1
AddressSanitizer
bash
./gradlew app:assembleDebug -Pandroid.enableAsan=true1
AddressSanitizer 能检测 Native 层的内存越界、Use-after-free,但会让 App 变慢。
性能与包体积权衡
| 策略 | 效果 | 代价 |
|---|---|---|
| 只保留 arm64-v8a | 包体积 -30% | 老设备不兼容 |
| 压缩 so 库 | 包体积 -10~20% | 运行时解压,启动稍慢 |
| 按需下载 so | 包体积极小 | 首次使用需下载 |
| 复用静态库 | 构建简化 | 体积可能增大 |
小结
Native 开发是 Android 专家的能力边界之一:
- 只在必要场景引入 C/C++ 。
- JNI 方法签名、局部/全局引用、数组释放要配对。
- 用 CMake 管理构建,合理选择 ABI。
- 跨 JNI 边界传简单数据,避免频繁对象交互。
- 保留符号表,用
ndk-stack/ASan定位 Native 崩溃。
V 0.13.16 |
基于 MIT Licensed版权所有 © 2009- 2026 CMONO.NET
本站访客数
--次 本站总访问量
--人次 