InputMethodService 与 Jetpack Compose - ComposeView 原因:组合到不传播 ViewTreeLifecycleOwner 的视图中

Yan*_*ick 13 android android-input-method android-view android-jetpack-compose

您可以在Github上找到一个示例项目来重现该问题

我一直在尝试使用 Jetpack Compose 作为键盘 UI。最终,当我尝试通过 InputMethodService 膨胀键盘时

class IMEService : InputMethodService() {

    override fun onCreateInputView(): View = KeyboardView(this)
}
Run Code Online (Sandbox Code Playgroud)

通过使用这个视图

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

    init {
        val view = ComposeView(context).apply {
            setContent {
                Keyboard() //<- This is the actual compose UI function
            }
        }
        addView(view)
    }

}
Run Code Online (Sandbox Code Playgroud)

或者

class KeyboardView2 constructor(
    context: Context,

    ) : AbstractComposeView(context) {

  
    @Composable
    override fun Content() {
        Keyboard()
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,当我尝试使用键盘时,出现以下错误

class KeyboardView2 constructor(
    context: Context,

    ) : AbstractComposeView(context) {

  
    @Composable
    override fun Content() {
        Keyboard()
    }
}
Run Code Online (Sandbox Code Playgroud)

官方文档指出

您必须将 ComposeView 附加到 ViewTreeLifecycleOwner。ViewTreeLifecycleOwner 允许视图重复附加和分离,同时保留组合。ComponentActivity、FragmentActivity 和 AppCompatActivity 都是实现 ViewTreeLifecycleOwner 的类的示例

但是,我无法使用ComponentActivityFragmentActivityAppCompatActivity来膨胀调用撰写代码的视图。我陷入了实施ViewTreeLifecycleOwner 的困境。我不知道该怎么做。

如何使用@Composable函数作为输入法视图?

编辑: 正如 CommonsWare 建议的那样,我使用了该方法,并且 ViewTreeLifecycleOwner.set(...)我还必须实现:ViewModelStoreOwnerSavedStateRegistryOwner

java.lang.IllegalStateException: Composed into the View which doesn't propagate ViewTreeLifecycleOwner!
        at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.kt:599)
        at android.view.View.dispatchAttachedToWindow(View.java:19676)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3458)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2126)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1817)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7779)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1031)
        at android.view.Choreographer.doCallbacks(Choreographer.java:854)
        at android.view.Choreographer.doFrame(Choreographer.java:789)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1016)
        at android.os.Handler.handleCallback(Handler.java:914)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:227)
        at android.app.ActivityThread.main(ActivityThread.java:7582)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:953)
Run Code Online (Sandbox Code Playgroud)

现在我收到一个新错误

  java.lang.IllegalStateException: You can consumeRestoredStateForKey only after super.onCreate of corresponding component
        at androidx.savedstate.SavedStateRegistry.consumeRestoredStateForKey(SavedStateRegistry.java:77)
        at androidx.compose.ui.platform.DisposableUiSavedStateRegistryKt.DisposableUiSavedStateRegistry(DisposableUiSavedStateRegistry.kt:69)
        at androidx.compose.ui.platform.DisposableUiSavedStateRegistryKt.DisposableUiSavedStateRegistry(DisposableUiSavedStateRegistry.kt:44)
        at androidx.compose.ui.platform.AndroidAmbientsKt.ProvideAndroidAmbients(AndroidAmbients.kt:162)
        at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.kt:261)
[...]
Run Code Online (Sandbox Code Playgroud)

这在某种程度上与生命周期事件传播有关,因为当我注释掉onCreateonDestroy方法时,键盘可以打开而不会崩溃,但键盘不可见

小智 13

我的答案主要基于 Yannick 的答案和其他链接来源,因此归功于他们。

本质上,Compose 需要包中的三个“Owner”类androidx.lifecycle才能工作:LifecycleOwnerViewModelStoreOwnerSavedStateRegistryOwnerAppCompatActivity并且Fragment已经实现了这些接口,因此ComposeView为它们设置 a 是开箱即用的。

但是,在构建 IME 应用程序时,您无权访问 Activity 或 Fragment。

