1. 程式人生 > >android系統重新整理機制

android系統重新整理機制

整理一下android系統的重新整理機制

一般重新整理包括View.postInvalidate到最後都是走到View.invalidate(),然後一直向上回到ViewRootImpl根據需要進行performMeasure,performLayout,performDraw,重新整理完成。

->invalidate
->invalidateInternal
->invalidateChild
->invalidateChildInParent
->ViewRootImpl.invalidateChildInParent(int[],Rect);
->ViewRootImpl.invalidateRectOnScreen(Rect);
->ViewRootImpl.scheduleTraversals();
->ViewRoot.doTraversals();  
->performTraversals();
    ->performMeasure()
    ->measure()
    ->onMeasure()
    ->performLayout()
    ->layout
    ->onLayout()
    ->performDraw()
    ->draw

android底層系統每16.6ms發出一個VSync訊號來切換一幀畫面,我們的app想要接受到這個訊號,就要註冊。那麼如何註冊呢?就是呼叫invalidate註冊,具體底層如何實現,原文作者並沒有說明,不過大體思路就是這樣。
註冊之後,只有接受到下一個重新整理的訊號,才開始我們的熟悉的重新整理工作。而不是一呼叫invalidate就開始重新整理。而且這期間重新invalidate沒有意義,相當於只調用了invalidate一次。
另外,CPU只負責計算,GPU負責渲染,底層決定什麼時候顯示。也就是接收到第一個重新整理訊號,開始計算,渲染,然後就取消了註冊,就沒CPU,GPU什麼事情了,CPU與GPU這一次的重新整理任務已經完成。
底層每16.6ms切換一幀畫面,如果有新的渲染好的畫面,就顯示,沒有的話就還顯示原來的(這裡有一個疑問,就是如果畫面沒有發生變化,那麼螢幕是直接沒有切換幀畫面還是切成了跟原來一樣的幀畫面),然後發出下一個訊號(這裡的發出訊號與切換幀畫面的先後順序並不確定,大體邏輯是這樣),app如果又需要invalidate,那麼接著計算渲染,一直迴圈。

這樣的話invalidate走到ViewRootImpl.scheduleTraversals和performTraversals之間就是這樣,這裡直接複製原文的內容

    scheduleTraversals() 會先過濾掉同一幀內的重複呼叫,在同一幀內只需要
    安排一次遍歷繪製 View 樹的任務即可,這個任務會在下一個螢幕重新整理訊號到來
    時呼叫 performTraversals() 遍歷 View 樹,
    遍歷過程中會將所有需要重新整理的 View 進行重繪;
    接著 scheduleTraversals() 會往主執行緒的訊息佇列中傳送一個同步屏障,攔
    截這個時刻之後所有的同步訊息的執行,但不會攔截非同步訊息,以此來儘可能的保
    證當接收到螢幕重新整理訊號時可以儘可能第一時間處理遍歷繪製 View 樹的工作;
    發完同步屏障後 scheduleTraversals() 才會開始安排一個遍歷繪製 View 
    樹的操作,作法是把 performTraversals() 封裝到 Runnable 裡面,然後
    呼叫 Choreographer 的 postCallback() 方法;
    postCallback() 方法會先將這個 Runnable 任務以當前時間戳放進一個待執
    行的佇列裡,然後如果當前是在主執行緒就會直接呼叫一個native 層方法,如果不
    是在主執行緒,會發一個最高優先順序的 message 到主執行緒,讓主執行緒第一時間調
    用這個 native 層的方法;
    native 層的這個方法是用來向底層註冊監聽下一個螢幕重新整理訊號,當下一個螢幕
    重新整理訊號發出時,底層就會回撥 Choreographer 的onVsync() 方法來通知上層 app;
    onVsync() 方法被回撥時,會往主執行緒的訊息佇列中傳送一個執行 doFrame() 
    方法的訊息,這個訊息是非同步訊息,所以不會被同步屏障攔截住;
    doFrame() 方法會去取出之前放進待執行佇列裡的任務來執行,取出來的這個任
    務實際上是 ViewRootImpl 的 doTraversal() 操作;
    上述第4步到第8步涉及到的訊息都手動設定成了非同步訊息,所以不會受到同步屏障的攔截

