Handler处理消息的优先级策略

Handler机制是Android中基于消息队列模式的一套线程消息机制,负责消息的分发和处理,分发什么消息和怎么去处理是需要我们自定义的,Handler机制只是提供了这样一套模式,我们只需要遵循这个模式就可以实现消息的发送,接收和处理。Handler机制的重要组成部分为:HandlerMessageMessageQueueLooper。代码位置在:frameworks/base/core/java/android/os

Handler主线程与子线程通信媒介,线程消息的处理者。

Message:线程间通信的数据单元。

MessageQueue:消息队列,存储Handler发过来的Message。

LooperMessageQueueHandler的通信媒介,用于循环取消息和分发消息。

https://i-blog.csdnimg.cn/blog_migrate/06ebd665f5cf1e3e3eb783dbbc36b225.png

一个消息的流程从创建,到入队列,再经过消息循环然后取出派发给对应的Handler进行处理。

1.消息的发送

消息的发送最常用的两种方式为:handler.sendMessage()handler.post()sendMessagepost的方法都是发送一个消息到消息队列中,实现方式上略有区别,post输入的是Runnable接口,封装成message。消息队列和handler都是依赖于同一个线程的。

两种消息发送方式最终都调用到了Handler.sendMessageDelayed()

可以看到sendMessageAtTime的第二个参数为系统从开机到现在的毫秒数加上delayMillis。如果sendMessage不提供时间参数的话,会默认为系统的uptimeMills。所以消息队列中的消息是按照时间进行插入的。在sendMessageAtTime中获取消息队列的实例化对象,

然后queue.enqueueMessag方法将消息插入消息队列

这里注意这个target属性,将当前的handler封装进了msg对象中,这个在循环取消息的时候会用到。

2.消息循环

Looper可以通过Looper.prepareMainLooper()或者Looper.prepare()来进行创建,其中Looper.prepareMainLooper()是为主线程创建Looper,Looper.prepare()是为子线程创建Looper

  1.  prepareMainLooper方法分析:

为主线程创建一个looper,在looper方法中会生成对应的MessageQueue,该方法在主线程生成时自动调用,prepare的参数为false,表示不允许退出,android应用进程启动时会默认创建一个主线程,自动调用主线程的静态main方法,main方法的部分代码:

代码路径为/frameworks/base/core/java/android/app/ActivityThread.java

main方法除了会调用prepareMainLooper创建Looper之外,还会自动调用Looper.loop()开启主线程的消息循环。

  1.  Prepare方法分析:

首先判断sThreadLocal是否为null,否则抛出异常,因为prepare只能被调用一次,一个线程对应一个looper实例。如果为首次调用prepare方法,则创建一个Looper对象,并且存放在sThreadLocal中。Looper在创建过程中会创建一个与之匹配的MessageQueue,

总结:

1.主线程looper会自动生成,且不允许退出,quiteAllowed=false,不需要手动生成,子线程的looper需要prepare手动创建。

2.子线程若不手动创建looper对象,则无法生成handler对象,因为handler对象构造的时候会获取当前线程的looper对象进行判断。

(3) 开启循环:

主线程的消息循环是自动开启的,main方法会自动调用prepareMainLooper方法,为主线程生成一个looper对象,同时生成MessageQueue,并开启消息循环。子线程完成LooperMessageQueue的创建之后,需要调用Looper.loop()方法开启消息循环,从消息队列中获取消息,分发给对应的handler

代码位置:frameworks/base/core/java/android/os/Looper.java

myLooper的作用是返回sThreadLocal存储的Looper实例,若为空则抛出异常,loop()之前必须调用prepare来完成looper的创建。loop通过for来实现消息循环:

在loopOnce中会通过me.mQueue.next 从消息队列中取消息,若取出的消息为空,则线程阻塞。其中nextPollTimeoutMillis代表下一个消息到来前,还需要等待的时长,当nextPollTimeoutMillis = -1 时,表示无消息,会一直等待下去,当队列中加入新消息,会更新nextPollTimeoutMillis。

nativePollOnce会调用到通过动态注册的jni层的android_os_MessageQueue _nativePollOnce,最终会调用到Looper::pollInner方法,pollInner部分代码:

这里会根据下一条消息的时间来计算timeoutMillis的时间,这段时间内队列取消息的返回值一直为空,处于阻塞状态,直到epoll_wait检测到超时或者指定事件返回。

3.消息分发:

取出来的消息会在loopOnce中通过msg.target.dispatchMessage(msg)把消息分发给msg.target对应的Handler,这个target就是在消息入队列的时候通过queue.enqueueMessag进行设置的。

