当前位置: 首页 > news >正文

Android Studio C++/JNI/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 computeFactorial(n: Int): Intprivate external fun computeFibonacci(n: Int): Int// 字符串处理private external fun reverseString(input: String): Stringprivate external fun countVowels(input: String): Int// 数组处理private external fun sumIntArray(array: IntArray): Int// 复杂对象处理data class User(val name: String, val age: Int)private external fun processUserData(user: User): Stringoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 计算5的阶乘和斐波那契数列第10项Log.d("Native", "5! = ${computeFactorial(5)}")Log.d("Native", "fib(10) = ${computeFibonacci(10)}")// 测试字符串反转和元音计数功能val testStr = "Hello, JNI!"Log.d("Native", "Reversed: ${reverseString(testStr)}")Log.d("Native", "Vowel count: ${countVowels(testStr)}")// 计算数组元素的和val numbers = intArrayOf(1, 2, 3, 4, 5)Log.d("Native", "Array sum: ${sumIntArray(numbers)}")// 创建User对象并传递给本地方法处理val user = User("张三", 25)Log.d("Native", processUserData(user))}
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1) #指定 CMake 的最低版本要求(这里是 3.4.1,Android NDK 的常见要求)
set(CMAKE_CXX_STANDARD 11)  # 启用C++11支持# 定义库和源文件
add_library( # 设置库名称native_code# 设置库类型为共享库SHARED# 提供源文件的相对路径jni_interface.cppmath_operations.cppstring_processor.cppdata_converter.cpp
)# 查找日志库
find_library( # 设置路径变量名称log-lib# 指定CMake要查找的NDK库名称log )# 指定库应该链接到的目标库
target_link_libraries( # 指定目标库native_code# 将目标库链接到日志库${log-lib} )

jni_interface.cpp

这段代码是用C++实现的JNI(Java Native Interface)接口,它连接了Java/Kotlin代码和本地C++代码。

#include "jni_interface.h"
#include <jni.h> //JNI核心头文件,提供与Java交互的所有必要定义
#include <string> //C++标准字符串库
#include <android/log.h> //Android日志输出功能
// 包含自定义类头文件
#include "math_operations.h"
#include "string_processor.h"
#include "data_converter.h"
//定义了日志宏LOGI,方便输出调试信息
#define LOG_TAG "NativeCode"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)extern "C" {// 数学运算示例
JNIEXPORT jint JNICALL
Java_com_demo_learn1_MainActivity_computeFactorial(JNIEnv* env,jobject /* this */,jint n) {return math_operations::factorial(n);
}JNIEXPORT jint JNICALL
Java_com_demo_learn1_MainActivity_computeFibonacci(JNIEnv* env,jobject /* this */,jint n) {return math_operations::fibonacci(n);
}// 字符串处理示例
JNIEXPORT jstring JNICALL
Java_com_demo_learn1_MainActivity_reverseString(JNIEnv* env,jobject /* this */,jstring input) {std::string cppStr = data_converter::convertJavaStringToCpp(env, input);std::string reversed = string_processor::reverseString(cppStr);return env->NewStringUTF(reversed.c_str());
}JNIEXPORT jint JNICALL
Java_com_demo_learn1_MainActivity_countVowels(JNIEnv* env,jobject /* this */,jstring input) {std::string cppStr = data_converter::convertJavaStringToCpp(env, input);return string_processor::countVowels(cppStr);
}// 数组处理示例
JNIEXPORT jint JNICALL
Java_com_demo_learn1_MainActivity_sumIntArray(JNIEnv* env,jobject /* this */,jintArray array) {std::vector<int> numbers = data_converter::convertJavaArrayToVector(env, array);int sum = 0;for (int num : numbers) {sum += num;}return sum;
}// 复杂对象示例
JNIEXPORT jstring JNICALL
Java_com_demo_learn1_MainActivity_processUserData(JNIEnv* env,jobject /* this */,jobject user) {jclass userClass = env->GetObjectClass(user);// 获取字段IDjfieldID nameField = env->GetFieldID(userClass, "name", "Ljava/lang/String;");jfieldID ageField = env->GetFieldID(userClass, "age", "I");// 获取字段值jstring name = (jstring)env->GetObjectField(user, nameField);jint age = env->GetIntField(user, ageField);// 处理数据std::string cppName = data_converter::convertJavaStringToCpp(env, name);std::string processed = "User: " + cppName + ", Age: " + std::to_string(age);// 清理引用env->DeleteLocalRef(name);env->DeleteLocalRef(userClass);return env->NewStringUTF(processed.c_str());
}} // extern "C"

processUserData方法讲解

  • 参数

    • JNIEnv* env:JNI环境指针,提供所有JNI功能

    • jobject this:Java中调用该native方法的对象实例(本例未使用)

    • jobject user:传入的Java User对象

1.获取Java类信息

jclass userClass = env->GetObjectClass(user);
  • GetObjectClass:获取传入Java对象的类对象

  • 返回的jclass是Java中Class<User>的本地表示

  • 这是后续访问字段和方法的基础

2. 获取字段ID

jfieldID nameField = env->GetFieldID(userClass, "name", "Ljava/lang/String;");
jfieldID ageField = env->GetFieldID(userClass, "age", "I");
  • GetFieldID:获取字段标识符,需要:

    • 类对象(userClass)

    • 字段名("name""age")

    • 字段签名:

      • "Ljava/lang/String;":Java的String类型

      • "I":Java的int类型

  • 字段ID是后续访问字段的"钥匙"

3. 获取字段值

jstring name = (jstring)env->GetObjectField(user, nameField);
jint age = env->GetIntField(user, ageField);
  • GetObjectField:获取对象类型字段的值(如String)

    • 需要对象实例和字段ID

    • 返回jobject,需要转型为具体类型(如jstring

  • GetIntField:获取基本类型int字段的值

    • 直接返回对应的基本类型(jint实际上是int的别名)

4. 数据处理

std::string cppName = data_converter::convertJavaStringToCpp(env, name);
std::string processed = "User: " + cppName + ", Age: " + std::to_string(age);
  • 将Java字符串转换为C++字符串(使用辅助工具类)

  • 构造新的字符串信息,组合name和age

  • std::to_string:将数字转换为字符串

5. 资源清理

env->DeleteLocalRef(name);
env->DeleteLocalRef(userClass);
  • DeleteLocalRef:删除本地引用,防止内存泄漏

  • JNI中的本地引用在函数返回后不会自动释放

  • 虽然现代JVM通常能处理这些引用,但显式释放是良好实践

6. 返回结果

return env->NewStringUTF(processed.c_str());
  • NewStringUTF:将C字符串(UTF-8)转换为Java字符串(jstring)

  • 这个返回的jstring会被自动管理,不需要手动释放

1. 基本类型映射

JNI定义了与Java基本类型对应的C/C++类型:

Java类型JNI类型C/C++类型大小
booleanjbooleanunsigned char8位
bytejbytesigned char8位
charjcharunsigned short16位
shortjshortshort16位
intjintint32位
longjlonglong long64位
floatjfloatfloat32位
doublejdoubledouble64位

2. 理解类型转换

字符串转换

Java字符串(jstring)与C/C++字符串的转换是常见操作:

// Java String → C++ std::string
std::string jstringToStdString(JNIEnv* env, jstring jStr) {if (!jStr) return "";const char* cStr = env->GetStringUTFChars(jStr, nullptr);std::string cppStr(cStr);env->ReleaseStringUTFChars(jStr, cStr);return cppStr;
}// C++ std::string → Java String
jstring stdStringToJstring(JNIEnv* env, const std::string& cppStr) {return env->NewStringUTF(cppStr.c_str());
}

数组转换

处理Java数组更复杂一些:

// Java int[] → C++ std::vector<int>
std::vector<int> jintArrayToVector(JNIEnv* env, jintArray array) {std::vector<int> result;jsize length = env->GetArrayLength(array);if (length <= 0) return result;jint* elements = env->GetIntArrayElements(array, nullptr);result.assign(elements, elements + length);env->ReleaseIntArrayElements(array, elements, 0);return result;
}// C++ std::vector<int> → Java int[]
jintArray vectorToJintArray(JNIEnv* env, const std::vector<int>& vec) {jintArray result = env->NewIntArray(vec.size());env->SetIntArrayRegion(result, 0, vec.size(), vec.data());return result;
}

3. 内存管理和引用释放

引用类型

JNI有三种引用类型:

  1. 局部引用(Local Reference)

    • 默认创建的引用

    • 仅在当前native方法有效

    • 方法返回后自动释放,但应及时手动释放

  2. 全局引用(Global Reference)

    • 需要显式创建:env->NewGlobalRef()

    • 跨多个native调用有效

    • 必须显式释放:env->DeleteGlobalRef()

  3. 弱全局引用(Weak Global Reference)

    • 类似全局引用但不阻止GC

    • 创建:env->NewWeakGlobalRef()

    • 释放:env->DeleteWeakGlobalRef()

最佳实践

JNIEXPORT void JNICALL Java_Example_memoryExample(JNIEnv* env, jobject obj) {// 1. 创建局部引用jclass localClass = env->FindClass("java/lang/String");// 2. 提升为全局引用jclass globalClass = (jclass)env->NewGlobalRef(localClass);// 3. 不再需要局部引用env->DeleteLocalRef(localClass);// ...使用globalClass...// 4. 最后释放全局引用env->DeleteGlobalRef(globalClass);
}

4. 高级主题

缓存字段ID和方法ID

// 在全局变量中缓存
struct CachedIDs {jclass exampleClass;jmethodID callbackMethod;jfieldID valueField;
};bool cacheIds(JNIEnv* env) {static CachedIDs cached;cached.exampleClass = (jclass)env->NewGlobalRef(env->FindClass("com/example/Example"));cached.callbackMethod = env->GetMethodID(cached.exampleClass, "callback", "(I)V");cached.valueField = env->GetFieldID(cached.exampleClass, "value", "I");return cached.exampleClass && cached.callbackMethod && cached.valueField;
}JNIEXPORT void JNICALL Java_Example_useCachedIds(JNIEnv* env, jobject obj) {static bool cached = cacheIds(env);if (!cached) return;// 使用缓存的IDsjint value = env->GetIntField(obj, cached.valueField);env->CallVoidMethod(obj, cached.callbackMethod, value + 1);
}

多线程注意事项

// 获取JVM指针以便在其他线程使用
JavaVM* g_jvm = nullptr;JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {g_jvm = vm;return JNI_VERSION_1_6;
}void backgroundThreadFunction() {JNIEnv* env;int status = g_jvm->AttachCurrentThread(&env, nullptr);if (status < 0) {// 处理错误return;}// 在这里可以安全使用JNI// ...g_jvm->DetachCurrentThread();
}

math_operations.h

#ifndef LEARN1_MATH_OPERATIONS_H
#define LEARN1_MATH_OPERATIONS_Hclass math_operations {
public:// 声明为静态成员函数(可通过类名直接调用)static int factorial(int n);static int fibonacci(int n);static bool isPrime(int num);
};#endif //LEARN1_MATH_OPERATIONS_H

1.头文件保护宏

#ifndef LEARN1_MATH_OPERATIONS_H
#define LEARN1_MATH_OPERATIONS_H
// ...
#endif

这是防止头文件被多次包含的标准做法,避免重复定义错误。

2.类定义

class math_operations {
public:// ...
};

定义了一个名为math_operations的类,public:表示后续成员都是公开的。

3.静态成员函数

static int factorial(int n);
static int fibonacci(int n);
static bool isPrime(int num);

这些是静态成员函数的声明,特点是:

  1. static关键字:表示这些函数是静态成员函数

    • 属于类而不是类的实例

    • 可以直接通过类名调用,不需要创建对象实例

    • 不能访问类的非静态成员

  2. 函数声明

    • 只提供了函数原型(返回类型、函数名、参数列表)

    • 没有函数体实现(通常在对应的.cpp文件中实现)

为什么使用静态成员函数

  1. 工具类:当函数逻辑不依赖于对象状态时

  2. 命名空间替代:在C++中可以用来组织相关函数(替代命名空间)

  3. 无需实例化:可以直接调用,使用更方便


math_operations.cpp

#include "math_operations.h"int math_operations::factorial(int n) {if (n <= 1) return 1;return n * factorial(n - 1);
}int math_operations::fibonacci(int n) {if (n <= 1) return n;return fibonacci(n-1) + fibonacci(n-2);
}bool math_operations::isPrime(int num) {if (num <= 1) return false;for (int i = 2; i * i <= num; i++) {if (num % i == 0) return false;}return true;
}
#include "math_operations.h"
  • 作用:包含对应的头文件,确保函数声明与定义一致

  • 语法:使用引号""表示从当前目录或项目目录查找头文件

data_converter.h

#ifndef LEARN1_DATA_CONVERTER_H
#define LEARN1_DATA_CONVERTER_H#include <vector>
#include <string>
#include <jni.h>  // 必须包含JNI头文件class data_converter {
public:static std::vector<int> convertJavaArrayToVector(JNIEnv* env, jintArray array);static std::string convertJavaStringToCpp(JNIEnv* env, jstring jStr);
};#endif //LEARN1_DATA_CONVERTER_H
  • <vector>:提供C++标准向量容器支持

  • <string>:提供C++字符串支持

  • <jni.h>:JNI开发必需的头文件,定义了Java和本地代码交互的所有类型和函数

  • JNIEnv* env:JNI环境指针,提供所有JNI函数访问

