You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 07:20:08