这是我参加11月更文应战的第16天,活动概况检查:2021最后一次更文应战

Android界面越来越杂乱,很多时分页面会呈现掉帧什么的,今天特意研究一下屏幕改写的原理,便利以后温习

屏幕改写机制大致流程

首要使用程序向体系服务申请一块buffer(缓存),体系服务回来buffer,使用拿到buffer之后就能够进行制作,制作完之后将buffer提交给体系服务,体系服务将buffer写到屏幕的一块缓存区,屏幕会以必定的帧率改写,每次改写的时分,就会从缓存区将图画数据读取显现出来。假如缓存区没有新的数据,就一直用旧的数据,这样屏幕看起来就没有变。

屏幕的图画缓存不止一个,假如只要一个缓存,假如屏幕这边正在读缓存,而体系服务又在写缓存,这有或许导致屏幕显现不正常,如一半显现第一帧图画的画面,另一半显现第二帧图画的画面。如何防止这种问题的产生呢?能够弄多个缓存,屏幕从一块缓存读取数据显现,体系服务向另一块缓存写入数据。假如要显现下一帧图画,将两个缓存换一下即可,即屏幕从缓存2读取显现,体系服务向缓存1写入数据。

Android-屏幕刷新机制

vsync(笔直同步机制)是固定频率的脉冲信号,屏幕根据这个信号周期性的改写,屏幕每次收到这个信号,就从屏幕缓存区读取一帧的图画数据进行显现,而制作是由使用端(任何时分都有或许)建议的,假如屏幕收到vsync信号,可是这一帧的还没有制作完,就会显现上一帧的数据,这并不是由于制作这一帧的时刻过长(超过了信号发送周期),只是信号快来的时分才开端制作,假如频频的呈现的这种情况,用户就会感知屏幕的卡顿,即使制作时刻优化的再好也杯水车薪,由于这是底层改写机制的缺陷。

Android-屏幕刷新机制

当然体系提供了解决方案,假如制作和vsync信号同步就好了,每次收到vsync信号时,一方面屏幕获取图画数据改写界面,另一方面使用开端制作预备下一帧图画数据。假如优化的好,每一帧图画制作控制在16ms以内,就能够十分流畅了。

使用层view的重绘一般调用requestLayout触发,这个函数随时都能调用,如何控制只在vsync信号来时触发重绘呢?有一个要害类Choreography(舞蹈辅导,编舞),它最大的效果便是你往里边发送一个音讯,这个音讯最快也要比及下一个vsync信号来的时分触发。

Choreography 原理剖析

进入ViewRootImpl类中的requestLayout 办法

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

进入scheduleTraversals 办法

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

scheduleTraversals办法里边,主要做了两件事:

  1. 线程的音讯行列中加入了syncBarrier。
  2. 往mChoreographer的mCallbackQueue数组插入了一个callback(需求履行的相关操作,这里主要是UI制作)

syncBarrier是一个屏障,将它插入到音讯行列后,这个屏障后边的普通音讯就不能处理了,比及屏障撤除之后才干处理。可是这个屏障对异步音讯是没有影响的。主要是有些类型的音讯十分紧急,需求立刻处理。假如普通音讯太多,容易耽误事(影响紧急音讯的履行),所以插入了一个屏障,优先处理异步音讯。

恳求同步Vsync信号,便是一个异步音讯,便是恳求体系服务SurfaceFlinger在下一次Vsync信号过来时,当即告诉咱们,咱们就能够当即履行mChoreographer的callback数组里边对应callback的相关操作,即UI制作了

public static Choreographer getInstance() {
    return sThreadInstance.get();
}

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

Choreographer要履行的操作便是mTraversalRunnable即TraversalRunnable的run办法,即doTraversal

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

进入doTraversal办法

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
       //移除同步音讯屏障 
       mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        //开端制作流程
        performTraversals();
。。。
    }
}

进入Choreography的postCallback办法