data_converter.cpp

#include "data_converter.h"
#include <vector>std::vector<int> data_converter::convertJavaArrayToVector(JNIEnv* env, jintArray array) {std::vector<int> result;jsize length = env->GetArrayLength(array);jint* elements = env->GetIntArrayElements(array, nullptr);result.assign(elements, elements + length);env->ReleaseIntArrayElements(array, elements, 0);return result;
}std::string data_converter::convertJavaStringToCpp(JNIEnv* env, jstring jStr) {const char* cStr = env->GetStringUTFChars(jStr, nullptr);std::string result(cStr);env->ReleaseStringUTFChars(jStr, cStr);return result;
}

1. convertJavaArrayToVector 方法

功能

将 Java 的 int[] 数组转换为 C++ 的 std::vector<int>

实现步骤:

  1. 创建空 vectorstd::vector<int> result

  2. 获取数组长度

    • env->GetArrayLength(array) 获取 Java 数组的长度

    • jsize 是 JNI 中表示大小的类型

  3. 获取数组元素指针

    • env->GetIntArrayElements(array, nullptr) 获取指向 Java 数组内容的指针

    • 第二个参数 nullptr 表示不关心是否复制了数组

  4. 填充 vector

    • result.assign(elements, elements + length) 将 Java 数组内容复制到 vector

  5. 释放资源

    • env->ReleaseIntArrayElements(array, elements, 0) 释放获取的数组指针

    • 参数 0 表示将内容复制回原数组并释放临时内存

  6. 返回结果:包含 Java 数组数据的 vector

