Activity 使用 AndroidX Navigation Compose 后方向改变后泄漏

Activity leaked after orientation change after using AndroidX Navigation Compose

MainActivity 在第二次方向改变后泄漏,但只有在使用 navHostController 导航到新目的地后才会泄漏。

可以复制的工作项目可用 here

这些是复制步骤:

  1. 运行 应用程序(它将加载 FooScreen,其中仅包含一个 TopAppBar 和一个 Button)。
  2. 单击“打开栏屏幕”Button(它将加载仅包含 TopAppBarBarScreen
  3. 将设备的方向从纵向更改为横向
  4. 将方向改回纵向

此时您应该看到 StrictMode 抱怨 logcat:

内部泄漏
2021-11-02 16:23:20.672 31230-31230/com.leinardi.template E/StrictMode: class com.leinardi.template.ui.MainActivity; instances=2; limit=1
    android.os.StrictMode$InstanceCountViolation: class com.leinardi.template.ui.MainActivity; instances=2; limit=1
        at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)

LeakCanary 通知也应该可见。这是 LeakCanary 日志:

​
┬───
│ GC Root: System class
│
├─ leakcanary.internal.InternalLeakCanary class
│    Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
│    ↓ static InternalLeakCanary.resumedActivity
├─ com.leinardi.template.ui.MainActivity instance
│    Leaking: NO (Activity#mDestroyed is false)
│    mApplication instance of com.leinardi.template.Template
│    mBase instance of android.app.ContextImpl
│    ↓ MainActivity.navHostController
│                   ~~~~~~~~~~~~~~~~~
├─ androidx.navigation.NavHostController instance
│    Leaking: UNKNOWN
│    Retaining 5.5 kB in 140 objects
│    activity instance of com.leinardi.template.ui.MainActivity with mDestroyed = false
│    context instance of com.leinardi.template.ui.MainActivity with mDestroyed = false
│    lifecycleOwner instance of com.leinardi.template.ui.MainActivity with mDestroyed = false
│    ↓ NavController.viewModel
│                    ~~~~~~~~~
├─ androidx.navigation.NavControllerViewModel instance
│    Leaking: UNKNOWN
│    Retaining 128.4 kB in 2406 objects
│    ↓ NavControllerViewModel.viewModelStores
│                             ~~~~~~~~~~~~~~~
├─ java.util.LinkedHashMap instance
│    Leaking: UNKNOWN
│    Retaining 128.3 kB in 2404 objects
│    ↓ LinkedHashMap.header
│                    ~~~~~~
├─ java.util.LinkedHashMap$LinkedHashMapEntry instance
│    Leaking: UNKNOWN
│    Retaining 32 B in 1 objects
│    ↓ LinkedHashMap$LinkedHashMapEntry.after
│                                       ~~~~~
├─ java.util.LinkedHashMap$LinkedHashMapEntry instance
│    Leaking: UNKNOWN
│    Retaining 127.8 kB in 2389 objects
│    ↓ HashMap$HashMapEntry.value
│                           ~~~~~
├─ androidx.lifecycle.ViewModelStore instance
│    Leaking: UNKNOWN
│    Retaining 127.7 kB in 2388 objects
│    ↓ ViewModelStore.mMap
│                     ~~~~
├─ java.util.HashMap instance
│    Leaking: UNKNOWN
│    Retaining 127.7 kB in 2387 objects
│    ↓ HashMap.table
│              ~~~~~
├─ java.util.HashMap$HashMapEntry[] array
│    Leaking: UNKNOWN
│    Retaining 127.7 kB in 2385 objects
│    ↓ HashMap$HashMapEntry[].[1]
│                             ~~~
├─ java.util.HashMap$HashMapEntry instance
│    Leaking: UNKNOWN
│    Retaining 126.6 kB in 2348 objects
│    ↓ HashMap$HashMapEntry.value
│                           ~~~~~
├─ androidx.navigation.compose.BackStackEntryIdViewModel instance
│    Leaking: UNKNOWN
│    Retaining 126.5 kB in 2347 objects
│    ↓ BackStackEntryIdViewModel.saveableStateHolder
│                                ~~~~~~~~~~~~~~~~~~~
├─ androidx.compose.runtime.saveable.SaveableStateHolderImpl instance
│    Leaking: UNKNOWN
│    Retaining 126.4 kB in 2343 objects
│    ↓ SaveableStateHolderImpl.parentSaveableStateRegistry
│                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~
├─ androidx.compose.ui.platform.DisposableSaveableStateRegistry instance
│    Leaking: UNKNOWN
│    Retaining 125.9 kB in 2321 objects
│    ↓ DisposableSaveableStateRegistry.onDispose
│                                      ~~~~~~~~~
├─ androidx.compose.ui.platform.DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry instance
│    Leaking: UNKNOWN
│    Retaining 125.6 kB in 2312 objects
│    Anonymous subclass of kotlin.jvm.internal.Lambda
│    ↓ DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry.$androidxRegistry
│                                                                                  ~~~~~~~~~~~~~~~~~
├─ androidx.savedstate.SavedStateRegistry instance
│    Leaking: UNKNOWN
│    Retaining 125.6 kB in 2310 objects
│    ↓ SavedStateRegistry.mComponents
│                         ~~~~~~~~~~~
├─ androidx.arch.core.internal.SafeIterableMap instance
│    Leaking: UNKNOWN
│    Retaining 125.5 kB in 2309 objects
│    ↓ SafeIterableMap.mEnd
│                      ~~~~
├─ androidx.arch.core.internal.SafeIterableMap$Entry instance
│    Leaking: UNKNOWN
│    Retaining 125.3 kB in 2302 objects
│    ↓ SafeIterableMap$Entry.mValue
│                            ~~~~~~
├─ androidx.activity.ComponentActivity$$ExternalSyntheticLambda1 instance
│    Leaking: UNKNOWN
│    Retaining 125.3 kB in 2301 objects
│    f[=12=] instance of com.leinardi.template.ui.MainActivity with mDestroyed = true
│    ↓ ComponentActivity$$ExternalSyntheticLambda1.f[=12=]
│                                                  ~~~
╰→ com.leinardi.template.ui.MainActivity instance
​     Leaking: YES (ObjectWatcher was watching this because com.leinardi.template.ui.MainActivity received
​     Activity#onDestroy() callback and Activity#mDestroyed is true)
​     Retaining 125.3 kB in 2300 objects
​     key = 2c6ea34a-19c0-4d26-a5ec-88625d79531f
​     watchDurationMillis = 42581
​     retainedDurationMillis = 37580
​     mApplication instance of com.leinardi.template.Template
​     mBase instance of android.app.ContextImpl
METADATA
Build.VERSION.SDK_INT: 24
Build.MANUFACTURER: Google
LeakCanary version: 2.7
App process name: com.leinardi.template
Stats: LruCache[maxSize=3000,hits=1619,misses=30537,hitRate=5%]
RandomAccess[bytes=1518368,reads=30537,travel=10623307269,range=18917491,size=20769812]
Heap dump reason: user request
Analysis duration: 1249 ms

我注意到的事情:

我已经尝试过的事情:

我也曾尝试使用 Android Studio Profiled 来调查泄漏,但我并没有真正成功。这里有一些屏幕截图:

我怀疑这可能是某些 AndroidX 库的错误,但也许我只是做错了什么。

repro 项目并没有完全精简:它是一个多模块单一 activity 我正在构建的仅 MVVM-MVI Compose 模板项目。尚未完成,但大部分事情应该到位。

这是模块的概述:

简而言之,该应用程序只有一个Activity,除了设置导航主机之外没有任何逻辑。导航逻辑在 core-navigation 内。每个功能模块都提供 UI 和业务逻辑。目前有 3 个功能,每个功能有 1 个屏幕:Foo、Bar 和 Debug。 Foo 屏幕只有一个按钮可通往 Bar 屏幕。如果您旋转设备两次,泄漏会发生在条形屏幕内。如果您通过 Deeplink.

访问 Bar,则不会发生这种情况

泄漏实际上是导航组合库中的一个错误,下一个版本将提供修复:https://issuetracker.google.com/issues/204905432#comment4