一、首先介绍一些基础知识

1.刷新率(Refresh Rate):

刷新率代表屏幕在一秒内刷新屏幕的次数,用赫兹来表示。赫兹是频率的单位,一秒震动的次数。这个刷新率取决于硬件固定的参数。这个值一般是60Hz。即每16.66ms刷新一次屏幕。

2.帧速率(Frame Rate):

帧速率代表了GPU在一秒内绘制操作的帧数。比如30FPS、60FPS。Frame Per Second。

3.如果两个设备独立运行,如果刷新率和帧速率不同步,会引发以下两种问题。

如果帧速率高于刷新率,制作的频率大于展示的频率,会导致屏幕图像的展示的跳跃,跳帧的现象。

如果帧速率小于刷新率,制作的频率小于展示的频率,会导致屏幕图像的展示的中断,掉帧的现象。

4.android为了解决上面的问题,在4.1版本中引入了Projectbuffer.

ProjectBuffer对Android Display系统进行了重构,引入了三个核心元素,即Vsync,TripleBuffer和Choreographer。

其中Vsync是Vertical Synchronization 垂直同步是缩写。是一种在PC上已经很早就广泛使用的技术。

引入是Vsync来进行控制CPUGPU的绘制和屏幕刷新同步进行。

而编舞者choreography的引入,主要是配合Vsync,给上层App的渲染提供一个稳定的时机。Vsync到来的时候,Choreographer可以根据Vsync信号,统一管理应用的输入、动画、绘制等任务的执行情况。Choreographer就像一个指挥家一样,来把控着UI的绘制,所以取名编舞者。

二、android源码中Choreographer是如何运行

1.首先在ViewRootImpl构造函数中创建了Choreographer对象

 public ViewRootImpl(Context context, Display display) {
     mChoreographer = Choreographer.getInstance();
 }
  public static Choreographer getInstance() {
         return sThreadInstance.get();
  }

当调用get时,如果为null,会调用initialValue()方法。并把Choreographer实例和ThreadLocal绑定。

private static final ThreadLocal<Choreographer> sThreadInstance =
        new ThreadLocal<Choreographer>() {
    @Override
    protected Choreographer initialValue() {
        //因为后面会用到handler通讯,所以必须有一个Looper循环
        Looper looper = Looper.myLooper();
        if (looper == null) {
            throw new IllegalStateException("The current thread must have a looper!");
        }
        Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
        //如果是主线程,则把choreographer赋值给mMainInstance
        if (looper == Looper.getMainLooper()) {
            mMainInstance = choreographer;
        }
        return choreographer;
    }
};

2.看Choreographer构造函数

mLastFrameTimeNanos:记录上一帧绘制的时间。

mFrameIntervalNanos:屏幕绘制一帧的时间间隔,这个是纳秒值。如果屏幕刷新率是60Hz,那么刷新一帧的时间间隔就是16.66.......毫秒。

private Choreographer(Looper looper, int vsyncSource) {
    // 传一个Looper进来,
    mLooper = looper;
    //用来处理消息的。
    mHandler = new FrameHandler(looper);
    //USE_VSYNC 是否使用Vsync
    //boolean USE_VSYNC = SystemProperties.getBoolean("debug.choreographer.vsync", true);
     mDisplayEventReceiver = USE_VSYNC
                    ? new FrameDisplayEventReceiver(looper, vsyncSource)
                    : null;
    //上一帧绘制的时间
    mLastFrameTimeNanos = Long.MIN_VALUE;
    //1秒是1000毫秒,1毫秒是1000微秒,1微秒是1000纳秒
    //1秒就是1*1000*1000*1000=10的九次方纳秒
    //绘制一帧的时间间隔----纳秒。如果是60Hz,那么刷新一帧展示的时间就是16.66毫秒。
    mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
    //初始化回调队列,后面会从这个回调队列中取出Runnable执行run方法。
     mCallbackQueues = new CallbackQueue[CALLBACK_LAST   1];
    for (int i = 0; i <= CALLBACK_LAST; i  ) {
        mCallbackQueues[i] = new CallbackQueue();
    }
}

获取屏幕的刷新率:

//屏幕的刷新率,一秒钟可以刷新屏幕多少次,通常是60Hz
private static float getRefreshRate() {
    DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
            Display.DEFAULT_DISPLAY);
    return di.getMode().getRefreshRate();
}