关键点:

  • 必须成对调用 GetIntArrayElements 和 ReleaseIntArrayElements

  • assign 方法高效地将 C 风格数组复制到 vector

  • 最后一个参数 0 可以是:

    • 0:复制回原数组并释放临时内存

    • JNI_ABORT:不复制回原数组但释放内存

    • JNI_COMMIT:复制回原数组但不释放内存

2. convertJavaStringToCpp 方法

功能

将 Java 的 String 对象转换为 C++ 的 std::string

实现步骤:

  1. 获取 UTF-8 字符指针

    • env->GetStringUTFChars(jStr, nullptr) 获取指向 Java 字符串 UTF-8 编码的指针

    • 第二个参数 nullptr 表示不关心是否复制了字符串

  2. 创建 std::string

    • std::string result(cStr) 用获取的字符指针构造 C++ 字符串

  3. 释放资源

    • env->ReleaseStringUTFChars(jStr, cStr) 释放获取的字符指针

  4. 返回结果:包含 Java 字符串内容的 std::string

关键点:

  • 必须成对调用 GetStringUTFChars 和 ReleaseStringUTFChars

  • 使用 UTF-8 编码转换,这是 Java 和 C++ 之间最常用的编码方式

  • 如果 Java 字符串包含非 ASCII 字符,这种方式能正确处理

