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