Android黄油计划之Choreographer原理解析

搞客户端开发,时间也有点了,但是每次想起来,总感觉自己掌握的东西零零散散,没有一点集在的感觉,应用层的懂,framework的也懂,框架啥的了解一点,分层的思想也有一些,JVM的原理啊,内存分配和管理啊,运行机制啊啥的也知道一点,每次下班或者没事了,就在考虑,自己应该有一个主攻方向,往这个方向集中发展一下,首选的几个目标应该是非常清楚的,我们要掌握android,那么关于android的View机制、动画原理这些都是必须要掌握的,所以呢,自己想在这几个方面花些时间,好好研究一下,这样才能使自己更具竞争力。

好了,不管是要了解View机制,还是android动画,我们应该都需要有Choreographer的知识,明白系统刷新机制到底是怎么样的,这样才能对其他方面有更好的辅助。本章博客,我们就来学习一下Android中的Choreographer的运行机制。

我们都知道,应用层的一个Activity对应一个根View(也就是一个DecorView)、一个WindowState、一个ViewRootImpl,每个对象都非常重要,都是在Activity添加过程中重量级的对象,DecorView是当前Activity的根View,它里面管理着当前界面的View树;WindowState对象是当前Activity窗口在系统侧WindowManagerService中代理对象;ViewRootImpl则肩负着View的标准三步曲的处理和事件分发,而View绘制也是由Choreographer指导的,Choreographer的英文意思就是编舞者、舞蹈指挥,看着非常形象。那我们就从Choreographer对象的构建开始说起吧,它的构建是在ViewRootImpl的构造方法中的,代码如下:

从构造方法中可以看到Choreographer是单例模式的,也就是一个ViewRootImpl对象对应一个Choreographer,当界面需要重绘时,都会调用到ViewRootImp类的scheduleTraversals()方法,这里的实现也比较简单,代码如下:

mTraversalScheduled表示是否已经发起重绘,每次scheduleTraversals()方法调用之后,就会将它置为true,然后在下次调用doTraversal()又先将它置为false,然后调用mChoreographer.postCallback()添加一个Runnable,请注意,第一个参数是Choreographer.CALLBACK_TRAVERSAL,在Choreographer当前,添加的类型一共有三种,分别是:CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL,分别表示事件回调、动画回调、绘制回调。postCallback()方法是转而调用postCallbackDelayed()方法的,最后一个参数delayMillis传的是0,表示当前的重绘不需要延时,我们跟进去看一下添加的postCallbackDelayed()方法的代码:

首先判断参数action是否为空,action就是我们要回调的对象,回调对象都为空了,那我们还干啥呢?其实判断callbackType,在整个过程中,只定义了上面描述的三种类型的事件,如果传入的type值不符合,那就抛出一个IllegalArgumentException("callbackType is invalid")异常。参数正常了,继续调用postCallbackDelayedInternal()进一步处理。postCallbackDelayedInternal()方法的代码如下:

此处获取当前时间,然后加上要延迟的时间,作为当前Callback的时间点,以这个时间点作为标准,把Callback对象添加到mCallbackQueues[callbackType]队列当中,这块的逻辑和Looper、MessageQueue、Handler中添加Message的逻辑很相似,大家可以对比学习。然后判断dueTime <= now,这块的逻辑看了半天,我确实没看懂,dueTime会有比now小的情况吗,也就是传进来的delayMillis小于0,再往上讲,就是当前要添加的回调要在上一次添加的回调之前,这感觉不太可能吧?如果有弄懂的朋友,烦请解答一下。此处应该是执行else分支,往当前的队列中添加一个Message,那么通过Handler机制就会进行处理,此处的mHandler是一个FrameHandler对象,我们来看一下FrameHandler的代码:

