Bottomnavigationview.selectedlistener内存泄漏

Bottomnavigationview.selectedlistener memory leak

目前,我正在调查我的应用程序中的一些内存泄漏问题。我遇到的问题之一是我的 bottomnavigationview 以某种方式导致内存泄漏。我的想法是,问题可能取决于(可能)错误地使用我的 activity 或片段中的数据绑定变量或我的扩展函数的使用。但这很奇怪,因为我使用的是视图绑定/数据绑定委托,它应该可以防止这种内存泄漏。

Activity

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private var _binding: ActivityMainBinding? = null
    private val binding get() = _binding!!

    override fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(savedInstanceState)
        _binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        changeBottomNavWhenUserLoggedIn(true)
    }

    private fun changeBottomNavWhenUserLoggedIn(loggedIn: Boolean) {
        if (loggedIn){
            setUpBottomNav(R.menu.bottom_nav_menu_logged_in)
        } else {
            setUpBottomNav(R.menu.bottom_nav_menu_logged_out)
        }
    }

    private fun setUpBottomNav(newMenu: Int) {
        with(binding.bottomNavigationView) {
            menu.clear()
            inflateMenu(newMenu)
            setupWithNavController(findNavController(R.id.fragment_container))
        }
    }

    fun hideBottomNav() {
        binding.bottomNavigationView.visibility = View.GONE
    }

    fun showBottomNav() {
        binding.bottomNavigationView.visibility = View.VISIBLE
    }

    fun isBottomNavVisible(): Boolean {
        return binding.bottomNavigationView.visibility == View.VISIBLE
    }

    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }
}

基础片段

abstract class BaseCalibrateRepairFragment(layout: Int) : Fragment(layout) {
    abstract val emailBinding: ViewDataBinding

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        hideBottomNav() <-- This is code called from the activity
    } 
}

校准DataoverviewFragment

@AndroidEntryPoint
class CalibrateRepairDataOverviewFragment : BaseCalibrateRepairFragment(
    R.layout.fragment_calibrate_repair_data_overview,
) {
    // using com.kirich1409.viewbindingpropertydelegate:viewbindingpropertydelegate
    override val emailBinding: FragmentCalibrateRepairDataOverviewBinding by viewBinding()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        bindObjects()
    }

    private fun bindObjects() = with(emailBinding) {
        lifecycleOwner = viewLifecycleOwner
        viewModel = emailViewModel
        outsideTT = toolbarTitle
    }
}

扩展函数(在basefragment中使用class)

fun Fragment.hideBottomNav() {
    if ((requireActivity() as MainActivity).isBottomNavVisible()) (requireActivity() as MainActivity).hideBottomNav()
}

LeakCanary 日志

