【问题标题】:setHasOptionsMenu in Fragment causes MEMORY LEAKFragment 中的 setHasOptionsMenu 导致 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时,那是什么意思?

标签: android performance android-layout android-fragments memory-leaks


【解决方案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

【讨论】:

    猜你喜欢
    • 2018-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-20
    • 2022-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多