实用指南:Android高级开发第三篇 - JNI异常处理与线程安全编程

Android高级开发第三篇 - JNI异常处理与线程安全编程

引言

在前两篇文章中,我们学习了JNI的基础知识和参数传递机制。然而,真正的生产环境中,我们必须面对两个关键挑战:异常处理和线程安全。这些看似复杂的概念其实是JNI开发中不可或缺的基础技能。本文将从新手的角度,逐步引导你理解和掌握这些重要概念。

为什么要关注异常处理和线程安全?

想象一下这样的场景:

  • 你的C代码访问了一个null指针,导致应用崩溃
  • 多个线程同时调用JNI方法,结果数据出现了不一致
  • Java代码抛出异常,但C代码没有正确处理,导致内存泄漏

这些都是JNI开发中的常见问题。掌握异常处理和线程安全,就是为你的应用程序构建一道安全防线。

第一部分:JNI异常处理基础

什么是JNI异常?

JNI异常可以分为两类:

  1. Java异常传播到C代码:Java方法抛出异常,需要在C代码中检查和处理
  2. C代码中的异常传播到Java:C代码发现错误,需要抛出Java异常

检查和处理Java异常

当你在C代码中调用Java方法时,这些方法可能会抛出异常。让我们看一个简单的例子:

// Java代码
public
class Calculator {
public
int divide(
int a,
int b) {
if (b == 0
) {
throw
new ArithmeticException("Division by zero"
)
;
}
return a / b;
}
public
native
void testDivision(
int a,
int b)
;
}
// C代码 - 错误的处理方式
JNIEXPORT void JNICALL
Java_com_example_Calculator_testDivision(JNIEnv *env, jobject thiz, jint a, jint b) {
// 获取divide方法
jclass cls = (*env)->
GetObjectClass(env, thiz)
;
jmethodID methodID = (*env)->
GetMethodID(env, cls, "divide"
, "(II)I"
)
;
// 调用divide方法 - 这里可能抛出异常!
jint result = (*env)->
CallIntMethod(env, thiz, methodID, a, b)
;
// 如果上面抛出异常,这里的代码可能不会正确执行
printf("Result: %d\n"
, result)
;
}

正确的处理方式

// C代码 - 正确的异常处理
JNIEXPORT void JNICALL
Java_com_example_Calculator_testDivision(JNIEnv *env, jobject thiz, jint a, jint b) {
jclass cls = (*env)->
GetObjectClass(env, thiz)
;
jmethodID methodID = (*env)->
GetMethodID(env, cls, "divide"
, "(II)I"
)
;
// 调用Java方法
jint result = (*env)->
CallIntMethod(env, thiz, methodID, a, b)
;
// 检查是否有异常发生
if ((*env)->
ExceptionCheck(env)
) {
// 获取异常信息(可选)
jthrowable exception = (*env)->
ExceptionOccurred(env)
;
// 打印异常堆栈(调试用)
(*env)->
ExceptionDescribe(env)
;
// 清除异常
(*env)->
ExceptionClear(env)
;
// 处理异常情况
printf("An exception occurred in Java code\n"
)
;
return
;
}
// 只有在没有异常时才执行
printf("Result: %d\n"
, result)
;
}

从C代码抛出Java异常

有时候,你需要在C代码中检测到错误并抛出Java异常:

// C代码 - 抛出Java异常
JNIEXPORT jstring JNICALL
Java_com_example_FileUtils_readFile(JNIEnv *env, jobject thiz, jstring filename) {
// 获取文件名
const
char* file = (*env)->
GetStringUTFChars(env, filename, NULL
)
;
// 尝试打开文件
FILE* fp = fopen(file, "r"
)
;
// 释放文件名字符串
(*env)->
ReleaseStringUTFChars(env, filename, file)
;
if (fp == NULL
) {
// 文件打开失败,抛出Java异常
jclass exceptionClass = (*env)->
FindClass(env, "java/io/FileNotFoundException"
)
;
(*env)->
ThrowNew(env, exceptionClass, "Cannot open file"
)
;
return NULL
;
}
// 读取文件内容...
char buffer[1024]
;
fgets(buffer,
sizeof(buffer)
, fp)
;
fclose(fp)
;
return (*env)->
NewStringUTF(env, buffer)
;
}