public void postCallback(int callbackType, Runnable action, Object token) {
    postCallbackDelayed(callbackType, action, token, 0);
}
@UnsupportedAppUsage
@TestApi
public void postCallbackDelayed(int callbackType,
        Runnable action, Object token, long delayMillis) {
    if (action == null) {
        throw new IllegalArgumentException("action must not be null");
    }
    if (callbackType < 0 || callbackType > CALLBACK_LAST) {
        throw new IllegalArgumentException("callbackType is invalid");
    }
    postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}

进入postCallbackDelayedInternal办法

private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    if (DEBUG_FRAMES) {
        Log.d(TAG, "PostCallback: type=" + callbackType
                + ", action=" + action + ", token=" + token
                + ", delayMillis=" + delayMillis);
    }
    synchronized (mLock) {
       //获取当时时刻
        final long now = SystemClock.uptimeMillis();
        //触发事件
        final long dueTime = now + delayMillis;
        // 将履行动作放在mCallbackQueue数组中
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        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);
        }
    }
}

mCallbackQueue数组里边的每一个元素都是一个callback的单链表,增加callback一方面要根据callback的类型callbackType插到对应的单链表,另一方面要根据callback履行的时刻次序排序,越是立刻要履行的callback,越是插入到链表的前面,然后等待被调用。

进入scheduleFrameLocked办法

private void scheduleFrameLocked(long now) {
    。。。。
      // 假如在Choreography的UI线程中,就直接调用当即组织笔直同步,不然就发送一个音讯到UI线程
     // 尽快组织恳求一个笔直同步
     if (isRunningOnLooperThreadLocked()) {
                scheduleVsyncLocked();
     } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtFrontOfQueue(msg);
    }
    。。。
}

vsync信号过来的时分,让体系服务SurfaceFlinger第一时刻告诉咱们,咱们去履行制作的相关操作。假如不在Choreography的UI线程,就发送异步音讯让UI线程恳求同步Vsync信号

关于音讯处理的逻辑:

private final class FrameHandler extends Handler {
    public FrameHandler(Looper looper) {
        super(looper);
    }
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_DO_FRAME:
                doFrame(System.nanoTime(), 0);
                break;
            case MSG_DO_SCHEDULE_VSYNC:
                doScheduleVsync();
                break;
            case MSG_DO_SCHEDULE_CALLBACK:
                doScheduleCallback(msg.arg1);
                break;
        }
    }
}

FramHandler拿到 whate特点值为MSG_DO_SCHEDULE_CALLBACK的时分会去履行 doScheduleCallback(msg.arg1)办法

void doScheduleCallback(int callbackType) {
    synchronized (mLock) {
        if (!mFrameScheduled) {
            final long now = SystemClock.uptimeMillis();
            if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
                scheduleFrameLocked(now);
            }
        }
    }
}

假如满足条件的情况下它会调用 scheduleFrameLocked()这个办法。终究都是调用scheduleVsyncLocked办法

private void scheduleVsyncLocked() {
    mDisplayEventReceiver.scheduleVsync办法();
}

进入scheduleVsync办法

@UnsupportedAppUsage
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办法
        // 恳求同步vsync信号
        nativeScheduleVsync(mReceiverPtr);
    }
}

当下一个vsync信号产生的时分,SurfaceFlinger就会告诉咱们,就会回调该类的onVsync函数,参数timestampNanos便是vsync的时刻戳,该函数里边会发送一个音讯到Choreography的作业线程里边去了,这里并不是要切换作业线程,由于onVsync本身就在Choreography的作业线程。这个音讯带了时刻戳的,表明音讯触发的时刻,有了这个时刻戳,就能够依照时刻戳的次序来处理音讯。到时刻了,就会去履行run办法,即履行doFrame办法

