当前位置: 萬仟网 > 移动技术>移动开发>Android > 荐 Android屏幕刷新——源码分析

荐 Android屏幕刷新——源码分析

2020年07月14日  | 萬仟网移动技术  | 我要评论

Android屏幕刷新原理——源码分析

概述

Android系统每16ms(一般的安卓手机的FPS(每秒的帧数)是60)会请求一次VSync(垂直同步)信号,进行一次屏幕刷新。在请求到VSync信号后系统会向主线程发送一个异步消息,为了保证UI的流畅,系统使用了消息队列的同步屏障来优先处理这个屏幕重绘的异步消息。

VSync信号

VSync是Vertical Synchronization的缩写,译作垂直同步,是一种在PC上已经很早就广泛使用的技术。垂直同步信号简单来说就是周期性的中断。

使用VSync信号的作用

强制帧率和显示器刷新频率同步。也就是让CPU和GPU在Choreographer(编舞者,名字起地还挺文艺)的协调下在VSync信号到来后进行一次屏幕刷新,即view的重绘。

没有使用VSync信号的情况

可能因为CPU和GPU处理不协调、CPU或GPU处理任务过多等因素而导致丢帧现象发生。

img

使用VSync信号的情况

CPU或GPU处理任务过多还是会导致丢帧现象发生。

img

三级缓冲

Android4.0之后基本都是默认硬件加速,CPU跟GPU都是并发处理任务的,CPU处理完之后就完工,等下一个VSYNC到来就可以进行下一轮操作。也就是CPU、GPU、显示都会用到Buffer,VSYNC+双缓冲在理想情况下是没有问题的,但如果某个环节出现问题,那就不一样了如下(帧耗时超过16ms):

img

可以看到在第二个阶段,存在CPU资源浪费,为什么呢?双缓冲Surface只会提供两个Buffer,一个Buffer被DisPlay占用(SurfaceFlinger用完后不会释放当前的Buffer,只会释放旧的Buffer,直观的想一下,如果新Buffer生成受阻,那么肯定要保留一个备份给SF用,才能不阻碍合成显示,就必定要一直占用一个Buffer,新的Buffer来了才释放老的),另一个被GPU处理占用,所以,CPU就无法获取到Buffer处理当前UI,在Jank的阶段空空等待。一般出现这种场景都是连续的:比如复杂视觉效果每一帧可能需要20ms(CPU 8ms +GPU 12ms),GPU可能会一直超负荷,CPU跟GPU一直抢Buffer,这样带来的问题就是滚雪球似的掉帧,一直浪费,完全没有利用CPU与GPU并行处理的效率,成了串行处理

解决的办法就是再增加一个Buffer给CPU用,让它提前忙起来,这样就能做到三方都有Buffer可用,CPU跟GPU不用争一个Buffer,真正实现并行处理。如下:

img

Android屏幕刷新本质上来说就是view周期性地重新绘制到屏幕上的过程,而与这个过程有着密切关系的就是View的invalidate方法,下面就深入源码分析这个方法背后的原理。

源码分析

View.invalidate()

public void invalidate() {
    invalidate(true);
}

public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
                        boolean fullInvalidate) {
    if (mGhostView != null) {
        mGhostView.invalidate(true);
        return;
    }

    if (skipInvalidate()) {	//跳过不可见和不再执行动画的view
        return;
    }

    // Reset content capture caches
    mCachedContentCaptureSession = null;

    //下面会对是否缓存做一些判断
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
        || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
        || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
        || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
        if (fullInvalidate) {
            mLastIsOpaque = isOpaque();
            mPrivateFlags &= ~PFLAG_DRAWN;
        }

        mPrivateFlags |= PFLAG_DIRTY;

        if (invalidateCache) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        // Propagate the damage rectangle to the parent view.
        final AttachInfo ai = mAttachInfo;
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            p.invalidateChild(this, damage);	//会调用到ViewGroup的invalidateChild方法
        }

        // Damage the entire projection receiver, if necessary.
        if (mBackground != null && mBackground.isProjected()) {
            final View receiver = getProjectionReceiver();
            if (receiver != null) {
                receiver.damageInParent();
            }
        }
    }
}

ViewGroup.onDescendantInvalidated(@NonNull View child, @NonNull View target)

    public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
        ...

        if (mParent != null) {
            mParent.onDescendantInvalidated(this, target);
        }
    }

可以看到,它会一直向上调用父View的onDescendantInvalidated方法,直到调用到ViewRootImpl的onDescendantInvalidated方法:

@Override
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
    // TODO: Re-enable after camera is fixed or consider targetSdk checking this
    // checkThread();
    if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
        mIsAnimating = true;
    }
    invalidate();
}

void invalidate() {
    mDirty.set(0, 0, mWidth, mHeight);
    if (!mWillDrawSoon) {
        scheduleTraversals();
    }
}

ViewRootImpl.scheduleTraversals()

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;		//这个变量保证下面的代码在一个垂直同步信号周期内只执行一次
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();	//向主线程的消息队列中post一个同步屏障
        //postCallback方法在队列中添加了一个mTraversalRunnable
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        //添加一个处理触摸事件的回调,防止中间有Touch事件过来
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

