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

news2024/11/24 20:49:09

书接上回: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、 总结:

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

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

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

相关文章

栈(Stack)汇总

栈简介 栈&#xff08;Stack&#xff09;是只允许在一端进行插入或者删除操作的线性表。它的操作特性可以概括为——后进先出&#xff08;Last In First Out&#xff0c;LIFO&#xff09;。栈顶&#xff08;Top&#xff09;——线性表允许进行插入删除的一端&#xff1b; 栈底…

SwaggerSpy:一款针对SwaggerHub的自动化OSINT安全工具

关于SwaggerSpy SwaggerSpy是一款针对SwaggerHub的自动化公开资源情报&#xff08;OSINT&#xff09;安全工具&#xff0c;该工具专为网络安全研究人员设计&#xff0c;旨在简化广大红队研究人员从SwaggerHub上收集已归档API信息的过程&#xff0c;而这些OSINT信息可以为安全人…

BigDecimal-解决java中的浮点运算

《阿里巴巴 Java 开发手册》中提到&#xff1a;“为了避免精度丢失&#xff0c;可以使用 BigDecimal 来进行浮点数的运算”。浮点数的运算竟然还会有精度丢失的风险吗&#xff1f;确实会&#xff01; 示例代码&#xff1a; float a 2.0f - 1.9f; float b 1.8f - 1.7f; Syst…

制作带有目录的电子书

有时候想自己制作一些.mobi格式的电子书在kindle上进行阅读&#xff0c;有两种简单做法。 方法一&#xff1a; 工具&#xff1a;markdown编辑器、calibre 在markdown编辑器中编辑想要制作电子书的文本&#xff0c;在想要设置目录的地方加一个#&#xff08;我只制作了一级标题…

线程安全问题【snychornized 、死锁、线程通信】

目录 一、线程安全1.1 线程安全问题?1.2 如何解决线程安全问题方法具体如何实现? 1.3 同步方法1.4 同步代码块1.5 总结1.6 售票例子1.8 补充 二、线程安全的集合三、死锁【了解】四、线程通信4.1 同步方法4.2 同步代码块4.3 wait和sleep本篇的思维导图 最后 一、线程安全 1.…

使用PHP对接企业微信审批接口的问题与解决办法(二)

在现代企业中&#xff0c;审批流程是非常重要的一环&#xff0c;它涉及到企业内部各种业务流程的规范和高效运转。而随着企业微信的流行&#xff0c;许多企业希望将审批流程整合到企业微信中&#xff0c;以实现更便捷的审批操作。本文将介绍如何使用PHP对接企业微信审批接口&am…

大数据学习——安装hive

一. 安装准备 1. 打开虚拟机&#xff0c;启动配置了NameNode节点的虚拟机&#xff08;一般和mysql在同一台虚拟机&#xff09;并连接shell 二. 安装 1. 上传hive安装包 hive安装包 提取码&#xff1a;6666 切换到/opt/install_packages目录下 可以将之前解压的rpm文件删除…

高效数据架构:分表流程实践

前言 ​ 随着业务的不断扩展&#xff0c;数据量激增成为不可避免的现象。当数据量达到某一临界点时&#xff0c;单一的数据表可能无法承载如此庞大的数据量&#xff0c;此时就需要考虑进行分库分表的策略。尽管业界普遍认为数据量达到1000万时就应考虑分表&#xff0c;但实际上…

线程池 (重点)概述7大参数理解

目录 1、线程池思想概述 2、什么是线程池&#xff1f; 3、不使用线程池的问题 4、线程池的工作原理 5、线程池实现的API、参数说明 5.1、谁代表线程池&#xff1f; 5.2、如何得到线程池对象 5.3、ThreadPoolExecutor构造器的参数说明 6、线程池常见面试题 6.1、临时线程什么时候…

Vue + Asp.NET调试时出现的证书问题 (OpenSSL)

Vue Asp.NET调试时出现的证书问题 1. 证书过期问题步骤一:创建新的私钥步骤 2: 创建新的证书签名请求&#xff08;CSR&#xff09;步骤 3: 使用 CSR 和 CA 私钥签署新证书步骤 4: 替换或使用新证书 2. 证书不受信任问题步骤: 3. 安全证书不指定使用者可选名称步骤一: 删除已生…

