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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 17:34:51