private final class FrameDisplayEventReceiver extends DisplayEventReceiver
        implements Runnable {
    private boolean mHavePendingVsync;
    private long mTimestampNanos;
    private int mFrame;
    public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
        super(looper, vsyncSource, CONFIG_CHANGED_EVENT_SUPPRESS);
    }
    @Override
    public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
        long now = System.nanoTime();
        if (timestampNanos > now) {
            Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
                    + " ms in the future!  Check that graphics HAL is generating vsync "
                    + "timestamps using the correct timebase.");
            timestampNanos = now;
        }
        if (mHavePendingVsync) {
            Log.w(TAG, "Already have a pending vsync event.  There should only be "
                    + "one at a time.");
        } else {
            mHavePendingVsync = true;
        }
        mTimestampNanos = timestampNanos;
        mFrame = frame;
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }
    @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
            }
            //当时时刻
            startNanos = System.nanoTime();
            //当时时刻和笔直同步时刻
            final long jitterNanos = startNanos - frameTimeNanos;
            //笔直同步时刻和当时时刻的差值假如大于一个周期就修正一下
            if (jitterNanos >= mFrameIntervalNanos) {
            //取插值和一直周期的余数
               final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
               //当时时刻减去上一步得到的余数当作最新的一直信号时刻
               frameTimeNanos = startNanos - lastFrameOffset;
            }
            //笔直同步时刻上一次时刻还小,就组织下次笔直同步,直接回来
            if (frameTimeNanos < mLastFrameTimeNanos) {
                scheduleVsyncLocked();
                return;
            }
            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            mFrameScheduled = false;
            mLastFrameTimeNanos = frameTimeNanos;
        }
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        if (DEBUG_FRAMES) {
            final long endNanos = System.nanoTime();
            Log.d(TAG, "Frame " + frame + ": Finished, took "
                    + (endNanos - startNanos) * 0.000001f + " ms, latency "
                    + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
        }
    }

doFrame分为两个阶段:

  • 第一个阶段,参数frameTimeNanos表明这一帧的时刻戳先核算当时时刻和这个时刻戳的距离有多大,距离越大,表明要延时了,假如延时超过一个周期(mFrameIntervalNanos), 就要核算到底推迟了几个周期,假如推迟周期数(丢帧数,越过的帧数)达到一个常量SKIPPED_FRAME_WARNING_LIMIT ,就会打印日志”使用在主线程做了太多的工作(耗时操作)”导致制作推迟,丢帧。
  • 第二阶段便是处理callback了,callback有四种类型,每种类型对应一个单链表callbackQueue,给vsync事件别离分发到四种callback,然后履行对应的doCallbacks函数,单链表里边的callback是有时刻戳的,只要到了时刻的的callback才会回调,extractDueCallbacksLocked从callbackQueue里边取出到了时刻的callback,然后在循环里边履行他们的run函数,requestLayout里边的scheduleTraversals函数传的callback是什么?便是预备制作,mTraversalRunnable的run办法其实调用的是performTraversals,真正的开端履行UI制作流程。

大致流程

Android-屏幕刷新机制

常见的面试问题

丢帧一般是什么原因引起的?

主线程有耗时操作,耽误了view的制作

Android改写频率60帧/秒,每隔16ms调onDraw制作一次?

60帧/秒也是vsync信号的频率,但不必定每次vsync信号都会去制作,先要使用端主动建议重绘,才会向SurfaceFlinger恳求接收vsync信号,这样当vsync信号来的时分,才会真正去制作。

onDraw履行完之后屏幕会立刻改写么?

不会立刻改写,会比及下一次vsync信号时才会改写。

假如界面没有重绘,还会每隔16ms改写屏幕么?

界面没有重绘,使用就不会收的vsync信号,屏幕仍是会改写,画面数据用的是旧的,看起来没什么改变罢了

假如屏幕快要改写的时分才去onDraw制作会丢帧么?

重绘不会当即履行,而是比及下一次vsync信号来时才开端, 所以什么时分建议重绘影响不大