Android 泄漏检测

Android 泄漏检测,android,android-dialogfragment,leakcanary,Android,Android Dialogfragment,Leakcanary,每次我试图显示DialogFragment时,都会出现内存泄漏 这就是我的测试对话框(取自android开发者页面)的样子: public class TestDialog extends DialogFragment { public static TestDialog newInstance(int title) { TestDialog frag = new TestDialog(); Bundle args = new Bundle();

每次我试图显示DialogFragment时,都会出现内存泄漏

这就是我的测试对话框(取自android开发者页面)的样子:

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();
    }
}
我使用以下代码启动它,该代码在按下按钮时执行:

DialogFragment newFragment = TestDialog.newInstance(R.string.company_title);
newFragment.show(getFragmentManager(), "dialog");
这里是最好的部分:


如何解决此泄漏(或者至少隐藏它,因为canaryleak对所有这些通知越来越讨厌)?

此泄漏导致的原因是
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
        ...
    }
让我们看看函数
Dialog.SetOnCancelListener(DialogInterface.OnCancelListener)
中发生了什么:

最后,将调用函数
Message.get(Handler、int、Object)

    /**
     * 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;
    }
/**
*与{@link#get()}相同,但设置目标、what和obj的值
*成员们。
*@param h要设置的目标值。
*@param要设置的值是多少。
*@param obj要设置的对象方法。
*@从全局池返回消息对象。
*/
获取公共静态消息(处理程序h、int-what、对象obj){
消息m=获取();
m、 目标=h;
m、 什么=什么;
m、 obj=obj;
返回m;
}

我们可以看到,
cancelMessage
保存了
DialogFragment
的实例,这导致了内存泄漏。我只想让你知道这一点,除了不要使用
DialogFragment
,我没有办法避免它。或者有更好的解决方案的人请告诉我。

如果有人仍然遇到这个问题:我通过将leakcanary更新到最新版本(目前为2.4)来解决这个问题。好像是假阳性检测。我使用的是leakcanary 2.0beta-3。

基于@EmMper的答案。如果您不需要onCancelListener,这里有一个解决方案

import android.app.Activity
import android.os.Bundle
import androidx.annotation.MainThread
import androidx.fragment.app.DialogFragment

open class PatchedDialogFragment : DialogFragment() {

    @MainThread
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        // Fixing the issue described here
        // https://stackoverflow.com/questions/53185154/leakcanary-dialogfragment-leak-detection
        val initialShowsDialog = showsDialog
        showsDialog = false
        super.onActivityCreated(savedInstanceState)
        showsDialog = initialShowsDialog

        if (!showsDialog) {
            return
        }
        val view = view
        if (view != null) {
            check(view.parent == null) { "DialogFragment can not be attached to a container view" }
            dialog!!.setContentView(view)
        }
        val activity: Activity? = activity
        if (activity != null) {
            dialog!!.ownerActivity = activity
        }
        dialog!!.setCancelable(isCancelable)
        if (savedInstanceState != null) {
            val dialogState =
                savedInstanceState.getBundle("android:savedDialogState")
            if (dialogState != null) {
                dialog!!.onRestoreInstanceState(dialogState)
            }
        }
    }
}

只需扩展PatchedDialogFragment而不是DialogFragment。

我通过从自定义的
DialogFragment
实现中删除
OnDismissListener
OnCancelListener
来消除此漏洞。
我还必须将null传递给负按钮侦听器:
.setNegativeButton(R.string.cancel,null)

代码是否驻留在您发布的可运行文件中?如果是这样的话,我想你可以忽略这一点。我不确定我是否明白。这只是一个普通的setOnClickListener按钮。
    /**
     * 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;
    }
import android.app.Activity
import android.os.Bundle
import androidx.annotation.MainThread
import androidx.fragment.app.DialogFragment

open class PatchedDialogFragment : DialogFragment() {

    @MainThread
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        // Fixing the issue described here
        // https://stackoverflow.com/questions/53185154/leakcanary-dialogfragment-leak-detection
        val initialShowsDialog = showsDialog
        showsDialog = false
        super.onActivityCreated(savedInstanceState)
        showsDialog = initialShowsDialog

        if (!showsDialog) {
            return
        }
        val view = view
        if (view != null) {
            check(view.parent == null) { "DialogFragment can not be attached to a container view" }
            dialog!!.setContentView(view)
        }
        val activity: Activity? = activity
        if (activity != null) {
            dialog!!.ownerActivity = activity
        }
        dialog!!.setCancelable(isCancelable)
        if (savedInstanceState != null) {
            val dialogState =
                savedInstanceState.getBundle("android:savedDialogState")
            if (dialogState != null) {
                dialog!!.onRestoreInstanceState(dialogState)
            }
        }
    }
}