掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流

屏幕刷新幀率不穩(wěn)定,掉幀嚴(yán)重,無法保證每秒60幀,導(dǎo)致屏幕畫面撕裂;
今天我們來講解下Vsync機制和UI刷新流程
VSync機制:Android系統(tǒng)每隔16ms發(fā)出VSYNC信號,觸發(fā)對UI進(jìn)行渲染,VSync是Vertical Synchronization(垂直同步)的縮寫,是一種在PC上很早就廣泛使用的技術(shù),可以簡單的把它認(rèn)為是一種定時中斷。而在Android 4.1(JB)中已經(jīng)開始引入VSync機制;
VSync機制下的繪制過程;CPU/GPU接收vsync信號,Vsync每16ms一次,那么在每次發(fā)出Vsync命令時,CPU都會進(jìn)行刷新的操作。也就是在每個16ms的第一時間,CPU就會響應(yīng)Vsync的命令,來進(jìn)行數(shù)據(jù)刷新的動作。CPU和GPU的刷新時間,和Display的FPS是一致的。因為只有到發(fā)出Vsync命令的時候,CPU和GPU才會進(jìn)行刷新或顯示的動作。CPU/GPU接收vsync信號提前準(zhǔn)備下一幀要顯示的內(nèi)容,所以能夠及時準(zhǔn)備好每一幀的數(shù)據(jù),保證畫面的流暢;
可見vsync信號沒有提醒CPU/GPU工作的情況下,在第一個16ms之內(nèi),一切正常。然而在第二個16ms之內(nèi),幾乎是在時間段的最后CPU才計算出了數(shù)據(jù),交給了Graphics Driver,導(dǎo)致GPU也是在第二段的末尾時間才進(jìn)行了繪制,整個動作延后到了第三段內(nèi)。從而影響了下一個畫面的繪制。這時會出現(xiàn)Jank(閃爍,可以理解為卡頓或者停頓)。這時候CPU和GPU可能被其他操作占用了,這就是卡頓出現(xiàn)的原因;
當(dāng)我們通過setText改變TextView內(nèi)容后,UI界面不會立刻改變,APP端會先向VSYNC服務(wù)請求,等到下一次VSYNC信號觸發(fā)后,APP端的UI才真的開始刷新,基本流程如下:
setText最終調(diào)用invalidate申請重繪,最后會通過ViewParent遞歸到ViewRootImpl的invalidate,請求VSYNC,在請求VSYNC的時候,會添加一個同步柵欄,防止UI線程中同步消息執(zhí)行,這樣做為了加快VSYNC的響應(yīng)速度,如果不設(shè)置,VSYNC到來的時候,正在執(zhí)行一個同步消息;
View會遞歸的調(diào)用父容器的invalidateChild,逐級回溯,最終走到ViewRootImpl的invalidate
- View.java
- void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
- boolean fullInvalidate) {
- // 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);
- }
- ViewRootImpl.java
- void invalidate() {
- mDirty.set(0, 0, mWidth, mHeight);
- if (!mWillDrawSoon) {
- scheduleTraversals();
- }
- }
ViewRootImpl會調(diào)用scheduleTraversals準(zhǔn)備重繪,但是,重繪一般不會立即執(zhí)行,而是往Choreographer的Choreographer.CALLBACK_TRAVERSAL隊列中添加了一個mTraversalRunnable,同時申請VSYNC,這個mTraversalRunnable要一直等到申請的VSYNC到來后才會被執(zhí)行;
- ViewRootImpl.java
- // 將UI繪制的mTraversalRunnable加入到下次垂直同步信號到來的等待callback中去
- // mTraversalScheduled用來保證本次Traversals未執(zhí)行前,不會要求遍歷兩邊,浪費16ms內(nèi),不需要繪制兩次
- void scheduleTraversals() {
- if (!mTraversalScheduled) {
- mTraversalScheduled = true;
- // 防止同步柵欄,同步柵欄的意思就是攔截同步消息
- mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
- // postCallback的時候,順便請求vnsc垂直同步信號scheduleVsyncLocked
- mChoreographer.postCallback(
- Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
- if (!mUnbufferedInputDispatch) {
- scheduleConsumeBatchedInput();
- }
- notifyRendererOfFramePending();
- pokeDrawLockIfNeeded();
- }
- }
- Choreographer.java
- private void postCallbackDelayedInternal(int callbackType,
- Object action, Object token, long delayMillis) {
- synchronized (mLock) {
- final long now = SystemClock.uptimeMillis();
- final long dueTime = now + delayMillis;
- mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
- if (dueTime <= now) {
- scheduleFrameLocked(now);
- }
- }
- }
- // mFrameScheduled保證16ms內(nèi),只會申請一次垂直同步信號
- // scheduleFrameLocked可以被調(diào)用多次,但是mFrameScheduled保證下一個vsync到來之前,不會有新的請求發(fā)出
- // 多余的scheduleFrameLocked調(diào)用被無效化
- private void scheduleFrameLocked(long now) {
- if (!mFrameScheduled) {
- mFrameScheduled = true;
- if (USE_VSYNC) {
- if (isRunningOnLooperThreadLocked()) {
- scheduleVsyncLocked();
- } else {
- // 因為invalid已經(jīng)有了同步柵欄,所以必須mFrameScheduled,消息才能被UI線程執(zhí)行
- Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
- msg.setAsynchronous(true);
- mHandler.sendMessageAtFrontOfQueue(msg);
- }
- }
- }
- }
- private final class FrameDisplayEventReceiver extends DisplayEventReceiver
- implements Runnable {
- private boolean mHavePendingVsync;
- private long mTimestampNanos;
- private int mFrame;
- public FrameDisplayEventReceiver(Looper looper) {
- super(looper);
- }
- @Override
- public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
- long now = System.nanoTime();
- if (timestampNanos > now) {
- 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);
- }
- }
- 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;
- 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;
- }
- if (frameTimeNanos < mLastFrameTimeNanos) {
- scheduleVsyncLocked();
- return;
- }
- mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
- mFrameScheduled = false;
- mLastFrameTimeNanos = frameTimeNanos;
- }
- try {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
- mFrameInfo.markInputHandlingStart();
- doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
- mFrameInfo.markAnimationsStart();
- doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
- mFrameInfo.markPerformTraversalsStart();
- doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
- } else {
- mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
- mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- }
- return renderNode;
- }
關(guān)于繪制還有很多知識點,后面會總結(jié)陸續(xù)發(fā)出來的;

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流