3.初始化工作完成,那么Choreographer是怎么跑起来的,入口函数在哪?

对于UI绘制来说是入口在RootViewImpl的scheduleTraversals()方法中。

void scheduleTraversals() {
 if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //发送一个屏障消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //注意第一个参数CALLBACK_TRAVERSAL,回调函数的类型。
            //mTraversalRunnable 回调函数要执行的runnable。
            //第三个参数token,传了一个null
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
 }

//第一个参数callbackType 有五种类型,这几个回调是有顺序的。
1.CALLBACK_INPUT 输入回调,首先运行
2.CALLBACK_ANIMATION 动画回调,这个在将动画原理的时候,会看到
3.CALLBACK_INSETS_ANIMATION inset和update相关的动画,运行在上面两个回调之后,
4.CALLBACK_TRAVERSAL 遍历回调,用于处理布局和绘制
5.CALLBACK_COMMIT Commit回调,在Traversal绘制回调之后。

接下来看postCallbackDelayedInternal方法

第二个参数就是上面的mTraversalRunnable。
第四个参数延迟的时间,这里延迟时间是0,没有延迟
所以这个方法走if判断的第一个分支

private void postCallbackDelayedInternal(int callbackType,
         Object action, Object token, long delayMillis) {
     synchronized (mLock) {
         final long now = SystemClock.uptimeMillis();
         final long dueTime = now   delayMillis;
         //将runnable加入回调队列
         mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
         上面传过来的delayMillis是0,所以走第一个分支。
         if (dueTime <= now) {
             scheduleFrameLocked(now);
         } else { //如果有延迟,则发送一个延迟的异步消息。这种消息在handler同步屏障文章中介绍过
             Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
             msg.arg1 = callbackType;
             msg.setAsynchronous(true);
             mHandler.sendMessageAtTime(msg, dueTime);
         }
     }
 }
private void scheduleFrameLocked(long now) {
     if (!mFrameScheduled) {
                mFrameScheduled = true;
        //如果使用垂直同步
         if (USE_VSYNC) {
                //判断是否运行在主线程,如果是则直接调用scheduleVsyncLocked()
                //如果运行在子线程则通过发送handler 的方式也会调用到scheduleVsyncLocked()
                if (isRunningOnLooperThreadLocked()) {//Looper.myLooper() == mLooper
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
         }else{
              final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS   sFrameDelay, now);
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
         }
     }
 }

scheduleVsyncLocked()方法。

private void scheduleVsyncLocked() {
     //调用父类 DisplayEventReceiver的方法
     mDisplayEventReceiver.scheduleVsync();
}

在scheduleVsync()方法中会调用nativeScheduleVsync,这是一个native方法,在native层执行完毕后会回调到java层的方法dispatchVsync()

scheduleVsync:向native层去请求一个Vsync信号。

dispatchVsync:请求到Vsync信号后,执行Java层的UI绘制和渲染逻辑。

public void scheduleVsync() {
    if (mReceiverPtr == 0) {
        Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                  "receiver has already been disposed.");
    } else { // 调用native 方法
        //调用Native方法请求一个Vsync信号,然后会从native层回调java层的dispatchVsync方法
        nativeScheduleVsync(mReceiverPtr);
    }
}

timestampNanos:从Native层传递过来的一个时间戳,Vsync从native层发出的时间。

 // Called from native code.
//从native层回调java层的dispatchVsync方法
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
    onVsync(timestampNanos, physicalDisplayId, frame);
}

在这又发送了一个异步消息,并且 Message.obtain(mHandler, this);第二个参数是一个callBack回调。所以没有handler的情况下,会执行这个回调函数。但是传的是this,所以就会执行this的run方法。这个this就是FrameDisplayEventReceiver的实例,在Choreographer的构造函数中初始化的。

 public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
      mTimestampNanos = timestampNanos;
        mFrame = frame;
        //得到message 添加了一个回调函数,this,则会调用run方法
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
 }

在FrameDisplayEventReceiver的run方法中,调用的doFrame方法

 @Override
 public void run() {
   mHavePendingVsync = false;
   doFrame(mTimestampNanos, mFrame);
 }
void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            if (!mFrameScheduled) {
                return; // no work to do
            }
            //sync信号发出的时间,
            long intendedFrameTimeNanos = frameTimeNanos;
            //当前的时间
            startNanos = System.nanoTime();
            //两者相减得到的时间差,就是底层消息通讯和回调所消耗的时间
            final long jitterNanos = startNanos - frameTimeNanos;
            //如果这个时间差大于了一帧的时间间隔。
            if (jitterNanos >= mFrameIntervalNanos) {
                //计算跳过了多少帧
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                //注意下面这行日子,如果跳帧大于30帧,系统会打印下面这行log,在主线程做了太多工作,会造成UI卡顿。
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    Log.i(TAG, "Skipped "   skippedFrames   " frames!  "
                              "The application may be doing too much work on its main thread.");
                }
                //取模,得到的值就是一帧多出来的时间
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                }
                //用当前时间减去多出来的时间,就是下一帧要绘制的时间
                //进行绘制时间的修正,保证每一次的绘制时间间隔都是mFrameIntervalNanos
                frameTimeNanos = startNanos - lastFrameOffset;
            }
             //如果底层传过来的时间,小于上一帧绘制的时间,正常情况下,frameTimeNanos都是大于上一帧绘制的时间的。
            if (frameTimeNanos < mLastFrameTimeNanos) {
                //跳过本次的绘制,请求下一帧的时间
                scheduleVsyncLocked();
                return;
            }
            //以上的判断,都是为了控制绘制的频率。
            if (mFPSDivisor > 1) {
                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                    scheduleVsyncLocked();
                    return;
                }
            }
            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            //重置标志位,可以再次进入scheduleFrameLocked
            mFrameScheduled = false;
            //将底层传过来的时间,记录为本次绘制的时间,也就是下一帧传过来时,上一帧绘制的时间。
            mLastFrameTimeNanos = frameTimeNanos;
        }
        try {
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
            mFrameInfo.markInputHandlingStart();
            //根据Choreographer的CallBack类型,进行callBack的回调。
            //输入
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
            mFrameInfo.markAnimationsStart();
            //动画
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
            mFrameInfo.markPerformTraversalsStart();
            //界面绘制
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
            //commit
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

这个是很重要的一个方法。

通过这个方法中的逻辑能够看出:Choreographer控制App层UI绘制的节奏和频率。

然后会按顺序执行一些列的doCallBacks函数。

首先会根据callbackType,从链表中取出CallBackRecord。然后再遍历CallBackRecord,调用他的run方法。

void doCallbacks(int callbackType, long frameTimeNanos) {
   CallbackRecord callbacks;
     synchronized (mLock) {
          final long now = System.nanoTime();
             //根据callbacktype,从链表中拿到 CallbackRecord
              callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                      now / TimeUtils.NANOS_PER_MS);
              if (callbacks == null) {
                  return;
              }
              mCallbacksRunning = true;
             for (CallbackRecord c = callbacks; c != null; c = c.next) {
                   //执行CallbackRecord的run方法
                    c.run(frameTimeNanos);
             }
     }
 }

根据token来进行区分是FrameCallback类型还是Runnable。

主要这里的token传进来的是null,所以会执行else分支。

这个action就是mTraversalRunnable,调用mTraversalRunnable的run方法。

mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

public void run(long frameTimeNanos) {
     if (token == FRAME_CALLBACK_TOKEN) {
         ((FrameCallback)action).doFrame(frameTimeNanos);
     } else {
         ((Runnable)action).run();
     }
}
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

在它的run方法中执行了doTraversal()。

void doTraversal() {
     if (mTraversalScheduled) {
         mTraversalScheduled = false;
         //删除屏障消息
         mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
         //调用测量、布局和绘制方法
         performTraversals();
     }
 }

performTraversals()方法中就会调用
performMeasure、performLayout、performDraw,对View进行测量、布局、和绘制。

到此这篇关于Android Choreographer源码详细分析的文章就介绍到这了,更多相关Android Choreographer内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Android Choreographer源码详细分析的更多相关文章

  1. HTML实现代码雨源码及效果示例

    这篇文章主要介绍了HTML实现代码雨源码及效果示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  2. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  4. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  5. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  6. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  7. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  8. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  9. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  10. 源码推荐:简化Swift编写的iOS动画,iOS Material Design库

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部