Android Handler机制详解原理
looper是整个跨线程通信的管理者
// 内部持有的变量如下: threadlocal<looper> mainlooper observer messagequeue thread
1.首先先回忆一下handler怎么用
android线程通信分为以下两种情况
- 1.子线程发消息给ui线程
- 2.ui线程发消息给子线程
- 3.子线程发消息给另个子线程
1.子线程发消息给ui线程
class fragmentcontentactivity : appcompatactivity() { val flag = 1 lateinit var handler: handler override fun oncreate(savedinstancestate: bundle?) { super.oncreate(savedinstancestate) setcontentview(r.layout.activity_main) handler = object : handler(looper.getmainlooper()) { override fun handlemessage(msg: message) { when (msg.what) { flag -> { findviewbyid<textview>(r.id.text).text = msg.data["text"].tostring() } } } } thread { thread.sleep(2000l) handler.sendmessage(message.obtain().apply { what = flag data = bundle().apply { this.putstring("text", "threadmessage") } }) } } }
2.ui线程/子线程发消息给子线程
class fragmentcontentactivity : appcompatactivity() { val thread_flag =2 lateinit var threadhandler: handler override fun oncreate(savedinstancestate: bundle?) { super.oncreate(savedinstancestate) setcontentview(r.layout.activity_main) thread { looper.prepare() threadhandler = object :handler(looper.mylooper()!!){ override fun handlemessage(msg: message) { when(msg.what){ thread_flag -> { toast.maketext( this@fragmentcontentactivity, "${msg.data["text"]}", toast.length_short ).show() } } } } looper.loop() } } override fun onresume() { super.onresume() findviewbyid<textview>(r.id.text).postdelayed({ threadhandler.sendmessage(message.obtain().apply { what = thread_flag data = bundle().apply { putstring("text","ui message") } }) },2000l) } }
**在子线程的使用中,我们发现必须要进行looper.prepare()和looper.loop()前后这两个操作,因此,带着这个疑问来看一下looper的逻辑
**
// 在调用prepare()之后一定要调用loop(),最后结束消息循环的时候调用quit() private static void prepare(boolean quitallowed) { if (sthreadlocal.get() != null) { throw new runtimeexception("only one looper may be created per thread"); } sthreadlocal.set(new looper(quitallowed)); } private looper(boolean quitallowed) { mqueue = new messagequeue(quitallowed); mthread = thread.currentthread(); }
prepare()就是将初始化一个looper对象放入到threadlocal中,初始化looper,同时mqueue
public static void loop(){ binder.clearcallingidentity() for (;;) { message msg = queue.next(); // might block long origworksource = threadlocalworksource.setuid(msg.worksourceuid); try { // 其实 loop()只做了这一个调用,其他的都是监控当前消息循环时间是否超时,应该和anr有关 msg.target.dispatchmessage(msg); if (observer != null) { observer.messagedispatched(token, msg); } dispatchend = needendtime ? systemclock.uptimemillis() : 0; } catch (exception exception) { if (observer != null) { observer.dispatchingthrewexception(token, msg, exception); } throw exception; } finally { threadlocalworksource.restore(origworksource); if (tracetag != 0) { trace.traceend(tracetag); } } if (logslowdelivery) { if (slowdeliverydetected) { if ((dispatchstart - msg.when) <= 10) { slog.w(tag, "drained"); slowdeliverydetected = false; } } else { if (showslowlog(slowdeliverythresholdms, msg.when, dispatchstart, "delivery", msg)) { // once we write a slow delivery log, suppress until the queue drains. slowdeliverydetected = true; } } } if (logslowdispatch) { showslowlog(slowdispatchthresholdms, dispatchstart, dispatchend, "dispatch", msg); } //消息实体回收 msg.recycleunchecked();
可以看到looper.loop其实只是在for循环中,获取mqueue的下一个msg节点,然后调用msg.target.dispatchmessage(msg)
。乍看只是msg对象内部的操作。
因为loop()其实逻辑上算死循环,这意味着,当前线程的自发的顺序执行命令到此结束了,只能通过其他线程触发handler机制,来被动的在当前线程执行命令,当前线程完全变成了一个响应线程
looper类只是初始化并开启线程死循环的一个开关,具体工作在messagequeue中进行
messagequeue 消息队列
队列内消息的添加不是直接调用messagequeue,而是由与looper相关联的handler调用
messagequeue的内部持有的变量如下: arraylist mmessages sparsearray idlehandler[] mblocked
messagequeue类的功能主要有:元素插入队列,获取队列的头部元素,查找队列中元素,综述就是对队列的增删改查,其中 mmessage就是这个队列的入口也是这个队列的头结点
boolean enqueuemessage(message msg,long when) //msg 元素插入队列 boolean hasmessages(handler h,int what,object object) //查找handler下的msg.what/object相同的msg boolean hasequalmessages(handler h,int what,object obj)//查找 msg.object.equal(obj)的msg removemessages(handler h,int what,object obj)/(handler h,runnable r,object obj) removeequalmessages(...) //删除与参数msg.object相同或equal的msg message next() //获取队列中的头部元素
可以看出,这些方法内部都调用了 synchronized(this),队列的操作都是线程同步的
message next() -> ... // linux机制下的总线进入轮询,线程相当于挂起状态,nextpolltimeout是挂起多长时间 nativepollonce(ptr, nextpolltimeoutmillis); synchronized (this) { final long now = systemclock.uptimemillis(); message prevmsg = null; message msg = mmessages; //先判断msg.target是否为null,表示当前消息是不是异步消息 if (msg != null && msg.target == null) { // stalled by a barrier. find the next asynchronous message in the queue. //同步屏障:取出当前队列中的异步消息 do { prevmsg = msg; msg = msg.next; } while (msg != null && !msg.isasynchronous()); } if (msg != null) { if (now < msg.when) { // next message is not ready. set a timeout to wake up when it is ready. //重新计算线程进入挂起状态的时间 nextpolltimeoutmillis = (int) math.min(msg.when - now, integer.max_value); } else { // got a message. mblocked = false; if (prevmsg != null) { prevmsg.next = msg.next; } else { mmessages = msg.next; } msg.next = null; if (debug) log.v(tag, "returning message: " + msg); msg.markinuse(); return msg; } } else { // no more messages. nextpolltimeoutmillis = -1; } ...
可以看出next()内部主要有两种获取msg的逻辑
1.当前消息都是普通消息,按照msg.when的大小排序,每一次循环执行,通过检测when是否大于now来决定是否获取msg,或是挂起当前线程。
2.当前消息中有异步消息,优先获取msg.isasynchronous()==true
的,或者按照此异步消息的等待时间,来重新设置挂起线程的时间,从而达到精准的获取异步消息。
通俗的来讲就是说,当前所有普通消息按照预约的执行时间的先后来排队,这样可基本上既可以达到按照预约时间执行消息,也可以最大效率的在一定时间段内执行最多的消息,但是这忽略了每个消息的执行消耗的时间,比如a消息是队列内的no.1,a消息预约执行时间是1s之后,整个队列是等待状态的,这个时候来了b消息,b消息预约的时间是0.999s之后,按照预约时间的排队机制,b消息要插队到a消息之前,b成了这个队列的no.1,a成了no.2,整个队列的等待时间还是1s(因为之前设置了等待时间,所以不用唤醒),但是b消息的执行过程长达0.5s,已经超过了之后的很多消息的预约执行时间点了,这样就不能保证某些重要的消息按时执行。
于是就有了异步消息同步屏障的机制,这相当于普通消息排队时来了一个vip消息,先按照预约时间找到自己的位置,然后大喝一声:“都把脚给我挪开,我的前面不允许有人”,这个时候排在他之前的普通消息就只能全部挪到队列的一边,然后队列重新按照这位vip消息,设置等待时间,期间新来的普通消息也插到队边等待,保证精准按时执行vip消息。等vip消息执行完,之后再把之前等待普通消息的队列合并执行。当然之前等待的消息全耽误了,但毕竟是普通消息不重要。
// 同步屏障的方法,此方法只在 viewrootimpl类中调用 private int postsyncbarrier(long when) { // enqueue a new sync barrier token. // we don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final int token = mnextbarriertoken++; final message msg = message.obtain(); msg.markinuse(); msg.when = when; msg.arg1 = token; //没有设置target message prev = null; message p = mmessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; //mmessages变为同步屏障消息,next()下一次循环,首先获取到的是同步屏障 mmessages = msg; } return token; } // viewrootimpl void scheduletraversals() { if (!mtraversalscheduled) { mtraversalscheduled = true; mtraversalbarrier = mhandler.getlooper().getqueue().postsyncbarrier(); mchoreographer.postcallback( choreographer.callback_traversal, mtraversalrunnable, null); if (!munbufferedinputdispatch) { scheduleconsumebatchedinput(); } notifyrendererofframepending(); pokedrawlockifneeded(); } //设置同步屏障之后,通过设置了aysnc标记位的handler发送的msg都是异步消息, //messagequeue也优先处理此类异步消息,直到移除同步屏障标记位,再恢复到普通消息队列。
由此可见,同步屏障的设置和view刷新机制有关,因为要保证vsync信号按时完成刷新操作,具体分析待续…
综述,异步消息可以保证精准的执行,但也因此消息事件的先后顺序被打乱,有可能在代码执行中执行了handler.sendmsg(1,time0.2)->asynchandler.sendmsg(2,time0.5)
,但是实际执行的是 2->1。
再看handler
handler的成员变量如下
mlooper :初始化时获取当前线程的looper对象引用 mqueue :通过looper.mqueue
获取到的messagequeue队列引用masynchronous :标记当前handler是否发送异步消息 mcallback : handler
自身的callback接口,此callback调用在message.callback
之前mmessenger :imessager
和进程通信相关
以上成员变量大都是final类型,表示handler也是在其使用上也是final类型,也就是说,没有办法通过将handler与context的生命周期相剥离来避免内存泄漏
handler的方法如下
//handler 发送message第一种方法,设置message的what,data //不设置 runnable:callback boolean sendmessage(message msg) -> boolean sendmessagedelayed(message msg,long delaytime) -> boolean sendmessageattime(message msg,systemclock.uptimemillis()+delaytime) -> mqueue.enqueuemessage(msg,uptime) //第二种方法,message只设置runnable:callback boolean postattime(runnable r,object token,long uptime) -> sendmessageattime(getpostmessage(r,token),uptime) --> message getpostmessage(runnable r,object token){ message.obtain().callback=r ... } //移除message和检验message removemessages() hasmessages() ... //message 回调执行 void dispatchmessage(message msg){ if(msg.callback!=null){ handlecallback(msg) -> }else{ if(mcallback!=null){ mcallback.handlemessage(msg) } handlemessage(msg) } //可以看到 message的回调分为三个等级 //no.1 msg自身的callback //no.2 handler自身的mcallback成员变量,mcallback是final类型 //no.3 handler的子类重载的handlemessage方法
message
message 实现了parcelable接口,也就是说可以作为进程间通信的载体
message成员变量如下
int what //handler发送主体下的message的消息码 int arg1 //低成本的参数传递 int arg2 object obj //可以为空的token对象,一般在进程通信中用到 bundle data //线程通信中常用的参数容器 handler target //发送主体 runnable callback //message自身回调 messenger replyto //进程通信,一般在ams中用到 ------ // message缓存池相关 object spoolsync = new object() // 同步标记 messsage next static message spool static int spoolsize
message方法如下
//可以看出这是一个非常巧妙的方法 static message obtain(){ synchronized(spoolssync){ if(spools!=null){ message m= spool; spool = m.next; m.next = null; spoolsize--; return m; } } return new message(); } //主体上是一个带缓存池链表的同步工厂模式,同时也考虑到较多线程阻塞时 //可以直接声明初始化对象 //回收message对象到缓存池链表 void recycleunchecked(){ ...参数=null synchronized(spoolsync){ if(spoolsize < max_size){ next = spool; spools = this; spoolsize++; } } }
到此这篇关于android handler机制详解原理的文章就介绍到这了,更多相关android handler内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
上一篇: 详解Sql基础语法
推荐阅读
-
Android Handler的使用详解
-
Android的线程通信:消息机制原理(Message,Handler,MessageQueue,Looper),异步任务AsyncTask,使用JSON
-
详解Android Scroller与computeScroll的调用机制关系
-
Android编程中的消息机制实例详解
-
Android编程中Handler原理及用法实例分析
-
Android中NestedScrolling滑动机制详解
-
Android学习笔记之ListView复用机制详解
-
Android Handler 原理分析及实例代码
-
Android 消息机制详解及实例代码
-
详解Android中AsyncTask机制