Android App 内存泄露之Handler
Handler也是造成内存泄露的一个重要的源头,主要Handler属于TLS(Thread Local Storage)变量,生命周期和Activity是不一致的
,Handler引用Activity会存在内存泄露。
看一下如下代码
- /**
- *
- * 实现的主要功能。
- * @version 1.0.0
- * @author Abay Zhuang <br/>
- * Create at 2014-7-28
- */
- public class HandlerActivity extends Activity {
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- // ...
- }
- };
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- mHandler.sendMessageDelayed(Message.obtain(), 60000);
- //just finish this activity
- finish();
- }
- }
是否您以前也是这样用的呢。
没有问题?
This Handler class should be static or leaks might occur (com.example.ta.HandlerActivity.1)
意思:class 使用静态声明否者可能出现内存泄露。
为啥出现这样的问题呢
Handler 的生命周期与Activity 不一致
- 当Android应用启动的时候,会先创建一个UI主线程的Looper对象,Looper实现了一个简单的消息队列,一个一个的处理里面的Message对象。主线程Looper对象在整个应用生命周期中存在。
- 当在主线程中初始化Handler时,该Handler和Looper的消息队列关联(没有关联会报错的)。发送到消息队列的Message会引用发送该消息的Handler对象,这样系统可以调用 Handler#handleMessage(Message) 来分发处理该消息。
handler 引用 Activity 阻止了GC对Acivity的回收
- 在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。
-
如果外部类是Activity,则会引起Activity泄露 。
当Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。
如何避免修?
- 使用显形的引用,1.静态内部类。 2. 外部类
- 使用弱引用 2. WeakReference
修改代码如下:
- /**
- *
- * 实现的主要功能。
- *
- * @version 1.0.0
- * @author Abay Zhuang <br/>
- * Create at 2014-7-28
- */
- public class HandlerActivity2 extends Activity {
- private static final int MESSAGE_1 = 1;
- private static final int MESSAGE_2 = 2;
- private static final int MESSAGE_3 = 3;
- private final Handler mHandler = new MyHandler(this);
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- mHandler.sendMessageDelayed(Message.obtain(), 60000);
- // just finish this activity
- finish();
- }
- public void todo() {
- };
- private static class MyHandler extends Handler {
- private final WeakReference<HandlerActivity2> mActivity;
- public MyHandler(HandlerActivity2 activity) {
- mActivity = new WeakReference<HandlerActivity2>(activity);
- }
- @Override
- public void handleMessage(Message msg) {
- System.out.println(msg);
- if (mActivity.get() == null) {
- return;
- }
- mActivity.get().todo();
- }
- }
上面这样就可以了吗?
当Activity finish后 handler对象还是在Message中排队。 还是会处理消息,这些处理有必要?
正常Activitiy finish后,已经没有必要对消息处理,那需要怎么做呢?
解决方案也很简单,在Activity onStop或者onDestroy的时候,取消掉该Handler对象的Message和Runnable。
通过查看Handler的API,它有几个方法:removeCallbacks(Runnable r)和removeMessages(int what)等。
代码如下:
- /**
- * 一切都是为了不要让mHandler拖泥带水
- */
- @Override
- public void onDestroy() {
- mHandler.removeMessages(MESSAGE_1);
- mHandler.removeMessages(MESSAGE_2);
- mHandler.removeMessages(MESSAGE_3);
- // ... ...
- mHandler.removeCallbacks(mRunnable);
- // ... ...
- }
如果上面觉的麻烦,也可以如下面:
- @Override
- public void onDestroy() {
- // If null, all callbacks and messages will be removed.
- mHandler.removeCallbacksAndMessages(null);
Android App 内存泄露之资源
资源内存泄露主要是资源申请未释放,还有资源没有重复使用。
- 第一种解决这部分问题的关键在于申请资源后能保证能释放资源。
- 第二种利用复用机制优化,如池的概念 。
1.引用资源没有释放
代码如下:
- private final class SettingsObserver implements Observer {
- public void update(Observable o, Object arg) {
- // todo ...
- }
- }
- ContentQueryMap.getInstance().addObserver(new SettingsObserver());
你对这段代码正常吗
答案是否定
存在的问题是没有办法注销观察者(SettingsObserver),这样带来的问题是没有办法释放该观察者。
那么这个对象将伴随整个单例生命周期,无形中就泄露一个SettingsObserver的内存。
1.1注册未取消造成的内存泄露
这种Android的内存泄露比纯Java的内存泄露还要严重,
因为其他一些Android程序可能引用我们的Anroid程序的对象(比如注册机制)。
即使我们的Android程序已经结束了,但是别的引用程序仍然还有对我们的Android程序的某个对象的引用,泄露的内存依然不能被垃圾回收。
还是我们的对象周期不一致引起的。
例如:BroadcastReceiver对象注册
... has leaked IntentReceiver ... Are you missing a call to unregisterReceiver()?
Observer对象
被观察对象生命周期和观察者的生命周期不一致
不观察的时候需要需要注销
1.2集合中对象没清理造成的内存泄露
我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。
如果这个集合是static的话,那情况就更严重了
在add 情况也要记得 remove
2.资源对象没关闭造成的内存泄露
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,
以便它们的缓冲及时回收内存。它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。
如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。因为有些资源性对象,
比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),
如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。
因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null.
在我们的程序退出时一定要确保我们的资源性对象已经关闭。
程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,
对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险
代码如下:
- try {
- Cursor c = queryCursor();
- int a = c.getInt(1);
- ......
- // 如果出错,后面的cursor.close()将不会执行
- ......
- c.close();
- } catch (Exception e) {
- }
合理的写法:
- Cursor c;
- try {
- c = queryCursor();
- int a = c.getInt(1);
- ......
- // 如果出错,后面的cursor.close()将不会执行
- //c.close();
- } catch (Exception e) {
- } finally{
- if (c != null) {
- c.close();
- }
- }
3.一些不良代码造成的内存压力
有些代码并不造成内存泄露,但是它们, 或是对没使用的内存没进行有效及时的释放,
或是没有有效的利用已有的对象而是频繁的申请新内存,
对内存的回收和分配造成很大影响的,容易迫使虚拟机不得不给该应用进程分配更多的内存,造成不必要的内存开支。
如果优化呢
频繁的申请内存对象和和释放对象,可以考虑Pool 池。
多线程可以考虑线程池
频繁的申请对象释放对象。可以考虑对象池。 例如:AbsListView 中RecycleBin 类是view对象池
频繁的链接资源和释放资源。可以考虑链接资源池。
3.1,Bitmap没调用recycle()
Bitmap对象在不使用时,我们应该先调用recycle()释放内存,然后才它设置为null.
虽然recycle()从源码上看,调用它应该能立即释放Bitmap的主要内存,
但是测试结果显示它并没能立即释放内存。但是我它应该还是能大大的加速Bitmap的主要内存的释放。
3.2,构造Adapter时,没有使用缓存的 convertView
Thread 内存泄露
线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。
1.看一下下面是否存在问题
- <span style="white-space:pre"> </span>/**
- *
- * @version 1.0.0
- * @author Abay Zhuang <br/>
- * Create at 2014-7-17
- */
- public class ThreadActivity extends Activity {
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- new MyThread().start();
- }
- private class MyThread extends Thread {
- @Override
- public void run() {
- super.run();
- dosomthing();
- }
- }
- private void dosomthing(){
- }
- }
这段代码很平常也很简单,是我们经常使用的形式。
真的没有问题吗
我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,
一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。
由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,
MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。
2.这种线程导致的内存泄露问题应该如何解决呢?
- 第一、将线程的内部类,改为静态内部类。
-
第二、在线程内部采用弱引用保存Context引用。 代码如下:
- <span style="white-space:pre"> </span>/**
- *
- * @version 1.0.0
- * @author Abay Zhuang <br/>
- * Create at 2014-7-17
- */
- public class ThreadAvoidActivity extends Activity {
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- new MyThread(this).start();
- }
- private void dosomthing() {
- }
- private static class MyThread extends Thread {
- WeakReference<ThreadAvoidActivity> mThreadActivityRef;
- public MyThread(ThreadAvoidActivity activity) {
- mThreadActivityRef = new WeakReference<ThreadAvoidActivity>(
- activity);
- }
- @Override
- public void run() {
- super.run();
- if (mThreadActivityRef == null)
- return;
- if (mThreadActivityRef.get() != null)
- mThreadActivityRef.get().dosomthing();
- // dosomthing
- }
- }
- }
上面的两个步骤其实是切换两个对象的双向强引用链接
- 静态内部类:切断Activity 对于 MyThread的强引用。
- 弱引用: 切断MyThread对于Activity 的强引用。
3.AsynTask 内部类会如何呢?
有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,
Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,
该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,
因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。
代码如下:
- /**
- *
- * 弱引用
- * @version 1.0.0
- * @author Abay Zhuang <br/>
- * Create at 2014-7-17
- */
- public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget>
- extends AsyncTask<Params, Progress, Result> {
- protected WeakReference<WeakTarget> mTarget;
- public WeakAsyncTask(WeakTarget target) {
- mTarget = new WeakReference<WeakTarget>(target);
- }
- @Override
- protected final void onPreExecute() {
- final WeakTarget target = mTarget.get();
- if (target != null) {
- this.onPreExecute(target);
- }
- }
- @Override
- protected final Result doInBackground(Params... params) {
- final WeakTarget target = mTarget.get();
- if (target != null) {
- return this.doInBackground(target, params);
- } else {
- return null;
- }
- }
- @Override
- protected final void onPostExecute(Result result) {
- final WeakTarget target = mTarget.get();
- if (target != null) {
- this.onPostExecute(target, result);
- }
- }
- protected void onPreExecute(WeakTarget target) {
- // Nodefaultaction
- }
- protected abstract Result doInBackground(WeakTarget target,
- Params... params);
- protected void onPostExecute(WeakTarget target, Result result) {
- // Nodefaultaction
- }
- }