【Android 消息机制】Handler
文章目录
- 1. 基本原理
- 2. 核心对象
- 2.1 Handler
- 2.1.1 常用方法
- 2.1.2 小结
- 2.2 Looper
- 2.2.1 概述
- 2.2.2 源码分析
- (1) prepare
- (2) loop()
- (3) quit()/quitSafely()
- (4) Looper.myLooper()
- (5) 构造方法
- 2.3 MessageQueue
- 2.3.1 概述
- 2.3.2 源码分析
- (1) enqueueMessage
- (2) 读取消息的next方法
- 2.4 Message
- 2.4.1 概述
- 2.4.2 源码分析
- (1)重要属性
- (2) obtain方法
- (3) recyler
- 3. 其他问题
- 3.1 HandlerThread
- 3.2 IdleHandler
- 3.3 主线程Handler的流程图
- 4. 思维导图
- 参考资料
- 添加链接描述
- 添加链接描述
- 添加链接描述
1. 基本原理
Handler机制是Android中基于单线消息队列模式的一套线程消息机制。
Handler的设计是基于生产者和消费者模型的方式;
工作流程如下:
- handler将消息发送到Looper的消息队列中(messageQueue)
- messageQueue 将数据按照时间先后排好队,等待Looper.loop()按照先后顺序取出Message
- Looper.loop()取出消息之后,调用消息的Message的target,即附属的Handler的dispatchMessage()方法,将该消息回调到handleMessage()方法中.
- handler 在 handleMessage(msg)方法中处理我们自己的逻辑。
2. 核心对象
2.1 Handler
添加链接描述
2.1.1 常用方法
2.1.2 小结
- 核心方法
- 主线程和子线程创建handler的区别
- 基本工作原理(与其他三个组件的联动关系)
- 内存泄漏问题
- 数量问题
2.2 Looper
添加链接描述
2.2.1 概述
2.2.2 源码分析
(1) prepare
Looper 采用静态变量 sThreadLocal:ThreadLocal 管理着与线程相关的 Looper 实例
当重复调用 Looper.prepare() 方法时,如果 sThreadLocal:ThreadLocal.get() 不为空,则会抛异常。
由此保证了一个线程 Thread 有且只有一个与之关联的 Looper 对象
(2) loop()
publicstaticvoidloop(){finalLooperme=myLooper();if(me==null){thrownewRuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}finalMessageQueuequeue=me.mQueue;// 无限循环for(;;){Messagemsg=queue.next();// 可能会阻塞if(msg==null){// No message indicates that the message queue is quitting.return;}// 分发消息前的钩子me.mLogging.println(">>>>> Dispatching to "+msg.target+" "+msg.callback);// 分发消息给对应的 Handlermsg.target.dispatchMessage(msg);// 分发消息后的钩子me.mLogging.println("<<<<< Finished to "+msg.target+" "+msg.callback);// 回收消息对象msg.recycleUnchecked();}}在一个无限循环中,不断重复以下操作:
① 调用 MessageQueue.next() 获取下一个消息:该方法会阻塞;如果返回 null,则退出循环。
② 当有返回消息且不为空时,调用 Message.target.dispatchMessage() 方法分发消息。
③ 把分发后的消息对象 Message,回收到消息池中以便复用。
进入下一个循环。
(3) quit()/quitSafely()
退出消息循环,终止 loop()。Looper 被退出后,任何向队列发布消息的尝试都将失败。
quit():直接移除消息队列中所有消息,使得 MessageQueue.next() 因为没有消息而返回null,致使Looper.loop() 退出循环。
quitSafely():只会剔除执行时刻 when 晚于当前调用时刻的 Message 消息。这样可以保证 quitSafely() 调用的时刻,满足执行时间条件的 Message 会继续保留在队列中被执行并在所有可执行的消息都执行完毕之后才退出 loop() 的轮询。
由上可知,方法内部是通过调用消息队列 MessageQueue.quit() 方法来实现的。
(4) Looper.myLooper()
返回与当前线程关联的 Looper 对象。 如果调用线程未与 Looper 关联,则返回 null。
(5) 构造方法
quitAllowed表示该 Looper 是否允许退出(即是否允许调用 quit())
privateLooper(booleanquitAllowed){mQueue=newMessageQueue(quitAllowed);mThread=Thread.currentThread();}2.3 MessageQueue
添加链接描述
2.3.1 概述
2.3.2 源码分析
(1) enqueueMessage
booleanenqueueMessage(Messagemsg,longwhen){// target是handler对象,检查消息是否有线程处理if(msg.target==null){thrownewIllegalArgumentException("Message must have a target.");}// msg是否已经被使用if(msg.isInUse()){thrownewIllegalStateException(msg+" This message is already in use.");}synchronized(this){// 处理消息的线程是否已经退出,如果线程退出,则将消息回收掉,无需进队列if(mQuitting){IllegalStateExceptione=newIllegalStateException(msg.target+" sending message to a Handler on a dead thread");Log.w(TAG,e.getMessage(),e);msg.recycle();returnfalse;}// 标记消息已经被使用msg.markInUse();msg.when=when;// 队头Messagep=mMessages;booleanneedWake;if(p==null||when==0||when<p.when){// 将消息放入队头位置// New head, wake up the event queue if blocked.msg.next=p;mMessages=msg;needWake=mBlocked;}else{// 将新消息插入到队列中,同步消息根据when来确定队列位置,如果是同步屏障,同步屏障消息target==null,且新消息是异步的,则needWake为true,则计划唤醒队列。// Inserted within the middle of the queue. Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake=mBlocked&&p.target==null&&msg.isAsynchronous();Messageprev;for(;;){prev=p;p=p.next;if(p==null||when<p.when){break;}// 如果队头的一下个消息也是异步消息,且未到唤醒时间,则将needWake置为false,不唤醒队列if(needWake&&p.isAsynchronous()){needWake=false;}}msg.next=p;// invariant: p == prev.nextprev.next=msg;}// 是否需要唤醒队列// We can assume mPtr != 0 because mQuitting is false.if(needWake){nativeWake(mPtr);}}returntrue;}首先是对消息进行检验,消息能否被处理和消息是否正在被使用等。msg.target是指Handler对象。当处理消息的线程已经退出,则消息会被回收。
针对同步消息:将时间小的消息放在靠近队头的位置,时间长的消息靠近队尾。
从队头开始,找到新消息在队列中合适的位置,然后将新消息插入到队列中,如果队列为null或新消息的等待时间小于队头消息的等待时间,则直接将新消息放入到队列的头部。如果队头消息的时间小于新消息,则新消息将从队头消息开始依次和队列中的消息进行比较,直到找到合适的位置。
这里特别说明下:同步消息和异步消息只有isAsynchronous()可以区分,即异步标志位区分,异步标志位是通过msg的setAsynchronous(boolean async)方法进行设置,异步和同步消息均是根据when来确定在队列中的位置。
(2) 读取消息的next方法
Messagenext(){// 只有当线程退出时,mPtr为null// Return here if the message loop has already quit and been disposed.// This can happen if the application tries to restart a looper after quit// which is not supported.finallongptr=mPtr;if(ptr==0){returnnull;}intpendingIdleHandlerCount=-1;// -1 only during first iterationintnextPollTimeoutMillis=0;for(;;){if(nextPollTimeoutMillis!=0){Binder.flushPendingCommands();}nativePollOnce(ptr,nextPollTimeoutMillis);synchronized(this){// Try to retrieve the next message. Return if found.finallongnow=SystemClock.uptimeMillis();MessageprevMsg=null;Messagemsg=mMessages;// 同步屏障,优先处理异步消息,此时同步消息不会被处理,体现出消息的优先级,可以优先处理高优先级的消息,即异步消息。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();returnmsg;}}else{// No more messages.nextPollTimeoutMillis=-1;}// Process the quit message now that all pending messages have been handled.if(mQuitting){dispose();returnnull;}...}}}在next方法中,当发现队列中有同步屏障时,此时会优先返回队列中的异步消息,从队列中获取异步消息,并将该异步消息从队列中移除,如果同步屏障不被移除,即使异步消息被处理完毕,同步消息也不会被处理,队列会进入阻塞状态。
如果队列中没有同步屏障,则从队列中获取同步消息,并将该同步消息从队列中移除。
2.4 Message
2.4.1 概述
- 消息类型
- 如何创建Message
2.4.2 源码分析
(1)重要属性
比如target属性,对于屏障消息这里target就是null;
when属性,handler.postDelay方法底层利用了when属性
// 用户自定义,主要用于辨别Message的类型publicintwhat;// 用于存储一些整型数据publicintarg1;publicintarg2;// 可放入一个可序列化对象publicObjectobj;// Bundle数据Bundledata;// Message处理的时间。相对于1970.1.1而言的时间// 对用户不可见publiclongwhen;// 处理这个Message的Handler// 对用户不可见Handlertarget;// 当我们使用Handler的post方法时候就是把runnable对象封装成Message// 对用户不可见Runnablecallback;// MessageQueue是一个链表,next表示下一个// 对用户不可见Messagenext;(2) obtain方法
publicstaticMessageobtain(){synchronized(sPoolSync){if(sPool!=null){Messagem=sPool;sPool=m.next;m.next=null;m.flags=0;// clear in-use flagsPoolSize--;returnm;}}returnnewMessage();}(3) recyler
publicvoidrecycle(){if(isInUse()){if(gCheckRecycle){thrownewIllegalStateException("This message cannot be recycled because it "+"is still in use.");}return;}recycleUnchecked();}voidrecycleUnchecked(){flags=FLAG_IN_USE;what=0;arg1=0;arg2=0;obj=null;replyTo=null;sendingUid=UID_NONE;workSourceUid=UID_NONE;when=0;target=null;callback=null;data=null;synchronized(sPoolSync){if(sPoolSize<MAX_POOL_SIZE){next=sPool;sPool=this;sPoolSize++;}}}Message的作用就是承载消息,他的内部有很多的属性用于给用户赋值。同时Message本身也是一个链表结构,无论是在MessageQueue还是在Message内部的回收机制,都是使用这个结构来形成链表。同时官方建议不要直接初始化Message,而是通过Message.obtain()方法来获取一个Message循环利用。一般来说我们不需要去调用recycle进行回收,在Looper中会自动把Message进行回收
3. 其他问题
3.1 HandlerThread
普通线程:一次性执行 → 跑完销毁
HandlerThread:常驻子线程,接收消息串行执行任务
3.2 IdleHandler
3.3 主线程Handler的流程图
4. 思维导图
概述
通过网盘分享的文件:消息机制Handler.xmind
链接: https://pan.baidu.com/s/1kEuvqI4LFb–Y3HBkXLnaQ 提取码: wce4 复制这段内容后打开百度网盘手机App,操作更方便哦源码思维导图
