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

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、 总结:

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Java项目:111 基于SpringBoot的在线家具商城设计与实现
  • Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena阅读笔记
  • opencv_特征检测和描述
  • 洛谷B3642 二叉树的遍历(前序、中序、后序)
  • JVM的几种常见垃圾回收算法
  • Flutter笔记:关于WebView插件的用法(上)
  • Linux基础IO【II】真的很详细
  • 什么是CSS的:target选择器
  • css实现优惠券样式
  • 破布叶(Microcos paniculata)单倍型染色体级别基因组-文献精读22
  • 软考初级网络管理员_08_网络单选题
  • Docker:镜像命令和容器命令
  • FPGA+金融|硬件行情加速系统 打造极速交易场景
  • Stability AI发布新版文生图模型:依然开源
  • C++面向对象程序设计 - 输入输出流进一步研究
  • 【Leetcode】104. 二叉树的最大深度
  • 【编码】-360实习笔试编程题(二)-2016.03.29
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • crontab执行失败的多种原因
  • CSS魔法堂:Absolute Positioning就这个样
  • download使用浅析
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • Java程序员幽默爆笑锦集
  • Theano - 导数
  • vue脚手架vue-cli
  • 京东美团研发面经
  • 聊聊directory traversal attack
  • 腾讯优测优分享 | Android碎片化问题小结——关于闪光灯的那些事儿
  • 微信端页面使用-webkit-box和绝对定位时,元素上移的问题
  • 用element的upload组件实现多图片上传和压缩
  • 《TCP IP 详解卷1:协议》阅读笔记 - 第六章
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • 昨天1024程序员节,我故意写了个死循环~
  • ​你们这样子,耽误我的工作进度怎么办?
  • #java学习笔记(面向对象)----(未完结)
  • (Note)C++中的继承方式
  • (附源码)springboot工单管理系统 毕业设计 964158
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (每日一问)设计模式:设计模式的原则与分类——如何提升代码质量?
  • (五)关系数据库标准语言SQL
  • (转)创业家杂志:UCWEB天使第一步
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • .java 指数平滑_转载:二次指数平滑法求预测值的Java代码
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .net wcf memory gates checking failed
  • .Net 基于MiniExcel的导入功能接口示例
  • .NET 简介:跨平台、开源、高性能的开发平台
  • .net打印*三角形
  • @RequestBody的使用
  • @SpringBootConfiguration重复加载报错
  • @Value读取properties中文乱码解决方案
  • [ 攻防演练演示篇 ] 利用通达OA 文件上传漏洞上传webshell获取主机权限
  • [ 网络通信基础 ]——网络的传输介质(双绞线,光纤,标准,线序)
  • [20170705]diff比较执行结果的内容.txt