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

news/2025/10/6 22:49:29/文章来源:https://www.cnblogs.com/slgkaifa/p/19128020

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

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

引言

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

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

想象一下这样的场景:

这些都是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>// 共享数据staticint sharedCounter = 0;static pthread_mutex_t counterMutex = PTHREAD_MUTEX_INITIALIZER;JNIEXPORT jint JNICALLJava_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 JNICALLJava_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 JNICALLJava_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;// 获取当前线程的JNIEnvif ((*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;staticvolatileint g_should_cancel = 0;static pthread_mutex_t g_cancel_mutex = PTHREAD_MUTEX_INITIALIZER;// 线程参数结构typedefstruct {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,constchar* methodName,constchar* 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);}elseif (strcmp(signature, "(Ljava/lang/String;)V") == 0) {constchar* str = va_arg(args,constchar*);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;// 附加到JVMint 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 JNICALLJNI_OnLoad(JavaVM* vm,void* reserved) {g_jvm = vm;return JNI_VERSION_1_6;}JNIEXPORT void JNICALLJava_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;}// 复制文件名constchar* 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 JNICALLJava_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开发中的核心技能。记住以下要点:

异常处理

线程安全

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

参考资源

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

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

相关文章

商城网站设计公司系统优化包括哪些

作者推荐 视频算法专题 本文涉及知识点 哈希映射 哈希集合 LeetCode 381. O(1) 时间插入、删除和获取随机元素 - 允许重复 RandomizedCollection 是一种包含数字集合(可能是重复的)的数据结构。它应该支持插入和删除特定元素&#xff0c;以及删除随机元素。 实现 Randomiz…

判断网站开发语言wordpress 访问速度

来源&#xff1a;德先生作者&#xff1a;朱圆恒&#xff0c;唐振韬&#xff0c;李伟凡&#xff0c;赵冬斌北京时间2019年1月25日2时&#xff0c;DeepMind在伦敦向世界展示了他们的最新成果——星际争霸2人工智能AlphaStar[1] 。图1. DeepMind AlphaStar挑战星际人类职业玩家直播…

分布式CAP理论 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

【闲话】2025.9.24 记梦

破碎,离奇。话说 \(9.25\) 吃了 \(hz\) 校庆期间 \(hs\) 食堂免费饭菜的人第二天都被抓去做黑工,我和 @wkh2008 因为当时润去不知道干什么了所以没有被抓走,第二天回到班里发现毫无人,然后被学校里巡游的中介带走。…

北京撒网站设计git网站开发

文章目录 目录1. 程序的翻译环境和执行环境2. 详解编译链接2.1 翻译环境2.2 编译本身也分为几个阶段2.3 运行环境 3. 预处理详解3.1 预定义符号3.2 #define3.2.1 #define 定义标识符3.2.2 #define 定义宏3.2.3 #define 替换规则3.2.4 #和##3.2.5 带副作用的宏参数3.2.6 宏和函数…

完整教程:Postgresql常规SQL语句操作

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

酷派Cool20/20S/30/40手机安装Play商店-谷歌三件套-GMS方式

酷派Cool20/20S/30/40手机安装Play商店-谷歌三件套-GMS方式pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consol…

拼多多电商网站建设学校网站建设方案设计

使用 services 指令&#xff0c;请先安装 brew tap gapple/services 安装完成后使用 brew services start mysql

关于网络编辑作业做网站栏目新闻的ppt免费网站软件大全

今年有哪些成功的发行版发布呢&#xff1f; 让我重点介绍最好的几个。 这些发行版在 2023 年引起了人们的关注&#xff01; 每年我们都会推出一些令人兴奋的新发行版&#xff0c;它们尝试以不同的方式工作&#xff0c;或者提供一些有意义的东西&#xff0c;而不仅仅是“又一个发…

【sa-token】 sa-token非 web 上下文无法获取 HttpServletRequest - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Cloudflare洛杉矶数据中心维护通知:技术架构与影响解析

本文详细介绍了Cloudflare将于2025年10月8日在洛杉矶数据中心进行的计划性维护,包括维护时间窗口、流量重路由机制、潜在延迟影响,以及针对PNI/CNI客户的重要技术建议和通知订阅方式。维护概览 计划维护时间:2025年…

正规的手机网站建设小程序官方平台

等到花都谢了&#xff0c;你怎么还不开机&#xff1f;这电脑开机真是离奇的慢&#xff0c;有心将它换了&#xff0c;奈何兜里空空。凑合着用又无法忍受这种煎熬。其实你只需要用鼠标点几下就可以不用等待这漫长的开机过程了。高铁&#xff0c;飞机&#xff0c;网络&#xff0c;…

友点企业网站管理系统模板保定市清苑区网站建设

看看如果设定了Rollover中某个状态的效果&#xff0c;则 CA&#xff0e;在动画面板上制作的动画适用于所有的Rollover状态B&#xff0e;动画不能应用在Rollover状态上C&#xff0e;在动画面板上制作的动画仅适用于当前的Rollover状态D&#xff0e;同一动画不能赋予多个Rollover…

沧州建设银行招聘网站电子商务网站建设中

参考 一个叫做Version&#xff0c;一个叫做Build&#xff0c;&#xff08;version是版本号&#xff0c;build是打正式包每次Archive时的都增加的值&#xff09;这两个值都可以在Xcode中选中target&#xff0c;点击“Summary”后看到。 Version在plist文件中的key是“CFBundleSh…

住房和城乡建设部官方网站已前程无忧招聘网

需求&#xff1a;项目中需要使用netty&#xff0c;本地测试的时候使用的是ws&#xff0c;然后要部署到服务器上&#xff0c;使用https连接&#xff0c;https下就不能用ws了&#xff0c;必须升级到wss 1.阿里云申请免费证书 2.保存证书到本地目录 3.修改代码 SslUtil 工具类…

Windows+VSCode搭建小智(xiaozhi)开发环境 - 教程

Windows+VSCode搭建小智(xiaozhi)开发环境 - 教程2025-10-06 22:21 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; disp…

StarTree支持Apache Iceberg扩展湖仓用例

StarTree宣布其云服务全面支持Apache Iceberg,实现直接查询Iceberg表而无需移动或转换底层数据,解决了高并发实时查询的挑战,同时避免了数据重复存储和复杂管道构建。StarTree支持Apache Iceberg以扩展湖仓用例 基于…

偏微分方程的解

偏微分方程的解求未知函数。

如何在 HTML 中添加按钮 - 实践

如何在 HTML 中添加按钮 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&quo…

网站备案的链接如何创建电商平台

前言 这篇文章用于记录阿里天池 NLP 入门赛&#xff0c;详细讲解了整个数据处理流程&#xff0c;以及如何从零构建一个模型&#xff0c;适合新手入门。 赛题以新闻数据为赛题数据&#xff0c;数据集报名后可见并可下载。赛题数据为新闻文本&#xff0c;并按照字符级别进行匿名…