J'ai un BottomNavigationView avec 3 onglets. À chaque clic d'onglet, il appelle .replace()

private void initBottomNavigation() {

        mBottomNavigation.setOnNavigationItemSelectedListener(menuItem -> {
            switch (menuItem.getItemId()) {
                case R.id.bottomnav_admin_home:                <---- Tab 1
                        getSupportFragmentManager().beginTransaction()
                                .replace(
                                        R.id.admin_fragment_container,
                                        new AdminHomeFragment(),
                                        Constants.FRAGMENT_ADMIN_HOME
                                )
                                .commit();
                    }

                    ...Tab two
                    ....Tab three

                    return true;
    ```

Dans mon troisième onglet, j'ai un bouton qui ajoute un fragment au-dessus du conteneur de fragments:

enter image description here

Lorsque je clique sur le bouton, cela appelle ceci pour ajouter un autre fragment au-dessus du conteneur de fragments:

getParentFragmentManager().beginTransaction()
                .add(
                        R.id.admin_fragment_container,
                        new DeactivateUserFragment(),
                        Constants.FRAGMENT_ADMIN_DEACTIVATE_USER
                )
                .addToBackStack(null)
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
                .commit();

Lorsque je suis sur le fragment ajouté , je clique sur le deuxième onglet qui appelle .replace(), cela provoque une fuite de mémoire dans mon fragment ajouté .

J'ai réduit tout le code dans le fragment ajouté, mais je ne peux pas comprendre pourquoi il fuit toujours.

Code entier pour le fragment ajouté:

public class DeactivateUserFragment extends Fragment{
    private static final String TAG = "FragmentDeactivatedUser";

    @BindView(R.id.recycler_view) RecyclerView mRecycler;

    @BindView(R.id.parent_layout) ViewGroup mParentLayout;

    @BindView(R.id.progress_bar) ProgressBar mProgressBar;

    private Context                mContext;
    private SimpleListUsersAdapter mAdapter;
    private ArrayList<UserInfo>    mList = new ArrayList<>();
    private DeactivateViewModel    mViewModel;
    private AuthStateManager mAuthStateManager;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = getContext();

    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_admin_deactivate_user, container, false);
        ButterKnife.bind(this, view);



        return view;
    }

    
    @OnClick(R.id.btn_back)
    public void onBackBtnClick(){
        getParentFragmentManager().popBackStack();
    }


}

La fuite:

androidx.coordinatorlayout.widget.CoordinatorLayout instance
​     Leaking: YES (ObjectWatcher was watching this because com.example.
​     testproject.ui.admin.deactivate.DeactivateUserFragment received
​     Fragment#onDestroyView() callback (references to its views should be
​     cleared to prevent leaks))
​     Retaining 106.7 kB in 1469 objects
​     key = e50280e9-58bd-4ecd-bc1b-a2d6cc188639
​     watchDurationMillis = 108539
​     retainedDurationMillis = 103532
​     View not part of a window view hierarchy
​     View.mAttachInfo is null (view detached)
​     View.mID = R.id.parent_layout
​     View.mWindowAttachCount = 1
​     mContext instance of com.example.testproject.ui.admin.AdminActivity
​     with mDestroyed = false

Modification 1: ________________________________

Pourquoi la suppression de ces trois lignes ne provoque-t-elle plus une fuite de mémoire?

@BindView(R.id.recycler_view) RecyclerView mRecycler;

    @BindView(R.id.parent_layout) ViewGroup mParentLayout;

    @BindView(R.id.progress_bar) ProgressBar mProgressBar;

Je n'utilise les variables dans aucun des codes, je les ai juste liées. Je suis extrêmement confus comment cela cause la fuite

Modifier 2: __________________________________

J'ai essayé de définir les vues sur nulles dans onDestroyView() et de dissocier Butterknife, mais des fuites persistent.

https://github.com/JakeWharton/butterknife/issues/585

 @Override
    public void onDestroyView() {
        Log.d(TAG, "onDestroyView: Called");
        unbinder.unbind();
        mRecycler = null;
        mParentLayout = null;
        mProgressBar = null;
        super.onDestroyView();
    }

J'ai essayé de supprimer les vues une par une ... barre de progression, recycleur et mParentLayout. Seulement jusqu'à ce que j'enlève les 3, ça ne fuit plus

J'ai supprimé Butterknife du fragment et n'utilise que findViewById mais j'ai toujours le même problème

4
DIRTY DAVE 28 févr. 2021 à 00:18

3 réponses

Meilleure réponse
           public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_admin_deactivate_user, container, false);
        unbinder=ButterKnife.bind(this, view);//initialise the unbinder here


      return view;
    }

    Override
    public void onDestroyView() {
    Log.d(TAG, "onDestroyView: Called");
  
    if(unbinder!=null){
        unbinder.unbind();
        unbinder = null;
    }
   
    super.onDestroyView();
    }

Essayez d'ajouter ceci dans le code de votre adaptateur de recyclage:

     @Override
        public void onDestroyView() {
        recyclerView.setAdapter(null);
       super.onDestroyView();
     }

Cela fonctionnera dans Sha Allah ta'aala

-1
Danish 28 févr. 2021 à 09:11

Le correctif définissait une variable globale pour le couteau à beurre Binder dans mon fragment et appelait Unbind dans le onDestroyView()

private Unbinder unbinder;
@Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_admin_link_family, container, false);
        
        return view;
    }

J'ai également lié mon adaptateur

private SimpleListUsersAdapter adapter;

Inside Adapter class


 class ViewHolder extends RecyclerView.ViewHolder {

        @BindView(R.id.action_type)
        TextView userAction;
        @BindView(R.id.date)
        TextView date;
        @BindView(R.id.amount)
        TextView amount;

        ViewHolder(@NonNull View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }

J'ai donc dû définir l'adaptateur sur null également dans onDestroyView

Fragment Class

@Override
    public void onDestroyView() {
        super.onDestroyView();
        adapter = null;
        unbinder.unbind();
}

Il faut faire les deux pour arrêter la fuite

0
DIRTY DAVE 8 mars 2021 à 10:58

Veuillez essayer de supprimer chaque référence initialisée dans onCreate ou onCreateView respectivement dans votre onDestroy et onDestroyView. Donc si vous avez:

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = getContext();

    }

Vous devriez également avoir:

    @Override
    public void onDestroy() {
        mContext = null;
        super.onDestroy();
    }

Je vous recommande de ne pas stocker le contexte dans les attributs car vous avez accès à la méthode getContext() partout dans une sous-classe android Fragment.

Le contexte est une source habituelle de fuite.

Je ne sais pas quels sont exactement les types de vos autres attributs mais vous devriez garder la même philosophie.

-1
Teddy Vallar 6 mars 2021 à 11:25