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;
所以,看起来像是内存泄漏,但实际上不是...)
目前,我正在调查我的应用程序中的一些内存泄漏问题。我遇到的问题之一是我的 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;
所以,看起来像是内存泄漏,但实际上不是...)