I’ve built a custom keyboard app using Compose for the UI and InputMethodService for IME functions. It works fine but I’m seeing memory leaks on Android 14 devices when switching orientations or changing keyboards.
The leak involves WindowOnBackInvokedDispatcher$OnBackInvokedCallbackWrapper and only happens on physical devices running API 34 like Motorola and Samsung phones. It also shows up on some API 34 emulators.
Older Android versions and some emulators don’t have this issue. The leak seems to be related to the InputMethodService lifecycle.
Here’s a simplified version of my IMEService class:
class MyIMEService : InputMethodService(), LifecycleOwner, SavedStateRegistryOwner {
private val lifecycleRegistry = LifecycleRegistry(this)
private val savedStateController = SavedStateRegistryController.create(this)
override fun onCreateInputView(): View {
window?.window?.decorView?.apply {
setViewTreeLifecycleOwner(this@MyIMEService)
setViewTreeSavedStateRegistryOwner(this@MyIMEService)
}
return ComposeKeyboardView(this)
}
override fun onCreate() {
super.onCreate()
savedStateController.performRestore(null)
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
override fun onDestroy() {
super.onDestroy()
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
}
Any ideas on how to fix this leak? It seems like it might be an Android framework issue. Thanks for any help!
I’ve encountered similar memory leak issues with custom IMEs on Android 14. From my experience, it’s likely related to how the system handles the InputMethodService lifecycle in conjunction with the new back behavior introduced in Android 14.
One potential workaround I’ve found is to manually clear any lingering references in onDestroy():
override fun onDestroy() {
super.onDestroy()
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
window?.window?.decorView?.setViewTreeLifecycleOwner(null)
window?.window?.decorView?.setViewTreeSavedStateRegistryOwner(null)
}
This explicitly removes the LifecycleOwner and SavedStateRegistryOwner references. Additionally, ensure you’re using the latest Jetpack Compose version, as there have been fixes for similar issues in recent updates. If the problem persists, consider filing a bug report with Google, as it may indeed be a framework issue.
I’ve dealt with this exact issue in my own keyboard app. It’s definitely a tricky one. What worked for me was implementing a custom BackHandler in my Compose UI:
BackHandler(enabled = true) {
// Handle back press
}
This seemed to prevent the WindowOnBackInvokedDispatcher from getting stuck in memory. Also, make sure you’re properly disposing of any resources in onFinish():
override fun onFinish() {
super.onFinish()
// Clear any custom listeners or observers here
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
If you’re still seeing leaks after trying these, it might be worth digging into your Compose state management. Sometimes holding onto unnecessary state can cause weird memory issues. Hope this helps!