Fragment 中的 setHasOptionsMenu 导致 MEMORY LEAK
Posted
技术标签:
【中文标题】Fragment 中的 setHasOptionsMenu 导致 MEMORY LEAK【英文标题】:setHasOptionsMenu in Fragment causes MEMORY LEAK 【发布时间】:2020-06-30 20:43:32 【问题描述】:我不知道为什么,但是在片段中的 onCreate 中调用 setHasMenuOption(true) 会导致内存泄漏。
我尝试在 onDestroyView 中调用 setHasMenuOption(false),但没有成功。
不调用 setHasMenuOption() 不会导致任何内存泄漏..
如何避免这种泄漏??
我使用 Fragment 作为容器来托管其他 Fragment。
Host Fragment 类 --
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View?
return inflater.inflate(R.layout.fragment_nested_fragments_container, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
super.onViewCreated(view, savedInstanceState)
val actionBarDrawerToggle = ActionBarDrawerToggle(
activity,
drawerLayout,
toolbar,
R.string.open_drawer,
R.string.close_drawer
)
drawerLayout.addDrawerListener(actionBarDrawerToggle)
actionBarDrawerToggle.syncState()
setToolbarTitle("Restaurants")
setNavigationItemClicks()
//calling fragment HOME_FRAGMENT within container fragment
childFragmentManager.beginTransaction()
.replace(R.id.nestedFragmentsContainer, HomeFragment(), FragmentsTag.HOME_FRAGMENT)
.commit()
// setting container fragment as primary navigation fragment
fragmentManager?.beginTransaction()?.setPrimaryNavigationFragment(this)?.commit()
override fun onOptionsItemSelected(item: MenuItem): Boolean
return if (item.itemId == android.R.id.home)
drawerLayout.openDrawer(GravityCompat.START)
true
else
false
private fun setNavigationItemClicks()
navigationView.setNavigationItemSelectedListener
when (it.itemId)
R.id.home ->
changeFragment(HomeFragment(), FragmentsTag.HOME_FRAGMENT)
setToolbarTitle("Restaurants")
R.id.profile ->
changeFragment(
ProfileFragment(),
FragmentsTag.PROFILE_FRAGMENT
)
setToolbarTitle("Profile")
R.id.favorites ->
changeFragment(
FavoritesFragment(),
FragmentsTag.FAVORITES_FRAGMENT
)
setToolbarTitle("Favorites")
R.id.faq ->
changeFragment(FaqFragment(), FragmentsTag.FAQ_FRAGMENT)
setToolbarTitle("FAQs")
R.id.logout -> showLogoutConfirmationDialog()
return@setNavigationItemSelectedListener true
private fun setToolbarTitle(title: String)
val activity = (activity as AppCompatActivity)
activity.setSupportActionBar(toolbar)
activity.supportActionBar?.title = title
private fun changeFragment(fragment: Fragment, tag: String)
drawerLayout.closeDrawer(GravityCompat.START)
childFragmentManager.beginTransaction()
.replace(R.id.nestedFragmentsContainer, fragment, tag)
.commit()
private fun showLogoutConfirmationDialog()
drawerLayout.closeDrawer(GravityCompat.START)
MaterialAlertDialogBuilder(activity as Context, R.style.AlertDialogTheme).apply
setTitle("Logout")
setMessage("Are you sure?")
setPositiveButton("Ok") _, _ ->
logout()
setNegativeButton("Cancel") dialog: DialogInterface?, _: Int -> dialog?.dismiss()
.show()
private fun logout()
clearSharedPref()
fragmentManager?.apply
popBackStack(
findFragmentById(R.id.fragmentContainer)?.tag,
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
beginTransaction().apply
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
replace(R.id.fragmentContainer, LoginFragment(), FragmentsTag.LOGIN_FRAGMENT)
addToBackStack(FragmentsTag.LOGIN_FRAGMENT)
.commit()
private fun clearSharedPref()
activity?.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE)?.apply
edit().clear().apply()
override fun onDestroyView()
super.onDestroyView()
clearFindViewByIdCache()
setHasOptionsMenu(false)
fragmentManager?.apply
beginTransaction().setPrimaryNavigationFragment(null).commit()
(activity as AppCompatActivity).setSupportActionBar(null)
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.example.foodrunner.activities.MainActivity instance
│ Leaking: NO (Activity#mDestroyed is false)
│ ↓ MainActivity.mFragments
│ ~~~~~~~~~~
├─ androidx.fragment.app.FragmentController instance
│ Leaking: UNKNOWN
│ ↓ FragmentController.mHost
│ ~~~~~
├─ androidx.fragment.app.FragmentActivity$HostCallbacks instance
│ Leaking: UNKNOWN
│ ↓ FragmentActivity$HostCallbacks.mFragmentManager
│ ~~~~~~~~~~~~~~~~
├─ androidx.fragment.app.FragmentManagerImpl instance
│ Leaking: UNKNOWN
│ ↓ FragmentManagerImpl.mCreatedMenus
│ ~~~~~~~~~~~~~
├─ java.util.ArrayList instance
│ Leaking: UNKNOWN
│ ↓ ArrayList.elementData
│ ~~~~~~~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ ↓ Object[].[0]
│ ~~~
╰→ com.example.foodrunner.fragments.NestedFragmentsContainerFragment instance
Leaking: YES (ObjectWatcher was watching this because com.example.foodrunner.fragments.NestedFragmentsContainerFragment received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
key = 22ecd71d-dbcd-470f-9ca3-0c992c1aada3
watchDurationMillis = 11383
retainedDurationMillis = 6382
key = adac2a58-8e2a-45f4-850b-f87ed815c9ce
watchDurationMillis = 11384
retainedDurationMillis = 6383
====================================
【问题讨论】:
你使用的是什么版本的 Fragments? @ianhanniballake 老一个。不使用导航组件。 喜欢...几岁? AndroidX 之前的版本?片段 1.0.0? 1.1.0? @ianhanniballake 我不知道片段版本。但我的项目是使用 androidx 依赖项和目标 api 29 构建的。 当你show your dependencies时,那是什么意思? 【参考方案1】:我有同样的问题,在我的情况下,将 Fragment 版本更新为 1.2.3
并没有解决内存泄漏问题。
class ExampleFragment : Fragment()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
return inflater.inflate(R.layout.fragment_example, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
super.onViewCreated(view, savedInstanceState)
configureToolbar(detailView.toolbar)
setHasOptionsMenu(true) ### This causes the memory leak
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater)
inflater.inflate(R.menu.detail_menu_example, menu)
private fun configureToolbar(toolbar: Toolbar)
(requireActivity() as AppCompatActivity).run setSupportActionBar(toolbar)
(requireActivity() as AppCompatActivity).supportActionBar.let
it?.setDisplayHomeAsUpEnabled(true)
it?.setHomeButtonEnabled(true)
toolbar.setNavigationOnClickListener
parentFragment?.apply
childFragmentManager.popBackStack()
override fun onDestroyView()
super.onDestroyView()
clearFindViewByIdCache()
setHasOptionsMenu(false)
fragmentManager?.apply
beginTransaction().setPrimaryNavigationFragment(null).commit()
(activity as AppCompatActivity).setSupportActionBar(null)
我的片段版本:
$ ./gradlew ui:androidDependencies | grep -i "fragment"
+--- androidx.fragment:fragment:1.2.3@aar
+--- androidx.fragment:fragment:1.2.3@aar
+--- androidx.fragment:fragment:1.2.3@aar
+--- androidx.fragment:fragment:1.2.3@aar
+--- androidx.fragment:fragment:1.2.3@aar
+--- androidx.fragment:fragment:1.2.3@aar
+--- androidx.fragment:fragment:1.2.3@aar
+--- androidx.fragment:fragment:1.2.3@aar
+--- androidx.fragment:fragment:1.2.3@aar
+--- androidx.fragment:fragment:1.2.3@aar
【讨论】:
以上是关于Fragment 中的 setHasOptionsMenu 导致 MEMORY LEAK的主要内容,如果未能解决你的问题,请参考以下文章
Fragment中的ViewPager的Fragment添加子Fragment要放在onAttach中添加
Fragment系列:布局xml中的fragment标签,如何隐藏?
android ViewPager+Fragment 如何在ViewPager的Activity中获取Fragment中的控件对象