这里的message消息也比较简单,MSG_DO_FRAME指系统在没有使用Vsync机制的时候,使用异步消息来刷新屏幕,当然,大家一定要理解,此处的刷新其实只是刷新屏幕工作的很小一部分,只是回调ViewRootImpl方法中添加的Runnable对象,最终是调用根View的draw方法,让每个子View有把自己的图像元素填充到分配好的显存当中,而要完全显示,还有很多工作要作,最终是在SurfaceFlinger类中对所有窗口的View进行合成,然后渲染,最终post到FrameBuffer上,才能显示出来的;MSG_DO_SCHEDULE_VSYNC当然就是指系统使用Vsync来刷新了;MSG_DO_SCHEDULE_CALLBACK就是指添加Callback或者FrameCallback完成的消息了。好了,我们继续看MSG_DO_SCHEDULE_CALLBACK的消息处理,它是调用doScheduleCallback(msg.arg1)来进行处理的,msg.arg1是刚才添加消息时的类型。我们整个看一下handleMessage()方法的代码,发现非常简单,这也是一个非常好的习惯,我们平时的代码当中,也应该尽量这样实现,这样一眼就可以看出来这个方法所要作的事情,把具体的处理放到每个细节方法中去。我们来看一下doScheduleCallback()方法的实现:

mFrameScheduled和ViewRootImpl的scheduleTraversals()方法中的变量mTraversalScheduled作用是一样的,也是判断当前是否正在执行添加,然后调用(mCallbackQueues[callbackType].hasDueCallbacksLocked(now))判断是否已处理过Callback事务,该方法的判断也很简单,(mHead != null && mHead.dueTime
<= now),如果当前队列头不为空,并且队列头元素的时间点小于当前的时间点,那就说明是之前添加的,则需要对它进行处理;相反,如果队列头为空或者添加的时间点大于当前的时间点,也就是要延迟处理,则不需要任何操作。条件符合的话,就调用scheduleFrameLocked(now)进一步处理,我们来看一下scheduleFrameLocked()方法的实现:

此片一开始就把mFrameScheduled赋值为true,表示事务开始执行了,那么上面doScheduleCallback()方法当中的代码此该就不会再执行了。接下来的逻辑以USE_VSYNC分开,意思也非常明了,就是系统是否使用Vsync刷新机制,它是通过获取系统属性得到的,private static final boolean USE_VSYNC =  SystemProperties.getBoolean("debug.choreographer.vsync",
true)。如果使用了Vsync垂直同步机制,则一步判断当前线程是否具备消息循环,如果有消息循环,则立即请求下一次Vsync信号,如果不具有消息循环,则通过当前进程的主线程请求Vsync信号;如果没有使用Vsync机制,则使用异步消息延时执行屏幕刷新。是否具有消息循环是通过调用isRunningOnLooperThreadLocked()方法完成判断的,它的实现很简单,return Looper.myLooper() == mLooper。因为当Choreographer对象在创建的时候,参数looper就是调用Looper
looper = Looper.myLooper()获取回来的,也就是说当前进程肯定是有消息循环的,所以此处的判断为true,其他两个分支:当前线程不具备消息循环和系统未使用Vsync同步机制的逻辑,我们就不分析了,大家有兴趣的话,可以自己跟踪一下。进入if分支,继续调用scheduleVsyncLocked()方法进行处理,它的实现非常简单,就是调用mDisplayEventReceiver.scheduleVsync()来请求下一次Vsync信号。

看到这里,是不是感觉逻辑有点多了,开始乱了,转来转去的,系统到底要干啥?呵呵,我们暂停下来梳理一下,系统作了这么多事情最终的目的就是在下一次Vsync信号到来的时候,将Choreographer当中的三个队列中的事务执行起来,这些事务是应用层ViewRootImpl在scheduleTraversals()方法中添加进去的,在Choreographer当中,我们要先将外边传进来的Callback放入队列,然后就要去请求Vsync信号,因为Vsync信号是定时产生的,你不请求,它就不会理你,当然你收不到回调,也就不知道啥时候通知ViewRootImpl执行View的measure、layout、draw了,这样说一下,大家清楚我们要干什么了吗?我第一次看Choreographer类的代码时候,看了半天,也是乱了,所以这里大概理一下。

