【问题标题】:LeakCanary DialogFragment leak detectionLeakCanary DialogFragment 泄漏检测
【发布时间】:2018-11-07 07:33:19
【问题描述】:

每次我尝试显示 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 对所有这些通知都感到非常烦人)?

【问题讨论】:

  • 代码是否驻留在您发布的可运行文件中?如果是这样,我认为您可以忽略这一点。
  • 我不确定我是否遵循。这只是按钮上的普通 setOnClickListener。

标签: android android-dialogfragment leakcanary


【解决方案1】:

造成这次泄露的原因是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)发生了什么:

/**
     * 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;
        }
    }

还有,这里是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);
    }

最后会调用函数Message.obtain(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;
    }

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

【讨论】:

    【解决方案2】:

    万一有人仍然遇到这个问题:我通过将leakcanary 更新到最新版本(此时为2.4)解决了这个问题。似乎这是一个误报检测。我使用的是leakcanary 2.0beta-3。

    【讨论】:

      【解决方案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。

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-08-18
          • 2018-03-30
          • 2020-04-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多