Android内存泄漏核查(2020-11-25修改)

内存泄露是指,由于调用栈中持有对象的强引用,使得垃圾回收器无法回收内存中的这个对象。

一、8 种内存泄露核查

8 种内存泄露的 Demo:https://github.com/NimbleDroid/Memory-Leaks

全局进程的static变量(措施:销毁 Activity 时将静态变量置空):

  • static Activity:类中定义了静态Activity变量,泄露 Activity
  • static View:类中定义了静态 View 变量,泄露 Context -> Activity
  • static Inner Class:Activity中定义了一个非静态的内部类(包括匿名内部类),创建该内部类对象由静态变量引用;泄露 Outer Class -> Activity

活在 Activity 生命周期之外的线程(措施:清空对 Activity 的强引用):

  • Anonymous AsyncTask:匿名的 AsyncTask 在后台执行耗时任务,Activity 不幸销毁,在异步任务结束前,泄露 Outer Class -> Activity
  • Handler:定义匿名的 Runnable 发送给 Handler,或用非静态内部类 Handler 执行消息,在 Message 执行前,泄露 MessageQueue -> Message -> Handler/Runnable -> Outer Calss -> Activity
  • 非静态内部类(包括匿名内部类) Thread:在 Thread 运行结束前,泄露 OuterClass
  • 非静态内部类(包括匿名内部类) TimerTask:在 TimerTask 运行结束前,泄露 OuterClass
  • 系统服务未取消监听器:注册监听器时,服务持有了 Context 引用,销毁时如果没有取消监听器,泄露 Context

防止内存泄露的手段:

  1. 上述有 4 种都和非静态内部类有关,非静态内部类会隐式地强引用外部类,所以常常是建议使用 static 内部类,包括 static AsyncTask、Handler、Thread、TimerTask,如果坚持使用匿名类,记得在 Activity 生命周期结束时中断线程;
  2. 避免在 static 变量中保存可能引用 Context 的对象
  3. 弱引用代替强引用
  4. 在 Activity.onDestroy 时注销监听器

二、Android 中的6种内存泄漏场景

1 资源

资源性对象(Cursor,File等)往往用了缓冲,不仅存在于Java虚拟机中,还存在于Java虚拟机外。仅仅把引用设置为null,而不关闭,往往会内存泄漏。

但不会导致内存溢出,因为资源性对象的finalize方法中往往会再调用一次close方法,但效率低,频繁GC影响系统性能。

Tips:在资源性对象不再使用时,先调用close函数关闭,再置为null。

2 Handler

Handler的运行涉及到Message、MessageQuere、Looper。

主线程的Looper除非程序退出,否则一直存在。存在两条可能内存泄漏的引用链:

  1. Looper -> MessageQueue -> Message -> Handler -> Activity
  2. Looper -> MessageQueue -> Message -> Runnable -> Activity

Tips:Handler和Runnable使用静态内部类或lambda表达式创建的匿名类。

(JDK 1.8之后,通过lambda表达式创建的匿名类中,如果没有引用外部类,则编译时生成的内部类不引用外部类)

Tips:在Activity的onDestroy时,移除自定义Handler的消息。

@Override
protected void onDestroy() {
  super.onDestroy();
	handler.removeCallbacksAndMessaged(null);
}

3 Thread

线程的run方法执行结束前,线程不会销毁,可能会泄漏所引用的Activity。

Tips:创建新线程时,要么切断线程对Activity的引用,要么在Activity的onDestroy方法中停止线程。

HandlerThread可以调用quitSafely,普通Thread可以用interrupt设置中断标记来停止(stop太暴力)。

class MyThread extends Thread {
	@Override
  pulic void run() {
    while (!isInterrupted()) {
      // do something
    }
  }
}

@Override
protected void onDestroy() {
  super.onDestroy();
	mMyThread.interrupt();
}

4 Context

应用中Context数量 = Activity个数 + Service个数 + 1(ApplicationContext)

当生命周期长于 Activity/Service的对象,持有其引用时,会泄漏Context。比如,ToastUitls。

Tips:生命周期长于 Activity/Service的对象,使用ApplicationContext。

5 集合

Tips:如果确定一个键不再使用了,先从集合中remove,再置为null;或者考虑使用WeakHashMap,弱引用Key,并且会自动清除。

6 非静态内部类

三、检测内存泄漏

dumpsys

查看退出Activity和Service后,AppContexts和Activities数量是否减少

Profile

Android Studio调试时,主动触发GC后,可以再生成堆的存储信息 hprof 文件。

通过platform-tools中的hprof-conv.exe将抓取的hprof 转换成eclipse MAT中可打开的格式。

通过独立版本的eclipse MAT(Memory Analyzer Tool)定位分析发生泄漏的代码:http://www.eclipse.org/mat/downloads.php

举个例子:

public class ToastUtils {
    private static ToastUtils sInstance = null;
    private Toast mToast;

    private ToastUtils() {

    }

    public static ToastUtils getInstance() {
        if (sInstance == null) {
            sInstance = new ToastUtils();
        }
        return sInstance;
    }

    public void show(Context context, String msg, int duration) {
        if (mToast != null) {
            mToast.cancel();
            mToast = null;
        }
        mToast = Toast.makeText(context, msg, duration);

        mToast.show();
    }

    public void cancel() {
        if (mToast != null) {
            mToast.cancel();
            mToast = null;
        }
    }
}

调用show,而没有调用cancel会导致mToast保存在静态引用中,导致Context的泄漏。

public class ContextLeakActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_context_leak);
    }


    public void switchToAnotherActivity(View view) {
        ToastUtils.getInstance().show(this, "show a toast", Toast.LENGTH_LONG);
        startActivity(new Intent(this, MainActivity.class));
        finish();
    }
}

MAT 分析引用用链(只保留强引用):

image-20200517173135378

截屏2020-05-17 下午5.25.40

使用 LeakCamary 自动检测APP中的内存泄露

https://github.com/square/leakcanary

及其简单,一步到位:

dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
}

P.S. 推荐阅读

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值