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

Android中通过JNI将C++ char* buffer与EditText双向绑定的实现方法

实现方案:自定义C++ buffer驱动的Editable

核心思路是实现一个自定义的Editable接口实现类,让它直接底层对接C++的char* buffer,替代EditText默认的内部Editable。这样EditText的所有文本操作都会直接作用于C++ buffer,无需额外拷贝;同时C++端修改buffer后,可通过JNI通知Editable触发UI刷新。

步骤1:实现C++端的buffer操作接口

先在C++层封装buffer的核心操作,供JNI调用:

#include <jni.h>
#include <cstring>
#include <mutex>

// 用UTF-16编码的buffer(和Java char数组兼容,减少转换开销)
wchar_t* g_cpp_buffer = nullptr;
size_t g_buffer_capacity = 0;
std::mutex g_buffer_mutex;

// 初始化buffer
extern "C" JNIEXPORT void JNICALL
Java_com_example_MyCppBackedEditable_initBuffer(JNIEnv* env, jobject thiz, jsize initialCapacity) {
    std::lock_guard<std::mutex> lock(g_buffer_mutex);
    if (g_cpp_buffer) delete[] g_cpp_buffer;
    g_buffer_capacity = initialCapacity;
    g_cpp_buffer = new wchar_t[initialCapacity];
    memset(g_cpp_buffer, 0, initialCapacity * sizeof(wchar_t));
}

// 获取buffer当前长度
extern "C" JNIEXPORT jsize JNICALL
Java_com_example_MyCppBackedEditable_getBufferLength(JNIEnv* env, jobject thiz) {
    std::lock_guard<std::mutex> lock(g_buffer_mutex);
    if (!g_cpp_buffer) return 0;
    return wcslen(g_cpp_buffer);
}

// 从buffer复制内容到Java char数组
extern "C" JNIEXPORT void JNICALL
Java_com_example_MyCppBackedEditable_getBufferContent(JNIEnv* env, jobject thiz, jcharArray dest, jsize start, jsize length) {
    std::lock_guard<std::mutex> lock(g_buffer_mutex);
    if (!g_cpp_buffer) return;
    jchar* dest_ptr = env->GetCharArrayElements(dest, nullptr);
    memcpy(dest_ptr + start, g_cpp_buffer + start, length * sizeof(jchar));
    env->ReleaseCharArrayElements(dest, dest_ptr, 0);
}

// 插入文本到buffer
extern "C" JNIEXPORT void JNICALL
Java_com_example_MyCppBackedEditable_insertIntoBuffer(JNIEnv* env, jobject thiz, jint index, jcharArray src, jint srcStart, jint srcLength) {
    std::lock_guard<std::mutex> lock(g_buffer_mutex);
    if (!g_cpp_buffer) return;
    
    jchar* src_ptr = env->GetCharArrayElements(src, nullptr);
    size_t current_len = wcslen(g_cpp_buffer);
    // 容量不足则扩容
    if (current_len + srcLength >= g_buffer_capacity) {
        g_buffer_capacity = current_len + srcLength + 1024;
        wchar_t* new_buffer = new wchar_t[g_buffer_capacity];
        memcpy(new_buffer, g_cpp_buffer, current_len * sizeof(wchar_t));
        delete[] g_cpp_buffer;
        g_cpp_buffer = new_buffer;
    }
    // 移动现有内容腾出空间
    memmove(g_cpp_buffer + index + srcLength, g_cpp_buffer + index, (current_len - index) * sizeof(wchar_t));
    // 插入新内容
    memcpy(g_cpp_buffer + index, src_ptr + srcStart, srcLength * sizeof(wchar_t));
    g_cpp_buffer[current_len + srcLength] = L'\0';
    
    env->ReleaseCharArrayElements(src, src_ptr, 0);
}

// 删除buffer中的文本
extern "C" JNIEXPORT void JNICALL
Java_com_example_MyCppBackedEditable_deleteFromBuffer(JNIEnv* env, jobject thiz, jint start, jint end) {
    std::lock_guard<std::mutex> lock(g_buffer_mutex);
    if (!g_cpp_buffer) return;
    
    size_t current_len = wcslen(g_cpp_buffer);
    if (start >= current_len) return;
    end = end > current_len ? current_len : end;
    size_t delete_len = end - start;
    memmove(g_cpp_buffer + start, g_cpp_buffer + end, (current_len - end) * sizeof(wchar_t));
    g_cpp_buffer[current_len - delete_len] = L'\0';
}

// C++端修改buffer后通知Java层刷新UI
extern "C" JNIEXPORT void JNICALL
Java_com_example_MyCppBackedEditable_notifyBufferChanged(JNIEnv* env, jobject thiz) {
    jclass cls = env->GetObjectClass(thiz);
    jmethodID notifyMethod = env->GetMethodID(cls, "notifyTextChanged", "(III)V");
    jsize len = getBufferLength(env, thiz);
    env->CallVoidMethod(thiz, notifyMethod, 0, len, 0);
}

步骤2:实现Kotlin自定义Editable类

创建MyCppBackedEditable,实现Editable接口,所有文本操作都通过JNI调用C++的buffer方法:

package com.example

import android.text.Editable
import android.text.Spanned
import android.text.TextWatcher

class MyCppBackedEditable : Editable {
    init {
        // 初始化C++ buffer,初始容量设为1024
        initBuffer(1024)
    }

    // JNI方法声明
    private external fun initBuffer(initialCapacity: Int)
    private external fun getBufferLength(): Int
    private external fun getBufferContent(dest: CharArray, start: Int, length: Int)
    private external fun insertIntoBuffer(index: Int, src: CharArray, srcStart: Int, srcLength: Int)
    private external fun deleteFromBuffer(start: Int, end: Int)
    external fun notifyBufferChanged()

