如何通过JNI在Android实现文本复制到剪贴板?
Android剪贴板功能的JNI实现修复方案
问题背景
尝试将Android平台下复制文本到剪贴板的Java代码转换为C语言JNI代码,但自行编写的JNI代码导致应用崩溃,因对JNI不熟悉且未找到相关参考源码,寻求解决办法。
原Java实现代码
public static void setClipboard(String text){ android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); android.content.ClipData clip = android.content.ClipData.newPlainText("Copied text", text); clipboard.setPrimaryClip(clip); }
自行编写的JNI崩溃代码
void setCliboard(const char *text){ JNIEnv *env = getEnv(); class context_cls = env->FindClass(OBFUSCATE("android/content/Context")); jfieldID cliepboardService_fid = env->getStaticFieldID(context_cls, OBFUSCATE("CLIPBOARD_SERVICE"), OBFUSCATE("Ljava/lang/String")); jmethodID getSystemService_mid = env->getMethodID(context_cls, OBFUSCATE("getSystemService"), OBFUSCATE("(Ljava/lang/String;)Ljava/lang/Object;")); jobject context_obj = env->GetStaticObjectField(activity_cls, currentActivity_fid); jobject clipboardService_obj = env->CallObjectMethod(context_obj, getSystemService_mid, (jstring)env->GetStaticObjectField(context_cls, cliepboardService_fid)); jstring myText = env->NewStringUTF(text); jstring copiedText = env->NewStringUTF("Copied text"); jclass clipData_cls = env->FindClass(OBFUSCATE("android.content.ClipData")); jmethodID newPLainText_mid = env->getMethodID(clipData_cls, OBFUSCATE("newPLainText"), OBFUSCATE("(Ljava/lang/CharSequence;Ljava/lang/CharSequence)Landroid.content.ClipData;")); env->CallVoidMethod(clipboardService_obj, newPLainText_mid, newPLainText_obj); }
代码错误分析
- 拼写错误:函数名
setCliboard应为setClipboard;字段名cliepboardService_fid拼写错误,应为clipboardService_fid;方法名newPLainText应为newPlainText(大小写错误)。 - 静态方法调用错误:
ClipData.newPlainText是静态方法,需使用CallStaticObjectMethod而非CallObjectMethod,且代码未正确创建ClipData对象,反而错误调用了剪贴板服务的方法。 - 类路径错误:JNI中
FindClass的类路径需用/分隔,而非.,比如android/content/ClipData而非android.content.ClipData。 - 类型签名错误:
newPlainText的方法签名中,返回值类型应为Landroid/content/ClipData;而非Landroid.content.ClipData;,且参数列表的括号未闭合(原代码第二个参数后少了;)。 - 未定义变量:
newPLainText_obj未定义就直接使用,属于非法引用。 - JNI规范缺失:标准JNI函数需包含
JNIEnv*和jobject(或jclass)作为参数,且未对FindClass、GetMethodID等可能返回NULL的操作做异常检查,容易触发崩溃。
修复后的JNI代码
假设你已经实现了获取当前Activity的全局引用(activity_cls和currentActivity_fid)以及getEnv()函数来获取JNI环境:
void setClipboard(JNIEnv *env, jclass clazz, jstring text) { // 1. 获取Context类和CLIPBOARD_SERVICE字段 jclass context_cls = (*env)->FindClass(env, "android/content/Context"); if (context_cls == NULL) return; jfieldID clipboard_service_fid = (*env)->GetStaticFieldID(env, context_cls, "CLIPBOARD_SERVICE", "Ljava/lang/String;"); if (clipboard_service_fid == NULL) { (*env)->DeleteLocalRef(env, context_cls); return; } jstring clipboard_service_str = (*env)->GetStaticObjectField(env, context_cls, clipboard_service_fid); // 2. 获取当前Context对象并调用getSystemService获取剪贴板服务 jobject context_obj = (*env)->GetStaticObjectField(env, activity_cls, currentActivity_fid); if (context_obj == NULL) { (*env)->DeleteLocalRef(env, context_cls); (*env)->DeleteLocalRef(env, clipboard_service_str); return; } jmethodID get_system_service_mid = (*env)->GetMethodID(env, context_cls, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); if (get_system_service_mid == NULL) { (*env)->DeleteLocalRef(env, context_cls); (*env)->DeleteLocalRef(env, clipboard_service_str); (*env)->DeleteLocalRef(env, context_obj); return; } jobject clipboard_service_obj = (*env)->CallObjectMethod(env, context_obj, get_system_service_mid, clipboard_service_str); // 3. 创建ClipData对象 jclass clip_data_cls = (*env)->FindClass(env, "android/content/ClipData"); if (clip_data_cls == NULL) { // 清理局部引用 (*env)->DeleteLocalRef(env, context_cls); (*env)->DeleteLocalRef(env, clipboard_service_str); (*env)->DeleteLocalRef(env, context_obj); (*env)->DeleteLocalRef(env, clipboard_service_obj); return; } jmethodID new_plain_text_mid = (*env)->GetStaticMethodID(env, clip_data_cls, "newPlainText", "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;"); if (new_plain_text_mid == NULL) { // 清理局部引用 (*env)->DeleteLocalRef(env, context_cls); (*env)->DeleteLocalRef(env, clipboard_service_str); (*env)->DeleteLocalRef(env, context_obj); (*env)->DeleteLocalRef(env, clipboard_service_obj); (*env)->DeleteLocalRef(env, clip_data_cls); return; } jstring label = (*env)->NewStringUTF(env, "Copied text"); jobject clip_data_obj = (*env)->CallStaticObjectMethod(env, clip_data_cls, new_plain_text_mid, label, text); // 4. 调用剪贴板服务的setPrimaryClip方法 jclass clipboard_manager_cls = (*env)->GetObjectClass(env, clipboard_service_obj); jmethodID set_primary_clip_mid = (*env)->GetMethodID(env, clipboard_manager_cls, "setPrimaryClip", "(Landroid/content/ClipData;)V"); if (set_primary_clip_mid != NULL && clip_data_obj != NULL) { (*env)->CallVoidMethod(env, clipboard_service_obj, set_primary_clip_mid, clip_data_obj); } // 清理所有局部引用,避免内存泄漏 (*env)->DeleteLocalRef(env, context_cls); (*env)->DeleteLocalRef(env, clipboard_service_str); (*env)->DeleteLocalRef(env, context_obj); (*env)->DeleteLocalRef(env, clipboard_service_obj); (*env)->DeleteLocalRef(env, clip_data_cls); (*env)->DeleteLocalRef(env, label); (*env)->DeleteLocalRef(env, clip_data_obj); (*env)->DeleteLocalRef(env, clipboard_manager_cls); }
额外说明
- 若使用C++风格的JNI(
env->而非(*env)->),只需将代码中的(*env)->替换为env->即可。 - 所有JNI操作后记得清理局部引用,避免内存泄漏。
- 建议在每次JNI调用后检查是否发生异常(
(*env)->ExceptionCheck(env)),及时处理异常避免应用崩溃。
内容的提问来源于stack exchange,提问作者NullCoder
相关产品推荐
相关产品推荐