异常处理的最佳实践

  1. 总是检查异常:调用Java方法后,使用ExceptionCheck()ExceptionOccurred()
  2. 及时清除异常:使用ExceptionClear()清除异常状态
  3. 资源清理:即使发生异常,也要确保资源得到正确释放
  4. 异常信息:提供有意义的异常信息,帮助调试
// 完整的异常处理示例
JNIEXPORT jbyteArray JNICALL
Java_com_example_DataProcessor_processData(JNIEnv *env, jobject thiz, jbyteArray input) {
jbyte* inputBytes = NULL
;
jbyteArray result = NULL
;
// 获取输入数据
inputBytes = (*env)->
GetByteArrayElements(env, input, NULL
)
;
if (inputBytes == NULL
) {
// 内存分配失败
jclass exceptionClass = (*env)->
FindClass(env, "java/lang/OutOfMemoryError"
)
;
(*env)->
ThrowNew(env, exceptionClass, "Failed to get array elements"
)
;
goto cleanup;
}
jsize length = (*env)->
GetArrayLength(env, input)
;
if (length <= 0
) {
jclass exceptionClass = (*env)->
FindClass(env, "java/lang/IllegalArgumentException"
)
;
(*env)->
ThrowNew(env, exceptionClass, "Input array is empty"
)
;
goto cleanup;
}
// 处理数据...
// 假设我们简单地复制数据
result = (*env)->
NewByteArray(env, length)
;
if (result == NULL
) {
jclass exceptionClass = (*env)->
FindClass(env, "java/lang/OutOfMemoryError"
)
;
(*env)->
ThrowNew(env, exceptionClass, "Failed to create result array"
)
;
goto cleanup;
}
(*env)->
SetByteArrayRegion(env, result, 0
, length, inputBytes)
;
cleanup:
// 清理资源
if (inputBytes != NULL
) {
(*env)->
ReleaseByteArrayElements(env, input, inputBytes, JNI_ABORT)
;
}
return result;
}

第二部分:线程安全基础

什么是线程安全问题?

在多线程环境中,多个线程可能同时访问和修改相同的数据,导致数据不一致或程序崩溃。JNI中的线程安全问题主要包括:

  1. JNIEnv不是线程安全的:每个线程都有自己的JNIEnv指针
  2. 全局引用的并发访问:多个线程访问同一个全局引用
  3. 静态变量的并发修改:C代码中的静态变量被多个线程修改

JNIEnv的线程安全性

错误的做法

// 全局变量 - 这是错误的!
JNIEnv* globalEnv = NULL
;
JNIEXPORT void JNICALL
Java_com_example_BadExample_initEnv(JNIEnv *env, jobject thiz) {
// 错误:保存JNIEnv到全局变量
globalEnv = env;
}
void someFunction(
) {
// 错误:在其他线程中使用全局的JNIEnv
jclass cls = (*globalEnv)->
FindClass(globalEnv, "java/lang/String"
)
;
// 这可能导致崩溃!
}

正确的做法

// 全局JavaVM指针是线程安全的
JavaVM* g_jvm = NULL
;
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm,
void* reserved) {
g_jvm = vm;
return JNI_VERSION_1_6;
}
// 在其他线程中获取JNIEnv
void someFunction(
) {
JNIEnv* env;
int result = (*g_jvm)->
GetEnv(g_jvm, (
void**
)&env, JNI_VERSION_1_6)
;
if (result == JNI_EDETACHED) {
// 当前线程没有附加到JVM,需要附加
result = (*g_jvm)->
AttachCurrentThread(g_jvm, &env, NULL
)
;
if (result != JNI_OK) {
// 处理错误
return
;
}
// 使用env...
jclass cls = (*env)->
FindClass(env, "java/lang/String"
)
;
// 分离线程
(*g_jvm)->
DetachCurrentThread(g_jvm)
;
}
else
if (result == JNI_OK) {
// 线程已经附加,直接使用
jclass cls = (*env)->
FindClass(env, "java/lang/String"
)
;
}
}

使用互斥锁保护共享资源

当多个线程需要访问共享数据时,我们需要使用同步机制:

