文化袁探索专栏——Activity、Window和View三者间关系
文化袁探索专栏——View三大流程#Measure
文化袁探索专栏——View三大流程#Layout
文化袁探索专栏——消息分发机制
文化袁探索专栏——事件分发机制
文化袁探索专栏——Launcher进程启动流程’VS’APP进程启动流程
文化袁探索专栏——Activity|Application启动流程
文化袁探索专栏——自定义View实现细节
文化袁探索专栏——线程安全
文化袁探索专栏——React Native启动流程

消息分发机制,必定离不开Handler、Looper、MessageQueue三者间关系;

关系: 一个线程之多有一个Looper;一个looper有一个MessageQueue;而一个MessageQueue对应了多个message;一个MessageQueue对应多个Handler。

先引出一个问题——为什么非静态类的 Handler 导致内存泄漏?如何解决?

非静态内部类、匿名内部类、局部内部类等,都会隐士的持有其外部类的引用。即若在Activity中创建了非静态Handler,那么会由此持有Activity的引用。当非静态Handler使用在主线程时,会默认绑定该线程的Looper对象,并关联它的MessageQueue,Handler发出的消息都会enQueue到MessageQueue消息队列中。(Looper对象的生命周期贯穿主线程整个生命周期)。当消息队列MessageQueue中还有未处理完的message消息时,因为每个message都持有Handler引用,handler无法被回收。同时handler持有的外部类Activity引用也无法回收,从而造成了泄露。

解决方案:使用静态内部类 + 弱引用方式

消息分发原理

发送消息

/**1.直接在Runnable中处理任务*/
handler!!.post(Runnable { ... }) //message.callback.run(),优先级最高
/**2.直接在Callback中接收处理任务*/
val handler = object:Handler(Callback{...})
/**3.直接在handleMessage中接收处理任务*/
val handler = object:Handler(){override fun handleMessage(msg: Message) {super.handleMessage(msg)}
}/*** Handle system messages here.*/public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg); // 优先级第一} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {// 优先级第二return;}}handleMessage(msg);// 优先级第三}}// 在这里发送消息有两种方式
// 1, handler.post*(Runnable)
// 2, handler.send*Message*(Message)

消息发送方式有有两种:handler.post开头的消息发送方法,和handler.send开头的消息发送方法。

handler.post和handler.send发送消息的区别?

以handler.send开头的消息发送方法,直接发送Message消息,通过调用sendMessageAtTime方法,最终调用消息入队方法enqueueMessage。处理消息时,在dispatchMessage 中优先级会低于 handleCallback(msg) 方法。

以handler.post开头的消息发送方法,Runnable最后会被封装成 Message 对象(Message.callback=runnable)。然后通过调用sendMessageAtTime方法,最终调用enqueueMessage将消息入队方法队列。只是在处理消息时,会在 dispatchMessage() 方法里首先被 handleCallback(msg) 方法执行。

在发送消息的时候,虽然都调用了sendMessageAtTime方法。好像是延迟**时间后发送,其实则是直接将发送的消息入队,并没有延迟。而真实延迟是在消息出队过程中。

消息插入队列(enQueue)

如果新消息的被执行时间when比队列中任何一个消息都早,则插入队头。且唤醒Looper。
如果当前队头是同步屏障消息,新消息是异步消息,则唤醒Looper。

【同步屏障消息】message.target=null,这类消息不会被真的执行,它起到标记作用,MessageQueue在遍历消息队列时,如果队头是同步屏障消息,那么会忽略同步消息,优先让异步消息得到执行。正如会在MessageQueue.java中看到的这几行代码:

// MessageQueue.java
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());
}

如果队头消息为空,或消息的when=0不需要延迟,或比队列中任何一个消息都早,则插入队列队头。否则按时间顺序,找到该消息合适的位置并插入。

 // MessageQueue.java 消息入队的逻辑方法boolean enqueueMessage(Message msg, long when) {//普通消息的target字段不能为空,否则不知道该交由谁来处理这条消息if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}synchronized (this) {msg.when = when;//mMessages始终是队头消息Message p = mMessages;boolean needWake;//如果队头消息为空,或消息的when=0不需要延迟,或比队列中任何一个消息都早        //则插入队头if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;//如果当前Looper处于休眠状态,则本次插入消息之后需要唤醒needWake = mBlocked;} else {//要不要唤醒Looper= 当前Looper处于休眠状态 & 队头消息是同步屏障消息 & 新消息是异步消息//目的是为了让异步消息尽早执行needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;//按时间顺序 找到该消息 合适的位置  if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}//跳转链表中节点的指向关系msg.next = p; prev.next = msg;}//唤醒looperif (needWake) {nativeWake(mPtr);}}return true;
}

