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
相关产品推荐
相关产品推荐