上面通过mChoreographer这个Choreographer对象(上面提到的编舞者)在队列中添加了一个mTraversalRunnable(这是一个Runnable对象,下面还会说道说道),同时请求并等待VSync信号。

Choreographer的postCallback方法

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


private void postCallbackDelayedInternal(int callbackType,
                                         Object action, Object token, long delayMillis) {
    
	...
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        //将传入的action这个Runnable封装后加入mCallbackQueues回调队列
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        if (dueTime <= now) {
            //请求vsync信号,请求到vsync垂直同步信号后,会向主线程的消息队列发送一个异步消息,让主线程更新UI
            scheduleFrameLocked(now);
        }
        ...
    }
}

//这是Choreographer中的回调队列
private final CallbackQueue[] mCallbackQueues;

Choreographer.scheduleFrameLocked(long now)

private void scheduleFrameLocked(long now) {
    if (!mFrameScheduled) {
        mFrameScheduled = true;		//这里也是保证下面代码在一个垂直同步周期内仅执行一次
        if (USE_VSYNC) {

            //这里其实就是判断是否在主线程中
            if (isRunningOnLooperThreadLocked()) {
                scheduleVsyncLocked();
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtFrontOfQueue(msg);
            }
        }
        ...
    }
}


private void scheduleVsyncLocked() {
    mDisplayEventReceiver.scheduleVsync();	//其中通过一个native方法请求vsync信号
}

上面mDisplayEventReceiver是一个FrameDisplayEventReceiver对象,它的声明如下,可以看到,这是一个Runnable对象,当请求到vsync信号后,就会回调下面的onVsync方法

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

        @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);	//将这个Runnable封装到异步Message中,并发送到主线程的消息队列中
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

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

上面run方法中的doFrame方法,异步消息在主线程的消息队列中被优先取出后会被执行。

void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        if (!mFrameScheduled) {
            return; // no work to do
        }

        long intendedFrameTimeNanos = frameTimeNanos;	//下一帧的预期时间
        startNanos = System.nanoTime();		//系统当前时间
        final long jitterNanos = startNanos - frameTimeNanos;	//抖动时间,也就是vsync信号到来的时刻到即将进行UI重绘的时间
        //如果这个抖动时间 >= 16ms就会导致丢帧
        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;
        }

        //出现了某种错误,会重新请求VSync信号
        if (frameTimeNanos < mLastFrameTimeNanos) {
            if (DEBUG_JANK) {
                Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                      + "previously skipped frame.  Waiting for next vsync.");
            }
            scheduleVsyncLocked();
            return;
        }

        if (mFPSDivisor > 1) {
            long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
            if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                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);
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);

        mFrameInfo.markPerformTraversalsStart();
        //处理UI重绘的回调,会对ViewRootImpl.scheduleTraversals()方法中加入的mTraversalRunnable进行回调
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    } finally {
        AnimationUtils.unlockAnimationClock();
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

TraversalRunnable

它是ViewRootImpl中的内部类,这就是上面ViewRootImpl.scheduleTraversals()方法中在回调队列中加入的mTraversalRunnable,直到上面的doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos)才会执行。

//上面队列中加入的mTraversalRunnable对象的声明如下:
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        //先将消息队列的同步屏障移除
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();	//这就是ViewRootImpl的绘制方法,它会通知所有子view进行measure、layout和draw等方法进行重绘

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

消息队列的同步屏障

作用

其实就是提高异步消息的优先级,让消息队列优先取出异步消息交给相应线程的Handler执行。

实现原理

同步屏障的实现首先要向消息队列中加入一个没有设置target的Message对象,也就是说这个Message持有的Handler对象为null,在下面的代码中可以看到:

//MessageQueue.postSyncBarrier()
public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        //下面会按照when,也就是消息的时序,将这个Message对象插入链表相应位置
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

然后我们看看**MessageQueue.next()**这个方法

如果Message对象在发送到消息队列前调用了 Message.setAsynchronous(true) 这个方法,就表明这个消息是一个异步消息,下面MessageQueue的next方法就会优先取出队列中的异步消息。

Message next() {
    ...
    int pendingIdleHandlerCount = -1;
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {	//如果消息头结点的target为null,即上面设置的同步屏障
            /*
                前方高能!!!下面就是内存屏障真正起作用的地方了。
                当发现这个内存屏障后,下面的循环就会遍历Message链表,忽略所有的同步消息,直到找到第一个异步消息。后面会将这个异步消息出队并返回。
            */
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }
            ...
        }
        ...
    }
}

同步屏障的应用

就是上面屏幕刷新时会开启主线程消息队列的同步屏障,让它优先取出异步的屏幕刷新消息并在主线程执行。这样做的主要目的应该就是提高UI流畅度。

参考资料

Android VSYNC与图形系统中的撕裂、双缓冲、三缓冲浅析

本文地址:https://blog.csdn.net/qq_43935080/article/details/107288309

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
Copyright © 2017-2020  萬仟网 保留所有权利. 粤ICP备17035492号-1
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com