[TOC]
# 顯示原理
關于畫面撕裂與垂直同步,可以參考這篇博文,講解的很清晰[《什么是畫面撕裂?垂直同步,G-sync,Freesync到底有啥用?》](https://zhuanlan.zhihu.com/p/41848908),本文部分摘錄于此文。
## 顯示基礎
在一個典型的顯示系統中,一般包括CPU、GPU、Display三個部分, CPU負責計算數據,把計算好數據交給GPU,GPU對圖形數據進行渲染,渲染完成后放到幀緩沖區中存儲,最后Display負責將幀緩沖區中的數據呈現到屏幕上。
Android手機作為微型計算機,同樣由三部分組成。應用層繪制View有三個主要的步驟,分別是measure、layout和draw,具體分析可參考[http://wiki.xuchongyang.com/1262569](http://wiki.xuchongyang.com/1262569)。measure、layout和draw方法主要是運行在系統的應用框架層,而真正將數據渲染到屏幕上的則是系統Native層的SurfaceFlinger服務來完成的,也就是GPU做的。具體流程如下:
Android中LayoutInflater將布局中的xml標簽轉換為對象,CPU經過計算將它轉化為多邊形(Polygons)或Texture(紋理)。GPU對多邊形或者紋理進行柵格化(Rasterization)操作,柵格化后的數據寫入幀緩沖區中等待顯示器顯示。CPU和GPU是通過圖形驅動層來進行連接的,圖形驅動層維護了一個隊列,CPU將display list添加到該隊列中,這樣GPU就可以從這個隊列中取出數據進行繪制。如圖:

### 幀

手機屏幕及顯示器也是如此,看上去是“動畫”,實際是靠連續播放無數張靜態畫面來達到一個視覺上的錯覺,通過人眼的視覺暫留,讓眼睛誤以為這是動畫。
### 幀速率
GPU是負責圖形渲染的,也就是繪制“幀”。
FPS(Frames Per Second)幀速率代表GPU在一秒內繪制的幀數。GPU繪制好幀后交由屏幕來進行展示,理想狀態下屏幕每秒展示60幀時人眼感受不到卡頓,動畫會比較流暢。1000ms/60幀,即每幀繪制時間不應超過16.67ms。因此要想畫面保持在60fps,則需要每個幀繪制時長在16ms以內。
### 屏幕刷新率
屏幕僅僅負責展示幀數據,屏幕都有一個自己的刷新頻率,代表屏幕在一秒內刷新的次數。60Hz的屏幕一秒鐘就刷新60張畫面,120Hz一秒鐘就刷新120張畫面,其中每一張畫面我們都稱之為一幀。目前的手機屏幕一般是60Hz,即每16.66ms刷新一幀,當然一些最新推出的手機有些開始采用高刷新率,比如一加7 pro的90Hz。
### 逐行掃描
屏幕在刷新一幀的時候并不是一次性把整個畫面刷新出來的,而是從上到下一行一行逐漸把圖片繪制出來的,如下圖。當然除了逐行掃描還有隔行掃描,也就是隔一行畫一行,目前大多數顯示器和手機屏幕都是采用逐行掃描。

## 緩沖機制
### 雙緩沖機制
屏幕的刷新率是固定的,60Hz的屏幕每隔16.66ms逐行掃描顯示一幀,但GPU繪制幀的時間是波動的,因為需要先由CPU進行measure、layout、draw操作,再將布局數據交給GPU進行渲染,當布局嵌套太多太過復雜時,CPU耗時變長,GPU繪制耗時變長,繪制一幀的時間相應也就變長。
GPU輸出多個幀時,時間間隔也不一樣,如第一幀與第二幀間隔0.01秒,第二幀與第三幀間隔0.04秒,第三幀與第四幀間隔0.03秒,GPU輸出幀并不像屏幕固定刷新率,而是會受到視圖復雜程度的波動影響。前半秒1幀,后半秒59幀,幀數也是60,但前半秒的幀耗時就很長。
為了避免屏幕刷新速率和GPU繪制幀速率不同造成沖突,引起畫面卡頓、閃爍、撕裂等,不能讓GPU直接提供幀數據給屏幕進行繪圖,而是增加緩沖機制。
只設置一個緩沖區時,由于需要等屏幕讀取完緩沖區中的幀數據,GPU才能再次往緩沖區中寫入數據,效率較低。因此一般提供2個緩沖區,分別為前緩沖和后緩沖(整個過程中兩者身份會互相交換),這就是雙緩沖機制。

### 可能出現的問題
雖然有了雙緩沖機制,但由于GPU繪制幀的時間受布局層級深度和視圖復雜度影響較大,因此GPU繪制幀速率不可控,就會出現幀速率大于刷新率和幀速率小于刷新率兩種情況:
1、幀速率大于刷新率:畫面撕裂與錯幀
當然雙緩沖機制也會存在一個問題,因為GPU輸出幀時間是不固定的,GPU就會出現搶跑的情況:

也就是GPU已經畫完了1和2,但是屏幕還在逐行掃描1,此時GPU開始畫3,就會把1的數據覆蓋掉,屏幕繼續逐行掃描出來的就是3的下半部分了,就會出現畫面撕裂現象。具體如圖所示:

在此例中,GPU輸出三幀分別是1、2、3。屏幕進行逐行掃描時,第一幀中 1在畫面中僅顯示一半,接著顯示了3的一半;第二幀顯示了完整的2;第三幀顯示了完整的3。因此雖然屏幕顯示了3幀,但肉眼只看到2.5幀。因此當幀速率大于刷新率時,易發生錯幀現象,也就是GPU繪制的幀數據并不會全都被顯示出來。
2、幀速率小于刷新率:卡頓
如果屏幕刷新率比幀速率快,也就是屏幕已經繪制完緩沖區的數據,但是GPU還未繪制完成下一幀,此時屏幕會在兩幀中顯示同一個畫面,當這種斷斷續續的情況發生時,用戶將會很明顯地察覺到動畫卡住了或者掉幀,然后又恢復了流暢。我們稱此現象為閃屏, 跳幀,延遲,也就是視覺上的卡頓。
3、總結
幀速率比屏幕刷新率高的時候,雖然有可能會丟失部分幀,但用戶看到的是非常流暢的畫面;而幀速率降下來(GPU繪制太多東西)時,屏幕刷新率比幀速率高,用戶看到的就是畫面卡頓。
## 垂直同步
垂直同步的出現是為了解決幀速率大于刷新率這種情況。
垂直同步會強制GPU幀速率和屏幕刷新率同步,如果屏幕還在顯示前緩沖,而GPU已經寫完了后緩沖將要寫入前緩沖,此時禁止GPU進行繪制。直到屏幕將前緩沖的畫面顯示完整,開始顯示后緩沖時,才允許GPU去寫入前緩沖。

在Android中,系統層會每隔16.66ms發出一個VSYNC信號,GPU會等待VSYNC信號發出,開始繪制幀數據,繪制完一幀后等待下一個VSYNC信號到來時繼續繪制。這樣GPU繪制幀速率可以和屏幕刷新率保持一致,達到手機的刷新率上限60fps,當然這只是理想情況,當幀速率達到60fps時,GPU需要在16.66ms內準備好一幀,這對于應用的性能要求很高。
# 繪制優化
了解了繪制的原理后,我們就知道了應該在哪里下手要優化繪制。GPU繪制幀的過程我們是無法控制的,但CPU的計算工作我們是可以控制的,我們可以盡量減少measure、layout、draw的時間,留足夠多的時間給GPU進行繪制幀。因此繪制優化最直接的操作就是在View的onDraw方法中要避免執行大量操作,具體如下:
1、onDraw中不要創建新的局部對象,因為onDraw方法會被頻繁調用,會在瞬間產生大量臨時對象,會占用過多內存而且導致頻繁gc,降低效率
2、onDraw方法中不要執行耗時任務,也不要執行循環成千上萬次的循環操作,大量循環會十分搶占CPU的時間片,造成View繪制過程不流暢。
# 分析GPU渲染速度
GPU 渲染模式分析工具以滾動直方圖的形式直觀地顯示渲染界面窗口幀所花費的時間(以每幀 16 毫秒的速度作為對比基準)。

注意的幾點:
* 沿水平軸的每個豎條代表一個幀,每個豎條的高度表示渲染該幀所花的時間(以毫秒為單位)
* 水平綠線表示 16 毫秒。要實現每秒 60 幀,代表每個幀的豎條需要保持在此線以下。當豎條超出此線時,可能會使動畫出現暫停
* 該工具通過加寬對應的豎條并降低透明度來突出顯示超出 16 毫秒閾值的幀
* 每個豎條都有與渲染管道中某個階段對應的彩色區段。區段數因設備的 API 級別不同而異
*****
Android 6.0 及更高版本的設備時分析器輸出中某個豎條的每個區段:

Android 4.0 和 5.0 中的豎條區段:

# GPU過度繪制
## 定義
過度繪制(Overdraw)描述的是屏幕上的某個像素在同一幀的時間內被繪制了多次。在多層次重疊的 UI 結構里面,如果不可見的 UI 也在做繪制的操作,會導致某些像素區域被繪制了多次,同時也會浪費大量的 CPU 以及 GPU 資源,繪制示意圖如下圖所示

當應用在同一幀中多次繪制相同像素時,便會發生過度繪制。使用開發者選項中的“調試GPU過度繪制”可以可視化的查看過度繪制情況,Android 將按如下方式為界面元素著色,以確定過度繪制的次數:

我們在開發應用過程中,應設法達到大部分顯示真彩色或只有 1 次過度繪制(藍色)的視覺效果,來提高應用性能。
## 解決方案
1、去掉Window的默認背景,在onCreate方法的setContentView()之后調用`getWindow().setBackgroundDrawable(null);`或者在theme中添加`android:windowbackground="null";`
2、去掉視圖中不必要的背景
3、使用ViewStub僅在需要時才加載
4、使用\<merge>標簽合并布局,來減少層級
5、使用\<include>標簽重用布局,使布局更清晰明了
6、采用性能較好的ViewGroup如Constraintlayout來精簡層級
# 源碼角度分析
以下源碼分析部分轉載于:
[面試官又來了:你的app卡頓過嗎?](https://juejin.im/post/5d837cd1e51d4561cb5ddf66)
從View的requestLayout方法開始分析源碼,當視圖內容有變更時可以調用此方法進行UI更新。
```java
protected ViewParent mParent;
//...
public void requestLayout() {
//...
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
}
```
會調用mParent#requestLayout方法,因為根View是DecorView,DecorView的parent是ViewRootImpl,所以來看看ViewRootImpl#requestLayout方法:
```java
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//1 檢測線程
checkThread();
mLayoutRequested = true;
//2
scheduleTraversals();
}
}
```
注釋1 檢測當前是否為主線程:
```java
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
```
注釋2:
```java
void scheduleTraversals() {
//1、注意這個標志位,多次調用 requestLayout,要這個標志位false才有效
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 2. 同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 3. 向 Choreographer 提交一個任務
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
//繪制前發一個通知
notifyRendererOfFramePending();
//這個是釋放鎖,先不管
pokeDrawLockIfNeeded();
}
}
```
注釋1:防止短時間多次調用 requestLayout 重復繪制多次,假如調用requestLayout 之后還沒有到這一幀繪制完成,再次調用是沒什么意義的。
注釋2: 涉及到Handler的一個知識點,同步屏障:往消息隊列插入一個同步屏障消息,這時候消息隊列中的同步消息不會被處理,而是優先處理異步消息。
注釋3:mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);,往Choreographer 提交一個任務 mTraversalRunnable,這個任務不會馬上就執行
下面我們來看看Choreographer#postCallback方法:
```java
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)?{
if?(action?==?null)?{
throw new?IllegalArgumentException("action?must?not?be?null");
}
if?(callbackType??CALLBACK\_LAST)?{
thrownew?IllegalArgumentException("callbackType?is?invalid");
}
postCallbackDelayedInternal(callbackType,?action,?token,?delayMillis);
}
private?void?postCallbackDelayedInternal(int?callbackType, Object?action,?Object?token,?long?delayMillis)?{
synchronized?(mLock)?{
finallong?now?=?SystemClock.uptimeMillis();
finallong?dueTime?=?now?+?delayMillis;
//1.將任務添加到隊列
mCallbackQueues[callbackType].addCallbackLocked(dueTime,?action,?token);
//2.?正常延時是0,走這里
if?(dueTime?<=?now)?{
scheduleFrameLocked(now);
}?else?{
//3. 什么時候會有延時,繪制超時,等下一個vsync?
????Message?msg?=?mHandler.obtainMessage(MSG\_DO\_SCHEDULE\_CALLBACK,?action);
????msg.arg1?=?callbackType;
????msg.setAsynchronous(true);
????mHandler.sendMessageAtTime(msg,?dueTime);
}
}
}
```
注釋1:將任務添加到隊列,不會馬上執行
注釋2:scheduleFrameLocked,正常的情況下delayMillis是0,走這里
注釋3:什么情況下會有延時,TextView中有調用到,暫時不管
下面來看看注釋2 的Choreographer#scheduleFrameLocked方法:
```java
// Enable/disable vsync for animations and drawing. 系統屬性參數,默認true
private static final boolean USE_VSYNC = SystemProperties.getBoolean(
"debug.choreographer.vsync", true);
...
private void scheduleFrameLocked(long now) {
//標志位,避免不必要的多次調用
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
//1 如果當前線程是UI線程,直接執行scheduleFrameLocked,否則通過Handler處理
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
```
注釋1: 判斷當前線程如果是UI線程,直接執行scheduleVsyncLocked方法,否則,通過Handler發一個異步消息到消息隊列,最終也是到主線程處理,所以直接看scheduleVsyncLocked方法。
那么來看看Choreographer#scheduleVsyncLocked方法:
```java
private final FrameDisplayEventReceiver mDisplayEventReceiver;
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
```
```java
/**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
*/
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 {
nativeScheduleVsync(mReceiverPtr); //1、請求vsync
}
}
// Called from native code. //2、vsync來的時候底層會通過JNI回調這個方法
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
```
這里的邏輯就是:通過JNI,跟底層說,下一個vsync脈沖信號來的時候請通知我。然后在下一個vsync信號來的時候,就會收到底層的JNI回調,也就是dispatchVsync這個方法會被調用,然后會調用onVsync這個空方法,由實現類去自己做一些處理。
```java
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
}
```
這里是屏幕刷新機制的重點,**應用必須向底層請求vsync信號,然后下一次vsync信號來的時候會通過JNI通知到應用,然后接下來才到應用繪制邏輯。**
那么來看看實現類在onVsync方法中做了什么呢?DisplayEventReceiver的實現類是 Choreographer 的內部類 FrameDisplayEventReceiver,來看看該類的onVsync方法:
```java
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) {
//...
} else {
mHavePendingVsync = true;
}
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this); //1 callback是this,會回調run方法
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame); //2
}
}
```
通過Handler,往消息隊列插入一個異步消息,指定執行的時間,然后看注釋1,callback傳this,所以最終會回調run方法,run里面調用doFrame(mTimestampNanos, mFrame);
重點來了,如果Handler此時存在耗時操作,那么需要等耗時操作執行完,Looper才會輪循到下一條消息,run方法才會調用,然后才會調用到doFrame(mTimestampNanos, mFrame);,doFrame干了什么?調用慢了會怎么樣?
來看看doFrame方法:
```java
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
...
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
// 1 當前時間戳減去vsync來的時間,也就是主線程的耗時時間
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
//1幀是16毫秒,計算當前跳過了多少幀,比如超時162毫秒,那么就是跳過了10幀
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
// SKIPPED_FRAME_WARNING_LIMIT 默認是30,超時了30幀以上,那么就log提示
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
// 取余,計算離上一幀多久了,一幀是16毫秒,所以lastFrameOffset 在0-15毫秒之間,這里單位是納秒
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the 8frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
// 出現掉幀,把時間修正一下,對比的是上一幀時間
frameTimeNanos = startNanos - lastFrameOffset;
}
//2、時間倒退了,可能是由于改了系統時間,此時就重新申請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.");
}
//這里申請下一次vsync信號,流程跟上面分析一樣了。
scheduleVsyncLocked();
return;
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
//3 能繪制的話,就走到下面
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);
}
}
```
分析:
1. 計算收到vsync信號到doFrame被調用的時間差,vsync信號間隔是16毫秒一次,大于16毫秒就是掉幀了,如果超過30幀(默認30),就打印log提示開發者檢查主線程是否有耗時操作。
2. 如果時間發生倒退,可能是修改了系統時間,就不繪制,而是重新注冊下一次vsync信號
3. 正常情況下會走到 doCallbacks 里去,callbackType 按順序是Choreographer.CALLBACK\_INPUT、Choreographer.CALLBACK\_ANIMATION、Choreographer.CALLBACK\_TRAVERSAL、Choreographer.CALLBACK\_COMMIT
來看 doCallbacks 里的邏輯:
```java
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
//1. 從隊列取出任務,任務什么時候添加到隊列的,上面有說過哈
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
...
//2.更新這一幀的時間,確保提交這一幀的時間總是在最后一幀之后
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
if (jitterNanos >= 2 * mFrameIntervalNanos) {
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos + mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
+ " ms which is more than twice the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Setting frame time to " + (lastFrameOffset * 0.000001f)
+ " ms in the past.");
mDebugPrintNextFrameTimeDelta = true;
}
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
}
}
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
// 3. 執行任務,
c.run(frameTimeNanos);
}
} ...
}
```
這里主要就是取出對應類型的任務,然后執行任務。
注釋2:if (callbackType == Choreographer.CALLBACK\_COMMIT)是流程的最后一步,數據已經繪制完準備提交的時候,會更正一下時間戳,確保提交時間總是在最后一次vsync時間之后。這里文字可能不太好理解,引用一張圖:

注釋3,還沒到最后一步的時候,取出其它任務出來run,這個任務肯定就是跟View的繪制相關了,記得開始requestLayout傳過來的類型嗎,Choreographer.CALLBACK\_TRAVERSAL,從隊列get出來的任務類對應是mTraversalRunnable,類型是TraversalRunnable,定義在ViewRootImpl里面,饒了一圈,回到ViewRootImpl繼續看~
來看看剛剛 scheduleTraversals 方法中的mTraversalRunnable被調用時做了什么:
```java
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
```
```java
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals();
}
}
```
先移除同步屏障消息,然后調用performTraversals 方法,performTraversals 這個方法代碼有點多,挑重點看:
```java
private void performTraversals() {
// mAttachInfo 賦值給View
host.dispatchAttachedToWindow(mAttachInfo, 0);
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
...
//1 測量
if (!mStopped || mReportNextDraw) {
// Ask host how big it wants to be
//1.1測量一次
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
//1.2、如果有設置權重,比如LinearLayout設置了weight,需要測量兩次
if (measureAgain) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
...
//2.布局
if (didLayout) {
// 會回調View的layout方法,然后會調用View的onLayout方法
performLayout(lp, mWidth, mHeight);
}
...
//3.畫
if (!cancelDraw && !newSurface) {
performDraw();
}
}
```
可以看到,View的三個方法回調measure、layout、draw是在performTraversals 里面,需要注意的點是LinearLayout設置權重的情況下會measure兩次。
## 源碼分析總結
View 的 requestLayout 會調到ViewRootImpl 的 requestLayout方法,然后通過 scheduleTraversals 方法向Choreographer 提交一個繪制任務,然后再通過DisplayEventReceiver向底層請求vsync信號,當vsync信號來的時候,會通過JNI回調回來,通過Handler往主線程消息隊列post一個異步任務,最終是ViewRootImpl去執行那個繪制任務,調用performTraversals方法,里面是View的三個方法的回調。

在我們應用中會造成掉幀有兩種情況:
* 一個是主線程有其它耗時操作,導致doFrame沒有機會在vsync信號發出之后16毫秒內調用
* 還有一個就是當前doFrame方法耗時,繪制太久,下一個vsync信號來的時候這一幀還沒畫完,造成掉幀
以上源碼分析部分轉載于:
[面試官又來了:你的app卡頓過嗎?](https://juejin.im/post/5d837cd1e51d4561cb5ddf66)
# 參考
[Android繪制優化(一)繪制性能分析](https://blog.csdn.net/itachi85/article/details/61914979)
[番外——深入垂直同步機制(VSYNC)](https://www.jianshu.com/p/5f93b8ed3bbb)
[什么是畫面撕裂?垂直同步,G-sync,Freesync到底有啥用?](https://zhuanlan.zhihu.com/p/41848908)
- 導讀
- Java知識
- Java基本程序設計結構
- 【基礎知識】Java基礎
- 【源碼分析】Okio
- 【源碼分析】深入理解i++和++i
- 【專題分析】JVM與GC
- 【面試清單】Java基本程序設計結構
- 對象與類
- 【基礎知識】對象與類
- 【專題分析】Java類加載過程
- 【面試清單】對象與類
- 泛型
- 【基礎知識】泛型
- 【面試清單】泛型
- 集合
- 【基礎知識】集合
- 【源碼分析】SparseArray
- 【面試清單】集合
- 多線程
- 【基礎知識】多線程
- 【源碼分析】ThreadPoolExecutor源碼分析
- 【專題分析】volatile關鍵字
- 【面試清單】多線程
- Java新特性
- 【專題分析】Lambda表達式
- 【專題分析】注解
- 【面試清單】Java新特性
- Effective Java筆記
- Android知識
- Activity
- 【基礎知識】Activity
- 【專題分析】運行時權限
- 【專題分析】使用Intent打開三方應用
- 【源碼分析】Activity的工作過程
- 【面試清單】Activity
- 架構組件
- 【專題分析】MVC、MVP與MVVM
- 【專題分析】數據綁定
- 【面試清單】架構組件
- 界面
- 【專題分析】自定義View
- 【專題分析】ImageView的ScaleType屬性
- 【專題分析】ConstraintLayout 使用
- 【專題分析】搞懂點九圖
- 【專題分析】Adapter
- 【源碼分析】LayoutInflater
- 【源碼分析】ViewStub
- 【源碼分析】View三大流程
- 【源碼分析】觸摸事件分發機制
- 【源碼分析】按鍵事件分發機制
- 【源碼分析】Android窗口機制
- 【面試清單】界面
- 動畫和過渡
- 【基礎知識】動畫和過渡
- 【面試清單】動畫和過渡
- 圖片和圖形
- 【專題分析】圖片加載
- 【面試清單】圖片和圖形
- 后臺任務
- 應用數據和文件
- 基于網絡的內容
- 多線程與多進程
- 【基礎知識】多線程與多進程
- 【源碼分析】Handler
- 【源碼分析】AsyncTask
- 【專題分析】Service
- 【源碼分析】Parcelable
- 【專題分析】Binder
- 【源碼分析】Messenger
- 【面試清單】多線程與多進程
- 應用優化
- 【專題分析】布局優化
- 【專題分析】繪制優化
- 【專題分析】內存優化
- 【專題分析】啟動優化
- 【專題分析】電池優化
- 【專題分析】包大小優化
- 【面試清單】應用優化
- Android新特性
- 【專題分析】狀態欄、ActionBar和導航欄
- 【專題分析】應用圖標、通知欄適配
- 【專題分析】Android新版本重要變更
- 【專題分析】唯一標識符的最佳做法
- 開源庫源碼分析
- 【源碼分析】BaseRecyclerViewAdapterHelper
- 【源碼分析】ButterKnife
- 【源碼分析】Dagger2
- 【源碼分析】EventBus3(一)
- 【源碼分析】EventBus3(二)
- 【源碼分析】Glide
- 【源碼分析】OkHttp
- 【源碼分析】Retrofit
- 其他知識
- Flutter
- 原生開發與跨平臺開發
- 整體歸納
- 狀態及狀態管理
- 零碎知識點
- 添加Flutter到現有應用
- Git知識
- Git命令
- .gitignore文件
- 設計模式
- 創建型模式
- 結構型模式
- 行為型模式
- RxJava
- 基礎
- Linux知識
- 環境變量
- Linux命令
- ADB命令
- 算法
- 常見數據結構及實現
- 數組
- 排序算法
- 鏈表
- 二叉樹
- 棧和隊列
- 算法時間復雜度
- 常見算法思想
- 其他技術
- 正則表達式
- 編碼格式
- HTTP與HTTPS
- 【面試清單】其他知識
- 開發歸納
- Android零碎問題
- 其他零碎問題
- 開發思路