Handler 实现发送延迟消息的原理是如何呢?
环境描述~
代码块中sendMessageDelayed–>>sendMessageAtTime,其中sendMessageAtTime方法参数第一个是消息本体,第二个则是要延迟发送的时间=SystemClock.uptimeMillis() + delayMillis。
uptimeMillis()表示自开机后所经过的时间,不包括深度休眠时间(如关机);currentTimeMillis()表示系统当前时间,可被修改;

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

我们常用 postDelayed() 与 sendMessageDelayed() 来发送延迟消息,其实最终都是将延迟时间转为入队时间,然后通过 sendMessageAtTime() -> enqueueMessage -> queue.enqueueMessage 这一系列方法将消息插入到 MessageQueue 中。所以并不是先延迟再发送消息,而是直接发送消息,再借助MessageQueue 的设计来实现消息的延迟处理。

消息延迟处理的原理涉及 MessageQueue 的两个静态方法 MessageQueue.next() 和 MessageQueue.enqueueMessage()。通过 nativePollOnce方法阻塞线程一定时间,等到消息的执行时间到后再取出消息执行。

// // MessageQueue$next方法中if (msg != null) {if (now < msg.when) {// 当Next message 还没到执行的时间,则设置nextPollTimeoutMillis=要阻塞的时间。// msg.when = SystemClock.uptimeMillis() + delayMillis// 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);}... ......

消息分发
Message消息分发逻辑,在MessageQueue类中的loop方法中执行。而在分发过程中,获取消息方法queue.next并不是会无限循环的执行,还会进入阻塞。

     // MessageQueue.java下loop方法执行消息分发public static void loop() {final Looper me = myLooper();...me.mInLoop = true;final MessageQueue queue = me.mQueue;...for (;;) {// 进行无限循环,.next()获取可执行的消息。其中 ‘might block’表示可能会阻塞// 具体的进入阻塞条件,进入到MessageQueue$next()方法可观察Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}...try {// 执行消息分发;target为当前Handler实例msg.target.dispatchMessage(msg);......}}

Message消息分发过程中,获取消息方法queue.next如何会进入阻塞? nextPollOne其实为指代阻塞的时间,如果nextPollTimeoutMillis>0则进入阻塞状态,直到nextPollTimeoutMillis时间过后。由于第一次nextPollTimeoutMillis=0,不会阻塞。如果第一次循环就没有需要分发的消息,nextPollTimeoutMillis会更新为nextPollTimeoutMillis=-1,再次进入循环时则进入阻塞,直到有新的消息进入。

// MessageQueue.java
Message next() {// 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.final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}// 执行进入阻塞的native方法 nativePollOne // nextPollTimeoutMillis其实是指代阻塞的时间。进入阻塞后会释放cpu资源。nativePollOnce(ptr, nextPollTimeoutMillis); // 当Looper被唤醒时,继续向下执行synchronized (this) {// Try to retrieve the next message.  Return if found.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 {// 从循环中看,若消息是同步消息则会一直.nextprevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {// 当Next message 还没到执行的时间,则设置nextPollTimeoutMillis=要阻塞的时间。// 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.表示下次消息循环会进入阻塞。知道有新的消息入队。nextPollTimeoutMillis = -1;}// Process the quit message now that all pending messages have been handled.if (mQuitting) {dispose();return null;}// If first time idle, then get the number of idlers to run.// Idle handles only run if the queue is empty or if the first message// in the queue (possibly a barrier) is due to be handled in the future.if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount <= 0) {// No idle handlers to run.  Loop and wait some more.mBlocked = true;continue;}if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// Run the idle handlers.// We only ever reach this code block during the first iteration.//注册 MessageQueue.IdleHandler,可以监听当前线程的Looper是否处于空闲状态。也就意味当前线程是否处于空闲状态。//在主线程中可以监听这个事件来做延迟初始化,数据加载,//而不是有任务就提交,从而避免抢占重要资源。for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}// Reset the idle handler count to 0 so we do not run them again.pendingIdleHandlerCount = 0;// While calling an idle handler, a new message could have been delivered// so go back and look again for a pending message without waiting.//此时置为0,既然监听了线程的空闲,那么在这个  //queueIdle回调里,有可能又会产生新消息,为让消息尽可能早的得到执行。设置=0表示此时不需要休眠nextPollTimeoutMillis = 0;}
}

