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编程中,内存管理是一个关键问题:

  1. 局部引用 : 每次JNI调用返回后自动释放,但在复杂函数中应使用DeleteLocalRef手动释放
  2. 全局引用 : 必须手动创建和释放,使用NewGlobalRefDeleteGlobalRef
  3. 弱全局引用 : 不会阻止垃圾回收,使用NewWeakGlobalRefDeleteWeakGlobalRef
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;
    }
}

性能优化提示

  1. 最小化JNI调用次数: 每次跨越JNI边界都有开销
  2. 批量处理数据: 一次传递大量数据比多次传递少量数据更高效
  3. 直接缓冲区 : 使用ByteBuffer.allocateDirect()创建直接缓冲区,减少复制
  4. 保持引用: 重复使用的类和方法ID应该缓存起来
  5. 合理释放资源: 及时释放不再需要的引用和本地资源

结论

JNI参数传递和双向调用是Android高级开发中的关键技能。掌握这些技术可以让你充分利用Java和C/C++的各自优势,构建高性能的Android应用。然而,JNI编程也带来了额外的复杂性和潜在的内存管理问题,因此需要谨慎使用并遵循最佳实践。

在实际开发中,可以考虑使用一些现代化的工具如DjinniSWIG来简化JNI开发过程,减少样板代码并提高开发效率。另外,Android NDK还提供了许多有用的库和工具,帮助开发者更轻松地进行本地开发。

参考资源


希望本文对你理解和应用JNI参数传递与双向调用有所帮助。在下一篇文章中,我们将探讨如何在JNI中处理异常和线程安全问题。

相关推荐
CYRUS_STUDIO19 小时前
Android 反调试攻防实战:多重检测手段解析与内核级绕过方案
android·操作系统·逆向
isysc120 小时前
面了一个校招生,竟然说我是老古董
java·后端·面试
黄林晴1 天前
如何判断手机是否是纯血鸿蒙系统
android
火柴就是我1 天前
flutter 之真手势冲突处理
android·flutter
法的空间1 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
循环不息优化不止1 天前
深入解析安卓 Handle 机制
android
恋猫de小郭1 天前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
道可到1 天前
Java 反射现代实践速查表(JDK 11+/17+)
java
jctech1 天前
这才是2025年的插件化!ComboLite 2.0:为Compose开发者带来极致“爽”感
android·开源
用户2018792831671 天前
为何Handler的postDelayed不适合精准定时任务?
android