创建一个名为Learn1项目(Android Studio)。
一、项目结构
二、配置 build.gradle
build.gradle.kts(:app)
plugins {alias(libs.plugins.android.application)alias(libs.plugins.jetbrains.kotlin.android)
}android {namespace = "com.demo.learn1"compileSdk = 35defaultConfig {applicationId = "com.demo.learn1"minSdk = 30targetSdk = 34versionCode = 1versionName = "1.0"testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"vectorDrawables {useSupportLibrary = true}externalNativeBuild {cmake {cppFlags.add("-std=c++17") // 推荐使用C++17标准// 现代Android设备主要支持arm64-v8a,可以精简ABIabiFilters.add("arm64-v8a")}}}externalNativeBuild {cmake {path = file("src/main/cpp/CMakeLists.txt")version = "3.22.1"}}buildTypes {release {isMinifyEnabled = falseproguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"),"proguard-rules.pro")}}compileOptions {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8}kotlinOptions {jvmTarget = "1.8"}buildFeatures {compose = true}composeOptions {kotlinCompilerExtensionVersion = "1.5.1"}packaging {resources {excludes += "/META-INF/{AL2.0,LGPL2.1}"}}sourceSets {getByName("main") {jni {srcDirs("src\\main\\jni", "src\\main\\jni")}}}
}dependencies {implementation(libs.androidx.core.ktx)implementation(libs.androidx.lifecycle.runtime.ktx)implementation(libs.androidx.activity.compose)implementation(platform(libs.androidx.compose.bom))implementation(libs.androidx.ui)implementation(libs.androidx.ui.graphics)implementation(libs.androidx.ui.tooling.preview)implementation(libs.androidx.material3)implementation(libs.androidx.appcompat)implementation(libs.androidx.media3.common.ktx)testImplementation(libs.junit)androidTestImplementation(libs.androidx.junit)androidTestImplementation(libs.androidx.espresso.core)androidTestImplementation(platform(libs.androidx.compose.bom))androidTestImplementation(libs.androidx.ui.test.junit4)debugImplementation(libs.androidx.ui.tooling)debugImplementation(libs.androidx.ui.test.manifest)
}
settings.gradle.kts(Learn1)
pluginManagement {repositories {google {content {includeGroupByRegex("com\\.android.*")includeGroupByRegex("com\\.google.*")includeGroupByRegex("androidx.*")}}mavenCentral()gradlePluginPortal()}
}
dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {google()mavenCentral()}
}rootProject.name = "Learn1"
include(":app")
三、创建 CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)# 定义库和源文件
add_library( # 设置库名称native_code# 设置库类型为共享库SHARED# 提供源文件的相对路径native_code.cpp )# 查找日志库
find_library( # 设置路径变量名称log-lib# 指定CMake要查找的NDK库名称log )# 指定库应该链接到的目标库
target_link_libraries( # 指定目标库native_code# 将目标库链接到日志库${log-lib} )
Android NDK(Native Development Kit)允许你在 Android 应用中使用 C/C++ 代码,通常用于高性能计算、游戏引擎、音视频处理等场景。NDK 项目的核心是通过 CMakeLists.txt
或 Android.mk
(较旧)来配置本地代码的编译。以下是详细的配置示例和解析:
1. 基础 NDK 项目配置示例
假设你有一个 JNI 模块,包含一个 C++ 文件 (native-lib.cpp
),需要编译成动态库 (.so
),并链接 Android NDK 提供的日志库 (liblog
)。
CMakeLists.txt
文件内容
# 指定 CMake 最低版本(NDK 推荐至少 3.4.1)
cmake_minimum_required(VERSION 3.4.1)# 定义项目名称(可选,但建议显式声明)
project(native-lib LANGUAGES CXX)# 设置 C++ 标准(NDK 默认支持 C++14,但显式声明更安全)
set(CMAKE_CXX_STANDARD 14)# 添加动态库
add_library(native-lib # 库名称(最终生成 libnative-lib.so)SHARED # 类型为动态库(Android 必须)native-lib.cpp # 源文件路径
)# 查找 NDK 提供的日志库(liblog.so)
find_library(log-lib # 变量名,保存 liblog 的路径log # 库名称
)# 链接日志库到目标库
target_link_libraries(native-lib # 目标库${log-lib} # 链接 liblog
)# 可选:添加其他 NDK 库(如 OpenMP、zlib 等)
# find_library(zlib-lib z)
# target_link_libraries(native-lib ${zlib-lib})
2. 关键配置解析
(1) add_library
作用:定义需要编译的本地库。
参数:
native-lib
:库名称,最终生成的动态库在 Android 中会被命名为libnative-lib.so
。SHARED
:指定为动态库(Android JNI 必须使用动态库)。native-lib.cpp
:源文件路径(可添加多个文件,如file1.cpp file2.cpp
)。
(2) find_library
作用:查找 Android NDK 提供的预编译系统库(如
liblog
、libz
、libandroid
等)。参数:
log-lib
:自定义变量名,保存找到的库路径。log
:要查找的库名称(实际查找的是liblog.so
)。
(3) target_link_libraries
作用:将目标库与依赖库链接。
参数:
native-lib
:目标库名称。${log-lib}
:引用之前找到的liblog
库路径。
3. 扩展配置
(1) 添加多个源文件
如果项目有多个 C++ 文件:
add_library(native-libSHAREDnative-lib.cpputils.cppdecoder/audio_decoder.cpp # 支持子目录
)
(2) 添加头文件路径
如果头文件在 cpp/include
目录:
# 添加头文件搜索路径
target_include_directories(native-libPRIVATE${CMAKE_SOURCE_DIR}/cpp/include
)
(3) 链接第三方库
假设你通过 NDK 编译了一个静态库 (libfoo.a
):
# 添加静态库路径
add_library(foo STATIC IMPORTED)
set_target_properties(fooPROPERTIES IMPORTED_LOCATION${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libfoo.a
)# 链接到主库
target_link_libraries(native-lib foo)
(4) 分平台配置(ABI 过滤)
针对不同 CPU 架构(armeabi-v7a、arm64-v8a 等):
# 只编译 arm64-v8a 和 x86_64
if(${ANDROID_ABI} STREQUAL "arm64-v8a" OR ${ANDROID_ABI} STREQUAL "x86_64")add_library(native-lib SHARED native-lib.cpp)
endif()
4. 在 build.gradle
中关联 CMake
NDK 配置需要通过 build.gradle
告诉 Android Studio 如何使用 CMake:
android {defaultConfig {externalNativeBuild {cmake {cppFlags "-std=c++14 -frtti -fexceptions" # 可选:自定义编译标志abiFilters "arm64-v8a", "x86_64" # 指定 ABI}}}externalNativeBuild {cmake {path "src/main/cpp/CMakeLists.txt" # CMake 文件路径version "3.22.1" # 指定 CMake 版本}}
}
5. 总结
核心步骤:
add_library
→find_library
→target_link_libraries
。NDK 特性:
必须用SHARED
库、通过find_library
链接系统库(如liblog
)。扩展能力:
支持多文件、头文件路径、ABI 过滤、第三方库链接等。
四、编写 C++ 代码
native-lib.cpp
#include "native_code.h"
#include <jni.h>
#include <string>
#include <android/log.h>#define LOG_TAG "NativeCode"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)// 示例1: 返回字符串
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_learn1_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "你好,来自C++";return env->NewStringUTF(hello.c_str());
}// 示例2: 计算两数之和
extern "C" JNIEXPORT jint JNICALL
Java_com_demo_learn1_MainActivity_addNumbers(JNIEnv* env,jobject /* this */,jint a,jint b) {return a + b;
}// 示例3: 处理字符串数组
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_learn1_MainActivity_processStringArray(JNIEnv* env,jobject /* this */,jobjectArray array) {jsize length = env->GetArrayLength(array);std::string result = "处理结果:\n";for (jsize i = 0; i < length; i++) {jstring str = (jstring)env->GetObjectArrayElement(array, i);const char* cStr = env->GetStringUTFChars(str, nullptr);result += "第" + std::to_string(i) + "项: " + cStr + "\n";env->ReleaseStringUTFChars(str, cStr);env->DeleteLocalRef(str);}return env->NewStringUTF(result.c_str());
}
1. 头文件和宏定义
#include "native_code.h" // 自定义头文件(如果有)
#include <jni.h> // JNI 核心头文件
#include <string> // C++ 字符串库
#include <android/log.h> // Android 日志库#define LOG_TAG "NativeCode"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
jni.h
:JNI 的核心头文件,定义了 JNI 函数、数据类型(如JNIEnv
、jstring
等)。android/log.h
:Android 专用的日志库,用于在 Logcat 中输出调试信息(类似 Java 的Log.i()
)。LOGI
是一个宏,简化日志打印(ANDROID_LOG_INFO
表示日志级别为 Info)。
2. JNI 函数的基本结构
所有 JNI 函数都需要遵循固定的命名规则和参数格式:
extern "C" JNIEXPORT 返回值类型 JNICALL
Java_包名_类名_方法名(JNIEnv* env, // JNI 环境指针jobject thiz, // Java 调用者对象(如果是静态方法则为 jclass)[其他参数...] // Java 方法传入的参数
) {// 函数实现
}
extern "C"
:确保 C++ 编译器按 C 风格生成函数名(避免名称修饰)。JNIEXPORT
和JNICALL
:宏定义,确保函数在动态库中可见且调用约定正确。命名规则:函数名必须为
Java_包名_类名_方法名
,其中包名的.
替换为_
。
3. 示例解析
示例3:处理字符串数组
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_learn1_MainActivity_processStringArray(JNIEnv* env,jobject /* this */,jobjectArray array) {jsize length = env->GetArrayLength(array);std::string result = "处理结果:\n";for (jsize i = 0; i < length; i++) {jstring str = (jstring)env->GetObjectArrayElement(array, i);const char* cStr = env->GetStringUTFChars(str, nullptr);result += "第" + std::to_string(i) + "项: " + cStr + "\n";env->ReleaseStringUTFChars(str, cStr); // 释放资源env->DeleteLocalRef(str); // 删除局部引用}return env->NewStringUTF(result.c_str());//.c_str() 是 C++ 中 std::string 类的一个成员函数。它返回一个指向以 null 结尾的 C 风格字符串(也称为 C 字符串或字符数组)的指针。这个 C 风格字符串是 std::string 对象内部存储的内容的一个常量视图(即你不能通过这个指针修改 std::string 的内容)。
}
功能:接收一个 Java 字符串数组,拼接所有元素后返回结果字符串。
关键点:
jstring
是 JNI 的字符串类型,对应 Java 的String
。jobjectArray
:JNI 的数组类型,对应 Java 的Object[]
(这里是String[]
)。env->GetArrayLength()
:获取数组长度。env->GetObjectArrayElement()
:获取数组中的元素(返回jstring
)。env->GetStringUTFChars()
:将jstring
转换为 C 风格的字符串(const char*
)。env->NewStringUTF()
:将 C 风格的字符串(const char*
)转换为 Java 可识别的jstring
。必须释放资源:
ReleaseStringUTFChars()
:释放由GetStringUTFChars
分配的字符串内存。DeleteLocalRef()
:删除局部引用,避免内存泄漏(JNI 的局部引用有数量限制)。
4. JNI 数据类型对照表
JNI 类型 | Java 类型 | C/C++ 类型 |
---|---|---|
jboolean | boolean | unsigned char |
jint | int | int32_t |
jlong | long | int64_t |
jfloat | float | float |
jdouble | double | double |
jstring | String | const char* |
jobject | Object | void* |
jobjectArray | Object[] | jobject[] |
5. 内存管理与注意事项
局部引用 vs 全局引用:
局部引用(如
jstring
)在函数返回后会自动释放,但大量创建时需手动调用DeleteLocalRef()
。全局引用需显式创建(
NewGlobalRef()
)和释放(DeleteGlobalRef()
)。
字符串转换:
GetStringUTFChars()
和GetStringChars()
返回的字符串必须调用对应的Release
方法。NewStringUTF()
创建的jstring
无需手动释放。
五、编写 Kotlin 代码
MainActivity.kt
package com.demo.learn1import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivityclass MainActivity : ComponentActivity() {// 加载原生库init {System.loadLibrary("native_code")}// 声明原生方法private external fun stringFromJNI(): Stringprivate external fun addNumbers(a: Int, b: Int): Intprivate external fun processStringArray(array: Array<String>): Stringoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 示例1: 调用返回字符串的native方法val helloFromCpp = stringFromJNI()Log.d("MainActivity", "String from C++: $helloFromCpp")// 示例2: 调用计算两个数和的native方法val sum = addNumbers(5, 7)Log.d("MainActivity", "Sum from C++: $sum")// 示例3: 处理字符串数组val strings = arrayOf("Kotlin", "Java", "C++", "JNI")val processedStrings = processStringArray(strings)Log.d("MainActivity", "Processed strings:\n$processedStrings")}
}
1. 加载原生库
init {System.loadLibrary("native_code")
}
作用:在类初始化时加载名为
native_code
的本地动态库(.so
文件)。关键点:
native_code
对应CMakeLists.txt
中定义的库名(add_library(native_code SHARED ...)
)。必须在使用任何
external
(native)方法之前加载,否则会抛出UnsatisfiedLinkError
。
2. 声明原生方法
private external fun stringFromJNI(): String
private external fun addNumbers(a: Int, b: Int): Int
private external fun processStringArray(array: Array<String>): String
external
关键字:表示这些方法在本地代码(C/C++)中实现,而非 Kotlin/Java。方法签名:
stringFromJNI()
→ 对应 C++ 的Java_com_demo_learn1_MainActivity_stringFromJNI
。addNumbers(a: Int, b: Int)
→ 对应 C++ 的Java_com_demo_learn1_MainActivity_addNumbers
。processStringArray(array: Array<String>)
→ 对应 C++ 的Java_com_demo_learn1_MainActivity_processStringArray
。
JNI 规则:
方法名必须严格匹配
Java_包名_类名_方法名
(包名的.
替换为_
)。参数和返回类型要对应 JNI 类型(如
Int
→jint
,String
→jstring
)。