Android 9+下TextureView InputConnection支持问题求助
我开发了一个继承自TextureView的子类,用来支持键盘文本输入。这段代码在Android 12的实体设备和模拟器上运行正常,但在Android 9设备上无法正常工作。我需要适配Android 9及以上版本,希望大家帮忙排查问题,以下是我的代码:
import android.content.* import android.text.InputType import android.util.AttributeSet import android.view.* import android.view.inputmethod.* import androidx.core.view.ViewCompat val myView = object : TextureView(context as Context) { private var inputConnection: BaseInputConnection? = null override fun onAttachedToWindow() { super.onAttachedToWindow() isFocusable = true isFocusableInTouchMode = true requestFocus() showKeyboard() ViewCompat.setOnReceiveContentListener( this, arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN) ) { _, content -> val text = content.clip.getItemAt(0).coerceToText(context as Context) if (text.isNotEmpty()) { (cbreceivecontent as Function1<Any, *>).invoke(text.toString()) } null } } private fun showKeyboard() { ((context as Context).getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) .showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) } override fun onCreateInputConnection(outAttrs: EditorInfo?): InputConnection { outAttrs?.apply { inputType = InputType.TYPE_CLASS_TEXT imeOptions = EditorInfo.IME_ACTION_DONE } inputConnection = object : BaseInputConnection(this, true) { override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean { text?.let { (cbcommittext as Function1<Any, *>).invoke(it.toString()) } return super.commitText(text, newCursorPosition) } } return inputConnection!! } override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { event?.unicodeChar?.takeIf { it != 0 }?.let { (cbkeydown as Function1<Any, *>).invoke(it.toChar().toString()) return true } return super.onKeyDown(keyCode, event) } }
1. ViewCompat.setOnReceiveContentListener 兼容性问题
ViewCompat.setOnReceiveContentListener是AndroidX Core 1.5.0及以上版本引入的API,Android 9(API 28)对应的默认AndroidX版本通常低于该要求,导致该方法无法生效,这会直接影响粘贴等内容插入操作的处理。
解决方法:
移除该监听的使用,统一通过BaseInputConnection的回调处理文本输入和粘贴逻辑。可以在BaseInputConnection中重写commitContent方法来处理粘贴:
override fun commitContent(content: InputContentInfo?, flags: Int, opts: Bundle?): Boolean { content?.let { val text = runCatching { it.contentUri?.let { uri -> context.contentResolver.openInputStream(uri)?.bufferedReader()?.readText() } ?: it.description.label }.getOrNull() text?.takeIf { it.isNotEmpty() }?.let { (cbreceivecontent as Function1<Any, *>).invoke(it) } } return super.commitContent(content, flags, opts) }
2. 焦点与输入法唤起时机问题
onAttachedToWindow阶段View可能还未完成布局,直接调用requestFocus()和showKeyboard()容易导致焦点请求失败,输入法无法正常唤起,低版本系统对View生命周期的校验更严格。
解决方法:
将焦点请求和输入法唤起逻辑移至onWindowFocusChanged方法:
override fun onWindowFocusChanged(hasWindowFocus: Boolean) { super.onWindowFocusChanged(hasWindowFocus) if (hasWindowFocus) { isFocusable = true isFocusableInTouchMode = true if (requestFocus()) { showKeyboard() } } }
3. InputConnection 重复创建问题
每次调用onCreateInputConnection都重新创建BaseInputConnection实例,会导致输入法与View的连接不稳定,低版本系统中这种问题表现更明显。
解决方法:
仅在inputConnection为null时创建实例:
override fun onCreateInputConnection(outAttrs: EditorInfo?): InputConnection { outAttrs?.apply { inputType = InputType.TYPE_CLASS_TEXT imeOptions = EditorInfo.IME_ACTION_DONE // 补充属性帮助低版本输入法识别输入类型 hintText = "输入文本" label = "文本输入框" } if (inputConnection == null) { inputConnection = object : BaseInputConnection(this, true) { // 重写commitText和commitContent逻辑 } } return inputConnection!! }
4. onKeyDown 方法的冗余与兼容性问题
低版本系统中,软键盘的字符输入通常不会通过onKeyDown传递,而是直接走InputConnection的commitText方法,依赖onKeyDown会导致部分字符无法被捕获。
解决方法:
移除onKeyDown的重写,统一通过commitText处理所有文本输入逻辑。
5. Context 强转安全问题
代码中多次使用context as Context,若Context为空会直接抛出异常,低版本系统中View的Context初始化可能存在延迟。
解决方法:
使用requireContext()获取安全的Context实例:
private fun showKeyboard() { val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) }
import android.content.Context import android.text.InputType import android.util.AttributeSet import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputConnection import android.view.inputmethod.InputContentInfo import android.view.inputmethod.InputMethodManager import android.view.TextureView import android.widget.BaseInputConnection class TextInputTextureView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : TextureView(context, attrs, defStyleAttr) { private var inputConnection: BaseInputConnection? = null lateinit var cbcommittext: (String) -> Unit lateinit var cbreceivecontent: (String) -> Unit override fun onWindowFocusChanged(hasWindowFocus: Boolean) { super.onWindowFocusChanged(hasWindowFocus) if (hasWindowFocus) { isFocusable = true isFocusableInTouchMode = true if (requestFocus()) { showKeyboard() } } } private fun showKeyboard() { val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) } override fun onCreateInputConnection(outAttrs: EditorInfo?): InputConnection { outAttrs?.apply { inputType = InputType.TYPE_CLASS_TEXT imeOptions = EditorInfo.IME_ACTION_DONE hintText = "请输入文本" label = "文本输入框" } if (inputConnection == null) { inputConnection = object : BaseInputConnection(this, true) { override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean { text?.toString()?.takeIf { it.isNotEmpty() }?.let { cbcommittext.invoke(it) } return super.commitText(text, newCursorPosition) } override fun commitContent(content: InputContentInfo?, flags: Int, opts: Bundle?): Boolean { content?.let { val text = runCatching { it.contentUri?.let { uri -> context.contentResolver.openInputStream(uri)?.bufferedReader()?.readText() } ?: it.description.label }.getOrNull() text?.takeIf { it.isNotEmpty() }?.let { cbreceivecontent.invoke(it) } } return super.commitContent(content, flags, opts) } } } return inputConnection!! } }
内容的提问来源于stack exchange,提问作者Genealogy