#
include <pthread.h>
  // 共享数据
  static
  int sharedCounter = 0
  ;
  static pthread_mutex_t counterMutex = PTHREAD_MUTEX_INITIALIZER;
  JNIEXPORT jint JNICALL
  Java_com_example_ThreadSafe_incrementCounter(JNIEnv *env, jobject thiz) {
  int result;
  // 获取锁
  pthread_mutex_lock(&counterMutex)
  ;
  // 修改共享数据
  sharedCounter++
  ;
  result = sharedCounter;
  // 释放锁
  pthread_mutex_unlock(&counterMutex)
  ;
  return result;
  }
  JNIEXPORT jint JNICALL
  Java_com_example_ThreadSafe_getCounter(JNIEnv *env, jobject thiz) {
  int result;
  pthread_mutex_lock(&counterMutex)
  ;
  result = sharedCounter;
  pthread_mutex_unlock(&counterMutex)
  ;
  return result;
  }

线程安全的全局引用管理

#
include <pthread.h>
  // 线程安全的全局引用管理
  static jobject g_callback = NULL
  ;
  static pthread_mutex_t g_callback_mutex = PTHREAD_MUTEX_INITIALIZER;
  JNIEXPORT void JNICALL
  Java_com_example_ThreadSafe_setCallback(JNIEnv *env, jobject thiz, jobject callback) {
  pthread_mutex_lock(&g_callback_mutex)
  ;
  // 删除旧的全局引用
  if (g_callback != NULL
  ) {
  (*env)->
  DeleteGlobalRef(env, g_callback)
  ;
  }
  // 创建新的全局引用
  if (callback != NULL
  ) {
  g_callback = (*env)->
  NewGlobalRef(env, callback)
  ;
  }
  else {
  g_callback = NULL
  ;
  }
  pthread_mutex_unlock(&g_callback_mutex)
  ;
  }
  void callbackFromNativeThread(
  ) {
  JNIEnv* env;
  jobject callback;
  // 获取当前线程的JNIEnv
  if ((*g_jvm)->
  GetEnv(g_jvm, (
  void**
  )&env, JNI_VERSION_1_6) != JNI_OK) {
  return
  ;
  }
  // 安全地获取回调对象
  pthread_mutex_lock(&g_callback_mutex)
  ;
  callback = g_callback;
  if (callback != NULL
  ) {
  // 创建局部引用以防止回调对象在使用过程中被删除
  callback = (*env)->
  NewLocalRef(env, callback)
  ;
  }
  pthread_mutex_unlock(&g_callback_mutex)
  ;
  if (callback != NULL
  ) {
  // 调用回调方法
  jclass cls = (*env)->
  GetObjectClass(env, callback)
  ;
  jmethodID method = (*env)->
  GetMethodID(env, cls, "onCallback"
  , "()V"
  )
  ;
  (*env)->
  CallVoidMethod(env, callback, method)
  ;
  // 删除局部引用
  (*env)->
  DeleteLocalRef(env, callback)
  ;
  }
  }

第三部分:实际应用示例

让我们创建一个完整的示例,展示如何在实际项目中应用异常处理和线程安全:

