前言
目录
1. 背景
我们先来看下日常Handler的一般用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { //主线程创建时便自动创建Looper和对应的MessageQueue,之前执行Loop()进入消息循环 super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //实例化Handler //这里并无指定Looper,即自动绑定当前线程(主线程)的Looper和MessageQueue private Handler showhandler = new Handler(){ //通过复写handlerMessage()从而决定如何进行更新UI操作 @Override public void handleMessage(Message msg) { //UI更新操作 } }; //启动子线程 new Thread(){ @Override public void run() { super.run(); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); showhandler.sendEmptyMessageDelayed(0x1,10000); } } }.start(); finish(); }
|
在上面的例子中,你会发现出现了严重的警告:
从上图可以看出来,这个警告的原因是:该Handler造成了严重的内存泄漏
那么,该Handler是怎么样造成内存泄露的呢?
2. 内存泄露原因
2.1 造成内存泄露的源头
根据图片可以分析,内存泄露显示出现在:
- Handler类
即Handler四件套:Looper+MessageQueue+Message+Handler
- 最终的泄露发生在Handler类的外部类 - MainActivity类
2.2 如何造成内存泄露
首先,我们需要了解到:
- 主线程的Looper对象会伴随该应用程序的整个生命周期
- 在Java里,非静态内部类和匿名类都会潜在引用它们所属的外部类
在了解到上述两条后,从上面的代码中可以知道:
- 在发送的延迟空消息(EmptyMessageDelayed)后、消息处理被前,该消息会一直保存在主线程的消息队列里持续10s
- 在这延时10s内,该消息内部持有对handler的引用,由于handler属于非静态内部类,所以又持有对其外部类(即MainActivity实例)的潜在引用,引用关系如下图
- 这条引用关系会一直保持直到消息得到处理,从而,这阻止了MainActivity被垃圾回收器(GC)回收,同时造成应用程序的内存泄漏,如下图:
3. 解决方案
3.1 解决方案1:使用静态内部类+弱引用
上面提到,在Java里,非静态内部类和匿名类都会潜在的引用它们所属的外部类。
但是,静态内部类不会。
所以,避免内存泄露的解决方案是:只需要将Handler的子类设置成静态内部类
- 同时,还可以加上 使用WeakReference弱引用持有Activity实例
- 原因:弱引用的对象拥有短暂的生命周期。在垃圾回收器线程扫描时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
解决代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public class MainActivity extends AppCompatActivity { //将Handler改成静态内部类 private static class FHandler extends Handler{ //定义弱引用实例 private WeakReference<Activity> reference; //在构造方法中传入需要持有的Activity实例 public MyHandler(Activity activity) { reference = new WeakReference<Activity>(activity); } //通过复写handlerMessage()从而决定如何进行更新UI操作 @Override public void handleMessage(Message msg) { //省略代码 } } @Override protected void onCreate(Bundle savedInstanceState) { //主线程创建时便自动创建Looper和对应的MessageQueue,之前执行Loop()进入消息循环 super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //实例化Handler的子类 //这里并无指定Looper,即自动绑定当前线程(主线程)的Looper和MessageQueue private final Handler showhandler = new FHandler(); //启动子线程 new Thread(){ @Override public void run() { super.run(); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); showhandler.sendEmptyMessageDelayed(0x1,10000); } } }.start(); }
|
3.2 解决方案2:当外部类结束生命周期时清空消息队列
- 从上面分析,内存泄露的原因是:
当Activity结束生命周期时,Handler里的Message可能还没处理完,从而导致一系列的引用关系。
- 其实,我们只要在当Activity结束生命周期时清除掉消息队列(MessageQueue)里的所有Message,那么这一系列引用关系就不会存在,就能防止内存泄露。
- 解决方案:当Activity结束生命周期时(调用onDestroy()方法),同时清除消息队列里的所有回调消息(调用removeCallbacksAndMessages(null))
代码如下:
1 2 3 4 5
| @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }
|
经过上述两个解决方案,在Handler里的内存泄露问题就不会再出现了!
4. 总结
- 本文总结的是关于Handler的一些小事:内存泄露,阅读完本文后相信你已经懂得Handler内存泄露的原理和详细的解决方案
- 接下来,我会继续讲解Android开发中关于Handler和多线程的知识,包括Handler源码、继承Thread类、实现Runnable接口、Handler等等,有兴趣可以继续关注Carson_Ho的安卓开发笔记
不定期分享关于安卓开发的干货,追求短、平、快,但却不缺深度。