欢迎光临

我们一直在努力
当前位置:首页 > 编程技术 >

Android Choreographer源码详细分析

日期:
后台-插件-广告管理-首页/栏目/内容广告位一(PC)
后台-插件-广告管理-首页/栏目/内容广告位一(手机)
目录
  • 一、首先介绍一些基础知识
  • 二、android源码中Choreographer是如何运行

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

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()方法中。[!--empirenews.page--]

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
                    scheduleVandroidsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
          js          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) {
   > public void> @Override
 public void run() {
   mHavePendingVsync = false;
   doFrame(mTimestampNanos, mFrame);
 }[!--empirenews.page--]
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>androidHandlingStart();
            //根据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) {
                  rekopoJThUturn;
              }
              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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

后台-插件-广告管理-首页/栏目/内容广告位二(PC)
后台-插件-广告管理-首页/栏目/内容广告位二(手机)
后台-插件-广告管理-内容广告位三(PC)
后台-插件-广告管理-内容广告位三(手机)

相关阅读

后台-插件-广告管理-内容广告位四(PC)
后台-插件-广告管理-内容广告位四(手机)

热门文章

后台-插件-广告管理-侧边广告位一(PC)
后台-插件-广告管理-侧边广告位一(手机)
  • HTML 表单组件实例代码

  • HTML 表单用于搜集不同类型的用户输入。下文通过代码给大家分享html 表单组件实例代码,感兴趣的朋友参考下吧 废话不多说了,直接给大家贴代码了,具体代码如下所示: <!DOCTYPE
  • html2canvas 将html代码转为图片的使用方法

  • 转换代码到图片使用 html2canvas,这是一个非常著名的从浏览器网页截图的开源库,使用很方便,功能也很强大。 使用 html2canvas http:// html2canvas 的使用非常简单,简单
  • HTML网页中插入视频的方法小结

  • 现在如果要在页面中使用video标签,需要考虑三种情况,支持Ogg Theora或者VP8(如果这玩意儿没出事的话)的(Opera、Mozilla、Chrome),支持H.264的(Safari、IE 9、Chrome),都不支持的(IE6、
  • HTML实现文本框只读不能修改其中的内容

  • 废话不多说了,直接给大家贴代码了,具体代码如下所示: <!--方法1:>http:// 当鼠标放不上就离开焦点 --> <input type="text" name="input1" value=http://www.cppcns.com/web
  • 移动端专用的meta标签设置大全

  • 前言 之前学习前端中,对meta标签的了解仅仅只是这一句。 <meta charset="UTF-8"> 但是打开任意的网站,其head标签内都有一列的meta标签。比如我们我们网站,但是自己却很不熟
后台-插件-广告管理-侧边广告位二(PC)
后台-插件-广告管理-侧边广告位二(手机)

最新文章

  • 在Asp.net core项目中使用WebSocket

  • 今天小试了一下在ASP.NET core中使用websocket,这里记录一下: 在 Startup 类的 Configure 方法中添加 WebSocket 中间件。 app.UseWebSockets(); 它也可以传入一些参数 app.Us
  • Vue快速理解事件绑定是什么

  • 目录一、监听事件二、事件修饰符1、stop修饰符阻止事件冒泡2、capture修饰符3、self修饰符4、prevent修饰符5、键盘事件修饰符6、鼠标事件修饰符一、监听事件 监听事件一般
  • C#实现模拟ATM自动取款机功能

  • 目录(1)关于用户帐号的类:Account(2)关于银行数据库的类:BankDatabase(3)关于ATM屏幕显示的类:Screen(4)关于ATM键盘的类:Keypad(5)关于进钞、出钞口的类:DepositSlot(6)关于ATM
  • Java设计模式之抽象工厂模式浅析讲解

  • 1.介绍 当系统准备为用户提供一系列相关对象,又不想让用户代码和这些对象形成耦合时,就可以使用抽象工厂模式。 2.如何实现 1)抽象产品--Car 2)具体产品--BYDCar、TSLCar 3)抽象
  • 如何动态替换Spring容器中的Bean

  • 目录动态替换Spring容器中的Bean原因方案实现Spring中的bean替换问题动态替换Spring容器中的Bean 原因 最近在编写单测时,发现使用 Mock 工具预定义 Service 中方法的行为特
  • C#优雅的实现INotifyPropertyChanged接口

  • INotifyPropertyChanged接口在wpF或WinFrom程序中使用还是经常用到,常用于通知界面属性变更。标准写法如下: class NotifandroidyObject : INotifyPropertyChanged {
后台-插件-广告管理-侧边广告位三(PC)
后台-插件-广告管理-侧边广告位三(手机)