Android: DialogFragment memory leak

problem description

DialogFragment memory leak

the environmental background of the problems and what methods you have tried

clipboard.png

how can I fix the secondary leak?

Feb.14,2022

from this picture, the root lies in your brother's obj citation of Message, and the other citation is all WeakReference. Do you use any Handler to send messages?


solution: rewrite onActivityCreated, to set all three Listener to empty

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    dialog.setOnShowListener(null)
    dialog.setOnCancelListener(null)
    dialog.setOnDismissListener(null)
}

I hope to adopt it, thank you!

problem analysis: there is an onActivityCreated method in
DialogFragment. Android implements this method by default:

public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (this.mShowsDialog) {
            View view = this.getView();
            if (view != null) {
                if (view.getParent() != null) {
                    throw new IllegalStateException("DialogFragment can not be attached to a container view");
                }

                this.mDialog.setContentView(view);
            }

            Activity activity = this.getActivity();
            if (activity != null) {
                this.mDialog.setOwnerActivity(activity);
            }

            this.mDialog.setCancelable(this.mCancelable);
            this.mDialog.setOnCancelListener(this);
            this.mDialog.setOnDismissListener(this);
            if (savedInstanceState != null) {
                Bundle dialogState = savedInstanceState.getBundle("android:savedDialogState");
                if (dialogState != null) {
                    this.mDialog.onRestoreInstanceState(dialogState);
                }
            }

        }
    }
SetOnCancelListener and setOnDismissListener are set by default in

code

this.mDialog.setOnCancelListener(this);
this.mDialog.setOnDismissListener(this);

use setOnDismissListener to analyze, setOnCancelListener is the same:

// Dialog 
private Message mDismissMessage;

// DialogmDismissMessage;
public void setOnDismissListener(@Nullable OnDismissListener listener) {
        if (mCancelAndDismissTaken != null) {
            throw new IllegalStateException(
                    "OnDismissListener is already taken by "
                    + mCancelAndDismissTaken + " and can not be replaced.");
        }
        if (listener != null) { // listenerMessage
            mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener);
        } else {
            mDismissMessage = null;
        }
    }

definition of mListenersHandler:

private static final class ListenersHandler extends Handler {
        private final WeakReference<DialogInterface> mDialog;

        public ListenersHandler(Dialog dialog) {
            mDialog = new WeakReference<>(dialog);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DISMISS:
                    ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
                    break;
                case CANCEL:
                    ((OnCancelListener) msg.obj).onCancel(mDialog.get());
                    break;
                case SHOW:
                    ((OnShowListener) msg.obj).onShow(mDialog.get());
                    break;
            }
        }
    }

ListenersHandler mainly calls the implementation of Listener through Handler when dealing with Listener.

call location:
when the pop-up window is closed

@Override
    public void dismiss() {
        if (Looper.myLooper() == mHandler.getLooper()) {
            dismissDialog(); // 
        } else {
            mHandler.post(mDismissAction);
        }
    }
    
void dismissDialog() {
        if (mDecor == null || !mShowing) {
            return;
        }

        if (mWindow.isDestroyed()) {
            Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
            return;
        }

        try {
            mWindowManager.removeViewImmediate(mDecor);
        } finally {
            if (mActionMode != null) {
                mActionMode.finish();
            }
            mDecor = null;
            mWindow.closeAllPanels();
            onStop();
            mShowing = false;

            sendDismissMessage(); // 
        }
    }
    
    private void sendDismissMessage() {
        if (mDismissMessage != null) {
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mDismissMessage).sendToTarget(); // HandlerHandlermListenersHandler(ListenersHandler)
        }
    }

because mDismissMessage is a global variable, obj references Listener (DialogFragment), Dialog references mDismissMessage, holds references to each other, there is a number of references, resulting in a memory leak.

if there are any problems in the analysis, please correct them, thank you.

reference: https://www.cnblogs.com/endur.


@ Coolspan Brother's use is wrong. If set to empty, the displayed Dialog will be redisplayed after Activity returns to the background and then re-enters!


https://www.imooc.com/article.

Menu