Android UI 刷新机制

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

问题

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

屏幕基本的刷新原理

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

在这里插入图片描述

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

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

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

在这里插入图片描述

屏幕是根据 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();
    }

  • 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);
    }
	上面方法先将线程的消息队列中插入一个屏障消息,让普通消息先停止,先执行屏障消息后在执行其他消息
	然后通过 mChoreographer 发送了一个 callback 传入 mTraversalRunnable runnable 等待 async 信号来的时候执行

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

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

首次调用后 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();
        }
    }
  • 那么 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);
            }
        }
    }
  • 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);
                }
    }

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);
        }
    }

上面方法最终会执行到 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);
        }
	}

上面第二阶段 执行 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);
            }
        }
    }

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

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

猜你喜欢

转载自blog.csdn.net/ldxlz224/article/details/127703343