文化袁探索专栏——消息分发机制相关推荐

  1. 文化袁探索专栏——事件分发机制

    文化袁探索专栏--Activity.Window和View三者间关系 文化袁探索专栏--View三大流程#Measure 文化袁探索专栏--View三大流程#Layout 文化袁探索专栏--消息分发机 ...

  2. 文化袁探索专栏——React Native启动流程

    文化袁探索专栏--Activity.Window和View三者间关系 文化袁探索专栏--View三大流程#Measure 文化袁探索专栏--View三大流程#Layout 文化袁探索专栏--消息分发机 ...

  3. 文化袁探索专栏——Activity|Application启动流程

    文化袁探索专栏--Activity.Window和View三者间关系 文化袁探索专栏--View三大流程#Measure 文化袁探索专栏--View三大流程#Layout 文化袁探索专栏--消息分发机 ...

  4. 文化袁探索专栏——线程池执行原理|线程复用|线程回收

    文化袁探索专栏--Activity.Window和View三者间关系 文化袁探索专栏--View三大流程#Measure 文化袁探索专栏--View三大流程#Layout 文化袁探索专栏--消息分发机 ...

  5. 文化袁探索专栏——自定义View实现细节

    文化袁探索专栏--Activity.Window和View三者间关系 文化袁探索专栏--View三大流程#Measure 文化袁探索专栏--View三大流程#Layout 文化袁探索专栏--消息分发机 ...

  6. 文化袁探索专栏——Activity、Window和View三者间关系

    文化袁探索专栏--Activity.Window和View三者间关系 <文化袁探索专栏--View三大流程#Measure 文化袁探索专栏--View三大流程#Layout 文化袁探索专栏--H ...

  7. delphi VCL研究之消息分发机制(转)

    原文来源,http://blog.csdn.net/sushengmiyan/article/details/8635550 1.VCL 概貌 先看一下VCL类图的主要分支,如图4.1所示. 在图中可 ...

  8. Cocos2d-x 3.0 屏幕触摸及消息分发机制

    ***************************************转载请注明出处:http://blog.csdn.net/lttree************************** ...

  9. kafka 消息分发机制、分区和副本机制

    一.消息分发机制 1.1 kafka 消息分发策略 消息是 kafka 中最基本的数据单元,在 kafka 中,一条消息由key.value两部分构成,在发送一条消息 时,我们可以指定这个key,那么 ...

最新文章

  1. hive环境搭建提示: java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkArgument
  2. 打印http地址打印双斜杠
  3. 去除cpp中注释的小程序
  4. SAP 月结F.19与GR/IR
  5. 客户信息管理系统——Java
  6. opencv计算两数组的乘积_#剑指Offer#12. 构建乘积数组
  7. linux中gid和groups区别,linux用户与组管理
  8. java制作加载界面_Java如何制作启动界面?
  9. Qt 程序访问 sqlite 权限错误
  10. java中string类的常用方法举例说明
  11. Windows版本Apache+php的Xhprof应用__[2]
  12. UE4(虚幻4)引擎下载与安装
  13. 关于CWMP基础(三)----(TR111)DHCP
  14. 【Matlab创建word文档,插入图注或表注】
  15. MySQL数据库学习日志(一):数据库概述及SQL语言基础
  16. csu1706irrational root
  17. KERNEL_DIR、系统平台、交叉编译器的指定,以及内核模块驱动文件的签名
  18. uni-app引入阿里巴巴icon在线图标
  19. SMTP命令与ESMTP命令简介(附带命令通信)
  20. 计算机网络技术期末论文,计算机网络技术专业论文题目 计算机网络技术论文题目怎么定...

热门文章

  1. 用CCS搭建简单的F28069M工程并控制LED闪烁
  2. Android 设置Switch样式
  3. 百家号在电脑上如何查看作者的其它文章
  4. 图像处理基础之颜色空间
  5. 手写redis@Cacheable注解 支持过期时间设置
  6. 鸿蒙 3.0 来了!新版本就是强啊!!
  7. 使用Python库valuequant和利润表历史数据计算股权价值
  8. 连英文都不懂怎么学python_在英语完全不懂的情况下如何学编程?
  9. 无接任何usb设备却提示无法识别usb设备
  10. DCT变换的基函数与基图像