java实现文件的压缩及解压

一、起因 开发中需要实现文件的压缩及解压功能&#xff0c;以满足某些特定场景的下的需要&#xff0c;在此说下具体实现。 二、实现 1.定义一个工具类ZipUtils,实现文件的压缩及解压&#xff0c;代码如下&#xff1a; import java.io.*; import java.nio.charset.Charset; impo…

网络安全等级保护基本要求 第1部分:安全通用要求

基本要求 第三级 安全物理环境 物理位置选择 a) 机房场地应选择在具有防震、防风和防雨等能力的建筑内&#xff1b; b) 机房场地应避免设在建筑物的顶层或地下室&#xff0c;否则应加强防水和防潮措施 物理访问控制 a) 机房出入口应配置电子门禁系统&#xff0c;控制、鉴…

Linux 基本指令3

date指令 date[选项][格式] %Y--年 %m--月 %d--日 %H--小时 %M--分 %S--秒 中间可用其他符号分割&#xff0c;不能使用空格。 -s 设置时间&#xff0c;会返回设置时间的信息并不是改变当前时间 设置全部时间年可用-或者&#xff1a;分割日期和时间用空格分隔&#xff…

自动驾驶场景下TCP协议参数优化调整案例分享

RTT 往返时间&#xff0c;从tcp协议栈决定发包&#xff0c;到收到回包的时间。 包含本地驱动&#xff0c;网卡硬件&#xff0c;网线&#xff0c;交换机&#xff0c;收包方处理的耗时。需注意如果开了delayed ack&#xff0c;协议栈未做特殊处理&#xff08;默认没做&#xff…

攻防演练之-成功的钓鱼邮件溯源

书接上文&#xff0c;《网络安全攻防演练风云》专栏之攻防演练之-网络安全产品大巡礼二&#xff0c;这里。 演练第一天并没有太大的波澜&#xff0c;白天的时间过得很快。夜色降临&#xff0c;攻防演练中心内的灯光依旧明亮。对于网络安全团队来说&#xff0c;夜晚和白天并没有…

基于STM32和人工智能的智能家居监控系统

目录 引言环境准备智能家居监控系统基础代码实现&#xff1a;实现智能家居监控系统 4.1 数据采集模块4.2 数据处理与分析4.3 控制系统4.4 用户界面与数据可视化应用场景&#xff1a;智能家居环境监控与管理问题解决方案与优化收尾与总结 1. 引言 随着智能家居技术的发展&…

如何从微软官方下载Edge浏览器的完整离线安装包

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 正文内容 📒🚀 官方直链下载🚬 手动选择下载🎈 获取方式 🎈⚓️ 相关链接 ⚓️📖 介绍 📖 在网上搜索Microsoft Edge浏览器的离线安装包时,很多用户都会发现大部分都是在线安装包,无法满足他们在无网络环境下进…

【日常记录】【vue】vite-plugin-inspect 插件的使用

文章目录 1、vite-plugin-inspect2、安装3、使用4、链接 1、vite-plugin-inspect vite-plugin-inspect 可以让开发者在浏览器端就可以看到vue文件编译后的代码、vue文件的相互依赖关系 2、安装 npm i -D vite-plugin-inspect// vite.config.ts import Inspect from vite-plugi…

基于STM32的简易智能家居设计(嘉立创支持)

一、项目功能概述 1、OLED显示温湿度、空气质量&#xff0c;并可以设置报警阈值 2、设置4个继电器开关&#xff0c;分别控制灯、空调、开关、风扇 3、设计一个离线语音识别系统&#xff0c;可以语音控制打开指定开关、并且可以显示识别命令词到OLED屏上 4、OLED实时显示&#…

Nginx06-rewrite模块详解与实验

目录 写在前面Nginx06nginx rewriterewrite 模块return案例01 访问/admin/ 返回403案例02 域名间跳转 if案例03 只允许GET、POST请求&#xff0c;其他禁止访问 set案例04 设置是否处于维护状态&#xff0c;是则返回503&#xff0c;否则正常访问 rewrite案例05 域名跳转案例06 r…