創建一個名為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
)。