Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LeakCanary DialogFragment leak detection

Everytime I try to show DialogFragment I get memory leaks.

This is how my test dialog (taken from android developers page) looks like:

public class TestDialog extends DialogFragment {

    public static TestDialog newInstance(int title) {
        TestDialog frag = new TestDialog();
        Bundle args = new Bundle();
        args.putInt("title", title);
        frag.setArguments(args);
        return frag;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        int title = getArguments().getInt("title");

        return new AlertDialog.Builder(getActivity())
                .setIcon(R.drawable.ic_action_about)
                .setTitle(title)
                .setPositiveButton(R.string.ok,
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int whichButton) {
                                //((FragmentAlertDialog)getActivity()).doPositiveClick();
                            }
                        }
                )
                .setNegativeButton(R.string.cancel,
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int whichButton) {
                                //((FragmentAlertDialog)getActivity()).doNegativeClick();
                            }
                        }
                )
                .create();
    }
}

I launch it with following code which is executed on button press:

DialogFragment newFragment = TestDialog.newInstance(R.string.company_title);
newFragment.show(getFragmentManager(), "dialog");

And here's the best part: enter image description here

How to solve this leak (or atleast hide it, because canaryleak is getting really annoying with all those notifications)?

like image 683
SMGhost Avatar asked Nov 07 '18 07:11

SMGhost


2 Answers

The reason this leak caused is the source code of DialogFragment:

@Override
    public void onActivityCreated(Bundle savedInstanceState) {
        ...
        // other codes
        ...
        mDialog.setCancelable(mCancelable);
        // hear is the main reason
        mDialog.setOnCancelListener(this);
        mDialog.setOnDismissListener(this);
        ...
        // other codes
        ...
    }

Let's see what happened in function Dialog.SetOnCancelListener(DialogInterface.OnCancelListener):

/**
     * Set a listener to be invoked when the dialog is canceled.
     *
     * <p>This will only be invoked when the dialog is canceled.
     * Cancel events alone will not capture all ways that
     * the dialog might be dismissed. If the creator needs
     * to know when a dialog is dismissed in general, use
     * {@link #setOnDismissListener}.</p>
     * 
     * @param listener The {@link DialogInterface.OnCancelListener} to use.
     */
    public void setOnCancelListener(@Nullable OnCancelListener listener) {
        if (mCancelAndDismissTaken != null) {
            throw new IllegalStateException(
                    "OnCancelListener is already taken by "
                    + mCancelAndDismissTaken + " and can not be replaced.");
        }
        if (listener != null) {
            // here
            mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);
        } else {
            mCancelMessage = null;
        }
    }

And, here is the source code of Handler.obtainMessage(int, Object):

    /**
     * 
     * Same as {@link #obtainMessage()}, except that it also sets the what and obj members 
     * of the returned Message.
     * 
     * @param what Value to assign to the returned Message.what field.
     * @param obj Value to assign to the returned Message.obj field.
     * @return A Message from the global message pool.
     */
    public final Message obtainMessage(int what, Object obj)
    {
        return Message.obtain(this, what, obj);
    }

Finally, function Message.obtain(Handler, int, Object) will be called:

    /**
     * Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, and <em>obj</em>
     * members.
     * @param h  The <em>target</em> value to set.
     * @param what  The <em>what</em> value to set.
     * @param obj  The <em>object</em> method to set.
     * @return  A Message object from the global pool.
     */
    public static Message obtain(Handler h, int what, Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.obj = obj;

        return m;
    }

We can see that cancelMessage holds the instance of DialogFragment, which leads to memory leak. I just want to let you know this, and I have no way to avoid it except DO NOT USE DialogFragment. Or someone who has better solutions please let me know.

like image 180
EmMper Avatar answered Sep 28 '22 03:09

EmMper


In case someone still bumps into this issue: I fixed this by updating leakcanary to the latest version (2.4 at this point). Seems like it was a false-positive detection. I was using leakcanary 2.0beta-3.

like image 32
Rene Avatar answered Sep 28 '22 01:09

Rene