// Java代码
public
class SecureFileProcessor {
public
interface ProgressCallback {
void onProgress(
int percentage)
;
void onError(String error)
;
void onComplete(String result)
;
}
static {
System.loadLibrary("securefileprocessor"
)
;
}
public
native
void processFileAsync(String filename, ProgressCallback callback)
;
public
native
void cancelProcessing(
)
;
}
// C代码
#
include <pthread.h>
  #
  include <stdio.h>
    #
    include <stdlib.h>
      #
      include <unistd.h>
        // 全局变量
        static JavaVM* g_jvm = NULL
        ;
        static pthread_t g_processing_thread;
        static
        volatile
        int g_should_cancel = 0
        ;
        static pthread_mutex_t g_cancel_mutex = PTHREAD_MUTEX_INITIALIZER;
        // 线程参数结构
        typedef
        struct {
        char* filename;
        jobject callback;
        } ProcessingParams;
        // 线程安全的取消检查
        int shouldCancel(
        ) {
        int result;
        pthread_mutex_lock(&g_cancel_mutex)
        ;
        result = g_should_cancel;
        pthread_mutex_unlock(&g_cancel_mutex)
        ;
        return result;
        }
        // 调用Java回调方法
        void callJavaCallback(JNIEnv* env, jobject callback,
        const
        char* methodName,
        const
        char* signature, ...
        ) {
        if (callback == NULL
        )
        return
        ;
        jclass cls = (*env)->
        GetObjectClass(env, callback)
        ;
        jmethodID method = (*env)->
        GetMethodID(env, cls, methodName, signature)
        ;
        if (method == NULL
        ) {
        // 方法不存在,抛出异常
        jclass exceptionClass = (*env)->
        FindClass(env, "java/lang/NoSuchMethodError"
        )
        ;
        (*env)->
        ThrowNew(env, exceptionClass, "Callback method not found"
        )
        ;
        return
        ;
        }
        va_list args;
        va_start(args, signature)
        ;
        if (strcmp(signature, "(I)V"
        ) == 0
        ) {
        int value = va_arg(args,
        int
        )
        ;
        (*env)->
        CallVoidMethod(env, callback, method, value)
        ;
        }
        else
        if (strcmp(signature, "(Ljava/lang/String;)V"
        ) == 0
        ) {
        const
        char* str = va_arg(args,
        const
        char*
        )
        ;
        jstring jstr = (*env)->
        NewStringUTF(env, str)
        ;
        (*env)->
        CallVoidMethod(env, callback, method, jstr)
        ;
        (*env)->
        DeleteLocalRef(env, jstr)
        ;
        }
        va_end(args)
        ;
        // 检查回调是否抛出异常
        if ((*env)->
        ExceptionCheck(env)
        ) {
        (*env)->
        ExceptionDescribe(env)
        ;
        (*env)->
        ExceptionClear(env)
        ;
        }
        }
        // 处理线程函数
        void* processingThread(
        void* params) {
        ProcessingParams* p = (ProcessingParams*
        )params;
        JNIEnv* env;
        // 附加到JVM
        int result = (*g_jvm)->
        AttachCurrentThread(g_jvm, &env, NULL
        )
        ;
        if (result != JNI_OK) {
        free(p->filename)
        ;
        (*g_jvm)->
        DeleteGlobalRef(g_jvm, p->callback)
        ;
        free(p)
        ;
        return NULL
        ;
        }
        // 检查文件是否存在
        FILE* file = fopen(p->filename, "r"
        )
        ;
        if (file == NULL
        ) {
        callJavaCallback(env, p->callback, "onError"
        , "(Ljava/lang/String;)V"
        , "File not found"
        )
        ;
        goto cleanup;
        }
        // 模拟文件处理
        for (
        int i = 0
        ; i <= 100
        ; i += 10
        ) {
        if (shouldCancel(
        )
        ) {
        callJavaCallback(env, p->callback, "onError"
        , "(Ljava/lang/String;)V"
        , "Processing cancelled"
        )
        ;
        goto cleanup;
        }
        // 报告进度
        callJavaCallback(env, p->callback, "onProgress"
        , "(I)V"
        , i)
        ;
        // 模拟工作
        usleep(100000
        )
        ;
        // 100ms
        }
        // 处理完成
        callJavaCallback(env, p->callback, "onComplete"
        , "(Ljava/lang/String;)V"
        , "File processed successfully"
        )
        ;
        cleanup:
        if (file) fclose(file)
        ;
        free(p->filename)
        ;
        (*env)->
        DeleteGlobalRef(env, p->callback)
        ;
        free(p)
        ;
        // 分离线程
        (*g_jvm)->
        DetachCurrentThread(g_jvm)
        ;
        return NULL
        ;
        }
        JNIEXPORT jint JNICALL
        JNI_OnLoad(JavaVM* vm,
        void* reserved) {
        g_jvm = vm;
        return JNI_VERSION_1_6;
        }
        JNIEXPORT void JNICALL
        Java_com_example_SecureFileProcessor_processFileAsync(JNIEnv *env, jobject thiz, jstring filename, jobject callback) {
        // 参数验证
        if (filename == NULL || callback == NULL
        ) {
        jclass exceptionClass = (*env)->
        FindClass(env, "java/lang/IllegalArgumentException"
        )
        ;
        (*env)->
        ThrowNew(env, exceptionClass, "Filename and callback cannot be null"
        )
        ;
        return
        ;
        }
        // 准备线程参数
        ProcessingParams* params = malloc(
        sizeof(ProcessingParams)
        )
        ;
        if (params == NULL
        ) {
        jclass exceptionClass = (*env)->
        FindClass(env, "java/lang/OutOfMemoryError"
        )
        ;
        (*env)->
        ThrowNew(env, exceptionClass, "Failed to allocate memory"
        )
        ;
        return
        ;
        }
        // 复制文件名
        const
        char* file = (*env)->
        GetStringUTFChars(env, filename, NULL
        )
        ;
        params->filename = malloc(strlen(file) + 1
        )
        ;
        if (params->filename == NULL
        ) {
        (*env)->
        ReleaseStringUTFChars(env, filename, file)
        ;
        free(params)
        ;
        jclass exceptionClass = (*env)->
        FindClass(env, "java/lang/OutOfMemoryError"
        )
        ;
        (*env)->
        ThrowNew(env, exceptionClass, "Failed to allocate memory for filename"
        )
        ;
        return
        ;
        }
        strcpy(params->filename, file)
        ;
        (*env)->
        ReleaseStringUTFChars(env, filename, file)
        ;
        // 创建回调的全局引用
        params->callback = (*env)->
        NewGlobalRef(env, callback)
        ;
        // 重置取消标志
        pthread_mutex_lock(&g_cancel_mutex)
        ;
        g_should_cancel = 0
        ;
        pthread_mutex_unlock(&g_cancel_mutex)
        ;
        // 创建处理线程
        int result = pthread_create(&g_processing_thread, NULL
        , processingThread, params)
        ;
        if (result != 0
        ) {
        free(params->filename)
        ;
        (*env)->
        DeleteGlobalRef(env, params->callback)
        ;
        free(params)
        ;
        jclass exceptionClass = (*env)->
        FindClass(env, "java/lang/RuntimeException"
        )
        ;
        (*env)->
        ThrowNew(env, exceptionClass, "Failed to create processing thread"
        )
        ;
        }
        }
        JNIEXPORT void JNICALL
        Java_com_example_SecureFileProcessor_cancelProcessing(JNIEnv *env, jobject thiz) {
        pthread_mutex_lock(&g_cancel_mutex)
        ;
        g_should_cancel = 1
        ;
        pthread_mutex_unlock(&g_cancel_mutex)
        ;
        }

