【问题标题】:android Navigation UI Component Memory leakandroid导航UI组件内存泄漏
【发布时间】:2020-09-14 15:21:03
【问题描述】:

刚开始通过 Leak Canary 了解内存泄漏,如果我混淆或误解任何内容,请原谅我。 Leak canary 告诉我存在内存泄漏,我已将其范围缩小到这一行

NavigationUI.setupActionBarWithNavController(
    it,
    navController
)

导航控制器被调用并设置为这样的变量

val navController = findNavController(this@PokemonDetailFragment)

'it' 是我的活动转换为应用兼容活动,完整的 sn-p 看起来像这样:

    mainActivity = (activity as AppCompatActivity)
    mainActivity?.let {
        it.setSupportActionBar(toolbar)
        val navController = findNavController(this@PokemonDetailFragment)
        NavigationUI.setupActionBarWithNavController(
            it,
            navController
        )
    }

我尝试了三件事,1. 改为注入活动,2. 将活动设置为全局可为空的变量并在 onDestroyView 中将其设置为空,以及 3. 尝试使用 NavigationUI.setupWithNavController 而不是 setupActionBarWithNavController 需要工具栏和导航控制器

    NavigationUI.setupWithNavController(
        binding.toolbar,
        findNavController(this@PokemonDetailFragment)
    )

但这些都不能解决问题。

删除第一个代码块肯定会消除泄漏,但是 Leak Canary 并未将泄漏显示为与第三方库有关,并且确实说它是我下面代码中的一个变量是堆转储

    ```
    ┬───
    │ GC Root: Local variable in native code
    │
    ├─ android.os.HandlerThread instance
    │    Leaking: NO (PathClassLoader↓ is not leaking)
    │    Thread name: 'LeakCanary-Heap-Dump'
    │    ↓ HandlerThread.contextClassLoader
    ├─ dalvik.system.PathClassLoader instance
    │    Leaking: NO (InternalLeakCanary↓ is not leaking and A ClassLoader is never leaking)
    │    ↓ PathClassLoader.runtimeInternalObjects
    ├─ java.lang.Object[] array
    │    Leaking: NO (InternalLeakCanary↓ is not leaking)
    │    ↓ Object[].[502]
    ├─ leakcanary.internal.InternalLeakCanary class
    │    Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
    │    ↓ static InternalLeakCanary.resumedActivity
    ├─ com.sealstudios.pokemonApp.MainActivity instance
    │    Leaking: NO (FragmentContainerView↓ is not leaking and Activity#mDestroyed is false)
    │    ↓ MainActivity._binding
    ├─ com.sealstudios.pokemonApp.databinding.ActivityMainBinding instance
    │    Leaking: NO (FragmentContainerView↓ is not leaking)
    │    ↓ ActivityMainBinding.navHostFragment
    ├─ androidx.fragment.app.FragmentContainerView instance
    │    Leaking: NO (View attached)
    │    mContext instance of com.sealstudios.pokemonApp.MainActivity with mDestroyed = false
    │    View.parent androidx.constraintlayout.widget.ConstraintLayout attached as well
    │    View#mParent is set
    │    View#mAttachInfo is not null (view attached)
    │    View.mID = R.id.nav_host_fragment
    │    View.mWindowAttachCount = 1
    │    ↓ FragmentContainerView.mKeyedTags
    │                            ~~~~~~~~~~
    ├─ android.util.SparseArray instance
    │    Leaking: UNKNOWN
    │    ↓ SparseArray.mValues
    │                  ~~~~~~~
    ├─ java.lang.Object[] array
    │    Leaking: UNKNOWN
    │    ↓ Object[].[0]
    │               ~~~
    ├─ androidx.navigation.NavHostController instance
    │    Leaking: UNKNOWN
    │    ↓ NavHostController.mOnDestinationChangedListeners
    │                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ├─ java.util.concurrent.CopyOnWriteArrayList instance
    │    Leaking: UNKNOWN
    │    ↓ CopyOnWriteArrayList.array
    │                           ~~~~~
    ├─ java.lang.Object[] array
    │    Leaking: UNKNOWN
    │    ↓ Object[].[1]
    │               ~~~
    ├─ androidx.navigation.ui.ActionBarOnDestinationChangedListener instance
    │    Leaking: UNKNOWN
    │    ↓ ActionBarOnDestinationChangedListener.mContext
    │                                            ~~~~~~~~
    ├─ android.view.ContextThemeWrapper instance
    │    Leaking: UNKNOWN
    │    ContextThemeWrapper wraps an Activity with Activity.mDestroyed false
    │    ↓ ContextThemeWrapper.mBase
    │                          ~~~~~
    ├─ dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper instance
    │    Leaking: UNKNOWN
    │    ViewComponentManager$FragmentContextWrapper wraps an Activity with Activity.mDestroyed false
    │    ↓ ViewComponentManager$FragmentContextWrapper.fragment
    │                                                  ~~~~~~~~
    ╰→ com.sealstudios.pokemonApp.ui.PokemonDetailFragment instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.sealstudios.pokemonApp.ui.PokemonDetailFragment received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
    ​     key = 724affdf-d1ac-47ff-82b8-6907ced5b666
    ​     watchDurationMillis = 9052
    ​     retainedDurationMillis = 4051

    METADATA

    Build.VERSION.SDK_INT: 29
    Build.MANUFACTURER: Google
    LeakCanary version: 2.4
    App process name: com.sealstudios.pokemonApp
        Analysis duration: 14474 ms```

