Android性能优化实战(三)----避免内存泄露

Android性能优化中一个非常重要的点就是内存泄露,内存泄露一般情况下并不会影响程序的正常运行,但是它会导致程序的内存占用率过高,随之而至的很可能就是内存溢出。通过这篇博客记录一下项目中避免内存泄露的一些方法。

Android常见的内存泄露场景

单例造成的内存泄漏

  单例的特点是其生命周期和Application一样长,如果使用不当,很容易引起内存泄露,比如下面这个例子:

private static DLNAManager mDLNAManager = null;
public static DLNAManager getInstance(Context context) {
    if (mDLNAManager == null) {
        mDLNAManager = new DLNAManager(context);
    }
    return mDLNAManager;
}

  当创建DLNAManager这个单例的时候,需要传入一个Context,如果传入的是一个Activity的Context,当这个Activity退出的时候,由于该Context仍然被静态单例引用mDLNAManager持有,所以Activity的内存得不到释放,这就导致了内存泄露。正确的方式应该是这样的:

private static DLNAManager mDLNAManager = null;
private Context mContext;
private DLNAManager(Context context) {
    mContext = context.getApplicationContext();
}
public static DLNAManager getInstance(Context context) {
    if (mDLNAManager == null) {
        mDLNAManager = new DLNAManager(context);
    }
    return mDLNAManager;
}

非静态内部类 — Handler、AsyncTask

  在Java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用(通常是Activity),所以在内部类未释放时,外围对象也不会被释放,从而造成内存泄漏。而在Android开发过程中,我们通常会创建Handler来处理一些回调,例如下面的例子:
public class FaceShowHandler extends Handler {

@Override
public void handleMessage(Message msg) {
    ......
}

}
  这样就创建了一个Handler的非静态的内部类,而Handler、Message 和 MessageQueue 都是相互关联在一起的,一旦Handler发送的Message没有得到处理(调用postDelayed()),该 Message 和 Handler 对象将被线程 MessageQueue 一直持有,那么外部类对象Activity在退出时,还是被Handler持有着,无法得到释放。
因此,在 Activity 中避免使用非静态内部类,我们应该将 Handler 声明为静态的,这样Handler的存活期跟 Activity 的生命周期解耦了,同时通过弱引用的方式引入 Activity,避免直接将 Activity 作为 context 传进去:

public static class FaceShowHandler extends Handler {

    private final WeakReference<FaceShowActivity> mActivity;

    public FaceShowHandler(FaceShowActivity activity) {
        mActivity = new WeakReference<FaceShowActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        FaceShowActivity activity = mActivity.get();
        if(activity != null) {
        ......
        }
    }
}

  如上代码所示,Activity退出时可以得到正确释放,从而避免了 Activity 泄漏,但是 Looper 线程的消息队列中还是可能存在未处理的Message,所以我们在 Activity 的 onDestroy() 或者 onStop中可以采用以下几个方法清除 MessageQueue 中的Message:

public final void removeCallbacks(Runnable r);
public final void removeCallbacks(Runnable r, Object token);
public final void removeCallbacksAndMessages(Object token);
public final void removeMessages(int what);
public final void removeMessages(int what, Object object);

  对于AsyncTask我们也经常采用的是非静态内部类的形式,同样的,在Activity退出的时候,doInBackground()这个子线程可以还没有执行完,Asynctask继续持有Activity的实例,无法得到正确地释。为了避免内存泄露,和Handler的处理一样,将非静态内部类改为静态内部类+WeakReference的方式。同时,在Activity的onDestroy()中根据实际情况决定是否需要cancel掉Asynctask。
  

Bitmap造成的OOM

  Android谈到内存,就不得不提Bitmap这一超级内存占用大户,Bitmap的使用不当,很容易引起OOM。在开发过程中,为了提高读取速度,我们经常将Bitmap缓存起来,如果缓存的Bitmap数量很多时占用的内存就非常的大,发生OOM的概率就成倍增加,为此,我们可以采取以下措施来减少此类情况的发生:
1、通过设置Options.inSampleSize 对大图片进行压缩,可先设置Options.inJustDecodeBounds,获取Bitmap的外围数据,宽和高等。然后计算压缩比例,进行压缩;
2、如果不需要透明度,可把默认值ARGB_8888改为RGB_565,节约一半内存;
3、在Bitmap不再使用时(ps:一定是不在使用之后才能回收,不然会报异常)尽快将Bitmap资源回收:

  if(bitmap != null && !bitmap.isRecycled()){
      bitmap.recycle();
      bitmap = null;
  }

4、对于加载Bitmap的照片资源时,强烈推荐使用Glide等三方图片资源加载框架,减少代码量的同时极大地提高内存使用效率。

资源未关闭造成的内存泄漏
  和Bitmap资源回收的情况一样,对于注册了BraodcastReceiver,ContentObserver等资源的使用,应该在Activity销毁时及时关闭或者注销掉,否则这些资源不会主动释放,造成内存泄漏。

内存泄露检测工具

  Java检测内存泄露工具很多,最常使用的是MAT(Memory Analysis Tools),不过对于Android项目,推荐使用LeakCanary(https://github.com/square/leakcanary),因为使用非常简单,一般的内存泄露修复,大多需要经过以下步骤:
1、发现内存泄露问题。
2、重现问题;
3、在未发生内存泄露的时候,取出此时内存hprof文件;
4、在发生内存泄露的时候,取出此时内存hprof文件;
5、对比两份文件,发现不同点;
6、修复。
如果使用LeakCanary的话,使用步骤将得到极大简化:
1、在Gradle中添加依赖

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'

2、在定义的Application类中加入以下代码

    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        LeakCanary.install(this);
        // Normal app init code...
}

3、接下来,无需做更多工作,像平时一样正常使用app,发生内存泄漏时,LeakCanary会给出提示

这里写图片描述

点进进去,具体哪行代码发生内存泄露一目了然

这里写图片描述

4、此外,LeakCanary会在Download下将每次的内存泄露自动生成hprof文件,这样也通过MAT工具对这些hprof文件进行分析。

这里写图片描述

总结

Android的内存泄漏问题,可以总结为以下几点:
1、尽量不要在静态变量(单例)或者静态内部类中传入非静态外部成员变量(例如context ),一定要使用,在适当时候把外部成员变量置null;对于内部类中可以使用弱引用来引用外部类的变量。
2、对 Activity 等组件的引用,其生命周期如果比Acitivity更长时,考虑使用 getApplicationContext 或者 getApplication,从而保证Activity退出时其引用可以得到正常释放;
3、生命周期比Activity还长的非静态内部类(Handler、Asynctask等)最好改为静态内部类+WeakReference的形式,在Activity退出时,根据实际情况清除Handler中的Message,取消未完成的Asynctask异步任务;
4、使用Bitmap时,考虑使用RGB_565格式,对Bitmap进行压缩处理,使用完Bitmap 调用 recycle(),然后置为null,清空对图片等资源有直接引用或者间接引用的数组、List、Map等;
5、正确关闭资源,如果使用了BraodcastReceiver,ContentObserver等资源,应该在Activity销毁时及时关闭或者注销。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值