使用 Android Studio(AS)进行 NDK(Native Development Kit)开发是 Android 应用开发中的一个常见需求,尤其是当需要用 C 或 C++ 编写性能要求较高的代码时。NDK 提供了一种在 Android 设备上运行本地代码的方式,可以直接操作硬件和执行更高效的计算。以下是使用 Android Studio 进行 NDK 开发的完整指南。
1. 准备工作
1.1 安装 Android NDK 和 CMake
首先,确保你已经安装了 Android NDK 和 CMake。你可以通过 Android Studio 安装这些工具:
- 打开 Android Studio。
- 点击
File>Settings>Appearance & Behavior>System Settings>Android SDK。 - 在
SDK Tools标签下,勾选NDK (Side by side)和CMake,然后点击OK安装。
1.2 配置 Gradle 构建文件
确保在项目的 build.gradle 文件中启用了 NDK 支持。通常,Android Studio 会为你自动设置,但你可以手动检查或修改。
kotlin
复制代码
android {
compileSdkVersion 34 // 或者你所使用的 SDK 版本
defaultConfig {
applicationId = "com.example.myapplication"
minSdkVersion = 21
targetSdkVersion = 34
versionCode = 1
versionName = "1.0"
externalNativeBuild {
cmake {
path = "src/main/cpp/CMakeLists.txt" // 指定 CMake 构建文件的路径
}
}
}
externalNativeBuild {
cmake {
version = "3.10.2" // CMake 版本(根据需要调整)
}
}
}
2. 创建 Native C/C++ 代码
2.1 创建 C++ 源文件
- 在你的 Android 项目中,右键点击
app文件夹,选择 New > Directory,然后创建一个cpp目录。 - 在
cpp目录下,创建一个 C++ 源文件,例如native-lib.cpp。
示例 native-lib.cpp:
cpp
复制代码
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
2.2 创建 CMakeLists.txt 文件
在 src/main/cpp 目录下,创建一个 CMakeLists.txt 文件,用于配置 CMake 构建过程。Android Studio 使用 CMake 来管理 C++ 构建。
示例 CMakeLists.txt:
cmake
复制代码
cmake_minimum_required(VERSION 3.10.2)
project("myapplication")
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
# 查找系统的 Android NDK
find_library(log-lib log)
# 将 C++ 源文件加入到构建中
add_library(native-lib SHARED
native-lib.cpp)
# 链接系统库
target_link_libraries(native-lib
${log-lib})
3. 在 Kotlin/Java 中使用 JNI
为了在 Kotlin 或 Java 中调用 C/C++ 编写的本地方法,你需要通过 JNI(Java Native Interface)进行连接。
3.1 声明本地方法
在 MainActivity.kt 中,声明一个与 C++ 中的本地方法相对应的方法,并使用 external 关键字。
kotlin
复制代码
package com.example.myapplication
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
// 声明本地方法
external fun stringFromJNI(): String
// 加载本地库
companion object {
init {
System.loadLibrary("native-lib")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 获取本地方法返回的字符串
val textView: TextView = findViewById(R.id.sample_text)
textView.text = stringFromJNI()
}
}
4. 同步项目并构建
- 点击 Sync Now 按钮同步项目。
- 点击 Build 或 Run 按钮构建并运行项目。你可以在设备上看到由 C++ 代码生成的字符串
"Hello from C++"显示在屏幕上。
5. 调试 Native 代码
5.1 设置调试器
如果你希望调试 C++ 代码,可以在 Android Studio 中设置调试器:
- 在
native-lib.cpp文件中设置断点。 - 使用 Android Studio 的 Debug 模式运行应用程序。
- 调试器会自动连接到设备,并在 C++ 代码的断点处停下。
5.2 配置 NDK 调试
你还需要在 build.gradle 中启用调试支持:
kotlin
复制代码
android {
defaultConfig {
externalNativeBuild {
cmake {
// 配置调试信息
cppFlags "-g"
}
}
}
}
6. 管理和调试多个 ABI 架构
你可以为不同的架构(如 armeabi-v7a、arm64-v8a、x86_64 等)配置 .so 文件。这对于支持多架构的应用非常有用。
配置支持的 ABI 架构:
kotlin
复制代码
android {
defaultConfig {
externalNativeBuild {
cmake {
abiFilters("armeabi-v7a", "arm64-v8a", "x86_64")
}
}
}
}
Gradle 会根据设备架构生成对应的 .so 文件,并在应用运行时加载适合的版本。
7 引入第三方so库
7.1 将 so 库和头文件拷贝到对应的目录
/app/src/main/jniLibs/arm/libxxx.so
7.2 修改 CMakeLists.txt 文件
第三方so库 这里和之前在第二步中介绍的创建一个新的原生库类似, 区别在于最后一个参数,我们通过IMPORTANT标志告知CMake只希 望将库导入到项目中。
- 目标库的路径 这里有几点需要说明: 1、CMAKE_SOURCE_DIR}表示的是CMakeLists.txt所在的路径,我们指定第三方so所在路径时,应当以这个常量为起点来说。 2、我们应当为每种ABI接口提供单独的软件包,那么,我们就可以在jinLibs下建立多个文件夹,每个文件夹对应一种ABI接口类型,之后再通过${ANDROID_ABI}来泛化这一层目录的结构,这样将有助于充分利用特定的CPU架构。 3、三方的库关联到原生库 这里和将NDK库关联到原生库的原理是一样的。 4、为了确保 CMake 可以在编译时定位 我们的 头文件,我们需要将 include_directories() 命令添加到 CMake 构建脚本中并指定头文件的路径
add_library( # 指定目标导入库.
imported-lib
# 设置导入库的类型(静态或动态) 为 shared
library.
SHARED
# 告知 CMake imported-lib 是导入的库
IMPORTED )
set_target_properties( # 指定目标导入库
imported-lib
# 指定属性(本地导入的已有库)
PROPERTIES IMPORTED_LOCATION
# 指定你要导入库的路径.
# ${CMAKE_SOURCE_DIR}
imported-
lib/src/${ANDROID_ABI}/libimported-lib.so )
#为了确保 CMake 可以在编译时定位到我们的 头文件,我们需要使用
include_directories() 命令,并包含 头文件的路
include_directories( imported-lib/include/ )
#要将预构建库关联到我们的原生库,请将其添加到 CMake 构建脚本的
target_link_libraries() 命令中
target_link_libraries( # 指定了三个库,分别是native-lib、
imported-lib和log-lib.
native-lib
imported-lib
# log-lib是包含在 NDK 中的一个日志库
${log-lib} )
在代码中引入第三方库的头文件,调用函数
8. 常见问题和解决方案
8.1 JNI 错误
- 找不到本地方法:确保本地方法在 C++ 代码中实现,并且 JNI 函数签名正确。你可以使用
javap -s工具查看 Java 方法的 JNI 签名。
8.2 CMake 构建失败
- 确保 CMake 配置文件正确,并且相关依赖库已正确链接。
8.3 Android 构建脚本错误
- 检查 Gradle 配置中的
externalNativeBuild和ndk配置,确保 CMake 和 NDK 版本正确。
10 资料文献
首推 Android NDK 官方文档(developer.android.google.cn/ndk/guides?… NDK 开发又觉得新建的 Hello World 项目过于简单时。建议把googlesamples - android-ndk(github.com/googlesampl…) 项目拉下来。里面有多个实例参考,比官方文档完整很多。
- Q1:怎么指定 C++标准?
A:在 build_gradle 中,配置 cppFlags -std
externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions -std=c++14"
arguments '-DANDROID_STL=c++_shared'
}
}
- Q2:add_library 如何编译一个目录中所有源文件? A: 使用 aux_source_directory 方法将路径列表全部放到一个变量中。
# 查找所有源码 并拼接到路径列表
aux_source_directory(${CMAKE_HOME_DIRECTORY}/src/api
SRC_LIST)
aux_source_directory(${CMAKE_HOME_DIRECTORY}/src/core
CORE_SRC_LIST)
list(APPEND SRC_LIST ${CORE_SRC_LIST})
add_library(native-lib SHARED ${SRC_LIST})
- Q3:怎么调试 CMakeLists.txt 中的代码? A:使用 message 方法
cmake_minimum_required(VERSION 3.4.1)
message(STATUS "execute CMakeLists")
然后运行后在
.externalNativeBuild/cmake/debug/{abi}/cmake_build_output.txt 中查 看 log。
- Q4:什么时候 CMakeLists.txt 里面会执行?
测试了下,好像在 sync 的时候会执行。执行一次后会生成 makefile 的文件缓存之类的东西放在 externalNativeBuild 中。所以如果 CMakeLists.txt 中没有修改的话再次同步好像是不会重新执行的。(或者删除.externalNativeBuild 目录)。
真正编译的时候好像只是读取 .externalNativeBuild 目录中已经解析好的makefile 去编译。不会再去执行 CMakeLists.txt
gcc/clang编译器的编译命令
编译命令: gcc/clang -g -O2 -o log ffmpeg_log.c -I -L -l(第一竖线是大写的i,第三个竖线是小写的L) 示例clang -g -O2 -o log ffmpeg_log.c -I …/ffmpeg -L …/ffmpeg/libavutil -lavutil
解析: -g 输出文件中的调试信息 -O2 对输出文件做指令优化(默认是-O1是不对指令进行优化,-O2编译器会按照自己的理解优化指令,让指令运行的更快)-o 输出文件的名字 -o后面跟的.c文件就是要编译的文件的名字 -I 指定头文件的位置 -L 指定库文件的位置 -l 指定引用的库文件名字 示例命令是使用ffmpeg的日志系统。
总结
通过 Android Studio 使用 NDK 开发原生代码,可以显著提高应用的性能,特别是处理需要高效计算的任务时。你只需创建 C++ 代码并通过 JNI 与 Kotlin 或 Java 进行交互,然后使用 CMake 来构建本地库。借助 Android Studio 提供的调试功能,可以轻松地开发、调试和优化本地代码。