接着会调用到Handler的dispatchMessage()方法进行派发处理

在消息分发的时候会进行判断是哪种发送方式:

  1. msg.callback属性不为空,代表message中定义了消息的处理方式,
  2. msg.callback属性为空,则代表message中没有定义消息的处理方式,进而判断构建Handler时是否定义callback,若不为空,且mCallback.handlemessage方法的返回值为true,不做处理。
  3. msg.callback属性为空,message中没有定义消息的处理方式,handler.callback不为空,且mCallback.handlemessage方法的返回值为false,会执行handleMessage(msg)。

4.Handler.runWithScissors():

当我们在一个线程A中,通过将消息post到指定的handler(另一个线程B),随后A线程会挂起,等待B线程执行完毕唤醒A线程。

runWithScissors接受一个Runnable,并设置一个超时时间,首先会对Runnabletimeout进行参数校验。

然后判断当前线程和handler的处理线程是否一致,一致的话,直接运行这个Runnable。不一致的话,把这个Runnable封装进一个BlockingRunnable,并执行postAndWait()方法。

 

文件路径:frameworks/base/core/java/android/os/Handler.java

待执行的任务会被记入BlockingRunnablemTask,等待后续被调用执行。postAndWait()先通过 handler 尝试将 BlockingRunnable 发出去,之后进入 Synchronized 临界区,尝试 wait() 阻塞。如果用户设置了timeout,且当前还没到超时的时间点,则使用wait(timeout)进入阻塞,若超时被唤醒,则直接返回false,表示任务失败。

postAndWait返回false有两个场景:

  1. Handler.post失败,表示looper出现问题,正在退出。
  2. 等待超时,超时后会会继续判断mDone的值,若mDone此时依然不是true,则会重新计算delay延时,此时计算出来是负值,即等待时间超过timeout的时间,任务还没有执行结束。

除了超时唤醒,还需要在任务执行结束唤醒当前线程。

runWithScissors() 存在的问题:

  1. 超时没有取消逻辑

通过runWithScissors发送Runnable时,指定了超时时间,当超时被唤醒时,直接false退出,当超时退出时,这个Runnable并没有从MessageQueue中移除,最后还会被Handler线程调度执行。

  1. 可能造成死锁

我们通过Handler发送的消息一般都会得到执行,当线程的Looper通过quit()退出时,会清理掉还未执行的任务,此时发送线程,就永远得不到唤醒,如果当前持有别的锁,还会造成死锁。

所以安全使用runWithScissors必须满足两个条件:

  1. Handler的Looper不允许退出
  2. Looper的退出要使用quitSafely的方式退出

5.消息的优先级:

以上介绍了一个正常消息的发送,循环,然后派发和处理流程,消息每次都会按照时间的先后插入到队列中,考虑一个场景,如果有一个消息非常的紧急,必须要立刻处理,我们应该怎么做?

提升消息的优先级有两种方案:

  1. 通过sendMessageAtFrontOfQueuepostAtFrontOfQueue将消息放到队列的首部,提升消息优先级。
  2. 通过异步消息和屏障消息组合实现提升消息优先级。

在介绍两种方案的具体实现之前,我们先了解一下消息的分类:

同步消息:就是正常通过message.obtain()得到的普通消息。

异步消息

  1. 通过调用message. setAsynchronous(true)将消息设置为异步消息

  1. 创建handler的时候,参数asynctrue时,handler会为每一个消息调用setAsynchronous

创建Handler的时候,会把输入参数async的值赋给mAsynchronous,在消息入队列的时候会判断mAsynchronous,然后是否调用setAsynchronous将消息设置为异步消息。

屏障消息:通过MessageQueue#postSyncBarrier将屏障消息加入消息队列,MessageQueue#removeSyncBarrir移除屏障消息。

屏障消息与普通消息的主要区别就是屏障消息不具有target属性,消息在入队列的时候会把handler属性封装进msg的target属性中,而屏障消息target=null。

(1)sendMessageAtFrontOfQueue和postAtFrontOfQueue实现

postAtFrontOfQueue的实现原理也是通过sendMessageAtFrontOfQueue,我们主要分析一下sendMessageAtFrontOfQueue()

可以对比着sendMessageAtTime来进行分析

sendMessageAtTime()sendMessageAtFrontOfQueue()两者最终都会调用到enqueueMessage()方法,唯一不同的地方就是第三个输入参数,因为最终会根据时间的先后顺序来决定消息的插入位置。

