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)生成消息,并发送给mHandler
        msg.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);
    }

    @Override
    public 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;//复位mFrameScheduled
            mLastFrameTimeNanos = 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、 总结:

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自监督分类网络:创新的端到端学习方法

现代人工智能的快速发展中&#xff0c;分类任务的高效解决方案一直备受关注。今天&#xff0c;我们向大家介绍一种名为Self-Classifier的全新自监督端到端分类学习方法。由Elad Amrani、Leonid Karlinsky和Alex Bronstein团队开发&#xff0c;Self-Classifier通过优化同一样本的…

Web应用安全测试-认证功能缺陷

Web应用安全测试-认证功能缺陷 存在空口令 漏洞描述&#xff1a;认证登录环节允许空口令 测试方法&#xff1a; 找到网站登录页面&#xff0c;尝试输入用用户名&#xff0c;密码为空进行登录。 风险分析&#xff1a;攻击者可利用该漏洞登录网站后台&#xff0c;操作敏感数…

qmt量化交易策略小白学习笔记第30期【qmt编程之获取行业概念数据--如何获取板块分类信息数据以及板块成分股数据】

qmt编程之获取行业概念数据 qmt更加详细的教程方法&#xff0c;会持续慢慢梳理。 也可找寻博主的历史文章&#xff0c;搜索关键词查看解决方案 &#xff01; 感谢关注&#xff0c;咨询免费开通量化回测与获取实盘权限&#xff0c;欢迎和博主联系&#xff01; 获取行业概念数…

VS2022+Qt雕刻机单片机马达串口上位机控制系统

程序示例精选 VS2022Qt雕刻机单片机马达串口上位机控制系统 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《VS2022Qt雕刻机单片机马达串口上位机控制系统》编写代码&#xff0c;代码整洁&a…

另辟蹊径的终端防病毒

在数字时代的浪潮中&#xff0c;网络安全问题愈发凸显&#xff0c;防病毒成为了保护信息安全的重要一环。而白名单作为一种有效的安全策略&#xff0c;在防病毒方面发挥着不可或缺的作用。 首先&#xff0c;我们需要明确白名单的概念。白名单是一种管理和安全实践&#xff0c;用…

【代码随想录】【算法训练营】【第30天 1】 [322]重新安排行程 [51]N皇后

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 30&#xff0c;周四&#xff0c;好难&#xff0c;会不了一点~ 题目详情 [322] 重新安排行程 题目描述 322 重新安排行程 解题思路 前提&#xff1a;…… 思路&#xff1a;回溯。 重点&…

ABB控制主板3BHE024855R0101 UF C921 A101

控制板也是一种电路板&#xff0c;其运用的范围虽不如电路板来的宽泛&#xff0c;但却比普通的电路板来的智能、自动化。简单的说&#xff0c;能起到控制作用的电路板&#xff0c;才可称为控制板。大到厂家的自动化生产设备&#xff0c;小到孩童用的玩具遥控汽车&#xff0c;内…

【OpenCV】opencv-4.9.0源码编译(二)

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ&#xff1a;870202403 公众号&#xff1a;VTK忠粉 前言 本文分享OpenCV源码编译时所遇到的问题&#xff0c;希望对各位小伙伴有所帮助&#xff01; 感谢各位小伙伴的点赞关注&#xff0c;小易会继续努力分享&#xff0c;一…

一文教会你静态住宅代理IP的优势和选择技巧,跨境小白收好这份指南!

一、什么是静态住宅代理IP&#xff1f; 静态住宅代理IP是指分配给个人住宅网络的IP地址&#xff0c;这些IP地址在长时间内保持不变。它们是从互联网服务提供商&#xff08;ISP&#xff09;获取的&#xff0c;因此拥有更高的可信度和较低的被封禁风险。静态住宅代理IP因其独特的…

基于 Python 解析 XML 文件并将数据存储到 MongoDB 数据库

1. 问题背景 在软件开发中&#xff0c;我们经常需要处理各种格式的数据。XML 是一种常用的数据交换格式&#xff0c;它可以存储和传输结构化数据。很多网站会提供 XML 格式的数据接口&#xff0c;以便其他系统可以方便地获取数据。 我们有这样一个需求&#xff1a;我们需要从…

Python界面编辑器Tkinter布局助手 使用体验

一、发现 我今天在网上搜关于Python Tkinter方面的信息时&#xff0c;发现了Python界面编辑器 Tkinter布局助手 的使用说明。 https://blog.csdn.net/weixin_52777652/article/details/135291731?spm1001.2014.3001.5506 这个编辑器是个开源的项目&#xff0c;个人用户可以…

Ecahrts横向柱状图自动滚动

1.定义一个定时器标识 let timer: NodeJS.Timer; // 定时器 2.定义展示的数据的条数 const dataZoomEndValue 5; // 数据窗口范围的结束数值(一次性展示几个) 3.设置datazoom的相关参数 dataZoom: [{show: false, // 是否显示滑动条xAxisIndex: 0, // 表示从X轴的零刻度线…

香橙派 AIpro 评测与使用心得

引言 香橙派 AIpro&#xff08;OrangePi AIpro&#xff09;是一款尺寸小巧却功能强大的AI边缘设备&#xff0c;其仅有107*68mm的超小尺寸使得它在各种场景下都能轻松部署。它支持两种流行的操作系统&#xff0c;包括Ubuntu和openEuler&#xff0c;为用户提供了更多的选择和灵活…

Windows同一文件夹下支持大小写同名文件

举例&#xff1a;同一文件目录下需要存在A.java, a.java, Windows是不支持的&#xff0c;这时候需要建一个Linux子系统的文件夹 创建教程 1、在启用或关闭Windows功能下面找到 适用于Linux系统的Windows子系统 2、cmd 执行命令 fsutil file SetCaseSensitiveInfo 文件夹路径 …

新注册与新核准有什么区别?在哪可以找到新注册新核准的企业名单?

新注册&#xff1a;指的是公司刚刚完成工商注册登记&#xff0c;成为法律意义的经营实体。 新核准&#xff1a;指的是企业通过证券监管机构的审核&#xff0c;获得公开发行股票或债券的资格。 注册主要关注企业的基本资质和合规性&#xff0c;而核准是已经注册的公司进行财务…

【React】如何使用npm run start命令运行两个服务

我们开发前端项目时&#xff0c;有时候需要本地 mock 数据&#xff0c;这样就需要启动两个服务&#xff0c;一个是接口服务&#xff0c;一个是前端项目。可以安装一个插件来帮助我们通过一个命令启动两个服务。 方法一 添加& npm run server 注意&#xff1a;Windows系统…

人脸匹配——OpenCV

人脸匹配 导入所需的库加载dlib的人脸识别模型和面部检测器读取图片并转换为灰度图比较两张人脸选择图片并显示结果比较图片创建GUI界面运行GUI主循环运行显示全部代码 导入所需的库 cv2&#xff1a;OpenCV库&#xff0c;用于图像处理。 dlib&#xff1a;一个机器学习库&#x…

用Python绘制yolo训练结果比较图-论文需要

代码内容来自于网络用博客记录 利用训练生成的result.csv中数据&#xff0c;形成多模型的比较图。 代码中演示的是map50、map50-95、losss的比较图 import matplotlib.pyplot as plt import pandas as pd import numpy as npif __name__ __main__:# 列出待获取数据内容的…

webstorm自定义vue模板

<!--* Description: ${COMPONENT_NAME} 页面* Author: * Date: ${DATE} --> <template><div>${COMPONENT_NAME} </div> </template><script> export default {name: "${COMPONENT_NAME}",components: {},data() {return {};},co…