Android内存泄漏总结

Android内存泄漏常见场景

监听器

场景:

public class LeaksActivity extends Activity implements LocationListener {

    private LocationManager locationManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leaks);
        locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
        locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
                TimeUnit.MINUTES.toMillis(5), 100, this);
    }
}

解释:
在这个例子中,我们让Android的 LocationManager通知我们位置更新。我们所需要做的就是获取系统服务本身和设置一个回调来接收更新。在这里,我们在Activity中实现了位置监听接口,这意味着LocationManager将持有该Activity的引用。
如果该设备被旋转,新的Activity将被创建并取代已经注册位置更新接口的旧的Activity。由于系统服务存活时间肯定比任何Activity都要长,LocationManager仍然持有以前的Activity的引用,这使GC不可能回收依赖于以前的Activity的资源,从而导致内存泄漏。
解决办法:
在Activity的onDestroy方法里,将LocationManager监听移除即可。

locationManager.removeUpdates(this);

内部类

之前有文章提到关于内部类的讲解,详细请移步到: Java内部类的总结Java内存泄漏总结
可以看出,内部类的不正当使用也是内存泄漏的一个重要场景之一。内部类可以以这样的方式来定义:即只有外部类可以实例化它们。很多人可能没有意识到的是这样的类会持有外部类的隐式引用。隐式引用很容易出错,尤其是当两个类具有不同的生命周期。
例如

 public class AsyncActivity extends Activity {

    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async);
        textView = (TextView) findViewById(R.id.textView);

        new BackgroundTask().execute();
    }

    private class BackgroundTask extends AsyncTask<Void, Void, String> {

        @Override
        protected String doInBackground(Void... params) {
            // Do background work. Code omitted.
            return "some string";
        }

        @Override
        protected void onPostExecute(String result) {
            textView.setText(result);
        }
    }
}

解释:
由于BackgroundTask持有一个AsyncActivity隐式引用并运行在另一个没有取消策略的线程上,它将保留AsyncActivity在内存中的所有资源连接,直到后台线程终止运行。

解决办法:
1. 使用静态内部类,来消除指向Activity的引用,但是这样就不能直接访问textView了,因此需要第二步;
2. 添加一个构造函数,把textView作为参数传递进来。最后,我们需要引入AsyncTask文档中所述的取消策略。
3. 但是这样还是不行,由于textView持有一个mContext的引用,为了解决这个问题,一种简单的方法是使用WeakReference。我们持有的resultTextView引用是强引用,具有防止GC回收的能力。相反,WeakReference不保证其引用的实例存活。当一个实例最后一个强引用被删除,GC会把其资源回收,而不管这个实例是否有弱引用。
最后,版本如下:

public class AsyncActivity extends Activity {

    TextView textView;
    AsyncTask task;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async);
        textView = (TextView) findViewById(R.id.textView);

        task = new BackgroundTask(textView).execute();
    }

    @Override
    protected void onDestroy() {
        task.cancel(true);
        super.onDestroy();
    }

    private static class BackgroundTask extends AsyncTask<Void, Void, String> {

        private final WeakReference<TextView> textViewReference;

        public BackgroundTask(TextView resultTextView) {
            this.textViewReference = new WeakReference<>(resultTextView);
        }

        @Override
        protected void onCancelled() {
            // Cancel task. Code omitted.
        }

        @Override
        protected String doInBackground(Void... params) {
            // Do background work. Code omitted.
            return "some string";
        }

        @Override
        protected void onPostExecute(String result) {
            TextView view = textViewReference.get();
            if (view != null) {
                view.setText(result);
            }
        }
    }
}

提醒一下:在onPostExecute我们要检查空值,判断实例是否被回收。


匿名类

匿名类和内部类有同样的缺点,即他们持有外部类的引用。如同内部类,一个匿名类在Activity生命周期之外执行或在其他线程执行工作时,可能会导致内存泄漏。

结论

后台任务独立于Activity的生命周期运行是一件麻烦事。再加上需要协调用户界面和各种后台任务之间的数据流,因此处理不好容易导致内存泄漏。
1 尽量使用静态内部类。每个非静态内部类将持有一个外部类的隐式引用,这可能会导致不必要的问题。使用静态内部类代替非静态内部类,并通过弱引用存储一些必要的生命周期引用。
2 考虑后台服务等手段, Android提供了多种在非主线程工作的方法,如HandlerThread,IntentService和AsyncTask,它们每个都有自己的优缺点。另外,Android提供了一些机制来传递信息给主线程以更新UI。譬如,广播接收器就可以很方便实现这一点。

多说两句

关于上面的结论2 为什么将内部类改为静态内部类 解释一下内部类和静态内部类的区别 以及 静态内部类的作用
首先,静态内部类可以说是内部类+一些使用约束条件,从使用的范围上来说:内部类>静态内部类
说一下区别:
1 非静态内部类不能声明静态方法和变量 静态内部类可以声明静态方法和变量
2 非静态内部类可以访问外部类的方法和变量(无论是静态的还是非静态的),即使外部类的方法和变量是private的,因为在编译的时候,非静态内部类会有一个外部类的一个成员变量,因此即使是private,非静态内部类也是可以访问的;而静态内部类不可以访问外部类的非静态成员,只能够引用外部类的静态成员。(这就是为什么将内部类改为静态内部类的原因),所以,使用静态内部类的话,可以有效防止编码人员错误的引用了外部变量,导致内存泄漏的情况。

参考

Java静态内部类(static class)详解
Android 内存泄漏查找和解决 (长篇)
Android内存泄漏 ——检测、解决和避免

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值