3. 可能的改进

  1. 添加空指针检查

    if (!jStr) return std::string();
  2. 错误处理增强

    const char* cStr = env->GetStringUTFChars(jStr, nullptr);
    if (!cStr) {// 处理错误
    }

string_processor.h

#ifndef LEARN1_STRING_PROCESSOR_H
#define LEARN1_STRING_PROCESSOR_H#include <string>class string_processor {
public:// 1. 字符串反转static std::string reverseString(const std::string& input);// 2. 统计元音字母数量static int countVowels(const std::string& input);// 3. 凯撒加密(字母位移)static std::string encryptString(const std::string& input, int shift);
};#endif //LEARN1_STRING_PROCESSOR_H
  • const std::string& input:要反转的字符串(常量引用,避免拷贝)

const std::string& 讲解

1. 基本组成解析

可以分解为三个部分:

  1. std::string - C++标准库中的字符串类型

  2. & - 表示这是一个引用

  3. const - 表示这是一个常量(不可修改)

2. 为什么要使用这种形式

2.1 避免不必要的拷贝
  • 不使用引用void func(std::string str)
    传递参数时会创建字符串的完整副本(拷贝构造)

  • 使用引用void func(const std::string& str)
    只传递引用(内存地址),不创建副本

2.2 保证原字符串不被修改
  • const限定确保函数内不能修改原字符串

  • 既享受引用的高效,又保证数据安全