一些引發重新整理的操作:

invalidate(請求重繪)
requestLayout(重新佈局)
requestFocus(請求焦點)
startActivity(開啟新介面)
onRestart(重新開啟介面)
KeyEvent(遙控器事件,本質上是焦點導致的重新整理)
Animation(各種動畫,本質上是請求重繪導致的重新整理)
RecyclerView滑動(頁面滑動,本質上是動畫導致的重新整理)
setAdapter(各種adapter的更新)

另一篇部落格提到的重新整理

直接呼叫invalidate方法.請求重新draw,但只會繪製呼叫者本身。
觸發setSelection方法。請求重新draw,但只會繪製呼叫者本身。
觸發setVisibility方法。 當View可視狀態在INVISIBLE轉換VISIBLE時會間接呼叫invalidate方法,繼而繪製該View。當View的可視狀態在INVISIBLE\VISIBLE 轉換為GONE狀態時會間接呼叫requestLayout和invalidate方法,同時由於View樹大小發生了變化,所以會請求measure過程以及draw過程,同樣只繪製需要“重新繪製”的檢視。
觸發setEnabled方法。請求重新draw,但不會重新繪製任何View包括該呼叫者本身。

上面的作者還有一篇文章:
【Andorid原始碼解析】View.post() 到底幹了啥:
https://www.jianshu.com/p/85fc4decc947
這篇文章跟重新整理機制也有點關係,一起記錄下。這裡只貼下結論,用作自己記錄,具體請檢視原文。

View.post(Runnable) 內部會自動分兩種情況處理,當 View 還沒 attachedToWindow 時,會先將這些 Runnable 操作快取下來;否則就直接通過 mAttachInfo.mHandler 將這些 Runnable 操作 post 到主執行緒的 MessageQueue 中等待執行。

如果 View.post(Runnable) 的 Runnable 操作被快取下來了,那麼這些操作將會在 dispatchAttachedToWindow() 被回撥時,通過 mAttachInfo.mHandler.post() 傳送到主執行緒的 MessageQueue 中等待執行。

mAttachInfo 是 ViewRootImpl 的成員變數,在建構函式中初始化,Activity View 樹裡所有的子 View 中的 mAttachInfo 都是 ViewRootImpl.mAttachInfo 的引用。

mAttachInfo.mHandler 也是 ViewRootImpl 中的成員變數,在宣告時就初始化了,所以這個 mHandler 繫結的是主執行緒的 Looper,所以 View.post() 的操作都會發送到主執行緒中執行,那麼也就支援 UI 操作了。

dispatchAttachedToWindow() 被呼叫的時機是在 ViewRootImol 的 performTraversals() 中,該方法會進行 View 樹的測量、佈局、繪製三大流程的操作。

Handler 訊息機制通常情況下是一個 Message 執行完後才去取下一個 Message 來執行(非同步 Message 還沒接觸),所以 View.post(Runnable) 中的 Runnable 操作肯定會在 performMeaure() 之後才執行,所以此時可以獲取到 View 的寬高。

大致意思就是如果還沒有attachedToWindow,那麼就先快取,等到第一次ViewRootImpl呼叫performTraversals時會呼叫dispatchAttachedToWindow將之前的 runnable放進訊息佇列中去,又因為訊息佇列中的訊息是一條一條執行的,所以必須等當前的操作執行完畢,才會執行到剛剛放到訊息佇列中的。dispatchAttachedToWindow之後立馬就是performMeasure,performLayout,performDraw等到執行到剛剛的runnable時已經進行了測量工作,就可以取到寬高了。