因此,您必须实现自己的“Owner”类,并将其与从InputMethodService. 具体做法如下:

  1. 创建一个单独的类,负责处理生命周期所有权任务:
class KeyboardViewLifecycleOwner : 
LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner {

    fun onCreate() {
        savedStateRegistryController.performRestore(null)
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
    }

    fun onResume() {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
    }

    fun onPause() {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    }

    fun onDestroy() {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        store.clear()
    }
    
    /**
      Compose uses the Window's decor view to locate the
      Lifecycle/ViewModel/SavedStateRegistry owners. 
      Therefore, we need to set this class as the "owner" for the decor view.
    */
    fun attachToDecorView(decorView: View?) {
        if (decorView == null) return

        ViewTreeLifecycleOwner.set(decorView, this)
        ViewTreeViewModelStoreOwner.set(decorView, this)
        ViewTreeSavedStateRegistryOwner.set(decorView, this)
    }
    
    // LifecycleOwner methods
    private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
    override fun getLifecycle(): Lifecycle = lifecycleRegistry

    // ViewModelStore methods
    private val store = ViewModelStore()
    override fun getViewModelStore(): ViewModelStore = store

    // SavedStateRegistry methods
    private val savedStateRegistryController = SavedStateRegistryController.create(this)
    override fun getSavedStateRegistry(): SavedStateRegistry =
        savedStateRegistryController.savedStateRegistry
}
Run Code Online (Sandbox Code Playgroud)
  1. 现在,从扩展类中InputMethodService,覆盖回调并将这些消息中继到我们在步骤 1 中定义的类的实例:
class MyKeyboardService : InputMethodService() {
    private val keyboardViewLifecycleOwner = KeyboardViewLifecycleOwner()

    override fun onCreate() {
        super.onCreate()
        keyboardViewLifecycleOwner.onCreate()
    }

    override fun onCreateInputView(): View {
        //Compose uses the decor view to locate the "owner" instances
        keyboardViewLifecycleOwner.attachToDecorView(
            window?.window?.decorView
        )

        return MyComposeView(this)
    }

    override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {
        keyboardViewLifecycleOwner.onResume()
    }

    override fun onFinishInputView(finishingInput: Boolean) {
        keyboardViewLifecycleOwner.onPause()
    }

    override fun onDestroy() {
        super.onDestroy()
        keyboardViewLifecycleOwner.onDestroy()
    }
}
Run Code Online (Sandbox Code Playgroud)

通过这样做,现在 Compose 有了一个适当的生命周期来监听,它用它来确定何时执行重组等。

资料来源:


Yan*_*ick 12

在寻找类似的实现之后,ComponentActivity 我终于想出了一个可行的解决方案:

class IMEService : InputMethodService(), LifecycleOwner, ViewModelStoreOwner,
    SavedStateRegistryOwner {

    override fun onCreateInputView(): View {
        val view = ComposeKeyboardView(this)
        ViewTreeLifecycleOwner.set(view, this)
        ViewTreeViewModelStoreOwner.set(view, this)
        ViewTreeSavedStateRegistryOwner.set(view, this)
        return view
    }


    //Lifecylce Methods

    private var lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)

    override fun getLifecycle(): Lifecycle {
        return lifecycleRegistry
    }


    private fun handleLifecycleEvent(event: Lifecycle.Event) =
        lifecycleRegistry.handleLifecycleEvent(event)

    override fun onCreate() {
        super.onCreate()
        savedStateRegistry.performRestore(null)
        handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
    }



    override fun onDestroy() {
        super.onDestroy()
        handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    }


    //ViewModelStore Methods
    private val store = ViewModelStore()

    override fun getViewModelStore(): ViewModelStore = store

    //SaveStateRegestry Methods

    private val savedStateRegistry = SavedStateRegistryController.create(this)

    override fun getSavedStateRegistry(): SavedStateRegistry = savedStateRegistry.savedStateRegistry
}
Run Code Online (Sandbox Code Playgroud)

我不知道这是否是性能方面的最佳实现,但即使在较旧的设备上它也能正常工作。改进想法受到赞赏

  • 由于 ViewTreeSavedState 已被破坏...自 2023 年起已被破坏 (2认同)