    override fun get(index: Int): Char {
        val charArr = CharArray(1)
        getBufferContent(charArr, index, 1)
        return charArr[0]
    }

    override fun length(): Int = getBufferLength()

    override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
        val len = endIndex - startIndex
        val charArr = CharArray(len)
        getBufferContent(charArr, startIndex, len)
        return String(charArr)
    }

    override fun append(text: CharSequence?): Editable {
        text?.let { insert(length(), it, 0, it.length) }
        return this
    }

    override fun append(text: CharSequence?, start: Int, end: Int): Editable {
        text?.let { insert(length(), it, start, end) }
        return this
    }

    override fun append(text: Char): Editable {
        insert(length(), text.toString(), 0, 1)
        return this
    }

    override fun insert(where: Int, text: CharSequence?): Editable {
        text?.let { insert(where, it, 0, it.length) }
        return this
    }

    override fun insert(where: Int, text: CharSequence?, start: Int, end: Int): Editable {
        text?.let {
            val charArr = it.subSequence(start, end).toString().toCharArray()
            insertIntoBuffer(where, charArr, 0, charArr.size)
            notifyTextChanged(where, 0, charArr.size)
        }
        return this
    }

    override fun insert(where: Int, text: Char): Editable {
        insert(where, text.toString(), 0, 1)
        return this
    }

    override fun delete(start: Int, end: Int): Editable {
        deleteFromBuffer(start, end)
        notifyTextChanged(start, end - start, 0)
        return this
    }

    override fun replace(start: Int, end: Int, text: CharSequence?): Editable {
        delete(start, end)
        insert(start, text)
        return this
    }

    override fun replace(start: Int, end: Int, text: CharSequence?, startAt: Int, endAt: Int): Editable {
        delete(start, end)
        insert(start, text, startAt, endAt)
        return this
    }

    // Spanned接口默认实现(无需样式时可直接使用)
    override fun getSpanStart(tag: Any): Int = -1
    override fun getSpanEnd(tag: Any): Int = -1
    override fun getSpanFlags(tag: Any): Int = 0
    override fun nextSpanTransition(start: Int, limit: Int, type: Class<*>?): Int = limit
    override fun setSpan(what: Any?, start: Int, end: Int, flags: Int) {}
    override fun removeSpan(what: Any?) {}
    override fun getSpans(start: Int, end: Int, type: Class<*>?): Array<Any> = emptyArray()

    // 文本变更通知逻辑,触发EditText刷新
    private val textWatchers = mutableListOf<TextWatcher>()
    fun addTextWatcher(watcher: TextWatcher) {
        textWatchers.add(watcher)
    }
    fun removeTextWatcher(watcher: TextWatcher) {
        textWatchers.remove(watcher)
    }
    internal fun notifyTextChanged(start: Int, before: Int, count: Int) {
        textWatchers.forEach {
            it.beforeTextChanged(this, start, before, count)
            it.onTextChanged(this, start, before, count)
            it.afterTextChanged(this)
        }
    }
}

步骤3:绑定EditText与自定义Editable

在创建EditText后,将自定义的MyCppBackedEditable设置为它的文本缓冲区:

import android.widget.EditText
import android.widget.TextView

// 创建EditText实例
val editText = EditText(context)
// 初始化C++驱动的Editable
val cppEditable = MyCppBackedEditable()
// 设置EditText使用该Editable,必须指定BufferType.EDITABLE
editText.setText(cppEditable, TextView.BufferType.EDITABLE)

// 可选:添加文本监听(如需额外逻辑)
cppEditable.addTextWatcher(object : TextWatcher {
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
    override fun afterTextChanged(s: Editable?) {}
})

步骤4:C++端主动修改buffer后的UI刷新

当C++端直接修改g_cpp_buffer后,调用JNI方法通知Java层刷新:

// C++端更新buffer示例
void updateCppBuffer(const wchar_t* newText) {
    std::lock_guard<std::mutex> lock(g_buffer_mutex);
    if (!g_cpp_buffer) return;
    
    size_t new_len = wcslen(newText);
    // 扩容检查
    if (new_len >= g_buffer_capacity) {
        g_buffer_capacity = new_len + 1024;
        delete[] g_cpp_buffer;
        g_cpp_buffer = new wchar_t[g_buffer_capacity];
    }
    wcscpy(g_cpp_buffer, newText);
    
    // 通知Java层刷新UI
    JNIEnv* env = getCurrentJNIEnv(); // 需自行实现获取当前线程JNIEnv的逻辑
    jobject editableObj = getGlobalEditableInstance(); // 需持有MyCppBackedEditable的全局引用
    env->CallVoidMethod(editableObj, env->GetMethodID(env->GetObjectClass(editableObj), "notifyBufferChanged", "()V"));
}

关键注意事项

  • 字符编码:示例用UTF-16兼容Java char数组,避免编码转换开销;若C++端用UTF-8,需在JNI层做UTF-8到UTF-16的转换,会增加少量开销。
  • 线程安全:C++ buffer操作必须加锁,防止UI线程与C++工作线程并发访问冲突。
  • 内存管理:需在MyCppBackedEditable的finalize()方法中调用JNI释放C++ buffer,避免内存泄漏。
  • 样式支持:若需EditText支持文本样式(粗体、颜色等),需完善Spanned接口的Span相关实现逻辑。

内容的提问来源于stack exchange,提问作者Jatin guglani

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 09:08:09