D/LeakCanary: ====================================
D/LeakCanary: HEAP ANALYSIS RESULT
D/LeakCanary: ====================================
D/LeakCanary: 1 APPLICATION LEAKS
D/LeakCanary: References underlined with "~~~" are likely causes.
D/LeakCanary: Learn more at https://squ.re/leaks.
D/LeakCanary: 24574 bytes retained by leaking objects
D/LeakCanary: Signature: f4ffffe80b845e518ea3bc4f5c13cdac5bc76
D/LeakCanary: ┬───
D/LeakCanary: │ GC Root: Global variable in native code
D/LeakCanary: │
D/LeakCanary: ├─ dalvik.system.PathClassLoader instance
D/LeakCanary: │    Leaking: NO (InternalLeakCanary↓ is not leaking and A ClassLoader is never leaking)
D/LeakCanary: │    ↓ PathClassLoader.runtimeInternalObjects
D/LeakCanary: ├─ java.lang.Object[] array
D/LeakCanary: │    Leaking: NO (InternalLeakCanary↓ is not leaking)
D/LeakCanary: │    ↓ Object[].[3322]
D/LeakCanary: ├─ leakcanary.internal.InternalLeakCanary class
D/LeakCanary: │    Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
D/LeakCanary: │    ↓ static InternalLeakCanary.resumedActivity
D/LeakCanary: ├─ com.example3000.app.framework.ui.view.MainActivity instance
D/LeakCanary: │    Leaking: NO (BottomNavigationView↓ is not leaking and Activity#mDestroyed is false)
D/LeakCanary: │    mApplication instance of com.example3000.app.App
D/LeakCanary: │    mBase instance of androidx.appcompat.view.ContextThemeWrapper, not wrapping known Android context
D/LeakCanary: │    ↓ MainActivity.mainBinding
D/LeakCanary: ├─ com.example3000.app.databinding.ActivityMainBindingImpl instance
D/LeakCanary: │    Leaking: NO (BottomNavigationView↓ is not leaking)
D/LeakCanary: │    ↓ ActivityMainBindingImpl.bottomNavigationView
D/LeakCanary: ├─ com.google.android.material.bottomnavigation.BottomNavigationView instance
D/LeakCanary: │    Leaking: NO (View attached)
D/LeakCanary: │    View is part of a window view hierarchy
D/LeakCanary: │    View.mAttachInfo is not null (view attached)
D/LeakCanary: │    View.mID = R.id.bottomNavigationView
D/LeakCanary: │    View.mWindowAttachCount = 1
D/LeakCanary: │    mContext instance of com.example3000.app.framework.ui.view.MainActivity with mDestroyed = false
D/LeakCanary: │    ↓ BottomNavigationView.selectedListener
D/LeakCanary: │                           ~~~~~~~~~~~~~~~~
D/LeakCanary: ├─ androidx.navigation.ui.NavigationUI instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 12 bytes in 1 objects
D/LeakCanary: │    Anonymous class implementing com.google.android.material.bottomnavigation.
D/LeakCanary: │    BottomNavigationView$OnNavigationItemSelectedListener
D/LeakCanary: │    ↓ NavigationUI.val$navController
D/LeakCanary: │                     ~~~~~~~~~~~~~~~~~
D/LeakCanary: ├─ androidx.navigation.NavHostController instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 39055 bytes in 1222 objects
D/LeakCanary: │    mActivity instance of com.example3000.app.framework.ui.view.MainActivity with mDestroyed = false
D/LeakCanary: │    mContext instance of dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper, wrapping
D/LeakCanary: │    activity com.example3000.app.framework.ui.view.MainActivity with mDestroyed = false
D/LeakCanary: │    ↓ NavHostController.mOnDestinationChangedListeners
D/LeakCanary: │                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
D/LeakCanary: ├─ java.util.concurrent.CopyOnWriteArrayList instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 26069 bytes in 907 objects
D/LeakCanary: │    ↓ CopyOnWriteArrayList.array
D/LeakCanary: │                           ~~~~~
D/LeakCanary: ├─ java.lang.Object[] array
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 26045 bytes in 905 objects
D/LeakCanary: │    ↓ Object[].[1]
D/LeakCanary: │               ~~~
D/LeakCanary: ├─ androidx.navigation.ui.ToolbarOnDestinationChangedListener instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 25997 bytes in 902 objects
D/LeakCanary: │    mContext instance of android.view.ContextThemeWrapper, wrapping activity com.example3000.app.framework.ui.view.
D/LeakCanary: │    MainActivity with mDestroyed = false
D/LeakCanary: │    ↓ ToolbarOnDestinationChangedListener.mContext
D/LeakCanary: │                                          ~~~~~~~~
D/LeakCanary: ├─ android.view.ContextThemeWrapper instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 25548 bytes in 891 objects
D/LeakCanary: │    mBase instance of dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper, wrapping
D/LeakCanary: │    activity com.example3000.app.framework.ui.view.MainActivity with mDestroyed = false
D/LeakCanary: │    ContextThemeWrapper wraps an Activity with Activity.mDestroyed false
D/LeakCanary: │    ↓ ContextThemeWrapper.mBase
D/LeakCanary: │                          ~~~~~
D/LeakCanary: ├─ dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 25424 bytes in 885 objects
D/LeakCanary: │    mBase instance of com.example3000.app.framework.ui.view.MainActivity with mDestroyed = false
D/LeakCanary: │    ViewComponentManager$FragmentContextWrapper wraps an Activity with Activity.mDestroyed false
D/LeakCanary: │    ↓ ViewComponentManager$FragmentContextWrapper.fragment
D/LeakCanary: │                                                  ~~~~~~~~
D/LeakCanary: ╰→ com.example3000.app.framework.ui.view.fragments.home.calibrateAndRepair.CalibrateRepairDataOverviewFragment instance
D/LeakCanary: ​     Leaking: YES (ObjectWatcher was watching this because com.example3000.app.framework.ui.view.fragments.home.
D/LeakCanary: ​     calibrateAndRepair.CalibrateRepairDataOverviewFragment received Fragment#onDestroy() callback and
D/LeakCanary: ​     Fragment#mFragmentManager is null)
D/LeakCanary: ​     Retaining 24574 bytes in 851 objects
D/LeakCanary: ​     key = f8abb21d-5cf1-4e09-964a-07f5f5099164
D/LeakCanary: ​     watchDurationMillis = 8307
D/LeakCanary: ​     retainedDurationMillis = 3305
D/LeakCanary: ​     componentContext instance of dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper,
D/LeakCanary: ​     wrapping activity com.example3000.app.framework.ui.view.MainActivity with mDestroyed = false
D/LeakCanary: ====================================

应用堆转储

我遇到了同样的问题。

NavigationUI.setupWithNavController(this, navController, configuration)

public final class NavigationUI {
   navController.addOnDestinationChangedListener(...)

这个监听器在 navigationContoller 存活时保持存活。 此侦听器还引用了工具栏,这就是为什么片段及其所有组件不会被 GC 清除的原因。这看起来像内存泄漏。但是:

 class ToolbarOnDestinationChangedListener(..){
    private final WeakReference<Toolbar> mToolbarWeakReference;

所以,看起来像是内存泄漏,但实际上不是...)