尽管在 onDestroyView 中将父视图设置为 null,但片段视图内存泄漏

Fragment view memory leak despite setting parent view to null in onDestroyView

我知道向 backstack 添加一个片段事务,然后从该片段移动到另一个片段 reference of the previous fragment's view is still available,它只会在按下后退按钮时被销毁。 为了避免这种情况,I have set the view to null in onDestroyView 但问题是,leakcanary still shows view is not null and the view reference is still available 而记录视图时说它是空的。

为什么会这样? 另外,如果我错了或遗漏了什么,请纠正我。

片段class-


private var mView: View? = null
private lateinit var btnSignUp: Button

 override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        mView = inflater.inflate(R.layout.fragment_login, container, false)
        return mView
    }

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

        btnSignUp = view.findViewById(R.id.btnSignUp)

        btnSignUp.setOnClickListener {
            // calling function changeFragment()
            changeFragment(SignUpFragment(), FragmentsTag.SIGNUP_FRAGMENT)
        }
    }

override fun onDestroyView() {
        super.onDestroyView()      
         mView=null
    }

LeakCanary 分析日志 --

  HEAP ANALYSIS RESULT
    ====================================
    1 APPLICATION LEAKS

    References underlined with "~~~" are likely causes.
    Learn more at https://squ.re/leaks.

    43817 bytes retained by leaking objects
    Signature: 6e77557c8a679dd41391c1c5badaac98217366ad
    ┬───
    │ 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 (LoginFragment↓ is not leaking and Activity#mDestroyed is false)
    │    ↓ MainActivity.mFragments
    ├─ androidx.fragment.app.FragmentController instance
    │    Leaking: NO (LoginFragment↓ is not leaking)
    │    ↓ FragmentController.mHost
    ├─ androidx.fragment.app.FragmentActivity$HostCallbacks instance
    │    Leaking: NO (LoginFragment↓ is not leaking)
    │    ↓ FragmentActivity$HostCallbacks.mFragmentManager
    ├─ androidx.fragment.app.FragmentManagerImpl instance
    │    Leaking: NO (LoginFragment↓ is not leaking)
    │    ↓ FragmentManagerImpl.mActive
    ├─ java.util.HashMap instance
    │    Leaking: NO (LoginFragment↓ is not leaking)
    │    ↓ HashMap.table
    ├─ java.util.HashMap$HashMapEntry[] array
    │    Leaking: NO (LoginFragment↓ is not leaking)
    │    ↓ HashMap$HashMapEntry[].[0]
    ├─ java.util.HashMap$HashMapEntry instance
    │    Leaking: NO (LoginFragment↓ is not leaking)
    │    ↓ HashMap$HashMapEntry.value
    ├─ com.example.foodrunner.fragments.LoginFragment instance
    │    Leaking: NO (Fragment#mFragmentManager is not null)
    │    Fragment.mTag=Login Fragment
    │    ↓ LoginFragment.btnLogin
    │                    ~~~~~~~~
    ├─ com.google.android.material.button.MaterialButton instance
    │    Leaking: YES (View detached and has parent)
    │    mContext instance of com.example.foodrunner.activities.MainActivity with mDestroyed = false
    │    View#mParent is set
    │    View#mAttachInfo is null (view detached)
    │    View.mID = R.id.btnLogin
    │    View.mWindowAttachCount = 1
    │    ↓ MaterialButton.mParent
    ╰→ androidx.constraintlayout.widget.ConstraintLayout instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.example.foodrunner.fragments.LoginFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks))
    ​     key = b72a82a6-b9dd-46c6-afb2-0ea6c7025001
    ​     watchDurationMillis = 9582
    ​     retainedDurationMillis = 4582
    ​     key = 0554b63a-c700-4c86-a451-b0daae06607a
    ​     watchDurationMillis = 9581
    ​     retainedDurationMillis = 4580
    ​     mContext instance of com.example.foodrunner.activities.MainActivity with mDestroyed = false
    ​     View#mParent is null
    ​     View#mAttachInfo is null (view detached)
    ​     View.mWindowAttachCount = 1
    ====================================

@Rafsanjani 所述,您也可以使用它:

FragmentManager manager = getActivity().getSupportFragmentManager();
FragmentTransaction trans = manager.beginTransaction();
trans.remove(myFrag);
trans.commit();
manager.popBackStack();

在你的 onBackPressed 中使用这个

您在 onDestroyView 之后仍然坚持对 btnSignUp 的引用 - 是泄漏的内容。您必须在刚刚销毁的视图中删除对 all 视图的 all 引用。

因此,您应该使用相同的方法(使其成为可为空的 var),或者根本不要在您的 Fragment 中保留对 btnSignUp 的引用 - 至少在您的代码示例中,它很容易成为局部变量。 (实际上,这同样适用于您的 mView - 您将 View 作为 onViewCreated() 的输入,没有理由在片段级别保留它)。