文章目录
- [Android高级开发第二篇 - JNI 参数传递与 Java → C → Java 双向调用](#Android高级开发第二篇 - JNI 参数传递与 Java → C → Java 双向调用)
Android高级开发第二篇 - JNI 参数传递与 Java → C → Java 双向调用
引言
在Android开发中,JNI (Java Native Interface) 是连接Java代码和本地C/C++代码的桥梁。通过JNI,我们可以利用C/C++的高性能特性来处理计算密集型任务,同时保持Java的跨平台优势。本文将深入探讨JNI参数传递机制以及Java和C之间的双向调用实现。
JNI基础回顾
在深入参数传递之前,让我们先回顾JNI的基本概念:
- JNI: Java本地接口,允许Java代码调用C/C++等本地语言编写的函数
- JNIEnv: 提供大多数JNI函数的接口指针
- jobject: 表示Java对象的引用
- jclass: 表示Java类的引用
JNI中的参数传递
基本数据类型传递
JNI提供了一系列与Java基本数据类型对应的C数据类型:
Java类型 | JNI类型 | C/C++类型 |
---|---|---|
boolean | jboolean | unsigned char |
byte | jbyte | signed char |
char | jchar | unsigned short |
short | jshort | short |
int | jint | int |
long | jlong | long long |
float | jfloat | float |
double | jdouble | double |
示例代码:
java
// Java代码
public native int calculateSum(int a, int b);
c
// C代码
JNIEXPORT jint JNICALL
Java_com_example_MyClass_calculateSum(JNIEnv *env, jobject thiz, jint a, jint b) {
return a + b;
}
字符串传递
字符串是最常见的复杂参数类型之一。在JNI中,我们需要在Java的String和C的字符数组之间进行转换:
java
// Java代码
public native String reverseString(String input);
c
// C代码
JNIEXPORT jstring JNICALL
Java_com_example_MyClass_reverseString(JNIEnv *env, jobject thiz, jstring input) {
// 将Java字符串转换为C字符串
const char* str = (*env)->GetStringUTFChars(env, input, NULL);
// 处理字符串(例如反转)
int len = strlen(str);
char* reversed = malloc(len + 1);
for (int i = 0; i < len; i++) {
reversed[i] = str[len - i - 1];
}
reversed[len] = '\0';
// 释放资源
(*env)->ReleaseStringUTFChars(env, input, str);
// 将C字符串转换回Java字符串
jstring result = (*env)->NewStringUTF(env, reversed);
free(reversed);
return result;
}
数组传递
JNI提供了访问和修改Java数组的方法:
java
// Java代码
public native void processIntArray(int[] array);
c
// C代码
JNIEXPORT void JNICALL
Java_com_example_MyClass_processIntArray(JNIEnv *env, jobject thiz, jintArray array) {
// 获取数组长度
jsize length = (*env)->GetArrayLength(env, array);
// 获取数组元素
jint* elements = (*env)->GetIntArrayElements(env, array, NULL);
// 处理数组
for (int i = 0; i < length; i++) {
elements[i] *= 2; // 每个元素乘以2
}
// 更新Java数组并释放资源
(*env)->ReleaseIntArrayElements(env, array, elements, 0);
}
对象传递
在JNI中传递Java对象需要使用反射机制:
java
// Java类
public class Person {
private String name;
private int age;
// getter和setter方法
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
// Java接口
public native void updatePerson(Person person);
c
// C代码
JNIEXPORT void JNICALL
Java_com_example_MyClass_updatePerson(JNIEnv *env, jobject thiz, jobject person) {
// 获取Person类
jclass personClass = (*env)->GetObjectClass(env, person);
// 获取setName方法ID
jmethodID setNameMethod = (*env)->GetMethodID(env, personClass, "setName", "(Ljava/lang/String;)V");
// 调用setName方法
jstring newName = (*env)->NewStringUTF(env, "Updated from JNI");
(*env)->CallVoidMethod(env, person, setNameMethod, newName);
// 获取setAge方法ID
jmethodID setAgeMethod = (*env)->GetMethodID(env, personClass, "setAge", "(I)V");
// 调用setAge方法
(*env)->CallVoidMethod(env, person, setAgeMethod, 30);
}
Java → C → Java 双向调用
JNI最强大的特性之一是支持双向调用:不仅可以从Java调用C/C++代码,还可以从C/C++回调Java方法。
从C/C++调用Java方法
java
// Java类
public class Callback {
// 这个方法将被C代码调用
public void onProgress(int progress) {
System.out.println("Progress: " + progress + "%");
}
// JNI方法
public native void startLongTask();
}
c
// C代码
JNIEXPORT void JNICALL
Java_com_example_Callback_startLongTask(JNIEnv *env, jobject thiz) {
// 获取Callback类
jclass callbackClass = (*env)->GetObjectClass(env, thiz);
// 获取onProgress方法ID
jmethodID onProgressMethod = (*env)->GetMethodID(env, callbackClass, "onProgress", "(I)V");
// 模拟一个长时间运行的任务
for (int i = 0; i <= 100; i += 10) {
// 执行一些工作...
// 调用Java的回调方法
(*env)->CallVoidMethod(env, thiz, onProgressMethod, i);
// 模拟延迟
usleep(500000); // 500毫秒
}
}
实现一个完整的回调机制
下面是一个更完整的例子,展示了如何实现一个回调接口:
java
// Java回调接口
public interface TaskCallback {
void onStart();
void onProgress(int progress);
void onComplete(String result);
}
// Java类
public class NativeTask {
private TaskCallback callback;
public NativeTask(TaskCallback callback) {
this.callback = callback;
}
// JNI方法
public native void executeTask();
// 静态代码块加载本地库
static {
System.loadLibrary("nativetask");
}
}
c
// C代码
JNIEXPORT void JNICALL
Java_com_example_NativeTask_executeTask(JNIEnv *env, jobject thiz) {
// 获取NativeTask类
jclass taskClass = (*env)->GetObjectClass(env, thiz);
// 获取callback字段ID
jfieldID callbackField = (*env)->GetFieldID(env, taskClass, "callback", "Lcom/example/TaskCallback;");
// 获取callback对象
jobject callback = (*env)->GetObjectField(env, thiz, callbackField);
// 获取TaskCallback接口的类引用
jclass callbackClass = (*env)->FindClass(env, "com/example/TaskCallback");
// 获取接口方法ID
jmethodID onStartMethod = (*env)->GetMethodID(env, callbackClass, "onStart", "()V");
jmethodID onProgressMethod = (*env)->GetMethodID(env, callbackClass, "onProgress", "(I)V");
jmethodID onCompleteMethod = (*env)->GetMethodID(env, callbackClass, "onComplete", "(Ljava/lang/String;)V");
// 调用onStart
(*env)->CallVoidMethod(env, callback, onStartMethod);
// 模拟任务进度
for (int i = 0; i <= 100; i += 10) {
// 执行一些工作...
// 调用onProgress
(*env)->CallVoidMethod(env, callback, onProgressMethod, i);
// 模拟延迟
usleep(200000); // 200毫秒
}
// 调用onComplete
jstring result = (*env)->NewStringUTF(env, "Task completed successfully!");
(*env)->CallVoidMethod(env, callback, onCompleteMethod, result);
// 释放局部引用
(*env)->DeleteLocalRef(env, result);
}
在实际应用中的使用示例:
java
// 使用示例
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TaskCallback callback = new TaskCallback() {
@Override
public void onStart() {
Log.d("NativeTask", "Task started");
}
@Override
public void onProgress(int progress) {
Log.d("NativeTask", "Progress: " + progress + "%");
// 更新UI进度条
}
@Override
public void onComplete(String result) {
Log.d("NativeTask", "Task completed: " + result);
// 显示结果
}
};
NativeTask task = new NativeTask(callback);
new Thread(() -> task.executeTask()).start();
}
}
内存管理与注意事项
在JNI编程中,内存管理是一个关键问题:
- 局部引用 : 每次JNI调用返回后自动释放,但在复杂函数中应使用
DeleteLocalRef
手动释放 - 全局引用 : 必须手动创建和释放,使用
NewGlobalRef
和DeleteGlobalRef
- 弱全局引用 : 不会阻止垃圾回收,使用
NewWeakGlobalRef
和DeleteWeakGlobalRef
c
// 创建全局引用示例
jobject globalCallback;
JNIEXPORT void JNICALL
Java_com_example_NativeTask_initialize(JNIEnv *env, jobject thiz, jobject callback) {
// 创建全局引用
globalCallback = (*env)->NewGlobalRef(env, callback);
}
JNIEXPORT void JNICALL
Java_com_example_NativeTask_cleanup(JNIEnv *env, jobject thiz) {
// 释放全局引用
if (globalCallback != NULL) {
(*env)->DeleteGlobalRef(env, globalCallback);
globalCallback = NULL;
}
}
性能优化提示
- 最小化JNI调用次数: 每次跨越JNI边界都有开销
- 批量处理数据: 一次传递大量数据比多次传递少量数据更高效
- 直接缓冲区 : 使用
ByteBuffer.allocateDirect()
创建直接缓冲区,减少复制 - 保持引用: 重复使用的类和方法ID应该缓存起来
- 合理释放资源: 及时释放不再需要的引用和本地资源
结论
JNI参数传递和双向调用是Android高级开发中的关键技能。掌握这些技术可以让你充分利用Java和C/C++的各自优势,构建高性能的Android应用。然而,JNI编程也带来了额外的复杂性和潜在的内存管理问题,因此需要谨慎使用并遵循最佳实践。
在实际开发中,可以考虑使用一些现代化的工具如Djinni
或SWIG
来简化JNI开发过程,减少样板代码并提高开发效率。另外,Android NDK还提供了许多有用的库和工具,帮助开发者更轻松地进行本地开发。
参考资源
希望本文对你理解和应用JNI参数传递与双向调用有所帮助。在下一篇文章中,我们将探讨如何在JNI中处理异常和线程安全问题。