sendMessageAtFrontOfQueue()方法中的enqueueMessage的第三个参数为0,而sendMessageAtTime()方法中的enqueueMessage的第三个参数为uptimeMillis,这个uptimeMillisSystemClock.uptimeMillis() + delayMillisdelayMilli是发消息的延迟时间,SystemClock.uptimeMillis()是系统从开机到现在的毫秒数(不包括睡眠时间),所以sendMessageAtFrontOfQueue()方法会把消息插入到消息队列的首部,直接进行处理。

(2) 异步消息和+屏障消息实现

首先说明异步消息和同步消息之间的主要的差别就是异步消息调用了setAsynchronous(true),如果不配合屏障消息使用的话,异步消息和同步消息没有区别。而屏障消息与普通的同步消息的区别就是target=null

MessageQueue.next()方法中,进行消息遍历的时候,如果发现了一个msg.target =null的时候,就说明当前消息就是一个屏障消息,然后会遍历消息链表直到msg.isAsynchronous为true,也就是找到第一个异步消息的时候,才能继续进行消息的分发和处理,否则会一直遍历直到移除屏障消息,然后才能够执行队列中的同步消息。

6. Handler机制消息优先级验证:

创建四条消息用于消息验证:

  1. Message message1 = Message.obtain(handler, new Runnable() {  
  2.     @Override  
  3.     public void run() {  
  4.         Log.d(TAG, "run message1: 1000");  
  5.     }  
  6. });  
  7. Message message2 = Message.obtain(handler, new Runnable() {  
  8.     @Override  
  9.     public void run() {  
  10.         Log.d(TAG, "run message2: 2000");  
  11.     }  
  12. });  
  13. Message message3 = Message.obtain(handler, new Runnable() {  
  14.     @Override  
  15.     public void run() {  
  16.         Log.d(TAG, "run message3: 3000");  
  17.     }  
  18. });  
  19. Message message4 = Message.obtain(handler, new Runnable() {  
  20.     @Override  
  21.     public void run() {  
  22.         Log.d(TAG, "run message4: 4000");  
  23.     }  
  24. }); 
  1.  普通的同步消息:

消息的处理顺序为:1,2,3,4。按照消息在队列中的先后顺序进行处理,我们验证在此情况下的消息的处理顺序:

  1. handler.sendMessageDelayed(message1, 1000); // 发送1秒后执行的同步消息  
  2. handler.sendMessageDelayed(message2, 2000); // 发送2秒后执行的同步消息  
  3. handler.sendMessageDelayed(message3, 3000); // 发送3秒后执行的同步消息  
  4. handler.sendMessageDelayed(message4, 4000); // 发送4秒后执行的同步消息  

得到的log信息为

  1. com.sprd.tv.mysyncbarrier D/MainActivity: run message1: 1000  
  2. com.sprd.tv.mysyncbarrier D/MainActivity: run message2: 2000  
  3. com.sprd.tv.mysyncbarrier D/MainActivity: run message3: 3000  
  4. com.sprd.tv.mysyncbarrier D/MainActivity: run message4: 4000  

对于普通的同步消息,会按照时间大小插入队列,然后遍历消息队列,对消息进行分发处理,在队列中靠前的会被优先处理。

        2. sendMessageAtFrontOfQueue插入消息:

消息的处理顺序为4,1,2,3。sendMessageAtFrontOfQueue会把当前的消息插入到消息队列的首部,然后按照队列中消息的前后顺序进行分发处理。

  1. handler.sendMessageDelayed(message1,1000); // 发送1秒后执行的同步消息  
  2. handler.sendMessageDelayed(message2, 2000); // 发送2秒后执行的同步消息  
  3. handler.sendMessageDelayed(message3, 3000); // 发送3秒后执行的同步消息  
  4. handler.sendMessageAtFrontOfQueue(message4); // 发送立刻执行的同步消息  

得到的log信息为:

  1. com.sprd.tv.mysyncbarrier D/MainActivity: run message4: 4000
  2. com.sprd.tv.mysyncbarrier D/MainActivity: run message1: 1000  
  3. com.sprd.tv.mysyncbarrier D/MainActivity: run message2: 2000  
  4. com.sprd.tv.mysyncbarrier D/MainActivity: run message3: 3000 

对于通过sendMessageAtFrontOfQueue发送的同步消息,会被优先处理。

考虑一个场景,当一条消息正在处理中,通过sendMessageAtFrontOfQueue发送的一个放在队列首位的消息,那么哪条消息先执行?

 

