Android帧绘制流程深度解析 (二)

书接上回:Android帧绘制流程深度解析 (一)请添加图片描述

5、 dispatchVsync:

在请求Vsync以后,choreographer会等待Vsync的到来,在Vsync信号到来后,会触发dispatchVsync函数,从而调用onVsync方法:

private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,VsyncEventData vsyncEventData) {onVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData);
}

不过这里需要注意的是,DisplayEventReciever这个类中就有一个onVsync方法,不过这个onVsync方法是空的,所以其实这里是调用的是FrameDisplayEventReciever中的onVsync方法。
FrameDisplayEventReciever类是Choreographer类的一个内部类,其继承自DisplayEventReciever类,所以就有了onVsync方法。其实这里从头到尾调用的都是FrameDisplayEventReciever类的方法,只是因为该类没有dispatchVsync方法,所以才会调用到了DisplayEventReciever类。

6、 onVsync:

public void onVsync(long timestampNanos, long physicalDisplayId, int frame,VsyncEventData vsyncEventData) {try {if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {Trace.traceBegin(Trace.TRACE_TAG_VIEW,"Choreographer#onVsync "+ vsyncEventData.preferredFrameTimeline().vsyncId);}long now = System.nanoTime();//获取当前时间if (timestampNanos > now) {//如果该帧的理论绘制时间比现在晚,可直接修改到现在立即绘制Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)+ " ms in the future!  Check that graphics HAL is generating vsync "+ "timestamps using the correct timebase.");timestampNanos = now;}if (mHavePendingVsync) {//还是为了同一时刻只绘制一帧Log.w(TAG, "Already have a pending vsync event.  There should only be "+ "one at a time.");} else {mHavePendingVsync = true;}mTimestampNanos = timestampNanos;//确认帧时间mFrame = frame;//mLastVsyncEventData = vsyncEventData;Message msg = Message.obtain(mHandler, this);//(1)生成消息,并发送给mHandlermsg.setAsynchronous(true);//设置为异步消息mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);//发送消息} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}
}

(1)处可见,生成一个消息,其调用的函数是Message中的这个方法:

public static Message obtain(Handler h, Runnable callback) {Message m = obtain();m.target = h;m.callback = callback;return m;
}

第一个参数是目标handler,第二个参数是callback;这里涉及到消息机制中一个知识点就是在dispatchMessage的时候,会优先查看消息的runnable内容,再到

handler的callback,如果前两个存在的话,就不会去再调用handleMessage流程了。
public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);//(2)} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}
	所以上面(1)处的功能就是构建一个发送给mHandler的,callback为当前对象的消息,当前对象就是FrameDisplayEventReceiver这个类的对象,所以再mHandler端读取消息时,会做什么呢? 当然是执行上面(2)处的功能,而且FrameDisplayEventReciever类也实现了Runnable,所以这里就是要去执行FrameDisplayEventReciever类对象的run方法了。而在消息发送时,具体发送到哪呢?根据mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);这行代码可以看出,调用的是mHandler的发送函数。所以跟到Handler类中的sendMessageAtTime函数:
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);
}
	从老方法中可以看出,最后就是将消息入队到队列queue中,而queue就是当前Handler类的对象mHandler的成员变量mQueue。再回到Choreographer类,mHandler的定义是FrameHandler类的成员变量;而FrameHandler类继承自Handler:
private final class FrameHandler extends Handler {public FrameHandler(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_DO_FRAME:doFrame(System.nanoTime(), 0, new DisplayEventReceiver.VsyncEventData());break;case MSG_DO_SCHEDULE_VSYNC:doScheduleVsync();break;case MSG_DO_SCHEDULE_CALLBACK:doScheduleCallback(msg.arg1);break;}}
}

看到这里发现FrameHandler不过是重写了handleMessage方法而已,其实对我们上面发送的消息都没影响的。然后还有就是要找到mHandler对应的mQueue是什么。
还记得之前说的消息机制吗?消息队列是在哪初始化的?对了就是Looper的初始化方法中初始化的,而这里的,然后Handler类构建对象时,会将Looper的消息队列赋值给自己。所以能将Handler和Looper关联上。而Looper又是跟当前线程相关联的,所以这里的消息队列就是Choreographer所在的线程的消息队列了。

7、 run:

在接受到Vsync信号触发的帧绘制的消息后,主线程就会执行这个Runnable的run方法,也就是FrameDisplayEventReciever中的run方法;该方法很简单,就一个功能,就是doFrame;

