Android 消息机制 Handler

Read The Fucking Source Code

消息机制模型:

  • Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息;
  • MessageQueue:消息队列的主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);
  • Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);
  • Looper:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者。

源码阅读心得:

消息机制的分层与解耦:
  • 消息机制分为Java层和Native层,Java端创建了Native端,Native端与Java端组成了一个完成的整体。
  • Java层和Native层的有且只有MessageQueue通过JNI建立关联,各自实现自己的逻辑,功能相似,彼此独立。
Java层总结:
  • Message的消息池:尽可能用Handler的obtainMessage()方法,而不是去创建Message,因为这样会取Message消息池的缓存Message,提高效率。
  • MessageQueue:在Java层面,MessageQueue才是核心,里面包含了很多native方法,它是连接native的唯一入口。
  • MessageQueue.enqueueMessage():是将消息按照处理时序进行添加。当然添加消息还有postSyncBarrier(这是hide方法)
  • Looper.loop():无限循环(无限循环的意义在于消息队列的信息获取),取出MessageQueue的Message。
  • MessageQueue.next():无限循环(无限循环的意义在于线程等待退出,重新进行消息队列取值计算),next()是MessageQueue的处理核心,可能会有阻塞操作,等待nativePollOnce()结束或者消息队列被唤醒,也会处理SyncBarrier逻辑。当消息取值失败,那么会进入Idle状态,处理Idle任务集合(只执行一次),所以queueIdle()的方法还是返回false即可,或者主动remove idle也行。
  • 异步消息是为了同步屏障(SyncBarrier)而设计的,如果不设置SyncBarrier,那么异步消息是没有作用的,而SyncBarrier是hide方法,只提供给系统来使用,比如UI刷新。设置SyncBarrier(好比给消息设置优先级一样),首先处理异步消息,然后取消SyncBarrier,再依次处理消息队列。(系统级别的消息:因为像系统级别的消息,会封装成异步消息,插入异步消息的时候,会向队头插入一个屏障消息,当发现到有这个屏障消息的时候,就不会处理后面的普通消息了,就会在队列后面找异步消息,优先处理异步消息,所以主线程插入异步消息的时候,总是会优先处理这个消息,所以我们主线程所做的一些系统级别的优先级比较高的消息,并不会阻塞)
  • Java层也可以主动唤醒线程(当然需要调用Native的方法),可以进行唤醒的地方有:MessageQueue的quit()/removeSyncBarrier()/enqueueMessage()
  • quit()与quitSafely()的区别,仅仅在于是否移除当前正在处理的消息。移除当前正在处理的消息可能会出现不安全的行为。
Native层总结:
  • Native层提供epoll机制(是一种 IO 多路复用机制,可以同时监控多个文件描述符,当某个文件描述符就绪,则立刻通知相应程序进行读写操作拿到最新的消息,进而唤醒等待的线程)来保证消息队列处理的低功耗。Java层通过Looper的loop(),在MessageQueue中next()中,最终直通Native层的epoll_wait()方法,线程挂起。(就拿主线程来说,如果主线程没有视图刷新请求进行epoll唤醒,那么epoll_wait()会等到timeout结束才会再次唤醒。否则,线程处于空闲等待也就是挂起状态。所以主线程大多数的时候都是出于休眠状态,并不会消耗大量的 CPU 资源,这就是为什么主线程不会因为Loop.loop()里的死循环卡死)
  • Native层处理Native Request和Native Message,而Java层处理Java message。通过Native层的Looper.pollInner()方法可以看出,消息处理流程是先处理Native Message,再处理Native Request,最后处理Java Message。理解了该流程,也就明白有时上层消息很少,但响应时间却较长的真正原因。

Handler的通俗解释

  • Android中主线程是不能进行耗时操作的,子线程是不能进行更新UI的。所以就有了handler,它的作用就是实现线程之间的通信。
  • 安卓是由 事件驱动 的,对事件进行处理的就是 looper(每一个点击触摸或者Activity每一个生命周期),Handler 的创建依赖 该线程 的Looper(Handler利用哪个线程的Looper创建的实例,它就和相应的线程绑定到一起,处理该线程上的消息),这就决定了Looper查询出的msg分发到Handler的handleMessage()方法是运行在 该线程
  • 主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

Handler、Thread和HandlerThread的差别

  • Handler是线程的消息通讯的桥梁,主要用来发送消息及处理消息
  • Thread普通线程,如果需要有自己的消息队列,需要调用Looper.prepare()创建Looper实例,调用loop()去循环消息。
  • HandlerThread是一个带有Looper的线程,在HandleThread的run()方法中调用了Looper.prepare()创建了Looper实例,并调用Looper.loop()开启了Loop循环,循环从消息队列中获取消息并交由Handler处理。利用该线程的Looper创建Handler实例,此Handler的handleMessage()方法是运行在子线程中的。
  • HandlerThread适合处理本地IO读写操作(读写数据库或文件),因为本地IO操作耗时不长,对于单线程+异步队列不会产生较大阻塞,而网络操作相对比较耗时,容易阻塞后面的请求,因此HandlerThread不适合加入网络操作。

问题思考

Android如何保证在请求绘制流程过程中,不会因为其他消息处理事件的阻塞,导致Vsync刷新信号的处理延时?

  • 这其实就是Handler如何做到线程优先级的问题。我们来跟进一下绘制流程的处理。
    1. 页面请求绘制流程刷新,会调用View的 requestLayout / invalidate 方法。
    1. 最终会调入到ViewRootImpl的 scheduleTraversals 方法。
    1. 在 scheduleTraversals 方法中,首先在Handler消息队列中插入同步屏障。
    1. 然后在 scheduleTraversals方法中向Choreographer(Vsync处理引擎)注册Vsync刷新信号回调。
    1. 在Choreographer中,如果有跨线程通信,那么都会用异步消息,并且放在消息队列头部,消息不会堵塞,优先处理。
    1. DisplayEventReceiver接收到native侧的调用 dispatchVsync(),然后通过跨线程,将onVsync()转到doFrame()。
    1. doFrame()已经在主线程中了,通过回调 scheduleTraversals 在 Choreographer 注册的回调Runnable。
    1. 返回ViewRootImpl中注册了Vsync刷新信号回调的Runnable中,会走到 doTraversal 。
    1. 在doTraversal中,移除同步屏障,然后执行真正的绘制分发流程 performTraversals(绘制流程分发总方法)。
    1. 等待绘制流程处理结束,才会进行下一个消息的轮询,这样就保证了绘制流程的高优先级线程处理。
    1. 设置同步屏障是hide方法,只给系统来用,所以设计初衷就是保证系统的某些操作要高优先级。
    1. 线程的高优先级需要两个必不可少的条件 = 同步屏障 + 异步消息。

小编的扩展链接

《Android 基础组件 全家桶》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容