-
什么是handler
- handler是Android中的消息传递机制也是多线程异步通信机制。
- handler 通过send或者post发送Message事件或者Runnable对象到looper持有的messageQueue再通过Looper的loop轮询和匹配到对应的handler中去处理事件实现了Android的消息传递,Android ui的更新 activity的生命周期都是依赖于handler的消息传递实现。
- handler和线程绑定的,通过handler可以实现ui线程和子线程的双向通信,也方便了子线程更新UI。
-
为什么使用handler
- handler主要解决了多线程通信的问题。
- Android的UI更新是单线程的,假如多线程可以刷新ui且无锁状态,那ui及其事件响应就会出现错乱,所以Android设置了UI线程且只有ui线程单线程串行更新ui,其他线程可以借助于handler发送消息到ui线程更新UI
- 且不说多线程更新UI,即时多线程通过handler刷新ui,如果不加以控制还会出现数据错乱或者卡顿的现象。
-
handler的原理
- 线程的工具类threadlocal,线程提供的一个数据存储的工具类,对外提供set和get api方法,最大的好处是可以保证缓存的数据仅被当前的线程访问且缓存对象可以是任意对象。
- Lopper:Android的线程独立是依赖于threadlocal的,将lopper存储于threadlocal中保持线程独立,Lopper:意为循环,内部持有同步消息队列对象用来接受绑定的handler发来的消息和以链表的方式存储消息,并在loop api中将消息分发给绑定的handler去处理,直到Looper的api quit和quitsafe执行才会停掉looper的循环。
- Handler 存在两个构造方法,一个是默认的构造方法,创建一个当前线程的handler,创建handler前需要注意创建当前线程的Looper,另外一个构造方法就是指定handler的Looper,此方法将handler和线程独立的Looper绑定,通过Looper,handler可以发送消息到Looper且获取到消息交由handMessage去处理。
- Message:消息对象,串联handler和Looper的数据对象,封装了消息标示,消息对象,消息数据链表等,具体可以参考下面的Message和MessageQueue数据结构。
- Android UI线程也就是Activity线程,ui线程绑定的Looper被称为MainLooper,Android的线程通信通常指定是Android Ui线程和子线程的通信,ui线程可以获取到子线程的Looper并创建对应的handler,利用此handler既可以实现ui线程和当前线程的通信,Android提供了getmainlooper的api方法。子线程通过这个api可以获取ui线程的Looper 并创建UI线程Handler,利用此handler可以发送消息到Ui线程实现子线程和ui线程的通信。
- 子线程创建handler的前提是初始化对应的Looper即通过api looper的prepare 初始化,创建handler后且不要忘记调用Looper的loop方法启动消息轮询。
-
Looper中loop代码都做了那些工作
final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0; final long dispatchEnd; try { msg.target.dispatchMessage(msg); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); msg.recycleUnchecked();
- 如上所示:上面是looper的源码,删除了大量的log等非核心代码,源码可知 loop主要做了下面几个工作:
- 获取到Looper和消息队列
- for循环且借助Queue的next api 寻找最近的有效消息
- 将消息分发匹配给绑定的handler去执行
- 消息执行后回调空闲handler且重置当前的消息的状态到回收可用状态。
- 如上所示:上面是looper的源码,删除了大量的log等非核心代码,源码可知 loop主要做了下面几个工作:
-
Looper的quit和quitsafe的区别
- 两个api都可以停止Looper的轮询且两个api都是调用MessageQueue的Quit api不同的是传递参数不同。
- 两个api的不同点是 quit执行后清空当前所有的消息无论是否为延迟消息,quitsafe是仅清空当前的延迟消息 当前消息队列的普通消息还是分发匹配给handler去处理。
-
Looper的loop中代码一直在死循环为何不会造成anr
- ANR全称是 application not response,应用程序ui线程不在响应用户输入事件,activity中达到5s系统弹出anr弹窗告知用户,anr通常有两方面原因:
- 不响应用户事件
- 响应用户事件执行时间过长超出了响应时间,activity是5s,Broadcast是10s,services是20s
- Looper是事件的存储,轮询,分发匹配,是事件处理流程的一部分,确切地说 anr是发生在事件的分发后处理的部分中,只要事件的分发流程不间断,就不会出现anr现象,所以looper的死循环不会出现anr现象。
- ANR全称是 application not response,应用程序ui线程不在响应用户输入事件,activity中达到5s系统弹出anr弹窗告知用户,anr通常有两方面原因:
-
Looper中的loop api 方法中添加一行代码是否会执行?
- 若代码添加在next 消息前 必定会执行
- 若代码添加在nexy消息后是否执行取决于当前是否有消息,若没有消息则msg为null 直接return了 不会执行,若有消息 则分发执行 会执行这行代码。
-
非ui线程能否修改UI
* view的操作前都会进行ViewRootImpl中方法checkThread的校验,若view的操作不是在ui线程则会直接报异常。- view的checkthread校验依赖于ViewRootImpl,然而ViewRootImpl的创建是onresume 页面进入前台后。
- 综上,ViewRootImpl创建前即当前页面进入前台前是可以子线程更新ui的,但是进入前台即ViewRootImpl创建后view的修改都添加了checkThread的校验 不能在子线程修改view。
-
Message MessageQueue的数据结构
- Message的数据结构:
- 消息的数据,what:消息标示,用户可以根据此标示处理发送到handler的消息,object:消息附带的数据对象,arge1,arge2:两个int的变量和object一致,也是附带数据变量,是以基本变量的形式附带
- Next对象:Message对象变量,指向当前消息的下一条消息,和messagequeue组合实现了消息的串联,具体在MessageQueue中解释。
- spool对象,和next类似,也是Message对象,当前消息匹配执行后将spool对象指向这个消息用于回收复用,具体参考下一个知识点。
- MessageQueue数据结构:
- MessageQueue名为队列 其实内部的消息实现是通过单链表实现的,借助于每一条Message都持有一个next 消息对象。
- mMessage对象:Message对象,指向消息链表的头对象,mMessage和message和Message中的next三个串联起了messageQueue。
- messageQueue消息add流程:第一个消息mmessage指向消息且消息的next为null,第二个消息add,校验消息的时间,若小于当前头消息则mmessage执行新消息并将其next执行原消息,若时间大于第一条消息则将第一条消息 的next指向第二条消息,后续消息进来一样都是从头消息开始依次对比时间,或称为最新头消息或插入到相应的消息后面。
- MessageQueue的get流程:next api 获取到消息由于add时候消息按时间排序插入所以获取的时候按照链头获取并交验消息的合法性若不合法则通过链表继续获取。
- Message的数据结构:
-
Message的Obtain比new 对象的优势
- Message中特殊对象spool对象 也是message对象,指向匹配处理后的message自身可以被复用,消息的复用存储如下:
- spool对象是message对象和messagequeue中的mMessage对象一致,指向复用消息链表的链头消息。
- add消息流程:第一个消息 spool指向消息本身且next为null,第二个消息 将当前spool消息(第一个消息)指向第二个消息的next,此时spool消息再指向第二个消息并size加1 ,后续消息到来插入方式相同,这样形成了复用消息链。
- get消息流程:通过链头spool获取 获取后重置其what并size减一。
- Looper中loop api 最后会将分发匹配执行后消息重置给上面提到的spool对象,并将next对象置空,待Message的obtain api 获取新的messgae 对象时候,若spool不为空 则会将spool对象修改what object对象后返回复用 不需要重新创建对象提升性能(减少了对象分配,创建,调整内存的操作)。
- 使用obtain并不会空指针 因为若没有复用消息对象则会新建一个对象返回
- MessageQueue中最多复用50条消息,若超出50条则不再复用消息(注意是消息50条内可以复用 并不是messageQueue消息条数最多50条)。
- Message中特殊对象spool对象 也是message对象,指向匹配处理后的message自身可以被复用,消息的复用存储如下:
-
Handler中存在几个链表?
- 两个 一个是message和Messagequeue中的mmessage生成的消息链表,另一个是message和message中的spool生成的消息复用的链表。
-
new Handler().obtainMessage和Message.obtainMessage() 有何区别?
* 没有区别,前面的api调用后面的api。
- Message 中的obtain会不会脏数据
- 不会 一是在looper的loop中会重置Message,二是在obtain的时候会重置Message的what和object。
- Idlehandler怎么理解且使用场景
- Idlehandler:空闲handler,当前线程handler对于事件执行完成后会回调idlehandler,可以看作是handler对外提供的回调监听api,类似于listener
- MessageQueue 持有了idlehandler的数组列表,对外提供add和remove api,可以添加了多个handler。
- idlehandler使用场景多是一些数据或者行为依赖于当前单个或者多个事件执行后,比如获取某个view的宽高但是由于测绘和布局不能够在oncreate中直接获取,所以此时可以添加idlehandler,借助于特性获取。
- 消息如何实现延迟的
- message中一个数据对象是when,long型 存储了当前的消息执行时间。
- handler 发送消息指定了执行时间,delay内部是系统时间加上当前指定时间 存储的还是消息的执行时间
- 获取消息的时候会校验时间是否到达消息的执行时间。
参考文章:
Handler的机制原理
Handler:Message数据结构
Handler:Message为什么obtain更高效
Handler:MessageQueue 插入及其读取解析
Handler:MessageQueue quit和quitsafe的区别
Handler:Idlehandler的原理及其应用
Handler:loop死循环为什么不出现anr
Handler:loop死循环为什么不出现主线程anr
网友评论