• Android UI 刷新机制


    之前在 Android性能优化中的刷新机制 中大概了解过 Android 的刷新机制,今天再带着问题了解一下 Android UI 的刷新机制。

    问题

    • 丢帧一般是什么原因引起的
    • Android 刷新频率 60帧/S,每隔 16ms 调 onDraw() 绘制一次么?
    • onDraw() 完成之后会马上刷新么?
    • 如果界面没有重绘,还会每隔 16ms 刷新屏幕么?
    • 如果在屏幕快要刷新的时候才去 onDraw() 绘制会丢帧么?

    屏幕基本的刷新原理

    	首先:应用从系统服务申请 buffer ,系统服务返回给应用 buffer,应用拿到 buffer 之后进行绘制,绘制完成后交给系统服务。
    	系统服务会将 buffer 写到缓冲区里面,屏幕会有一定的帧率刷新,每次刷新会从缓冲区中取出 buffer 然后显示出来。
    	如果没有新的数据可以取就一直从老的数据,这样看起来屏幕就一直没有变,这就是基本的显示原理,如下图
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    • 那么屏幕的图像缓存是啥样的呢

    系统服务并没有用一个缓存,因为如果屏幕正在读缓存,这时候正好又写缓存,可能导致屏幕显示的东西不正常,所以用的是两个缓存,一个读一个写,如果要显示下一帧,将两个缓存交换就行了。
    在这里插入图片描述

    应用端是从什么时候开始绘制的

    在这里插入图片描述

    屏幕是根据 vsync 信号周期性的刷新的,vsync 信号是一个固定频率的脉冲信号,屏幕每次收到 vsync 信号就会去缓冲区取出一帧信号进行显示。绘制是由客户端随时发起的。

    上面这个图,第一个vsync 屏幕显示的是第 0 帧图像,第一个周期显示的是第 1 帧图像,因为第一帧在 Vsync 来的时候已经准备好了,第三个信号周期还是显示的是第一帧,原因是 vsync 信号来的时候,第二帧没有准备好(也有可能是vsync信号快来的时候才开始绘制,所以即准备时间短也有可能造成这种现象)如果这种现象经常发生的话用户就可以感觉得到页面会有一点卡顿。如果绘制也能和 vsync 信号一致的话这种类型的问题就可以解决了如下图:

    在这里插入图片描述
    如果每一次信号来的时候,页面开始绘制,如果页面优化的非常好,每次都能在16ms内完成页面就可以非常流畅了。那么有个问题是requestLayout() 发起绘制是随时可以发起的,那么android系统是怎么做的才能达到这样的效果?Android 系统服务有一个类 Choreographer ,往其内部发送一个消息,这个消息最快也要等到下一个 vsync 信号来的时候才能触发,相当于UI绘制的节奏完全由 Choreographer 来控制。

    Choreographer 实现原理

    我们从客户端发起刷新 UI 重绘的方法 ViewRootImpl 的 requestLayout() 开始。

        @Override
        public void requestLayout() {
        		// 检查线程
                checkThread();
                scheduleTraversals();
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • scheduleTraversals()
        void scheduleTraversals() {
        		// 往线程的消息队列里面插入了一个 SyncBarrier 消息
        		// Barrier 是屏障的意思 消息队列中插入该屏障消息以后,普通消息就停止处理等待它处理完成。
        		// 但是屏障对异步消息是没有影响的
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                // 往 mChoreographer 中插入了一个 postCallback
                // mChoreographer = Choreographer.getInstance(); 他是从 sThreadInstance threadLocal 获取的 并不是单例模式,所以在不同的线程取出来的是不用的 mChoreographer 对象
                // mChoreographer 是在 viewRootImpl 的构造器中创建的
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    	上面方法先将线程的消息队列中插入一个屏障消息,让普通消息先停止,先执行屏障消息后在执行其他消息
    	然后通过 mChoreographer 发送了一个 callback 传入 mTraversalRunnable runnable 等待 async 信号来的时候执行
    
    • 1
    • 2

    如果多次调用 requestLayout 会怎么样。

        void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                // mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                // mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    首次调用后 mTraversalScheduled 会设置成 true ,防止多次调用。那么什么时候设置回 false 的呢,答案是在传入到 mChoreographer 的 runnable mTraversalRunnable中,也就是下一次信号来的时候执行 runnable 设置成 false。

        final class TraversalRunnable implements Runnable {
            @Override
            public void run() {
                doTraversal();
            }
        }
    	// 将  mTraversalScheduled = false; 设置成 false
        void doTraversal() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
                performTraversals();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 那么 mChoreographer.postCallback() 是怎么添加到 mChoreographer 中去的
        public void postCallback(int callbackType, Runnable action, Object token) {
            postCallbackDelayed(callbackType, action, token, 0);
        }
    	public void postCallbackDelayed(int callbackType,Runnable action, Object token, long delayMillis){
            postCallbackDelayedInternal(callbackType, action, token, delayMillis);
        }
    	// 最终调用了 postCallbackDelayedInternal 方法
        private void postCallbackDelayedInternal(int callbackType,
                Object action, Object token, long delayMillis) {
            synchronized (mLock) {
                final long now = SystemClock.uptimeMillis();
                final long dueTime = now + delayMillis;
                // 根据不同的 callbackType 插入到对应的单链表中,然后通过 dueTime 进行排序
                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);
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • scheduleFrameLocked(now);
        private void scheduleFrameLocked(long now) {
      				// 如果当前线程就是 Choreographer 的工作线程,直接调用 scheduleVsyncLocked() 
                    if (isRunningOnLooperThreadLocked()) {
                        scheduleVsyncLocked();
                    } else {
                    	// 否则发送 mHandler 到Choreographer的工作线程的 queue 的最前面
                        Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                        // 设置成异步消息
                        msg.setAsynchronous(true);
                        // 设置到最前面,当信号来时第一个执行
                        mHandler.sendMessageAtFrontOfQueue(msg);
                    }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    scheduleFrameLocked() 方法就是判断线程,如果当前就是 Choreographer 工作线程则直接 发送 mDisplayEventReceiver.scheduleVsync(); 如果不是则发送 Hander 到工作线程中。当 Vsync 信号来的时候 surfaceFlinger 第一时间通知 Choreographer 刷新。

    • surfaceFlinger 来的时候会回调到 DisplayEventReceiver 的 onVsync() 函数,它的视线类是 Choreographer. 的内部类,在 Choreographer. 的构造器中初始化的。
        private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
            public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
                super(looper, vsyncSource);
            }
            @Override
            public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
            	// 。。。
                mTimestampNanos = timestampNanos;
                mFrame = frame;
                // this 是把自己传进去了,到时候 mHandler 发送消息执行的 run 方法就是下面的 方法
                Message msg = Message.obtain(mHandler, this);
                msg.setAsynchronous(true);
                // 发送时带上时间戳 到时间再执行 run() 
                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
            }
    
            @Override
            public void run() {
                mHavePendingVsync = false;
                doFrame(mTimestampNanos, mFrame);
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    上面方法最终会执行到 run() doFrame 的函数如下

    • doFrame()
        void doFrame(long frameTimeNanos, int frame) {
    		// 第一阶段
    	            long intendedFrameTimeNanos = frameTimeNanos;
                startNanos = System.nanoTime();
                // 计算一下晚了多久
                final long jitterNanos = startNanos - frameTimeNanos;
                // 如果晚的时间超过一定时间
                if (jitterNanos >= mFrameIntervalNanos) {
                 // 计算一下晚了多少帧
                    final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                    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;
                    frameTimeNanos = startNanos - lastFrameOffset;
                }
               // 第二阶段 处理 callback
               
    
    
            try {
                doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
                doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
                doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
            }
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    上面第二阶段 执行 doCallbacks 根据不同类型,执行不同的 callback,callback 是有时间戳的 只有时间到了才会去回调。

        void doCallbacks(int callbackType, long frameTimeNanos) {
            CallbackRecord callbacks;
            synchronized (mLock) {
                final long now = System.nanoTime();
                // 取出到了时间的 callback
                callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                        now / TimeUtils.NANOS_PER_MS);
                if (callbacks == null) {
                    return;
                }
                // 	.....
            }
            try {
                for (CallbackRecord c = callbacks; c != null; c = c.next) {
                // 执行 run 方法
                    c.run(frameTimeNanos);
                }
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    doCallbacks 就是根据类型取出到了时间的 callback ,然后执行它的 run 方法。之前讲过传入的 callback run 执行的是 doTraversal(); ,它内部执行了 performTraversals(); 函数,performTraversals(); 就是真正的执行绘制的方法。之后就是执行 performMeasure() ,performLayout() performDraw() 了。

    • 总结一下上面流程
      在这里插入图片描述

    view调用了 requestLayout() 其实是在 choreographer 中添加了一个 callback 到队列中,choreographer 像 SurfaceFlinger 请求下一个 vsync 信号,当信号来了 SurfaceFlinger 通过 post 发送通知给 choreographer ,choreographer 在获取消息队列中的消息,执行 run 调用 performTraversal() 。

    • scheduleVsyncLocked(); 就是通知 SurfaceFlinger
        private void scheduleVsyncLocked() {
        	// private static native void nativeScheduleVsync(long receiverPtr);
            mDisplayEventReceiver.scheduleVsync();
        }
    
    • 1
    • 2
    • 3
    • 4

    这个方法执行到的是 native 层的 DisplayEventReceiver 的 scheduleVsync() 函数。

  • 相关阅读:
    嵌入式操作系统--机房管理系统
    如何解决Ubuntu系统域名解析失败的问题
    Spring Boot 3.x快速入门
    Kafka基本讲解
    基于taro开发微信小程序
    基于Sring+bootstrap+MySQL的住房公积金管理系统
    历史名人鲁迅介绍HTML个人网页作业作品下载 历史人物介绍网页设计制作 大学生英雄人物网站作业模板 dreamweaver简单个人网页制作
    【树】最大层内元素和 层序遍历
    DS18B20
    JNPF:让应用开发更简单、快捷
  • 原文地址:https://blog.csdn.net/ldxlz224/article/details/127703343