感谢任何帮助我只想设置需要 AppCompatActivity 的工具栏,然后正确处理它或允许系统执行它

我正在使用 Hilt 并发现这不确定它是否相关,尽管我的堆确实提到了 ContextThemeWrapper - https://github.com/google/dagger/issues/2070

【问题讨论】:

  • 能否提供 findNavController 和 NavigationUI.setupActionBarWithNavController 中的代码?
  • @D.Pereira 这些是导航架构组件方法
  • 日志似乎表明 PokemonDetailFragment 已被破坏,但仍在某处被引用。我的想法是 findNavController 返回的控制器持有这个引用

标签: android memory-leaks navigation leakcanary appcompatactivity


【解决方案1】:

对此没有任何帮助,手动设置它可以消除内存泄漏

@SuppressLint("DefaultLocale")
private fun setActionBar() {
    binding.toolbar.outlineProvider = null
    binding.appBarLayout.outlineProvider = null
    (activity as AppCompatActivity).setSupportActionBar(binding.toolbar)
    (activity as AppCompatActivity).supportActionBar.apply {
        this?.setHomeButtonEnabled(true)
        this?.setDisplayHomeAsUpEnabled(true)
    }

}

【讨论】:

    【解决方案2】:

    我认为问题出在NavigationUI.setupWithNavController() 函数上。

    我们看看下面的代码sn-p:

    public static void setupWithNavController(@NonNull Toolbar toolbar,
            @NonNull final NavController navController,
            @NonNull final AppBarConfiguration configuration) {
        navController.addOnDestinationChangedListener(
                new ToolbarOnDestinationChangedListener(toolbar, configuration));
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                navigateUp(navController, configuration);
            }
        });
    }
    

    正如我们所见,您正在为您的navController 添加一个侦听器,这是ToolbarOnDestinationChangedListener() 的一个新实例。

    因为你在activity/fragment生命周期进入Destroy状态时没有移除navController的监听器,所以这很可能是你的问题。

    【讨论】:

      【解决方案3】:

      我强烈建议在 onDestroy() 中取消设置(清空)片段中每个使用的对象,尤其是位图:

      @Override
      public void onDestroy() {
          super.onDestroy();
          // Here is my example:
          myBigBitmap = null;
          adapter = new MyAdapter(requireContext(), new String[0], new String[0], new Bitmap[0]);
          listView.setAdapter(adapter);
          
      }
      

      这是唯一正确的方法。工作 100%。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-03-05
        • 2011-09-03
        • 2020-10-24
        • 2014-12-11
        • 2018-10-14
        • 1970-01-01
        • 2011-12-31
        • 1970-01-01
        相关资源
        最近更新 更多