前言
dialog引起的内存泄露
最近在优化项目发现好几个地方出现了内存泄露
我封装了一个TipDialog,用在网络请求时候显示,请求结束时消息。上代码:
private void initDialog(Activity activity) {
dialog = (TipDialog) new TipDialog(activity)
.build()
.setTipText("正在加载")
.setIconId(R.drawable.icon_load)
.setOrientationHorizontal(false)
.setIsLoading(true)
.show();
}
public DialogCallback(Activity activity) {
super();
initDialog(activity);
}
@Override
public void onStart(Request<T, ? extends Request> request) {
super.onStart(request);
}
@Override
public void onFinish() {
//网络请求结束后关闭对话框
if (dialog != null ) {
dialog.dismissDialog();
}
}
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:小卷子
* @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() {
return R.style.TipDialog;
}
@Override
protected Object getDiaologlayoutIdOrView() {
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);
//垂直布局
tipViewLP.topMargin = dialogBuilder.getOrientationHorizontal() ? 0 : DensityUtils.dp2px(10);
//水平布局
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() {
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() {
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() {
dismiss();
}
}, dismissTime);
}
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
if (animator != null) {
animator.end();
}
}
});
}
public void dismiss() {
if (dialog != null && context != null && !context.isFinishing()) {
dialog.dismiss();
}
}
}
泄露的信息:
![3212115-223cd1db7be0f79e.jpg](https://upload-images.jianshu.io/upload_images/3212115-223cd1db7be0f79e.jpg)
看上去似乎没问题,但是在我快速进入退出页面的时候,leakcanary就报出了泄露的地方,TipDialog.context。提示消息很好理解,当页面消失的时候 TipDialog持有了context,这里的context就是当前Activity。TipDialog没有销毁导致Activity无法回收。
知道问题就开始分析,我想在dialog消失或者网络请求结束的时候把context置为null。再次运行,并没有效果。猜想可能是我封装的dialog有问题,仔细检查好像似乎并没有问题。我尝试用系统的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("请求网络中...");
}
public DialogCallback(Activity activity) {
super();
initDialog(activity);
}
@Override
public void onStart(Request<T, ? extends Request> request) {
if (dialog != null && !dialog.isShowing()) {
dialog.show();
}
}
@Override
public void onFinish() {
//网络请求结束后关闭对话框
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
}
点击运行,嗯 果然没有泄露。继续看泄露信息,message.obj 这个似乎好像是和Handler有关。可是我的代码里都没有用过Handler。最后在网上看到一篇博文介绍了dialog监听的实现方式。系统内部通过Handler来回调事件。博文里使用的是 DialogInterface.OnClickListener()造成内存泄露,最后的解决办法是做了一个包裹类,在dialog消失的时把 DialogInterface.OnClickListener()置为null。分析一波后就知道我这里泄露的原因,因为我设置dialog消失的监听,在消失后停止转圈的动画。
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
if (animator != null) {
animator.end();
}
}
});
}
我尝试在dialog的onDismiss监听里把OnDismissListener置为null,但是还是没有解决。这里贴出我最后的解决办法
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() {
}
@Override
public void onWindowDetached() {
onDialogDismiss();
}
});
}
}
protected void onDialogDismiss() {
if (dialogBuilder.getDialogListener() != null) {
dialogBuilder.getDialogListener().onDialogDismiss();
}
}
这里不再设置dialog消失的监听,但是对dialog的window做了脱离视图的监听。重新测试,没有在出现泄漏。代码已经加入HailHydr豪华午餐。
相关资料: