Android Choreographer 源码笔记

来源:互联网 发布:网络教育是什么意思 编辑:程序博客网 时间:2024/04/29 02:56
  1. 之前有写过一篇粗略分析的文章:
    http://blog.csdn.net/fyfcauc/article/details/43307253
    不过还是不够,这次再专门细读一下:

  2. Choreographer主要被外部使用的函数是postCallback(…), 就是在Choreographer中schedule一个Task,这个Task何时运行,则是是由Choreographer来自行安排,满足作Sync的需求

  3. postCallback(…) -> postCallbackDelayed(…) ->postCallbackDelayedInternal(…, long delayMillis), postCallbackDelayedInternal的具体步骤:

    • 首先获取mLock来同步操作mCallbackQueues这个Queue, 获取当前的时间存为now(SystemClock.uptimeMillis(), 再根据输入的delayMillis获得一个到期时间dueTime), 然后将dueTime以及输入的Task加入到mCallbackQueues[输入的callbackType]对应的Queue中.
    • 如果发现dueTime <= now,那么会直接scheduleFrameLocked(now)。
    • 否则, 会生成一个MSG_DO_SCHEDULE_CALLBACK类型的,runnable为输入的Task的Message,并且该msg的arg1是输入的callbackType.
    • 将Messgae设置为异步的(setAsynchronous(true)).
    • 然后将这个Message post到Choreographer的handler中(这个handler基于的线程是传入的Looper基于的线程,在这里就是UI线程).
  4. Choreographer不能直接构造(private ctor),而是使用getInstance()获得当前线程的TLS, 其构造时传入的looper也是调用getInstance()所在线程的looper, 因此在ViewRootImpl中使用的mChoreographer基于的looper就是ViewRootImpl构造时所在的线程,这里就是UI线程. 因此Choreographer的主体操作(doFrame这种重构UI的操作)就是在主线程的handler被post已经执行的.

  5. mHandler的类型是自定义的FrameHandler:

    • 其对MSG_DO_SCHEDULE_CALLBACK的处理是doScheduleCallback(msg.arg1):
      • doScheduleCallback(…)操作也会获取mLock, 然后检查当前是否已经scheduke过一个FrameTask了(mFrameScheduled), 如果还没有schedule过, 那么就获取现在的时间now, 然后以此来检测mCallbackQueues[callbackType]这个对应的CallType的Queue里是否已经有了到期需要执行的Task,如果有,那么就schedule一个FrameTask(scheduleFrameLocked(now))
  6. scheduleFrameLocked(long now):

    • 也需要先检查是否已经schedule了FrameTask(mFrameScheduled), 如果没有,那么先把mFrameScheduled=true来表明已经schedule了,然后开始真正的schedule过程.
    • 如果使用了USE_VSYNC(4.0以后为true),那么检查当前运行的thread是不是就是choregrapher的looper所在的线程(isRunningOnLooperThreadLocked()),如果是, 那么直接调用scheduleVsyncLocked().
    • 否则向mHandler schedule一个MSG_DO_SCHEDULE_VSYNC类型的Message, 而这个Msg在FrameHandler中的处理过程就是doScheduleVsync(),doScheduleVsync()很简单,获取mLock以后会检测是否已经schedule过FrameTask,如果没有,才会调用scheduleVsyncLocked()(其实和上面在自己线程的处理过程是一样的).
  7. scheduleVsyncLocked()这个操作就是调用了mDisplayEventReceiver.scheduleVsync(): 而mDisplayEventReceiver是一个在初始化时就以传入的looper为参数构造的一个DisplayEventReceiver(自定义了其子类FrameDisplayEventReceiver).

    • scheduleVsync()这个函数进一步跑到了native层,不过做的事情注释说的很清楚: Schedules a single vertical sync pulse to be delivered when the next display frame begins. 即在下一帧开始的时候,会要求其deliver一个同步脉冲
    • 上面说的这个同步脉冲其实就是一个回调,这个回调函数也在DisplayEventReceiver中,就是其onVsync(…)方法,这个方法(空)在FrameDisplayEventReceiver被具体实现了.
  8. FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable 实现了同步脉冲的回调函数onVsync(…):

    • 获取系统的当前时间(System.nanoTime(), 纳秒级了,以为同步脉冲回传的timeStamp是纳秒级的),然后比较同步脉冲的timeStamp和now, 如果timeStamp比now还要大, 这应该是不对的,但是考虑到时间本身不能做到很精确也可以接受,不过会将timeStamp再设置为now.
    • 将mHavePendingVsync设置为true表明当前有一个pending的同步脉冲没有被处理,如果之前已经有了pending的,那么这一次的就可以直接忽略的了.
    • 基于当前的handler构造一个Message,然后将其post到handler上, 而其到期的运行时间就是传入的timestamp,这就实现了想要的同步效果,不是想啥时候运行就啥时候运行,要听sync信号的话, 不然一窝蜂乱跑,效率会有问题, Message的runnable则是FrameDisplayEventReceiver自己,这也是其实现了Runnable的原因(独立搞一个Runnable也完全可以).
    • 其run()函数做的就是真正在下一帧的同步脉冲到达时(这个时间就是同步脉冲传过来的timeStamp)是要做的操作:doFrame(mTimestampNanos, mFrame) , 同时还会将mHavePendingVsync设置为false
  9. doFrame(…)也要获取mLock锁,然后检测是否之前schedule过FrameTask了,如果没有,那么就啥也不干(没啥可干的)。

    • 然后获取当前的时间, 并减去 同步脉冲给的时间,得到一个抖动值(jitter**反映的是一次同步脉冲的发生到真正执行操作之间的延迟), 如果抖动值已经大于了mFrameIntervalNanos((long)(1000000000 / getRefreshRate()), 就是每一帧持续的时间,以纳秒为单位), 那么就可以认为是出现了跳帧**,并且在跳帧大于一定阈值的情况下,会在log中写入.
    • jitterNanos % mFrameIntervalNanos得到理想中情况这一帧已经开始了多长时间. 再用startNanos - lastFrameOffset得到这一帧应该的开始时间并设置给frameTimeNanos
    • 然后再比较,如果frameTimeNanos < mLastFrameTimeNanos, 那么这一帧应该是错乱了,再scheduleVsyncLocked()等待下一次vsync.
    • mFrameScheduled设置为false, 也即在这之后,可以schedule新的FrameTask了, 然后mLastFrameTimeNanos = frameTimeNanos.
    • 下面就是依次处理三类CallType的task了:
      • doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); 输入(用户的触摸等操作)最优先被处理
        • doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); 然后是帧动画(可以看到ValueAnimator以及View等都使用这个type)
        • doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); 最后才是measure/layout/draw之类的
    • doCallbacks(…):
      • 将mCallbackQueues[callbackType]中到期的Callback全部取出来进行遍历处理(调用CallbackRecord的run函数.) CallbackRecord的run函数会判断自己的token值, 如果是FRAME_CALLBACK_TOKEN, 那么将action对象转为FrameCallback执行doFrame,否则转为Runnable执行run.
      • ViewRootImpl调用的是postCallback, 因此这里的是Runnable,执行的就是ViewRootImpl中的mTraversalRunnable的run()->doTraversal().
  10. 根据上面的分析来讨论一些情况:

    • 在Choreographer 处理doCallbacks(Choreographer.CALLBACK_INPUT/CALLBACK_ANIMATION, frameTimeNanos)时调用了requestLayout()/invalidate(), 那么就会ViewRootImpl->scheduleTraversals()->(如果之前没有schedule过的话)->mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)(要注意的是doCallback发生的线程也是ViewRootImpl的主线程) 虽然这一步会mLock加锁,但是doFrame在处理到doCallbacks(XXXX)时已经释放了mLock并且mFrameScheduled也变为了false, 接下来就是将mTraversalRunnable加入到mCallbackQueues[callbackType]队列中, 这时候的dueTime还是等于now的,于是会scheduleFrameLocked(now).如同前面分析的,这一步也会出发一次同步脉冲请求回调,至此scheduleTraversals()这个同步调用就结束了,然后就会继续到doCallbacks(CALLBACK_TRAVERSAL), 这一步将所有到期的Task都执行一遍,也包括了之前在CALLBACK_INPUT/CALLBACK_ANIMATION中加入的那个新的Task.
    • 但是如果在doCallbacks(CALLBACK_TRAVERSAL)的时候进行requestLayout()/invalidate(), 那么这时候mTraversalScheduled已经是false了,也会postCallback,但是达不到在这个drawCircle就生效的效果了(因为要处理的callback列表已经取出来了,新加入的callback并不在里面),而要等待下一个同步脉冲回调的处理(已经被触发了,只需要等待).
  11. 丢帧/卡 分析,可以看到FrameDisplayEventReceiver的onVsync也是一个被post到Choreographer所在的Handler的Task, 这样的话,如果前面的Task(就是那3个doCallback)花费了太长时间的话,那么onVsync的实际运行时间一定会比自己的timeStamp(“这个是同步脉冲在新的一帧开始时给的时间”)要晚,如果晚很多的,就会出现这些UI响应的实际操作时间和Vsync预定的执行时间差很远,就会出现卡/丢帧.

  12. Choregrapher请求一次VSYNC信号是通过自己内部定义的FrameDisplayEventReceiver的scheduleVsync()来实现一次VSYNC信号的schedule:

    • FrameDisplayEventReceiver extends DisplayEventReceiver.
    • DisplayEventReceiver的requestNextVsync()则是调用mEventConnection->requestNextVsync().
    • mEventConnection在DisplayEventReceiver的构造函数中被初始化: mEventConnection = sf(其实就是SurfaceFlinger)->createDisplayEventConnection();
    • 在SurfaceFlinger中的实现是: mEventThread->createEventConnection().
    • mEventThread则是在SurfaceFlinger中的init()中被new的EventThread(vsyncSrc).
    • EventThread的createEventConnection()返回new Connection(const_cast<EventThread*>(this)). Connection则是EventThread内部定义的类, 通过Connection的requestNextVsync()调用的还是EventThread的requestNextVsync(this(这个是Connection自己))
    • EventThread的requestNextVsync(const sp<EventThread::Connection>& connection)会检测connection->count 是否<0(<0代表Connection对VSYNC信号不感兴趣), 如果<0, 即此Connection当前对VSYNC信号并不感兴趣,那么需要让其对VSYNC感兴趣,将Connection的count设置为0(one-shot), 并调用mCondition的broadcast()来触发EventThread的threadLoop中的waitForEvent(…)来使其能够真正的scheduleu一次VSYNC信号, 并在信号来到时告知此Connection.
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 户户通智能卡坏了怎么办 秦岭云无法回看怎么办 身份证在火车站丢了怎么办 到火车站发现身份证丢了怎么办 广电宽带太慢了怎么办 车有后雷达想装前置雷达怎么办 现代朗动油耗大怎么办 雷达线雕头里有水怎么办 上古卷轴5免疫死亡奴役怎么办 dw手表时针不动了怎么办 雷达陶瓷表壳摔坏了怎么办 雷达表盘摔坏了怎么办 雷达测速60超了怎么办 卡西欧手表电池没电了怎么办 审稿人让引用他的文章怎么办 考二建未从事该行业满两年怎么办 2档换3档离合器怎么办 高铁车票丢了怎么办 事业编制调动原单位不同意怎么办 想去铁路上工作怎么办 房产权50年以后怎么办 在香港手机没电怎么办 学校官网登陆忘记密码怎么办 网上申报学校忘记密码怎么办 专转本想换专业怎么办 发生工伤没有平均公资怎么办? 单招过了不想去怎么办 22岁了还想复读怎么办 父母不让我读大专了怎么办 专升本考试失利怎么办 高考复读一年后失败了怎么办? 医保住院超过30万怎么办 北京医保超过2万怎么办 工伤公司垫付医疗费没法报销怎么办 司法考试照片耳朵露不出来怎么办 新华社毕业证照片用光了怎么办 农村父母投靠落户社保怎么办 退休后投靠父母户口怎么办 要离婚想儿子了怎么办 怀孕期间离婚了孩子户口怎么办 常州武进区怎么办居住证明