消息的处理顺序为12。虽然sendMessageAtFrontOfQueue发送的消息的优先级很高,但是当一个消息正在处理时,也只能等到当前消息处理完毕后,才能执行message2.

  1. Message message1 = Message.obtain(anotherHandler, new Runnable() {  
  2.     @Override  
  3.     public void run() {  
  4.         try {  
  5.             Log.d(TAG, "run: message1 : sleep 3000");  
  6.             Thread.sleep(3000);  
  7.         } catch (InterruptedException e) {  
  8.             e.printStackTrace();  
  9.         }  
  10.         Log.d(TAG, "run message1: 1000");  
  11.     }  
  12. });  

  1. anotherHandler.sendMessageDelayed(message1,0);  
  2. Log.d(TAG, "onCreate: sleep start message1");  
  3. Thread.sleep(2000);  
  4. Log.d(TAG, "onCreate: sleep end message1");  
  5. anotherHandler.sendMessageAtFrontOfQueue(message2);   
  6. Log.d(TAG, "onCreate: message2 is send");  

得到的log信息为:

  1. com.sprd.tv.mysyncbarrier D/MainActivity: onCreate: sleep start message1  
  2. com.sprd.tv.mysyncbarrier D/MainActivity: run: message1 : sleep 3000  
  3. com.sprd.tv.mysyncbarrier D/MainActivity: onCreate: sleep end message1  
  4. com.sprd.tv.mysyncbarrier D/MainActivity: onCreate: message2 is send  
  5. com.sprd.tv.mysyncbarrier D/MainActivity: run message1: 1000  
  6. com.sprd.tv.mysyncbarrier D/MainActivity: run message2: 2000 

log信息中可以看出,message1在执行的过程中,通过sendMessageAtFrontOfQueue发送message2,结果等message1执行完毕,再执行message2.

        3. 异步消息 + 屏障消息

 

消息的处理顺序为3,4,1,2,同步屏障加入得到消息队列中后,会优先处理消息队列中的异步消息,直到移除屏障消息之后才会处理消息队列中的同步消息。

  1. postSyncBarrier(); // 投递同步屏障到消息队列中,移除操作定义在messaage4的重写的run  
  2. message3.setAsynchronous(true);  
  3. message4.setAsynchronous(true);  
  4. handler.sendMessageDelayed(message1, 1000); // 发送1秒后执行的同步消息  
  5. handler.sendMessageDelayed(message2, 2000); // 发送2秒后执行的同步消息  
  6. handler.sendMessageDelayed(message3, 3000); // 发送3秒后执行的异步消息  
  7. handler.sendMessageDelayed(message4, 4000); // 发送4秒后执行的异步消息 

得到的log信息为:

  1. com.sprd.tv.mysyncbarrier D/MainActivity: postSyncBarrier   
  2. com.sprd.tv.mysyncbarrier D/MainActivity: run message3: 3000  
  3. com.sprd.tv.mysyncbarrier D/MainActivity: run message4: 4000  
  4. com.sprd.tv.mysyncbarrier D/MainActivity: removeSyncbarrier  
  5. com.sprd.tv.mysyncbarrier D/MainActivity: run message1: 1000  
  6. com.sprd.tv.mysyncbarrier D/MainActivity: run message2: 2000 

根据log信息可以看出,当消息队列中存在屏障消息时,会优先处理队列中的异步消息,直到移除屏障后才会处理队列中的同步消息。

 

处理消息的顺序为:1,3,4,2。屏障消息加入队列后,会优先处理它之后的异步消息,对于在屏障消息之前的消息没有任何影响。

  1. handler.sendMessageDelayed(message1,0); // 发送立即执行的同步消息  
  2. postSyncBarrier(); // 投递同步屏障到消息队列中  
  3. message3.setAsynchronous(true);  
  4. message4.setAsynchronous(true);  
  5. handler.sendMessageDelayed(message2, 2000); // 发送2秒后执行的同步消息  
  6. handler.sendMessageDelayed(message3, 3000); // 发送3秒后执行的异步消息  
  7. handler.sendMessageDelayed(message4, 4000); // 发送4秒后执行的异步消息  

得到的log信息为:

  1. com.sprd.tv.mysyncbarrier D/MainActivity: postSyncBarrier   
  2. com.sprd.tv.mysyncbarrier D/MainActivity: run message1: 1000  
  3. com.sprd.tv.mysyncbarrier D/MainActivity: run message3: 3000  
  4. com.sprd.tv.mysyncbarrier D/MainActivity: run message4: 4000  
  5. com.sprd.tv.mysyncbarrier D/MainActivity: removeSyncbarrier   
  6. com.sprd.tv.mysyncbarrier D/MainActivity: run message2: 2000  

从log信息中可以看出,屏障消息不影响在它之前的消息的派发和处理,只影响其后的消息的优先级,优先处理它之后的异步消息,移除屏障后才会处理它之后的同步消息。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值