Android内存泄漏总结

来源:互联网 发布:iss端口和wamp端口 编辑:程序博客网 时间:2024/05/18 13:44

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内存泄漏 ——检测、解决和避免

原创粉丝点击