IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 屏幕刷新机制与Choreographer的工作原理 -> 正文阅读

[移动开发]屏幕刷新机制与Choreographer的工作原理

前言

2021的进度已过50%,不知大家的小目标是否已经也到达了50%了,如果没有的话,还有2021的下一个50%。本文将给大家带来Android的屏幕刷新机制的介绍,同时通过Choreographer的源码了解它的工作原理。

正文

刷新率:代表一秒内屏幕刷新的次数,大部分手机的刷新率为60Hz,即一秒刷新60次,所以大概就是16.6ms刷新一次,这个是由硬件的参数决定的。

帧率:相信开发客户端的同学都听说过FPS,也就是帧率。帧率代表一秒内绘制的帧数,当某个页面或者某个操作的帧率比较低时,也就是出现丢帧的情况(没法在16.6ms内完成工作),有可能就会出现卡顿。

Android通过VSync来确保帧率的稳定,VSync是Vertical Synchronization(垂直同步)的缩写,它主要用来出发界面的更新绘制、处理用户的输入等操作,系统会定时发送Vsync信号,也就是16ms发送一次,在Android4.1开始引入Vsync机制。

我们通过下面两张图来看看VSync机制。

正常的Vsync机制:

在这里插入图片描述

发生卡顿:

在这里插入图片描述

(图片来源于网络,如有侵权请联系删除)

以上两张图如何理解:

正常帧:1、2、3、4每一帧内都能够计算完成,并且得到展示,给用户。

掉帧:第一帧的时候,B没能计算完成,所以第二帧继续展示A,直到第三帧才能展示出B,但A却不能在第三帧计算完成,所以第四帧继续展示的是B,就出现了掉帧的情况,可能给用户带来卡顿的感觉。

看完上图,可能会带来对问题是Vsync怎么去触发到具体页面的绘制等响应的呢?有个重要的类Choreographer,Vsync通过去唤醒Choreographer,然后它进行一系列的处理,比如输入事件、控件的更新等等,同时Choreographer也会进行请求Vsync信号。接下来就来看看Choreographer这个类是怎么样的。

除了VSync信号,Choreographer起到了很大的作用,假设没有Vsync跟Choreographer,那么每当渲染完一帧,那么下一帧就接着开始渲染,这导致了不稳定的情况。它们的存在便能够保证帧率的稳定,让用户的体验更好。

Choreographer通过接收Vsync的信号,进而对app的一些绘制、用户输入事件、动画等做处理,这些都是在onVsync回调方法里边进行统一的处理;同时它也负责对Vsync信号的请求。接下来,我们通过源码来了解它的工作原理。

1.注册:

我们都知道,每当要接收某个回调事件,都需要提前进行注册。Choreographer是怎么去进行注册的呢?

在了解之前,我们先看看Choreographer是怎么被创建出来的:

public static Choreographer getInstance() {
    return sThreadInstance.get();
}
private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            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);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };

可以看出它是一个线程单例,并且需要有looper与之绑定。

它的构造方法:

private Choreographer(Looper looper, int vsyncSource) {
    mLooper = looper;
    mHandler = new FrameHandler(looper);
    mDisplayEventReceiver = USE_VSYNC
            ? new FrameDisplayEventReceiver(looper, vsyncSource)
            : null;
    //......
    mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
    for (int i = 0; i <= CALLBACK_LAST; i++) {
        mCallbackQueues[i] = new CallbackQueue();
    }
    //......
}

在这里还没看到注册的代码,但我们可以看到Choreographer在初始化的时候,创建了FrameDisplayEventReceiver,FrameDisplayEventReceiver继承了DisplayEventReceiver,它的构造方法为:

public DisplayEventReceiver(Looper looper, int vsyncSource) {
    if (looper == null) {
        throw new IllegalArgumentException("looper must not be null");
    }
    mMessageQueue = looper.getQueue();
    mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
            vsyncSource);
  //......
}

可以看到,在构造方法里边进行了注册初始化的时候,注册方式则是通过native方法nativeInit去监听文件句柄,这样就建立了Vsync与Java层的监听通道。

2.接收:

FrameDisplayEventReceiver#onVsync

@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
    //省略部分代码
    Message msg = Message.obtain(mHandler, this);
    msg.setAsynchronous(true);
    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}

接收到msg时:

@Override
public void run() {
    mHavePendingVsync = false;
    doFrame(mTimestampNanos, mFrame);
}

