创建一个名为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 提供的预编译系统库(如 libloglibzlibandroid 等)。

  • 参数

    • 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 函数、数据类型(如 JNIEnvjstring 等)。

  • 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++ 类型
jbooleanbooleanunsigned char
jintintint32_t
jlonglongint64_t
jfloatfloatfloat
jdoubledoubledouble
jstringStringconst char*
jobjectObjectvoid*
jobjectArrayObject[]jobject[]

5. 内存管理与注意事项

  1. 局部引用 vs 全局引用

    • 局部引用(如 jstring)在函数返回后会自动释放,但大量创建时需手动调用 DeleteLocalRef()

    • 全局引用需显式创建(NewGlobalRef())和释放(DeleteGlobalRef())。

  2. 字符串转换

    • 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 → jintString → jstring)。

六、结果打印

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/913899.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/913899.shtml
英文地址,请注明出处:http://en.pswp.cn/news/913899.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Spring Boot+Vue的DIY手工社预约管理系统(Echarts图形化、腾讯地图API)

2.10 视频课程管理功能实现2.11手工互动&#xff08;视频弹幕&#xff09;2.8预约设置管理功能实现&#x1f388;系统亮点&#xff1a;Echarts图形化、腾讯地图API&#xff1b;文档包含功能结构图、系统架构图、用例图、实体属性图、E-R图。一.系统开发工具与环境搭建1.系统设计…

leetcode 每日一题 1353. 最多可以参加的会议数目

更多技术访问 我的个人网站 &#xff08;免费服务器&#xff0c;没有80/443端口&#xff09; 1353. 最多可以参加的会议数目 给你一个数组 events&#xff0c;其中 events[i] [startDayi, endDayi] &#xff0c;表示会议 i 开始于 startDayi &#xff0c;结束于 endDayi 。 …

AI+智慧园区 | 事件处置自动化——大模型重构园区治理逻辑

在智慧园区的建设浪潮中&#xff0c;事件管理一直是园区高效运营的关键环节。考拉悠然所推出的大模型 智慧园区解决方案&#xff0c;在事件智能闭环管理方面独树一帜&#xff0c;为园区的日常运营编织了一张严密、高效、智能的管理网络&#xff0c;实现了从事件感知到处置的全…

FFmpeg Windows安装

FFmpeg 用于音频文件转换 Builds - CODEX FFMPEG gyan.dev ffmpeg-release-full.7z 下载完成之后 zip解压 大概就是 ffmpeg/ └── bin/ └── ffmpeg.exe 配置环境变量 ffmpeg -version 有可能idea还是找不到命令 就把命令路径写在程序里 例如

【2025/07/10】GitHub 今日热门项目

GitHub 今日热门项目 &#x1f680; 每日精选优质开源项目 | 发现优质开源项目&#xff0c;跟上技术发展趋势 &#x1f4cb; 报告概览 &#x1f4ca; 统计项&#x1f4c8; 数值&#x1f4dd; 说明&#x1f4c5; 报告日期2025-07-10 (周四)GitHub Trending 每日快照&#x1f55…

JVM 基础 - JVM 内存结构

前言 本文主要对JVM 内存结构进行讲解&#xff0c;注意不要和Java内存模型混淆了。 运行时数据区 内存是非常重要的系统资源&#xff0c;是硬盘和 CPU 的中间仓库及桥梁&#xff0c;承载着操作系统和应用程序的实时运行。JVM 内存布局规定了 Java 在运行过程中内存申请、分配…

【案例】二手车交易价格预测-472

二手车交易价格预测 数据来源数据特征探索构建模型参考数据来源 天池 https://tianchi.aliyun.com/competition/entrance/231784/information 数据特征探索 目标特征工程做好之后,能同时进行 lightgbm catboost 神经网络等模型,所以尽量都转换为数值类特征。 如果仅仅是使用…

【Spring】Java SPI机制及Spring Boot使用实例

目录 一、SPI是什么 1.1 SPI 和 API 有什么区别&#xff1f; 二、使用场景 三、使用介绍 四、Spring Boot实例运用 五、总结 一、SPI是什么 SPI全称Service Provider Interface&#xff0c;是Java提供的一套用来被第三方实现或者扩展的API&#xff0c;它可以用来启用框架…

多维度数据资产测绘技术在安全管控平台中的应用实践

一、数据资产治理困境&#xff1a;从 “黑箱” 到 “可见性” 的行业挑战在数字化转型加速的当下&#xff0c;企业数据资产呈现爆发式增长&#xff0c;而传统资产梳理手段因维度单一、时效性差&#xff0c;导致 “资产黑箱” 问题频发。某省级运营商曾在安全评估中发现&#xf…