public void run() {mHavePendingVsync = false;doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}

不过整个doFrame的逻辑还是相当复杂的。

8、 doFrame:

	 void doFrame(long frameTimeNanos, int frame,DisplayEventReceiver.VsyncEventData vsyncEventData) {final long startNanos;final long frameIntervalNanos = vsyncEventData.frameInterval;try {FrameData frameData = new FrameData(frameTimeNanos, vsyncEventData);synchronized (mLock) {if (!mFrameScheduled) {//在scheduleFrameLocked处置为true,会在本函数中置为false,正常情况下在这里是为true的traceMessage("Frame not scheduled");return; // no work to do}long intendedFrameTimeNanos = frameTimeNanos;//记录传入的帧绘制时间startNanos = System.nanoTime();//获取当前时间final long jitterNanos = startNanos - frameTimeNanos;//当前时间减去当前帧应该绘制的时刻,如果出现掉帧,该值会基于掉帧时间越来越大if (jitterNanos >= frameIntervalNanos) {// frameIntervalNanos是单帧绘制的时间long lastFrameOffset = 0;//初始化掉帧后的偏移时间量if (frameIntervalNanos == 0) {Log.i(TAG, "Vsync data empty due to timeout");} else {lastFrameOffset = jitterNanos % frameIntervalNanos;//掉帧时间偏移量final long skippedFrames = jitterNanos / frameIntervalNanos;//掉帧数量if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {//掉了30帧以上Log.i(TAG, "Skipped " + skippedFrames + " frames!  "+ "The application may be doing too much work on its main "+ "thread.");}if (DEBUG_JANK) {Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "+ "which is more than the frame interval of "+ (frameIntervalNanos * 0.000001f) + " ms!  "+ "Skipping " + skippedFrames + " frames and setting frame "+ "time to " + (lastFrameOffset * 0.000001f)+ " ms in the past.");}}frameTimeNanos = startNanos - lastFrameOffset;//将帧的理论起始时间进行更新。frameData.updateFrameData(frameTimeNanos);}if (frameTimeNanos < mLastFrameTimeNanos) {//修正完后,此帧的理论开始时间竟然在上一帧的开始时间之前,显然有问题啊if (DEBUG_JANK) {Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "+ "previously skipped frame.  Waiting for next vsync.");}traceMessage("Frame time goes backward");scheduleVsyncLocked();return;}if (mFPSDivisor > 1) {//这里不知道是啥long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {traceMessage("Frame skipped due to FPSDivisor");scheduleVsyncLocked();return;}}mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos,vsyncEventData.preferredFrameTimeline().vsyncId,vsyncEventData.preferredFrameTimeline().deadline, startNanos,vsyncEventData.frameInterval);mFrameScheduled = false;//复位mFrameScheduledmLastFrameTimeNanos = frameTimeNanos;//将当前帧的消息作为历史帧进行记录了mLastFrameIntervalNanos = frameIntervalNanos;mLastVsyncEventData = vsyncEventData;}AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);mFrameInfo.markInputHandlingStart();doCallbacks(Choreographer.CALLBACK_INPUT, frameData, frameIntervalNanos);//处理输入事件mFrameInfo.markAnimationsStart();doCallbacks(Choreographer.CALLBACK_ANIMATION, frameData, frameIntervalNanos);//处理动画doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameData,frameIntervalNanos);mFrameInfo.markPerformTraversalsStart();doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameData, frameIntervalNanos);//处理界面的重绘doCallbacks(Choreographer.CALLBACK_COMMIT, frameData, frameIntervalNanos);//涉及视图的最终更新和提交。} finally {AnimationUtils.unlockAnimationClock();Trace.traceEnd(Trace.TRACE_TAG_VIEW);}if (DEBUG_FRAMES) {final long endNanos = System.nanoTime();Log.d(TAG, "Frame " + frame + ": Finished, took "+ (endNanos - startNanos) * 0.000001f + " ms, latency "+ (startNanos - frameTimeNanos) * 0.000001f + " ms.");}
}

从上面的代码看内容比较多,其实总结起来就是两件事:1、记录当前帧的信息,将其作为历史帧;2、按需执行五个callback。
这里的callback是采用一个二维数组进行存储的,数组为:
private final CallbackQueue[] mCallbackQueues;
这个数组在Choreographer的构造函数中初始化:
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}

其中CALLBACK_LAST值为4,所以就是一个5*n的数组,数组存储5类回调分别为:

public static final int CALLBACK_INPUT = 0;
public static final int CALLBACK_ANIMATION = 1;
public static final int CALLBACK_INSETS_ANIMATION = 2;
public static final int CALLBACK_TRAVERSAL = 3;
public static final int CALLBACK_COMMIT = 4;

其中2CALLBACK_INSETS_ANIMATIO的功能我不是很了解,而本次讲的界面刷新流程,就是第三类CALLBACK_TRAVERSAL了。也就是在postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis)方法中的这里,会根据type将action入队,然后等待后面的doFrame方法将action取出来再执行了。
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

9、 总结:

整个帧绘制的流程还是比较复杂的,但是我也在这个过程中,对消息机制等知识点,在流程中进行更加详细的分析和讲解。其中可能也存在理解不到位的地方,还希望大家多多指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/26568.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手机和模拟器的 Frida 环境配置

目录 一、配置 JDK 和 android 环境 二、连接设备和查看权限 1、连接设备 2、查看手机权限 三、手机配置 Frida 1、frida-server下载 2、验证 四、模拟器配置 Frida 1、下载模拟器并调节成手机版&#xff1a; 2、连接并查看架构 3、配置并开启 x86 的 frida-serve…

中文大数据训练的数据集

在训练中文大模型时&#xff0c;选择合适的数据集至关重要。以下是一些常用于中文大数据训练的数据集&#xff1a; 1. 新闻数据集 新闻数据集通常涵盖广泛的领域&#xff0c;包括时事、财经、体育、科技等&#xff0c;具有实时性和高质量的特点。 SogouCA&#xff1a;搜狗公…

shell脚本循环

循环&#xff1a; 循环是一种重复执行一段代码的结构。只要满足循环的条件会一直执行此代码。 组成部分&#xff1a;循环条件、循环体 **循环条件&#xff1a;**在一定范围之内&#xff0c;按照指定的次数来执行循环。 **循环体&#xff1a;**在指定的次数内&#xff0c;执行…

Phybers:脑纤维束分析软件包

摘要 本研究提供了一个用于分析脑纤维束数据的Python库(Phybers)。纤维束数据集包含由表示主要白质通路的3D点组成的流线(也称为纤维束)。目前已经提出了一些算法来分析这些数据&#xff0c;包括聚类、分割和可视化方法。由于流线的几何复杂性、文件格式和数据集的大小(可能包…

深度学习 - RNN训练过程推演

1. 数据准备 字符序列 “hello” 转换为 one-hot 编码表示&#xff1a; 输入: [‘h’, ‘e’, ‘l’, ‘l’]输出: [‘e’, ‘l’, ‘l’, ‘o’] 2. 初始化参数 假设我们使用一个单层的 RNN&#xff0c;隐藏层大小为2。初始参数如下&#xff1a; W x h ( 0.1 0.2 0.3 0.4…

HTML静态网页成品作业(HTML+CSS)—— 环保主题介绍网页(5个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有5个页面。 二、作品演示 三、代…

多层tablayout+ViewPager,NestedScrollView+ViewPager+RecyclerView,嵌套吸顶滑动冲突

先看实现的UI效果 其实就是仿BOSS的页面效果&#xff0c;第二层tab下的viewpager滑到最右边再右滑&#xff0c;就操作第一层viewpager滑动。页面上滑时把第一层tab和vp里的banner都推出界面&#xff0c;让第二层tab吸顶。 滑上去第二个tab块卡在顶部&#xff0c;如图 我混乱…

React 渲染函数render、初始化函数、更新函数运行了两次,原因为何,如何解决? React.StrictMode

文章目录 Intro官网解释解决另一篇官网文章——初始化函数或更新函数运行了两次 Intro 我在用 react 写一个 demo &#xff0c;当我在某个自定义组件的 return 语句之前加上一句log之后&#xff0c;发现&#xff1a;每次页面重新渲染&#xff0c;该行日志都打印了两次&#xf…

HOW - 锚点(Anchor)导航

目录 创建锚点导航目录结构页面内容 说明样式和体验优化关键点总结 在Web开发中&#xff0c;锚点&#xff08;Anchor&#xff09;通常用于创建页面内的导航链接&#xff0c;使用户可以点击链接跳转到页面的特定部分。这通常通过HTML中的id属性和链接中的哈希片段实现。 以下是…

vue-loader

Vue Loader 是一个 webpack 的 loader&#xff0c;它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件 起步 安装 npm install vue --save npm install webpack webpack-cli style-loader css-loader html-webpack-plugin vue-loader vue-template-compiler webpack…

论文阅读Rolling-Unet,卷积结合MLP的图像分割模型

这篇论文提出了一种新的医学图像分割网络Rolling-Unet&#xff0c;目的是在不用Transformer的前提下&#xff0c;能同时有效提取局部特征和长距离依赖性,从而在性能和计算成本之间找到良好的平衡点。 论文地址&#xff1a;https://ojs.aaai.org/index.php/AAAI/article/view/2…

使用nmcli命令创建、删除bond

前言 在之前的文章中&#xff0c;描述的创建bond的方式&#xff0c;是使用配置文件的方式&#xff0c;在创建bond的时候创建一个对应的配置文件&#xff0c;修改、删除都操作此配置文件&#xff0c;这种方式实现bond没有问题&#xff0c;但是对于某些系统下&#xff0c;bond灵…

用链表实现的C语言队列

一、队列概述 在数据结构中&#xff0c;队列是一种先进先出&#xff08;FIFO&#xff09;的线性表。它在许多应用场景中非常有用&#xff0c;例如任务调度、进程管理、资源管理等。队列是一种重要的数据结构&#xff0c;其主要特点是先进先出&#xff08;FIFO, First In First …

618购物狂欢节有哪些数码好物值得抢购?年终必备神器清单大揭秘!

一年一度的“618年中大促”即将拉开帷幕&#xff0c;大家是否已经挑选好了心仪的宝贝呢&#xff1f;那些平时心仪已久的商品&#xff0c;是否总期待着在价格最优惠时收入囊中&#xff1f;毫无疑问&#xff0c;618就是这样一个绝佳的时机&#xff0c;因为各大电商平台都会纷纷推…

python datetime time timedelta

datetime 参考&#xff1a;https://blog.csdn.net/lovedingd/article/details/134929553 time timedelta 参考&#xff1a;https://geek-docs.com/python/python-ask-answer/981_python_formatting_timedelta_objects.html timedelta 是 Python 中的一个类&#xff0c;用于…

怎样为Flask服务器配置跨域资源共享

为了在 Flask 服务器中配置跨域资源共享&#xff08;CORS&#xff09;&#xff0c;你可以使用 flask-cors 扩展。这个扩展可以帮助你轻松地设置 CORS 规则&#xff0c;从而允许你的 Flask 服务器处理来自不同源的请求。 以下是配置 CORS 的步骤&#xff1a; 安装 flask-cors …

Lecture2——最优化问题建模

一&#xff0c;建模 1&#xff0c;重要性 实际上&#xff0c;我们并没有得到一个数学公式——通常问题是由某个领域的专家口头描述的。能够将问题转换成数学公式非常重要。建模并不是一件容易的事&#xff1a;有时&#xff0c;我们不仅想找到一个公式&#xff0c;还想找到一个…

ansys有限元分析

1.悬臂梁 /prep7 ! 定义单元类型 et,1,beam4 ! 定义材料属性 mp,ex,1,200e9 ! 弹性模量 mp,prxy,1,0.3 ! 泊松比 ! 定义截面属性 sectype,1,beam,rect ! 定义矩形截面 secdata,0.1,0.1 ! 截面宽度和高度 ! 创建节点 n,1,0,0,0 n,2,2,0,0 n,3,4,0,0 n,4,6,0,0 n,5,8,0,…

什么叫做数据字典

数据字典是数据库或信息系统中用来存储关于数据的信息的集合。它包括了数据项、数据结构、数据流、数据存储、处理逻辑等方面的定义和描述。数据字典为系统的分析、设计和维护提供了有关数据的信息,是数据管理和数据维护的重要工具。 通俗地说,数据字典就像是一本“字典”,…

群晖NAS安装配置Joplin Server用来存储同步Joplin笔记内容

一、Joplin Server简介 1.1、Joplin Server介绍 Joplin支持多种方式进行同步用户的笔记数据&#xff08;如&#xff1a;Joplin自己提供的收费的云服务Joplin Cloud&#xff0c;还有第三方的云盘如Dropbox、OneDrive&#xff0c;还有自建的云盘Nextcloud、或者通过WebDAV协议来…