好,我们搞清楚目的了,继续往前走,我们现在已经将Callback添加到队列中了,下一步要作的就是请求Vsync信号了。mDisplayEventReceiver是一个FrameDisplayEventReceiver对象,我们来看一下它的代码定义:

我们可以看到这里的mTimestampNanos时间定义都是纳秒级别的,因为Vsync信号是用来同步屏幕刷新频率的,所以对时间的要求非常高,才采用了纳秒级别的,如果大家对Vsync信号的产生机制不了解的话,可以看我前面的博客:Vsync垂直同步信号分发和SurfaceFlinger响应执行渲染流程分析(一),mDisplayEventReceiver类变量是在Choreographer的构造方法中赋值的,我们继续来看它的scheduleVsync()方法的实现,因为FrameDisplayEventReceiver类是继承DisplayEventReceiver的,而它没用对scheduleVsync()方法重写,所以是调用父类的:

它的实现很简单,判断描述符mReceiverPtr是否合法,如果非法就打印日志,什么也不作了,合法的话,就继续调用native方法nativeScheduleVsync(mReceiverPtr)来请求Vsync信号。nativeScheduleVsync()方法实现在android_view_DisplayEventReceiver.cpp当中,是通过定义JNINativeMethod gMethods[]来定义方法调用指针的,因为此类的代码不多,这里就全部贴出来,方便大家查看:

我们来看一下nativeScheduleVsync方法的定义,{ "nativeScheduleVsync", "(J)V", (void*)nativeScheduleVsync },这里需要说明一下,java方法和JNI方法存在着对应关系,"(J)V"括号里边的表示该方法的入参,括号外边的表示返回值J表示long,而返回值V表示Void,关于这个,大家可以看我之前的博客:JNI字段描述符“([Ljava/lang/String;)V”,也是转载别人的,呵呵。好了,我们继续看这个方法的实现,它将java层传进来的描述符强制转换为NativeDisplayEventReceiver对象,这样的处理在JNI当中是非常多见的,大家要熟悉。然后调用它的scheduleVsync()方法,最后根据返回值判断当前请求Vsync信号是否成功,如果status非0,则抛出RuntimeException异常。很明显,我们从这都可以猜出,正常情况下,返回的status应该为0了。

我们继续来看NativeDisplayEventReceiver::scheduleVsync()方法的处理逻辑。首先检查mWaitingForVsync,如果当前正在请求Vsync信号,则就不需要重复请求了,只有在当前未请求的时候,才需要发出新的请求,然后调用processPendingEvents()将当前队列中还存在receiver处理掉,因此方法与我们的流程不相关,这里就不展开了,大致是使用pipe机制将mReceiver中还存在的receiver一一读出,大家如果了解Linux机制的话,就知道pipe机制对应了两个管道,管道中的数据被读出之后,也就相应的从管道中移除了,所以不需要两端对数据做任何移除的处理,每一个receiver处理完成后,就设置一下gotVsync
= true,

*outTimestamp = ev.header.timestamp,*outId = ev.header.id,*outCount = ev.vsync.count,gotVsync的意思就是当前的receiver已经收到Vsync信号通知了。好了,我们回到主流程,scheduleVsync()方法当中处理完队列中的receiver后,就开始调用mReceiver.requestNextVsync()请求新的Vsync信号了,mReceiver是一个DisplayEventReceiver对象,我们来看一下requestNextVsync()方法的实现,因这个类的代码也很少,这里就直接全部贴出来了:

requestNextVsync()方法中直接调用mEventConnection->requestNextVsync()来请求Vsync信号,mEventConnection对象是在DisplayEventReceiver类的构造函数中创建的,mEventConnection = sf->createDisplayEventConnection(),sf就是SurfaceFlinger对象,SurfaceFlinger类的createDisplayEventConnection()实现也非常简单,就是调用mEventThread->createEventConnection(),这又回到我们之前的博客了,大家可以去看一下。

EventThread一直在无限循环threadLoop()中请求Vsync信号的,当收到一个Vsync信号后,会调用status_t err = conn->postEvent(event)来进行分发,conn也就是上面的EventThread::Connection对象了,最后经过处理,回调到NativeDisplayEventReceiver::handleEvent(int receiveFd, int events, void*
data)方法当中,这里同样processPendingEvents()处理完队列中的回调后,就调用dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount)开始分发了,在NativeDisplayEventReceiver::dispatchVsync()这个方法中是通过当前的native层的执行环境env回调到java层的,env->CallVoidMethod(mReceiverObjGlobal,

gDisplayEventReceiverClassInfo.dispatchVsync, timestamp, id, count),再往下就回调到java层中DisplayEventReceiver类的dispatchVsync()方法中了。它里边的实现就是调用onVsync(),而FrameDisplayEventReceiver复写了onVsync()方法,所以就执行到Choreographer.FrameDisplayEventReceiver中的onVsync()方法了。

