Android屏幕刷新机制原理分析

60Hz刷新频率由来

  • 12fps:由于人类眼睛的特殊生理结构,如果所看画面之帧率高于每秒约10~12帧的时候,就会认为是连贯的
  • 24fps:有声电影的拍摄及播放帧率均为美秒24帧,对一般人而言已经算可接受
  • 30fps:早期的高动态电子游戏,帧率少于美秒30帧的话就会显得不连贯,这是因为没有动态模糊使流畅度降低
  • 60fps:在与手机交互的过程中,如触摸和反馈 60帧以下是能感觉出来的,60帧以上不能察觉变化
  • 当帧率低于60fps时感觉画面有卡顿迟滞现象

Android系统每隔16ms发出VSYNC信号(1000ms/60=16.66ms),触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着计算渲染的大多数操作都必须在16ms内完成。

屏幕刷新机制大致流程介绍

首先应用程序向系统服务申请一块buffer(缓存),系统服务返回buffer,应用拿到buffer之后就可以进行绘制,绘制完之后将buffer提交给系统服务,系统服务将buffer写到屏幕的一块缓存区,屏幕会以一定的帧率刷新,每次刷新的时候,就会从缓存区将图像数据读取显示出来。如果缓存区没有新的数据,就一直用旧的数据,这样屏幕看起来就没有变
在这里插入图片描述
屏幕的图像缓存不止一个,假如只有一个缓存,如果屏幕这边正在读缓存,而系统服务又在写缓存,这有可能导致屏幕显示不正常,如一半显示第一帧图像的画面,另一半显示第二帧图像的画面。如何避免这种问题的发生呢?可以弄多个缓存,屏幕从一块缓存读取数据显示,系统服务向另一块缓存写入数据。如果要显示下一帧图像,将两个缓存换一下即可,即屏幕从缓存2读取显示,系统服务向缓存1写入数据。
在这里插入图片描述
vsync(垂直同步机制)是固定频率的脉冲信号,屏幕根据这个信号周期性的刷新,屏幕每次收到这个信号,就从屏幕缓存区读取一帧的图像数据进行显示,而绘制是由应用端(任何时候都有可能)发起的,如果屏幕收到vsync信号,但是这一帧的还没有绘制完,就会显示上一帧的数据,这并不是因为绘制这一帧的时间过长(超过了信号发送周期),只是信号快来的时候才开始绘制,如果频繁的出现的这种情况,用户就会感知屏幕的卡顿,即使绘制时间优化的再好也无济于事,因为这是底层刷新机制的缺陷。
在这里插入图片描述

当然系统提供了解决方案,如果绘制和vsync信号同步就好了,每次收到vsync信号时,一方面屏幕获取图像数据刷新界面,另一方面应用开始绘制准备下一帧图像数据。如果优化的好,每一帧图像绘制控制在16ms以内,就可以非常流畅了。那么问题来了,应用层view的重绘一般调用requestLayout触发,这个函数随时都能调用,如何控制只在vsync信号来时触发重绘呢?有一个关键类Choreography(舞蹈指导,编舞),它最大的作用就是你往里面发送一个消息,这个消息最快也要等到下一个vsync信号来的时候触发。比如说绘制可能随时发起,封装一个Runnable丢给Choreography,下一个vsync信号来的时候,开始处理消息,然后真正的开始界面的重绘了。相当于UI绘制的节奏完全由Choreography来控制。
在这里插入图片描述

Choreography原理分析

就从比较熟系的requestLayout方法开始吧,进行UI操作时,通过checkThread方法进行线程检查,UI操作是否在UI线程,然后调用scheduleTraversals方法:

@Override
    public void requestLayout() {
   
        if (!mHandlingLayoutInLayoutRequest) {
   
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

在scheduleTraversals方法里面,主要做了两件事,一件事是向线程的消息队列中加入了syncBarrier,另外就是往mChoreographer的mCallbackQueue数组插入了一个callback(需要执行的相关操作,这里主要是UI绘制),syncBarrier是一个屏障,将它插入到消息队列后,这个屏障后面的普通消息就不能处理了,等到屏障撤除之后才能处理。但是这个屏障对异步消息是没有影响的。主要是有些类型的消息非常紧急,需要马上处理。如果普通消息太多,容易耽误事(影响紧急消息的执行),所以插入了一个屏障,优先处理异步消息。请求同步Vsync信号,就是一个异步消息,就是请求系统服务SurfaceFlinger在下一次Vsync信号过来时,立即通知我们,我们就可以立即执行mChoreographer的callback数组里面对应callback的相关操作,即UI绘制了,这里先简单提一下,后面会具体分析。

@UnsupportedAppUsage
    void scheduleTraversals() {
   
        if (!mTraversalScheduled) {
   // 注释1
        	// 注释2
            mTraversalScheduled = true;
            // 注释3
            // 插入同步屏障syncBarrier到消息队列,挡住普通的同步消息,优先执行异步消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 注释4
            
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
   
                scheduleConsumeBatchedInput();
            }
            ...
        }
    }

Choreography和ViewRootImpl一起创建的,是通过ThreadLocal存储的,即在不同的线程调用getInstance得到的是不同的Choreography对象。

public static Choreographer getInstance() {
   
		// ThreadLocal<Choreography>
        return sThreadInstance.get();
    }

Choreographer要执行的操作就是mTraversalRunnable即TraversalRunnable的run方法,即doTraversal

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
   
    @Override
    public void run() {
   
        
  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值