2.3 支持临时对象
  • 可以接受临时字符串对象(右值)

  • 例如:func("temporary string")

3. 与替代方案的对比

参数形式拷贝开销可修改原值接受临时对象备注
std::string副本可修改最安全但效率最低
std::string&可以修改需要非常量左值
const std::string&不可修改最佳平衡方案
std::string_view (C++17)不可修改现代替代方案

4. 典型使用场景

4.1 作为输入参数
void printString(const std::string& str) {std::cout << str;  // 只能读取,不能修改
}
4.2 与STL算法配合
bool contains(const std::string& str, const std::string& substr) {return str.find(substr) != std::string::npos;
}
4.3 类成员函数
class TextProcessor {
public:void process(const std::string& input) {// 处理输入但不修改它}
};

5. 注意事项

  1. 生命周期管理
    • 引用必须确保指向的对象在函数使用期间有效

    • 不要返回局部变量的const引用

  2. C++17后的替代方案
    • 考虑使用std::string_view作为只读参数

    • 更轻量级,支持更多类型的字符串数据

  3. 与移动语义的关系
    • 对于需要"夺取"所有权的情况,应使用std::string&&(右值引用)

    • const&会阻止移动语义的应用

  4. NULL问题
    • 不能直接传递NULL/nullptr

    • 如果需要可空引用,应使用指针或std::optional

string_processor.cpp

#include "string_processor.h"
#include <algorithm> // 提供std::reverse
#include <string>  // 提供std::stringstd::string string_processor::reverseString(const std::string& input) {std::string reversed = input;std::reverse(reversed.begin(), reversed.end());return reversed;
}int string_processor::countVowels(const std::string& input) {int count = 0;const std::string vowels = "aeiouAEIOU";for (char c : input) {if (vowels.find(c) != std::string::npos) {count++;}}return count;
}std::string string_processor::encryptString(const std::string& input, int shift) {std::string result;for (char c : input) {if (isalpha(c)) {char base = isupper(c) ? 'A' : 'a';c = ((c - base + shift) % 26) + base;}result += c;}return result;
}

结果

http://www.lryc.cn/news/588183.html

相关文章:

  • 清除 Android 手机 SIM 卡数据的4 种简单方法
  • 如何将数据从一部手机传输到另一部手机?
  • SSH 登录失败,封禁IP脚本
  • Oracle 学习笔记
  • 【橘子分布式】Thrift RPC(理论篇)
  • LINUX714 自动挂载/nfs;物理卷
  • 基于STM32的智能抽水灌溉系统设计(蓝牙版)
  • 前端开发中的常见问题及解决方案
  • 数据结构——优先队列(priority_queue)的巧妙运用
  • 渗透第一次总结
  • 【Python办公】Python如何批量提取PDF中的表格
  • 前端基础之《Vue(22)—安装MongoDB》
  • 【Java EE初阶 --- 网络原理】初识网络
  • 第十七节:第五部分:网络通信:TCP通信-支持与多个客户端同时通信
  • 如何使用Cisco DevNet提供的免费ACI学习实验室(Learning Labs)?(Grok3 回答)
  • 笔试——Day6
  • CISSP知识点汇总- 通信与网络安全
  • 内部文件审计:企业文件服务器审计对网络安全提升有哪些帮助?
  • 密码学中立方攻击的另类应用
  • 安全初级(一)
  • 多租户云环境下的隔离性保障:虚拟化、容器、安全组如何协同防护?
  • git 访问 github
  • 【深度学习框架终极PK】TensorFlow/PyTorch/MindSpore深度解析!选对框架效率翻倍
  • 智能Agent场景实战指南 Day 12:医疗咨询Agent设计模式
  • vue3+arcgisAPI4示例:自定义多个气泡窗口展示(附源码下载)
  • C#中发布订阅的阻塞非阻塞
  • Spring Boot + Vue2 实现腾讯云 COS 文件上传:从零搭建分片上传系统
  • QT——信号与槽
  • Zabbix在MySQL性能监控方面的运用
  • 闲庭信步使用图像验证平台加速FPGA的开发:第十五课——基于sobel算子边缘检测的FPGA实现