android - 因为 currentInputConnection 总是返回 null

标签 android kotlin android-service android-jetpack-compose android-input-method

我正在尝试为 Android 构建一个 IME(输入法编辑器)。我知道我必须创建一个扩展 InputMethodService 的类,访问getCurrentInputConnection 方法。我的理解是,这会将我返回到当前聚焦的文本字段,如果没有,则返回 null。

然后我知道我必须做这样的事情:

val focusedTextField = currentInputConnection ?: return

问题是我总是得到 null 结果。我的理论是,文本编辑器(当前聚焦的文本字段)无法将我的应用程序识别为 IME,或者可能“没有意识到”它正在聚焦。 所以也许我必须提供更多信息。我已经检查了声明服务并提供元数据的 list ,一切似乎都是正确的。 res/xml/method.xml 文件是正确的。

这是 list 文件。我被告知,从 Android 11 开始,我们必须请求位置许可才能使用服务

<service
    android:name=".IMEService"
    android:label="Amazing Keyboard"
    android:foregroundServiceType="location"
    android:permission="android.permission.BIND_INPUT_METHOD"
    android:exported="true">
        <intent-filter>
            <action android:name="android.view.InputMethod" />
        </intent-filter>
        <meta-data
            android:name="android.view.im"
            android:resource="@xml/method" />
</service>

这是方法文件

<?xml version="1.0" encoding="utf-8"?>
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
    android:settingsActivity="com.example.amazingkeyboard.MainActivity">
    <subtype
        android:name = "my app"
        android:label="English (U.S)"
        android:imeSubtypeLocale="en_US"
        android:imeSubtypeMode="keyboard"/>
</input-method>

这就是我正在做的事情,我正在使用 jetpack compose,但这不是问题,因为我已经尝试返回 xml View ,但总是出现错误

class IMEService : InputMethodService(), LifecycleOwner, 
      ViewModelStoreOwner, SavedStateRegistryOwner {
  fun sendText(text : CharSequence, newCursorPosition : Int) {
    val focusedTextField = currentInputConnection ?: return //always returns null 
    focusedTextField.commitText(text, newCursorPosition) 
  } 
  ...
}

这是我调用该方法的地方

val connection = IMEService()
@Composable fun TestKey(modifier: Modifier = Modifier) {
  Key( 
    modifier = modifier .clickable { 
      connection.sendText(unicodeToString(0x1F436), 1)
}...

当我删除验证时。正如我上面所说,问题是我总是得到空值。显然,如果我进行验证,就不会出现错误,但我也无法发送(因为我总是有 null)

// val focusedTextField = currentInputConnection ?: return
val focusedTextField = currentInputConnection

我有这个错误。

java.lang.NullPointerException:
Attempt to invoke interface method 'boolean android.view.inputmethod.InputConnection.commitText(java.lang.CharSequence, int)' on a null object reference
at com.chrrissoft.amazingkeyboard.IMEService.sendText(IMEService.kt:21)
at com.chrrissoft.amazingkeyboard.composables.GeneralKeysKt$TestKey$1.invoke(generalKeys.kt:32)
at com.chrrissoft.amazingkeyboard.composables.GeneralKeysKt$TestKey$1.invoke(generalKeys.kt:31)

Here是完整的项目,以防您想查看它。

最佳答案

您在以下代码的第二行收到 NullPointerException:

val focusedTextField = currentInputConnection
focusedTextField.commitText(text, newCursorPosition) 

因为当前 Activity 的 InputConnection 未绑定(bind)到输入法,这就是 currentInputConnection 为 null 的原因。

有一个onBindInput InputConnection 中的方法,当新客户端绑定(bind)到输入法时调用该方法。通过这次调用,您知道 currentInputConnection 返回有效的对象。 所以在使用currentInputConnection之前客户端应该绑定(bind)到输入法。

要在类范围之外使用 IMEService 的公共(public)方法,您需要拥有绑定(bind)服务的实例。深入研究 GitHub 上的源代码,通过将 IMEService 传递给 TestKey() 函数,问题似乎很容易解决。整个代码看起来像这样:

@Composable
fun TestKey(modifier: Modifier = Modifier, connection: IMEService) {
    Key(
        modifier = modifier
            //...
            .clickable {
                connection.sendText(unicodeToString(0x1F383), 1)
            }
    ) {
        Icon(/*...*/)
    }
}

@Composable
fun EmojiLayout(navController: NavHostController, connection: IMEService) {

    val (currentPage, onPageChange) = remember {
        mutableStateOf(EmoticonsAndEmotionsPage)
    }

    Column(modifier = Modifier.fillMaxSize()) {
        EmojiPage(
            //...
        )
        Row(
            //...
        ) {
            //...
            TestKey(Modifier.weight(1f), connection)
        }
    }
}

@Composable
fun KeyboardScreen(connection: IMEService) {

    AmazingKeyboardTheme() {
        Column(
            //...
        ) {
            val navController = rememberNavController()
            NavHost(navController = navController, startDestination = "qwertyLayout") {
                //...
                composable("emojiLayout") { EmojiLayout(navController, connection) }
            }
        }
    }
}

class AndroidKeyboardView(context: Context) : FrameLayout(context) {

    constructor(service: IMEService) : this(service as Context) {
        inflate(service, R.layout.keyboard_view, this)
        findViewById<ComposeView>(R.id.compose_view).setContent {
            KeyboardScreen(connection = service)
        }
    }
}

IMEService 类保持不变。

关于android - 因为 currentInputConnection 总是返回 null,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71713785/

相关文章:

android - 将 clickEvent 添加到 int 数组

java - 从 Firebase 数据库中删除特定值

kotlin - 我们什么时候使用launch(SupervisorJob())?

android - 如何处理我的 Android 应用程序中进程间通信的必要性?

java - 一行三个textView

android - 在字符串中检测\n 的正则表达式是什么?这里\和n存储在一个字符串的两个不同的单元格中

java - Micronaut 中 HttpServletRequest 和 HttpServletResponse 的替代方案

kotlin - 为什么不使用 GlobalScope.launch?

java - 简单服务不会绑定(bind) : bindService return false, 服务连接从未触发

Android 将 BroadcastReceiver 保持在后台