preface

Memory leak caused by dialog

Memory leaks have recently been found in several areas of the optimization project

I’ve wrapped a TipDialog to display a message at the end of a network request. The code:

    private void initDialog(Activity activity) {

        dialog = (TipDialog) new TipDialog(activity)
                .build()
                .setTipText("Loading")
                .setIconId(R.drawable.icon_load)
                .setOrientationHorizontal(false)
                .setIsLoading(true)
                .show();

    }

    public DialogCallback(Activity activity) {
        super(a); initDialog(activity); }@Override
    public void onStart(Request<T, ? extends Request> request) {
        super.onStart(request);
    }

    @Override
    public void onFinish(a) {
        // Close the dialog box after the network request ends
        if(dialog ! =null) { dialog.dismissDialog(); }}Copy the code

TipDialog:

package com.yeqiu.hydrautils.view.dialog;

import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.DialogInterface;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

import com.yeqiu.hydrautils.R;
import com.yeqiu.hydrautils.common.DensityUtils;
import com.yeqiu.hydrautils.ui.widget.marquee.MarqueeTextView;
import com.yeqiu.hydrautils.view.dialog.base.BaseDialog;

/ * * *@project: HailHydra *@author: Small paper *@date 2018/9/10
 * @describe: *@fix: * /
public class TipDialog extends BaseDialog {

    private ObjectAnimator animator;
    private ImageView imageView;
    private LinearLayout llRoot;

    public TipDialog(Activity context) {
        super(context);
    }


    @Override
    protected int getstyle(a) {

        return R.style.TipDialog;
    }

    @Override
    protected Object getDiaologlayoutIdOrView(a) {

        return R.layout.layout_tip_dialog;
    }

    @Override
    protected void initView(View view) {

        llRoot = (LinearLayout) view.findViewById(R.id.ll_tip_dialog_root);
        if (dialogBuilder.getOrientationHorizontal()) {
            llRoot.setOrientation(LinearLayout.HORIZONTAL);
            RelativeLayout.LayoutParams rootLayoutParams = (RelativeLayout.LayoutParams) llRoot
                    .getLayoutParams();
            rootLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
            rootLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
            llRoot.setLayoutParams(rootLayoutParams);

        } else {
            llRoot.setOrientation(LinearLayout.VERTICAL);
            RelativeLayout.LayoutParams rootLayoutParams = (RelativeLayout.LayoutParams) llRoot
                    .getLayoutParams();
            rootLayoutParams.width = DensityUtils.dp2px(100);
            rootLayoutParams.height = DensityUtils.dp2px(100);
            llRoot.setLayoutParams(rootLayoutParams);
        }
        if(dialogBuilder.getIconId() ! = -999) {
            imageView = new ImageView(context);
            LinearLayout.LayoutParams imageViewLP = new LinearLayout.LayoutParams(DensityUtils
                    .dp2px(30), DensityUtils.dp2px(30));
            imageView.setLayoutParams(imageViewLP);
            imageView.setImageResource(dialogBuilder.getIconId());
            llRoot.addView(imageView);
        }
        if(! TextUtils.isEmpty(dialogBuilder.getTipText())) { MarqueeTextView textView =new MarqueeTextView(context);
            LinearLayout.LayoutParams tipViewLP = new LinearLayout.LayoutParams(LinearLayout
                    .LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            // Vertical layout
            tipViewLP.topMargin = dialogBuilder.getOrientationHorizontal() ? 0 : DensityUtils.dp2px(10);

            // Horizontal layout
            tipViewLP.leftMargin = dialogBuilder.getOrientationHorizontal() ? DensityUtils.dp2px  (10) : 0;
            textView.setLayoutParams(tipViewLP);
            textView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
            textView.setSingleLine();
            textView.setGravity(Gravity.CENTER);
            textView.setTextColor(ContextCompat.getColor(context, R.color.color_white));
            textView.setTextSize(14); textView.setText(dialogBuilder.getTipText()); llRoot.addView(textView); }}public void startAnimate(a) {

        if(imageView ! =null) {
            animator = ObjectAnimator.ofFloat(imageView, "rotation".0f.360f);
            animator.setDuration(1000);
            animator.setInterpolator(new LinearInterpolator());
            animator.setRepeatCount(ObjectAnimator.INFINITE);
            animator.setRepeatMode(ObjectAnimator.RESTART);
        }
        animator.start();

    }


    @Override
    public void show(a) {

        if (context == null || context.isFinishing()) {
            return;
        }

        super.show();

        if (dialogBuilder.isLoading()) {
            startAnimate();
        }

        int dismissTime = dialogBuilder.getDismissTime();

        if(dismissTime ! =0&& llRoot ! =null) {
            llRoot.postDelayed(new Runnable() {
                @Override
                public void run(a) {
                    dismiss();
                }
            }, dismissTime);
        }


        dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                if(animator ! =null) { animator.end(); }}}); }public void dismiss(a) {
        if(dialog ! =null&& context ! =null&&! context.isFinishing()) { dialog.dismiss(); }}}Copy the code

Leaked information:

This seems fine, but as SOON as I quickly get to the exit page, LeakCanary reports the leak, tipdialog.context. The prompt message is easy to understand. When the page disappears, TipDialog holds the context, which is the current Activity. TipDialog is not destroyed, so the Activity cannot be recycled.

I want to set the context to null when the dialog disappears or the network request ends. Run it again and it doesn’t work. I guess there might be a problem with the dialog I encapsulated, but on closer inspection it doesn’t seem to be a problem. I tried using the system’s ProgressDialog.

private void initDialog(Activity activity) {
        dialog = new ProgressDialog(activity);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setCanceledOnTouchOutside(false);
        dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        dialog.setMessage("Requesting network...");
    }

    public DialogCallback(Activity activity) {
        super(a); initDialog(activity); }@Override
    public void onStart(Request<T, ? extends Request> request) {
        if(dialog ! =null&&! dialog.isShowing()) { dialog.show(); }}@Override
    public void onFinish(a) {
        // Close the dialog box after the network request ends
        if(dialog ! =null&& dialog.isShowing()) { dialog.dismiss(); }}Copy the code

Hit Run, and sure enough, no leaks. Continuing with the leak, message.obj seems to have something to do with Handler. But I’ve never used Handler in any of my code. Finally, I saw a blog post on the web explaining how dialog listening is implemented. The system uses Handler to mediate events back and forth. Blog posts are using DialogInterface. An OnClickListener () cause memory leaks, the final solution is to do a parcel class, in the dialog disappears when the DialogInterface. An OnClickListener () set to null. After a wave analysis, I know the reason for the leak here, because I set the dialog to listen for the disappearance, and stop the animation of the circle after the disappearance.

 dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                if(animator ! =null) { animator.end(); }}}); }Copy the code

I tried to dismiss The onDismiss Listener to NULL in the Dialog’s onDismiss listener, but that still doesn’t work. Here is my final solution

    public void clearOnDetach(final Dialog dialog) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            dialog.getWindow()
                    .getDecorView()
                    .getViewTreeObserver()
                    .addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() {
                        @Override
                        public void onWindowAttached(a) {}@Override
                        public void onWindowDetached(a) { onDialogDismiss(); }}); }}protected void onDialogDismiss(a) {
        if(dialogBuilder.getDialogListener() ! =null) { dialogBuilder.getDialogListener().onDialogDismiss(); }}Copy the code

The dialog is no longer monitored for vanishing, but the dialog window is monitored out of view. Retest, no more leakage. Code has been added to HailHydr luxury lunch.

Related information:

A memory leak caused a bloodbath. -Square

HailHydra