doFrame方法真正做绘制等逻辑的统一处理:

void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        if (!mFrameScheduled) {
            return; // no work to do
        }
				//省略掉帧逻辑
    try {
      //省略记录绘制信息代码
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    }
    //输出log
}

doFrame的开始主要作与上次最后一次调用的时间差,并计算掉帧的逻辑,接着开始边记录绘制信息,边执行每种callbackType的callback,这些callback分别为:

public static final int CALLBACK_INPUT = 0; //input事件callback
public static final int CALLBACK_ANIMATION = 1; //动画的callback
public static final int CALLBACK_INSETS_ANIMATION = 2;//insets 动画
public static final int CALLBACK_TRAVERSAL = 3;//遍历view tree渲染
public static final int CALLBACK_COMMIT = 4;//提交

每一种都会有对应一个CallbackQueue,里边其实维护的是一个元素为Callback Record的链表结构,其定义如下:

private static final class CallbackRecord {
    public CallbackRecord next;
    public long dueTime;
    public Object action; // Runnable or FrameCallback
    public Object token;

    @UnsupportedAppUsage
    public void run(long frameTimeNanos) {
        if (token == FRAME_CALLBACK_TOKEN) {
            ((FrameCallback)action).doFrame(frameTimeNanos);
        } else {
            ((Runnable)action).run();
        }
    }
}

3.请求:

每当我们跟新UI时,比如说TextView更新展示的文本,看似一个简单的调用,实际里边执行了很多逻辑,最后会调用到ViewRootImpl的scheduleTraversals方法:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
      //省略其他代码
    }
}

可以看到它通过Choreographer调用postCallback方法,post一个callback_type为CALLBACK_TRAVERSAL的任务,postCallback最后会调用到postCallbackDelayedInternal方法,源码如下:

private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    //省略log
    synchronized (mLock) {
        //计算时间
       mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}

将其添加到上边讲过的callbackType为CALLBACK_TRAVERSAL的CallbackQueue中去,然后再对比一下时间,如果没有延迟的话,则直接调用scheduleFrameLocked方法,否则延迟发送一个MSG_DO_SCHEDULE_CALLBACK,其实最后也是调用了scheduleFrameLocked方法:

private void scheduleFrameLocked(long now) {
    if (!mFrameScheduled) {
        mFrameScheduled = true;
        if (USE_VSYNC) {
            if (isRunningOnLooperThreadLocked()) {
              //发起一个Vsync信号请求
                scheduleVsyncLocked();
            } else {
              //切换线程再发送
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtFrontOfQueue(msg);
            }
        } else {
            //省略计算时间的方法
          //直接按时调用doFrame
            Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, nextFrameTime);
        }
    }
}

此方法主要做了三个事:

1.如果是使用VSYNC,则先判断是否运行在当前线程,是的话则发起VSync信号请求。

2.如果不是当前线程,则切换至当前线程再请求

3.如果没有使用VSYNC机制,则直接调用doFrame,无需等到下个VSync信号的到来。

那么真正发起请求是怎么发起的呢?其实就是最开始初始化的FrameDisplayEventReceiver发起的,通过调用native方法nativeScheduleVsync发起。

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 {
        nativeScheduleVsync(mReceiverPtr);
    }
}

这里只举了CALLBACK_TRAVERSAL这种callType的例子,像animation这些其实可以通过跟踪其start方法,也可以得知发送的是CALLBACK_ANIMATION这种callbackType的callback。

到此我们可以了解到以下三点:

  1. Choreographer通过接收一系列的事件,分别按照不同的callbackType进行存放,等到接收到VSync信号则统一处理。同时它也负责对VSync信号的请求。
  2. 它是一个线程单例,且初始化必须得有对应的looper
  3. 它持有DisplayEventReceiver,通过DisplayEventReceiver进行接收处理VSync信号的到来和请求VSync信号

结语

相信通过本次的分享,我们都可以对Android的页面刷新机制更加了解,同时也知道了平时没有直接接触到的Choreographer是怎么工作的。

这半年大家有可能读完了Q本书,迭代了N个版本,修复了M个bug,刷了X道LeetCode,Y次夜跑,刷了X个短视频,瞎逛了S个地方,写了Z篇文章,希望在下一个半年,大家能够继续实现下一个50%的目标。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章           查看所有文章
加:2021-07-04 21:10:24  更:2021-07-04 21:10:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/5 14:03:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码