转了好大一圈,我们终于又从native层回来了。好,我们继续java层往下分析,Vsync信号拿回来了,大家应该也知道,我们的目的快达到了!!

onVsync()方法中以this为对象,向mHandler中添加了一个消息,消息处理的时候,就会调用它的run()方法了。run方法中直接调用doFrame()来进行处理。我们来看一下它的实现:

如果(frameTimeNanos < mLastFrameTimeNanos)满足,则说明我们已经错过了本次的Vsync信号了,那么这种情况下,就什么也不用处理,重新获取下一次信号了。如果没有错过的话,那就进一步三次调用doCallbacks()分别对应三种事件类型来分发了。三种事件的顺序也是定义的顺序:CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL,这也是他们的处理优先级,输入事件放在第一,也是为了能尽快响应用户的操作,但是即使这样,Android的流畅性还是不如IOS,当然,这个原因就是其他方面的了,我们这里就不探讨了。我们来看一下doCallbacks()方法的实现:

这里就是将每种类型的事件队列中的元素取出来,通过for循环一一调用他们的run()方法了,调用完成后,将队列中的Callback回收掉。而这里的CallbackRecord对象就是我们在ViewRootImpl类当中添加的InvalidateOnAnimationRunnable、mConsumedBatchedInputRunnable、mTraversalRunnable这三类对象了,那么回到View的流程中,收到Vsync信号后,就会回调mTraversalRunnable的run()方法,再次发起一次measure、layout、draw流程,那么也就和Vsync信号对接上了。

好了,到这里呢,我们整个流程也就分析完了,希望对大家有所帮助,谢谢大家!

时间: 2024-10-11 12:41:01

Android黄油计划之Choreographer原理解析的相关文章

Android中微信抢红包插件原理解析和开发实现

