因为 currentInputConnection 总是 returns null

because currentInputConnection always returns null

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

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

val focusedTextField = currentInputConnection ?: return

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

这是清单文件。有人告诉我,从 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,但这不是问题,因为我已经尝试 return 一个 xml 视图,但我总是遇到错误

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.invoke(generalKeys.kt:32)
at com.chrrissoft.amazingkeyboard.composables.GeneralKeysKt$TestKey.invoke(generalKeys.kt:31)

Here 是完整的项目,以备不时之需。

我假设您遵循了此 documentation 并进行了所有必要的设置,包括将 IMEService 添加到 Android 清单。

val connection = IMEService()

不幸的是,您不能像那样使用 Service,您不能自己创建 Service class 的实例并依赖它会正常工作。服务由 Android 系统创建。如果你想使用 currentInputConnection 属性 你应该在 IMEService 中使用它,或者你需要以某种方式将它的实例传递到你想要使用它的地方,或者 to bind to it if you want to have an instance of IMEService service. Please refer also to these answers regarding how to bind to a Service.

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

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

因为当前激活的InputConnection没有绑定输入法,所以currentInputConnection为空

InputConnection中有一个onBindInput方法,在新客户端绑定输入法时调用。通过此调用,您知道 currentInputConnection return 有效对象。 所以在使用前currentInputConnection客户端要绑定输入法

要在 class 范围之外使用 IMEService 的 public 方法,您需要有绑定服务的实例。深入研究 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 class 保持不变。