调试技巧和常见错误

常见错误及解决方案

  1. 忘记检查异常

    // 错误
    (*env)->
    CallVoidMethod(env, obj, method)
    ;
    // 继续执行...
    // 正确
    (*env)->
    CallVoidMethod(env, obj, method)
    ;
    if ((*env)->
    ExceptionCheck(env)
    ) {
    (*env)->
    ExceptionClear(env)
    ;
    return
    ;
    }
  2. 在错误的线程中使用JNIEnv

    // 错误:直接使用其他线程的JNIEnv
    // 正确:获取当前线程的JNIEnv
    JNIEnv* env;
    (*g_jvm)->
    GetEnv(g_jvm, (
    void**
    )&env, JNI_VERSION_1_6)
    ;
  3. 没有正确管理全局引用

    // 错误:创建了全局引用但没有删除
    jobject globalRef = (*env)->
    NewGlobalRef(env, obj)
    ;
    // 正确:记得删除全局引用
    (*env)->
    DeleteGlobalRef(env, globalRef)
    ;

调试工具

  1. 使用CheckJNI:在开发阶段启用CheckJNI检查
  2. AddressSanitizer:检测内存错误
  3. 日志记录:在关键位置添加日志
  4. 异常堆栈:使用ExceptionDescribe()打印异常信息

总结

异常处理和线程安全是JNI开发中的核心技能。记住以下要点:

异常处理

  • 总是检查Java方法调用后的异常状态
  • 在C代码中适当地抛出Java异常
  • 确保异常情况下的资源清理

线程安全

  • JNIEnv不能跨线程使用
  • 使用JavaVM获取当前线程的JNIEnv
  • 保护共享资源访问
  • 正确管理全局引用的生命周期

虽然这些概念初看起来可能复杂,但通过实践和遵循最佳实践,你会发现它们是构建稳定JNI应用的基石。在下一篇文章中,我们将探讨JNI性能优化技巧和高级调试方法。

参考资源

posted on 2025-10-06 22:45  slgkaifa  阅读(0)  评论(0)    收藏  举报

导航