一.前言 自从去年中微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导致了.或许是网络的原因,而且这个也是最大的原因.但是其他的不可忽略的因素也是要考虑到进去的,比如在手机充电锁屏的时候,我们并不知道有人已经开始发红包了,那么这时候也是让我们丧失了一大批红包的原因.那么关于网络的问题,我们开发者可能用相关技术无法解决(当然在Google和Facebook看来的话,他们

Android热修复框架AndFix原理解析及使用

一.前言 最近腾讯弄出一个Tinker热修复框架,那么本文先不介绍这个框架,先来介绍一下阿里的一个热修复框架AndFix,这个框架出来已经很长时间了,但是看网上没有太多非常详细的讲解,这里就来做一次分析.正好项目中要使用到.首先这个框架是开源的:https://github.com/alibaba/AndFix 其实在最早的时候我已经分析了阿里的另外一个热修复框架:Dexposed框架,还不了解的同学可以点击这里查看:Dexposed框架原理解析以及使用 当时介绍这个框架的时候发现他的实现原理很

Android View 的事件分发原理解析

作为一名 Android 开发者,每天接触最多的就是 View 了.Android View 虽然不是四大组件,但其并不比四大组件的地位低.而 View 的核心知识点事件分发机制则是不少刚入门同学的拦路虎,也是面试过程中基本上都会问的.理解 View 的事件能够让你写出更好自定义 View 以及解决滑动冲突. 1. View 事件认识 1.1 MotionEvent 事件 当你用手指轻触屏幕,这个过程在 Android 中主要可以分为以下三个过程: ACTION_DOWN:手指刚接触屏幕,按下去

Android中的Apk的加固(加壳)原理解析和实现

Android中的Apk的加固(加壳)原理解析和实现 标签: android 2015-09-13 13:58 42287人阅读 评论(49) 收藏 举报 本文章已收录于:  Android知识库  分类: Android(140)  版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 一.前言 今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理.现阶段.我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个

Android应用市场省流量更新(增量升级)原理解析

一.前言 最近在看热修复相关的框架,之前我们已经看过了阿里的Dexposed和AndFix这两个框架了,不了解的同学可以点击这里进行查看:Dexposed框架原理解析 和 AndFix热修复框架原理解析,然后还有最近很火的一个是腾讯的Tinker热修复框架,再看他的原理实现的时候,发现了他使用到了开源的文件差分工具bsdiff/bspatch,所以就单独用这篇文章来详细介绍一下这个工具,因为这个工具有一个很大的用途就是增量更新,也就是我们看到现在大部分的应用市场推出的省流量更新应用的效果: 看到

Android中插件开发篇之----应用换肤原理解析

一.前言 今天又到周末了,感觉时间过的很快呀.又要写blog了.那么今天就来看看应用的换肤原理解析.在之前的一篇博客中我说道了Android中的插件开发篇的基础:类加载器的相关知识.没看过的同学可以转战: http://blog.csdn.net/jiangwei0910410003/article/details/41384667 二.原理介绍 现在市场上有很多应用都有换肤的功能,就是能够提供给用户一些皮肤包,然后下载,替换.而且有些皮肤是要收费的.对于这个功能的话,其实没有什么技术难度的,但

Android中的Apk的加固(加壳)原理解析和实现(转)

一.前言 今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理.现阶段.我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk,结果被人反编译了,那心情真心不舒服.虽然我们混淆,做到native层,但是这都是治标不治本.反编译的技术在更新,那么保护Apk的技术就不能停止.现在网上有很多Apk加固的第三方平台,最有名的应当属于:爱加密和梆梆加固了.其实加固有些人认为很高深的技术,其实不然,说的简单点就是对源Apk进行加密,然后

Android单排上王者系列之Dagger2注入原理解析

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 MVP模式讲解 在MVP中使用Dagger2 Dagger2的注入原理解析 在上篇博客中我们介绍了Dagger2该如何在项目中使用,这篇博客将继续分析Dagger2实现的原理,代码依然采用上篇的代码,看这里. Dagger2的注入原理 原理的讲解我们通过小明来带我们学习. 小明在看了MVP的实战解析和Dagger2的使用后知道了Dagger2该如何在MVP模式中使用,但是小明是一个要求上进的好同学,小明并不满足于如何使用,小明想

爱加密Android APk 原理解析

爱加密Android APK加壳原理解析 一.什么是加壳? 加壳是在二进制的程序中植入一段代码,在运行的时候优先取得程序的控制权,做一些额外的工作.大多数病毒就是基于此原理.PC EXE文件加壳的过程如下: 二.加壳作用 加壳的程序可以有效阻止对程序的反汇编分析,以达到它不可告人的目的.这种技术也常用来保护软件版权,防止被软件破解. 三.Android Dex文件加壳原理 PC平台现在已存在大量的标准的加壳和解壳工具,但是Android作为新兴平台还未出现APK加壳工具.Android Dex文