搭建react18+项目过程中遇到的问题(vite)

问题1. 页面中使用import.meta.env获取环境变量有红色波浪线提示错误按提示给ts.config.ts文件中的compilerOptions增加了"module": “esnext” (es2020 | es2022 | system)这几个也不行 但是另一个问题出现了安装的第三方库引入报错了 按照提示我们将module改成了’…

Linux epoll简介与C++TCP服务器代码示例

Linux epoll 简介与示例 TCP 服务器 1. 为什么要用 epoll select/poll 每次调用都把全部文件描述符从用户态拷贝到内核态,随连接数增长而线性变慢;epoll 采用事件驱动+就绪队列的方式,内核只把“已就绪”的描述符返回给用户态,O(1) 规模扩展;支持 边沿触发 Edge-Triggere…

IPv4和IPv6双栈配置

根据IPv6的学习&#xff0c;完成以下一个简单的双栈配置案例&#xff0c;具体结构如下图所示。PC1的 IPv4&#xff1a;192.168.2.1/24 、IPv6&#xff1a;2001:db8:2::2/64&#xff0c;PC2的 IPv4&#xff1a;192.168.3.1/24 、IPv6&#xff1a;2001:db8:3::2/64总共需要两台PC…

Robyn高性能Web框架系列08:使用 Rust 扩展 Robyn

使用 Rust 扩展 RobynPyO3 Bridge示例&#xff1a;一个简单的Rust扩展1、安装必须的组件2、初始化Rust项目3、编写Rust代码4、在Robyn中使用Rust代码在“Robyn高性能Web框架系列07&#xff1a;多进程、性能调优”一节中&#xff0c;我们讲解了Robyn丰富的性能调优方式&#xff…

利用Pandas进行条件替换与向前填充

目录一、需求二、实现代码案例代码详细解释1. 导入库和创建数据2. 条件替换与填充a. 条件掩码 - mask()b. 向前填充 - ffill()c. 类型转换 - astype(int)3. 打印结果三、实际应用场景四、可能的变体五、总结一、需求 示例数据&#xff1a; 项 目 0 1 0 1 0 1 2 0 2 3 …

springboot数据脱敏(接口级别)

文章目录自定义脱敏注解脱敏注解接口脱敏注解反射AOP实现字段脱敏切面定义脱敏策略脱敏策略的接口电话号码脱敏策略邮箱脱敏不脱敏姓名脱敏身份证号脱敏JacksonAOP实现脱敏定义序列化序列化实现脱敏切面定义JacksonThreadLocal拦截器实现脱敏定义ThreadLocal自定义序列化序列化…

Spring核心原理的快速入门:快速了解IoC与DI

IoC IoC&#xff1a;Inversion of Control(控制反转) Spring是一个包含了众多工具的IoC容器(即bean&#xff1a;spring管理的对象),也就是说Spring 是一个“控制反转”的容器。 之前是对象本身管理自己的生命周期等等&#xff0c;现在交给spring来管理对象的生命周期 IoC介绍 …

ffmpeg 中config 文件一些理解

依赖检查 config中看到最多的是&#xff1a; ... nvenc_deps"ffnvcodec" nvenc_deps_any"libdl LoadLibrary" nvenc_encoder_deps"nvenc" ... h264_crystalhd_decoder_select"crystalhd h264_mp4toannexb_bsf h264_parser" h264_cuvid…

Digital Rainwater Collection System (v1.0)

The law doesn’t punish the masses. If only one guy runs his own rainwater system, he gets fined for “illegal mining.” But if millions of households self-host their “digital wells,” the whole centralized model collapses. Cloud providers and regulators …

NFS文件存储及部署论坛(小白的“升级打怪”成长之路)

目录 一、概述 NFS挂载原理 NFS工作原理 RPC与NFS通讯过程 二、NFS服务安装与启停 NFS服务安装 NFS服务启停 三、NFS服务配置文件 四、NFS文件共享配置文件 配置参数说明 五、命令解析 六、客户端访问 七、客户端挂载 实战案例 部署NFS文件存储及discuz论坛应用 …

JavaScript 对象创建:new 操作符全解析

引言 在 JavaScript 中&#xff0c;new 操作符是实现面向对象编程的​​核心机制​​之一。本文将从原理层面对 new 操作符进行深度剖析&#xff0c;探讨其工作机制、内部实现和实际应用场景。无论您是 JavaScript 初学者还是资深开发者&#xff0c;都能从本文获得以下知识和技…