Утечка памяти во фрагментном представлении, несмотря на установку нулевого родительского представления в onDestroyView - PullRequest
0 голосов
/ 13 марта 2020

Я знаю, что добавление транзакции фрагмента к 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, тогда как запись в представлении говорит, что он нулевой.

Почему это так? Также, пожалуйста, исправьте меня, если я ошибаюсь или что-то упустил.

Класс фрагмента -


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 Analysis -

  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
    ====================================

Ответы [ 2 ]

3 голосов
/ 14 марта 2020

Вы все еще держитесь за ссылку на btnSignUp после onDestroyView - , что - это то, что просачивается. Вы должны отбросить all ссылку на all Представления в только что разрушенном представлении.

Поэтому вы должны либо использовать тот же подход (сделать его обнуляемым var) или вообще не удерживать ссылку на btnSignUp в вашем фрагменте - по крайней мере, в вашем примере кода, это может быть локальная переменная. (Фактически, то же самое относится к вашему mView - вы получаете View в качестве входа для onViewCreated(), нет причин удерживать его на уровне фрагмента).

0 голосов
/ 13 марта 2020

Как @ Rafsanjani упомяните, что вы также можете использовать это:

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

Используйте это в вашем onBackPressed

...