Android Handler 的原理分析
Handler 是安卓中最常用的组件。作用就是 线程间的消息通知 但是 Java的jdk 明明有很多但是为什么要有这个呢 1 几乎看不到多线程死锁 2 规范线程的使用 早期移动设备内存很低
首先要明白handler 的使用 1 线程a 创建一个handler 接受的地方 然后线程b 引用线程A 的handler 进行一个消息的发送
然后是多线程的思想
我有ABDCE 几个线程。然后进行 在A 中创建5个Handler 然后在其他几个线程中分别在 任务完成之后 像A 中持有的handler 进行消息的发送 这样看来 本质上 其实 就是内存共享的方式。和我们自己写的监听回调是不是差不多的 都是持有同一个对象 然后进行回调 但是 其他线程是不允许进行ui 会不安全 所有才设计了这个 我感觉最大的作用
就是用于和主线程进行通信 从而进行ui 的更新 安全问题。
在 A 线程启动的时候 都会进行looper 的创建 同时进行了MessageQueue 也就是消息队列的创建。所以说 我们的一个线程可以创建很多个handler 然后每个handler 这个线程会维护一个
属于自己的looper 和自己的消息队列。我们在其他线程进行 handler的send 或者post 本质上 都是调用的MessageQueue 的入队列方法。所以说 我有多个线程 每个线程都创建一个handler 那么其实我是有多个looper 和多个MessageQueue 的 大家自己用自己的。
looper 是线程自己调用的。在主线程中 我们不需要自己调用 因为activityTheard 会帮我们调用 在调用的时候也是先调用looper.prepare 在调用looper.loop 不然一样会报错
Handler :
最后都会执行到Handler.enquueMessage() ->MessageQueue.enquueMessage() 入队列
theard --------->Looper.loop -------->MessageQueue.next() 出队列 -》handler.dispatchMessage
所以源码重点分析 在looper MessageQueue 的消息的入队列 和 出队列中干了些什么事情。
首先是在looper.prepare 的时候创建了我们线程自己的looper 同时创建了属于自己的MessageQueue 然后将其保存在线程的副本 TheardLocal中
Looper.loop 中 首先是获取当前线程 的looper 并且 获取属于自己的MessageQueue
然后开启死循环 不断的 从 自己的MessageQueue队列中 Message msg = queue.next(); 取出来然后进行消息的分发
MessageQueue 中的enqueueMessage 方法中 是入队列的逻辑 这个函数会存在多线程 同时操作的情况 那么他就必须保证 自己的原子和防止重排序 所有 需要加锁
所以在这个函数里面 用了Synchronized 关键字 那么 同样的 在取出来的时候 next() 方法也是需要加锁的 因为你不能一边取一边存
我们创建入队列的时候 会给每一个消息 添加当前的时间戳 来进行标记 所以在加入的时候 是根据时间来进行优先级排序的 最早执行的会在前面
那么问题来了在next()函数中 首先是一个死循环 如果说执行到了还不到执行的时间任务 会将其当前的时间加上需要等待的时间 然后交给nativepollOnce 来进行阻塞的等待
如何进行阻塞的呢。是在进入循环之前有一个变量mPendingIdHandlers 的值为-1 如果是我的message 需要进行等待 会执行 这个函数 小于《=0 然后进行continue 同时mBlocked =true
然后再次进入死循环 然后在取消息之前 就会进入 naitivePollOnce 来进行阻塞 等待
所以在looper.loop 方法在Message msg = queue.next(); 这句话就会一直卡主 1 没有message msg=null 2 被阻塞了 msg=null
然后会执行 msg==null 然后return 掉 这样主线程就去做其他事情了 就不会阻塞掉 而主线程会永远执行 只要我们系统在运行 所以会一直挂起
然后是阻塞了 我们如何唤醒呢 因为 我们前面说了2种情况 一直是消息不到执行的时间 一种是 没有消息 消息队列为空 那么我们这个时候插入一个消息 如果队列
为空 那么是不是可以拿到消息进行执行了呢。如果不到时间 那么我们插入一个马上可以执行的消息 是不是也能够执行了呢。
但是都是需要唤醒
在源码里面我们发现 Synchronized 的判断
if(p==null||when==0||when<p.when){
needweak =mBlocked
}
其实就是2种情况
1 消息头为空其实就是我们的空队列 那么这个时候
2 如果当前最近的消息头的时间 比当前时间要大 那么就执行唤醒策略
在最下面会有 needweak=true 会执行唤醒策略
设计模式
handler 的dispatchMessage(Message msg){
if(msg.callbakc!=null){
handlercallbakc(msg)
}else{
if(mCallback!=null){
if(mcallback.handleMessage(msg)){
return
}
}
handleMessage(msg)
}
}
这个其实是一种责任链模式
还有在我们消息 处理了之后 或者说消息销毁的时候 会讲所有的消息 都存放到一个回收池中
那么sPool
不管是我们自己创建new 的message 还是 obtain()调用的message 都是会回到
sPool回收池中 这样不断的进行循环利用message 对象 减少内存的开销 和 尽量内存分配连续的区域 减少gc的发生
这个模式叫做享元模式
这个就是 目前对于Handler机制的了解 作为自己的一个学习记录吧。
来波面试题
1Handler是什么?
2消息机制是什么?
3为什么不能在子线程中访问UI?
4在子线程中创建Handler报错是为什么?
5如何在子线程创建Looper?
6 为什么通过Handler能实现线程的切换?
答:当在A线程中创建handler的时候,同时创建了MessageQueue与Looper,Looper在A线程中调用loop进入一个无限的for循环从MessageQueue中取消息,当B线程调用handler发送一个message的时候,会通过msg.target.dispatchMessage(msg);将message插入到handler对应的MessageQueue中,Looper发现有message插入到MessageQueue中,便取出message执行相应的逻辑,因为Looper.loop()是在A线程中启动的,所以则回到了A线程,达到了从B线程切换到A线程的目的
7 Handler.post的逻辑在哪个线程执行的,是由Looper所在线程还是Handler所在线程决定的?
答 是在Looper.loop()方法中,从MsgQueue中拿出msg,并且执行其逻辑,这是在Looper中执行的,因此有Looper所在线程决定。
8 Looper和Handler可以处于一个线程吗?子线程中可以用MainLooper去创建Handler吗?
答 1 可以 。
2 子线程中Handler handler = new Handler(Looper.getMainLooper());,此时两者就不在一个线程中。
9 Handler的post方法发送的是同步消息吗?可以发送异步消息吗?
答 1 用户层面发送的都是同步消息
2 不能发送异步消息
3 异步消息只能由系统发送。
10 Handler的post()和postDelayed()方法的异同?
答 底层都是调用的sendMessageDelayed() post()传入的时间参数为0 postDelayed()传入的时间参数是需要的时间间隔。
11 MessageQueue.next()会因为发现了延迟消息,而进行阻塞。那么为什么后面加入的非延迟消息没有被阻塞呢?
12 Looper.quit/quitSafely的本质是什么?
本质都是调用MsgQueue的quit() 只是前者会清空所有消息 而后者只会清楚延时消息 吧剩余的非延时的消息分发出去 都不会在接受新消息进来了