1. 传统 NDK 开发流程概述
下载并配置 NDK
- 为 NDK 配置环境变量
- 使用 ndk-build 命令编译 so 库
创建一个 Android 项目,并声明所需的 native 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package com.szy.JniTestApp;
import ...
public class MainActivity extends ActionBarActivity {
static {
System.loadLibrary("jni-test");
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.msg);
textView.setText(get());
set("Hello World From JniTestApp");
}
public native String get();
public native void set(String str);
}实现 Android 项目中所声明的 native 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// test.cpp
extern "C" {
jstring Java_com_szy_JniTestApp_MainActivity_get(JNIEnv *env, jobject thiz) {
printf("invoke get in C++\n");
return env -> NewStringUTF("Hello from JNI in libjni-test.so");
}
void Java_com_szy_JniTestApp_MainActivity_set(JNIEnv *env, jobject thiz, jstring string) {
printf("invoke set from C++\n");
char* str = (char*) env -> GetStringUTFChars(string, NULL);
printf("%s\n", str);
env -> ReleaseStringUTFChars(string, str);
}
}1
2
3
4
5
6
7
8
9
10
11
12// Android.mk
# Copyright (C) 2009 The Android Open Source Project
# blablabla
LOCAL_PATH := $(call my-dir)
includes $(CLEAR_VARS)
LOCAL_MODULE := jni-test
LOCAL_SRC_FILES := test.cpp
include $(BUILD_SHARED_LIBRARY)1
2// Application.mk
APP_ABI := armeabi // armeabi 是在移动设备中占据主要地位的 CPU 架构平台类型切换到 jni 目录的父目录,然后通过 ndk-build 命令编译产生 so 库
需要注意的是,ndk-build 命令会默认指定 jni 目录为本地源码的目录,如果源码存放的目录名不是 jni,那么 ndk-build 则无法成功编译
在
app/src/main
中创建一个名为 jniLibs 的目录,将生成的 so 库复制到 jniLibs 目录中,然后通过 AndroidStudio 编译运行即可在上面的步骤中,需要将 NDK 编译的 so 库放置到 jniLibs 目录下,这个是 AndroidStudio 所识别的默认目录。如果想使用其他目录,可修改 build.gradle 文件
1
2
3
4
5
6android {
...
sourceSets.main {
jniLibs.srcDir 'src/main/jni_libs'
}
}除了手动使用 ndk-build 命令创建 so 库,还可以通过 AS 来自动编译产生 so 库,需要配置 build.gradle 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14android {
...
defaultConfig {
applicationId "com.szy.JniTestApp"
minSdkVersion 8
targetSdkVersion 22
versionCode 1
versionName "1.0"
ndk {
moduleName "jni-test"
}
}
}接着需要将 JNI 的代码放在
app/src/main/jni
目录下,注意存放 JNI 的代码的目录名必须为 jni。如果不想使用 jni 这个名词,可以如下修改1
2
3
4
5
6android {
...
sourceSets.main {
jni.srcDirs 'src/main/jni_src'
}
}实际开发中只需要打 armeabi 平台的 so 库即可,如下修改 build.gradle 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16android {
...
productFlavors {
arm {
ndk {
abiFilter "armeabi"
}
}
x86 {
ndk {
abiFilter "x86"
}
}
}
}
2. AS 推荐 NDK 开发流程概述
开发环境搭建
需要安装的工具:NDK、Cmake/ndk-build、LLDB
配置特定版本的 Cmake
1
2
3
4
5
6
7
8
9android {
...
externalNativeBuild {
cmake {
...
version "cmake-version"
}
}
}添加 Cmake 到 path 环境变量
配置特定版本的 NDK
1
2
3android {
ndkVersion "major.minor.build" // e.g., ndkVersion '21.3.6528147'
}
创建支持 C/C++ 的项目
- 在向导的 Choose your project 部分中,选择 Native C++ 项目类型
- 在向导的 Customize C++ Support 部分中,选择 Toolchain Default 使用默认的 Cmake 设置
配置 Cmake 脚本文件
- Cmake 是一个纯文本文件,必须命名为:CMakeLists.txt
- 添加
cmake_minimum_required()
和add_library()
命令 - 添加
include_directories()
命令 使编译时 Cmake 找到头文件 - Cmake 库文件命名规范:liblibrary-name.so
- 添加
find_library()
命令,以找到 NDK 库并将其路径存储为一个变量
构建并运行应用(GUI 或命令行)
- Gradle 调用外部构建脚本 CMakeLists.txt
- CMake 按照构建脚本中的命令将 C++ 源代码文件 native-lib.cpp 编译到共享的对象库即 so 中,并将其命名为 libnative-lib.so,Gradle 随后会将后者打包到 APK 中
- 运行时,应用的
MainActivity
会使用System.loadLibrary()
加载原生库,然后应用就可以使用库的原生函数stringFromJNI()
MainActivity.onCreate()
会调用stringFromJNI()
,后者会返回Hello from C++
,并使用它来更新TextView
使用 APK Analyzer 查验 Gradle 打包到 APK 中的 so 库
- Build > Build Bundles/APK(s) > Build APK(s)
- Build > Analyzer APK
- 从 app/build/outputs/apk 目录中选择 APK