当前位置: 首页 > news >正文

Android从屏幕刷新到View的绘制(三)之Handler异步消息与同步屏障

0. 相关分享

Android从屏幕刷新到View的绘制(一)之 Window、WindowManager和WindowManagerService之间的关系
Android从屏幕刷新到View的绘制(二)之Choreographer、Vsync与屏幕刷新

1. 相关类

Handler

Handler中维护着它所在的线程的Looper,或者由用户设定的Looper。Looper分发消息Message的时候,会根据Message中设定的target,也就是Handler实例,来决定由哪个Handler来处理这个Message消息。通常我们通过重写handleMessage()方法来自定义对Message的处理。

Message

消息实体,内部的实例数据包括了:这个Message的code编号信息what,让处理者知道这是什么类型的消息,它所承载的数据obj,表示合适执行的when,表示分发给哪个Handler的target等。

它是单向链表的结构,next变量指向了下一个Message。同时对Message的对象池缓存,也定义在了Message类中,是最大长度为50的单向链表sPool。可以通过obtain()来获取合适的可复用的Message。

MessageQueue

它是访问Message的入口,管理着Message链表,与Looper一一对应,在Looper实例化的时候进行的MessageQueue实例化。提供了对Message的管理操作,例如next()获取下一个Message、removeMessages()删除指定的Message、以及我们本文要谈到的添加删除同步屏障消息。

Looper

它是一个循环主体,每个线程最多只能有一个Looper,可以通过sThreadLocal来获取Thread私有的Looper,也可以通过getMainLooper()来获取主线程的Looper从而实现与主线程的通信,或者叫做线程切换。它的loop()方法打开了死循环,循环不断地向MessageQueue索取下一个可用的Message,阻塞获取的功能在MessageQueue中实现。他能获取到同步消息、异步消息,具体线下要分发什么消息由MessageQueue来决定。

2. 核心方法

2.1 MessageQueue.postSyncBarrier()

添加同步屏障,我们可以通过MessageQueue的postSyncBarrier()来实现:

//MessageQueue.java
public int postSyncBarrier() {return postSyncBarrier(SystemClock.uptimeMillis());
}private int postSyncBarrier(long when) {synchronized (this) {//这个token用来表示这个同步屏障是谁,可以借此用来remove同步屏障final int token = mNextBarrierToken++;//复用或者获取一个Messagefinal Message msg = Message.obtain();msg.markInUse();msg.when = when;msg.arg1 = token;//但不给这个Message设置target,以此表征它是同步屏障Message prev = null;Message p = mMessages;if (when != 0) {while (p != null && p.when <= when) {prev = p;p = p.next;}}//它将会插入到还未到执行时间的Message之前。但已经到了执行时间的,不会被同步屏障挡住。if (prev != null) { // invariant: p == prev.nextmsg.next = p;prev.next = msg;} else {msg.next = p;mMessages = msg;}return token;}
}

在工程中,我们想要添加同步屏障,可以通过让MessageQueue来postSyncBarrier()的方式:

looper.getQueue().postSyncBarrier();

需要的是,就算加了同步屏障,也不代表异步消息能够立即执行,因为添加同步屏障的时候,会跳过那些执行时间已经到达,需要立即执行的任务。同步屏障Message只会插入在立即执行的Message之后非立即执行的Message之前

所以即使收到了Vsync,分发了performTraversals()的异步任务,也不一定能够立即进行View的绘制流程。如果主线程有耗时操作,就可能引发跳帧。我们这里需要知道一个知识,并不是调用完View的绘制流程就立刻刷新屏幕的,只是对一个缓存进行数据修改,等到下一次Vsync到来的时候,SurfaceFlinger才会进行渲染刷新。

如果分发的绘制消息超时了,没有执行对缓存的更新,那么SurfaceFlinger绘制的仍然是之前的缓存,所以会表现出画面卡顿,因为SurfaceFlinger只负责把缓存取出来进行渲染,而把数据写入缓存的任务由于前面Message的任务超时,导致没有及时将数据写入,所以屏幕没有变化,也就是画面卡顿。MessageQueue拿到这个异步绘制消息的时候会判断执行时间,发现超时了,说明前面卡顿导致了跳帧,就会提示下面这样的log打印消息:

Choreographer: Skipped 599 frames!  The application may be doing too much work on its main thread.

2.2 MessageQueue.next()

同步屏障的核心原理,其实就是MessageQueue在获取下个可用Message的时候,会判断获取到的是不是一个同步屏障,如果是,则继续往后找,找到可用的异步消息,将其返回出去。

Message next() {for (;;) {//阻塞获取nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {//如果msg的target为null,就认为是同步屏障do {prevMsg = msg;msg = msg.next;//遍历跳过它之后所有同步消息} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {//如果将要获取得到的消息执行时间还没到,则跳过,进入下一次循环阻塞,并设置阻塞时间!nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {//如果拿到的Message没问题,时间到了,也不是同步屏障,那么就把它从Message链表中移除,并返回出去mBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;//标记为正在使用msg.markInUse();return msg;}} else {//如果message=null,也就是没有获取到Message,那么就进入到生产消费者模型的无限期等待情况,直到有Message进入,进行唤醒。nextPollTimeoutMillis = -1;}//退出looper的处理if (mQuitting) {dispose();return null;}}
}

2.3 发送异步消息

在实例化Handler的时候,可以将该Handler设为专门发送异步消息的:

public Handler(@Nullable Callback callback, boolean async) {...mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;//异步标志mAsynchronous = async;
}

后续由这个Handler发送Message的时候,会把所有由它加入MessageQueue的Message打上异步标记:

//Handler
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {//将Handler赋值给Message的target变量msg.target = this;//mAsynchronous为false,为同步消息if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);
}

当然,普通Handler也可以发送异步消息,这就需要我们手动将Message设置为异步消息,核心就是:

msg.setAsynchronous(true);

我们知道 Choreographer 中使用的都是异步消息。

http://www.lryc.cn/news/22424.html

相关文章:

  • 最新版axios@1.3.x取消请求-AbortController-初体验-番茄出品
  • Git的简述
  • webpack实战,手写loader和plugin
  • STM32CubeMX按键模块化 点灯
  • C#专栏目录(长期更新)
  • BurpSuite配置抓取HTTPS数据包
  • 图片转base64格式返回给前端,前端如何展示?
  • C++入门知识【超详解】
  • 零基础、非计算机系学Python该如何上手?
  • 关于 vue3 模板引用
  • Redis | 安装Redis和启动Redis服务
  • 博客要考虑的最佳WordPress主题
  • C 学习笔记 —— 函数指针
  • FastDDS-3. DDS层
  • 9.2 IGMPv2
  • 巨头混战,抢着“兜底”自动驾驶安全
  • RightCapital 第一轮面试题
  • Python曲线肘部点检测-膝部点自动检测
  • 【算法题】最大矩形面积,单调栈解法
  • 活动策划|深度分析年货节活动该如何策划!
  • Idea启动遇到 Web server failed to start. Port 8080 was already in use. 报错
  • Python3中zip()函数知识点总结
  • 过滤器,监听器,拦截器的原理与在Servlet和Spring的应用
  • minio spring boot 秒传、分片上传、断点续传文件实现
  • MTK平台使用Omnipeek分析空口协议讲解
  • string和自动推断类型
  • 【软件测试】从功能到自动化测试,测试人的进阶之路细节,这些必不可少......
  • C语言青蛙跳台阶【图文详解】
  • 笔记(五)——list容器的基础理论知识
  • 浅谈网络中接口幂等性设计问题