Handler相关问题
- 1、设计 Handler 的初衷?
- 2、一个线程有几个 Looper?几个 Handler?
- 3、Handler 内存泄漏原因? 以及最佳解决方案?
- 4、为何主线程可以 new Handler?如果想要在子线程中 new Handler 要做些什么准备?
- 5、子线程中维护的 Looper,消息队列无消息的时候的处理方案是什么?有什么用?
- 6、既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个Handler 可能处于不同线程),那它内部是如何确保线程安全的?
- 7、我们使用 Message 时应该如何创建它?
- 8、Looper 死循环为什么不会导致应用卡死?
1、设计 Handler 的初衷?
Handler 就是针对 Android 系统中与 UI 线程通信而专门设计的多线程通信机制
2、一个线程有几个 Looper?几个 Handler?
一个 Thread 只能有一个 Looper,一个 MessageQueue,可以有多个 Handler。
以一个线程为基准,他们的数量级关系是: Thread(1) : Looper(1) :MessageQueue(1) : Handler(N)。
3、Handler 内存泄漏原因? 以及最佳解决方案?
泄露原因:
Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler持有,这样最终就导致 Activity 泄露。
解决方案
最直接的思路就是避免使用非静态内部类。
使用 Handler 的时候,放在一个新建的文件中来继承 Handler 或者使用静态的内部类来替代。静态内部类不会隐含的持有外部类的引用,因此这个 activity 也就不会出现内存泄漏问题。弱引用(WeakReference)
;如果你需要在 Handler 内部调用外部 Activity 的方法,你可以让这个 Handler 持有这个 Activity 的弱引用,这样便不会出现内存泄漏的问题了。public class SampleActivity extends Activity { /** * 弱引用的方式 */ private static class MyHandler extends Handler { private final WeakReference<SampleActivity> mActivity; public MyHandler(SampleActivity activity) { mActivity = new WeakReference<SampleActivity>(activity); } @Override public void handleMessage(Message msg) { SampleActivity activity = mActivity.get(); if (activity != null) { //to Something } } } }
静态
,对于匿名类 Runnable,我们同样可以设置成静态的,因为静态内部类不会持有外部类的引用。//定义成 static 的,因为静态内部类不会持有外部类的引用 private final MyHandler mHandler=new MyHandler(this); private static final Runnable sRunnable=new Runnable(){ @Override public void run(){//to something }; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); mHandler.postDelayed(sRunnable,1000*60*10); finish(); } }
4、为何主线程可以 new Handler?如果想要在子线程中 new Handler 要做些什么准备?
每一个 handler 必须要对应一个 looper,主线程会自动创建 Looper 对象,不需要我们手动创建,所以主线程可以直接创建 handler。
在 new handler 的时候没有传入指定的 looper 就会默认绑定当前创建 handler 的线程的 looper,如果没有 looper 就报错。
因为在主线程中,Activity 内部包含一个 Looper 对象,它会自动管理 Looper,处理子线程中发送过来的消息。而对于子线程而言,没有任何对象帮助我们维护Looper 对象,所以需要我们自己手动维护。
所以要在子线程开启 Handler 要先创建 Looper,并开启 Looper 循环
如果在子线程中创建了一个 Handler,那么就必须做三个操作:
- prepare();
- loop();
- quit();
5、子线程中维护的 Looper,消息队列无消息的时候的处理方案是什么?有什么用?
在 Handler 机制里面有一个 Looper,在 Looper 机制里面有两个函数,叫做
quitSafely()和 quit()函数
,这两个函数是调用的 MessageQueue 的 quit();
再进入到MessageQueue 的 quit()函数
,它会 remove 消息,把消息队列中的全部消息给干掉。把消息全部干掉,也就释放了内存
。而在 quit()函数的最后一行,有一个nativeWake()函数
。这个函数的调用,就会叫醒等待的地方,醒来之后,就接着往下执行。
往下执行后,发现Message msg = mMessages; 是空的
,没有消息,nextPollTimeoutMillis 复位
;如果消息队列正在处于退出状态返回 null,调用dispose();释放该消息队列
。这个时候 Looper 就结束了(跳出了死循环),则达成了第二个作用:释放线程
。
6、既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个Handler 可能处于不同线程),那它内部是如何确保线程安全的?
这里主要关注 MessageQueue 的消息存取即可,看源码内部的话,在往消息队列里面存储消息时,会拿当前的 MessageQueue 对象作为锁对象,这样通过加锁就可以确保操作的原子性和可见性了。
消息的读取也是同理,也会拿当前的 MessageQueue 对象作为锁对象,来保证多线程读写的一个安全性。
7、我们使用 Message 时应该如何创建它?
创建的它的方式有两种:
一种是直接new 一个 Message 对象
,
另一种是通过调用Message.obtain()
的方式去复用一个已经被回收的Message
当然日常使用者是推荐使用后者来拿到一个 Message,因为不断的去创建新对象的话,可能会导致垃圾回收区域中新生代被占满,从而触发 GC。
8、Looper 死循环为什么不会导致应用卡死?
Launch 桌面的图标第一次启动 Activity 时,会最终走到 ActivityThread 的 main 方法,在 main 方法里面创建 Looper 和 MessageQueue 处理主线程的消息,然后Looper.loop()方法进入死循环,我们的 Activity 的生命周期都是通过 Handler 机制处理的,包括 onCreate、onResume 等方法,下面是 loop 方法循环。
主线程的主要方法就是消息循环,一旦退出消息循环,那么你的应用也就退出了,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生 ANR 异常。
造成 ANR 的不是主线程阻塞,而是主线程的 Looper 消息处理过程发生了任务阻塞,无法响应手势操作,不能及时刷新 UI。
阻塞与程序无响应没有必然关系,虽然主线程在没有消息可处理的时候是阻塞的,但是只要保证有消息的时候能够立刻处理,程序是不会无响应的。
总结:应用卡死压根与这个 Looper 没有关系,应用在没有消息需要处理的时候,它是在睡眠,释放线程;卡死是 ANR,而 Looper 是睡眠。