Android View 的繪制流程 01 - 前置流程

Android View 的繪制流程 - 開篇 MeasureSpec
Android View 的繪制流程 01 - 前置流程
Android View 的繪制流程 02 - performMeasure
Android View 的繪制流程 03 - performLayout
Android View 的繪制流程 04 - performDraw
Android View 的繪制流程總結(jié)

之前文集中學(xué)習(xí)了幾個自定義的View, 那么一定還記得三個自定義View的重要流程.

  • measure, (測量, 測量每一個 View 及 ViewGroup 的尺寸 )
  • layout,???? (擺放, 根據(jù)測量的結(jié)果及參數(shù), 在布局上擺放每一個控件)
  • draw, ??????(繪制, 擺放好位置后, 就開始繪制, 然后顯示在屏幕上)

這個文集也主要是圍繞著三個流程來學(xué)習(xí). 正式開始.
?

1. 前置流程

View 的繪制流程最初就是在 ActivityThread.handleLaunchActivity() 中開始.


?
?

2.1 ActivityThread.handleLaunchActivity()

在 Activity 啟動的過程中, 會調(diào)用 ActivityThread.handleLaunchActivity() 方法.
handleLaunchActivity( ) 內(nèi)部調(diào)用以下方法.

  1. ActivityThread.performLaunchActivity() - 這個方法會調(diào)用 Activity 的 onCreate 方法
  2. ActivityThread.handleResumeActivity() - 這個方法會調(diào)用 Activity 的 onResume 方法.(起始)

這個方法雖是入口, 但是我們重點學(xué)習(xí)不在這里, 所以就不再貼代碼上來, 只是簡單說一下流程.


?
?

1.2 ActivityThread.handleResumeActivity()

ActivityThread 3599 行

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
     ...
    r = performResumeActivity(token, clearHide, reason);
        ...
        if (...) {
            //-------------1----------------
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            ...
            //------------2----------------
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            ...
            ...
            if (...) {
                if (...) {
                    ...
                    //-------------3--------------
                    wm.addView(decor, l);
                }
            }
              ...      
}

handleResumeActivity 方法中調(diào)用了 performResumeActivity, 我猜測里面可能會調(diào)用了 Activity 的 onResume 方法, 在這里不是重點, 不再表述.
重點是下面幾行.

第一部分
看到 View decor = r.window.getDecorView(); , 又見到了熟悉的 DecorView, 結(jié)合 Android 之 setContentView 流程 這個文集, 立刻能聯(lián)想到 DecorView 的一系列關(guān)系.

  • DecorView 在 PhoneWindow 中
  • PhoneWindow 是 Window 的唯一派生類
  • DecorView 是一個 FrameLayout.
  • DecorView 內(nèi)部有一個布局, 布局內(nèi)部有一個ID 為 android.id.content 的 ViewGroup.(mContentParent)
  • 執(zhí)行完 setContentView 后, DecorView 中 mContentParent 被改名為 NO_ID,
  • mContentParent 中包含了一個 SubDecorView ,
  • SubDecorView 中 有一個 ContentFrameLayout, 改名為 android.id.content.
  • 我們調(diào)用 setContentView ,傳入的資源文件, 就在 SubDecorView 的 ContentFrameLayout 控件中.
  • ...

怎么樣, 有沒有想起上面的那些?
所以說在 View 的 繪制流程中, 和這個 DecorView 有著非常密切的關(guān)系.
那么現(xiàn)在我們可以知道, PhoneWindow 賦值給了 r.window 屬性, DecorView 賦值給了 decor 變量.
?
?
第二部分
接著看 ViewManager wm = a.getWindowManager();
a 就是 Activity , Activity 中 getWindowManager() 返回的是 WindowManager 對象 mWindowManager,

public WindowManager getWindowManager() {
        return mWindowManager;
}
public interface WindowManager extends ViewManager {
}

可是 WindowManager 只是一個接口, ViewManager 也是一個接口, 那么肯定有實現(xiàn)類, 那么繼續(xù)在 Activity.java 中搜索, 看是實例化的哪個類.
在 Activity.java 6955 行, 看到 mWindowManager 是在這里被賦值的.
mWindowManager = mWindow.getWindowManager();.
接著跟進去, 看 mWindowManager 在 Window.java 769 行中被賦值.
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
那么現(xiàn)在就知道了, WindowManagerImpl 就是 ViewManager 子類的子類 . (WindowManagerImpl 繼承自 WindowManager, WindowManager 是一個接口, 又繼承自 ViewManager),
至此, 我們得知, vm 其實可以看做是 WindowManagerImpl 對象

接著看 WindowManager.LayoutParams l = r.window.getAttributes();
這個比較簡單, 代碼跟進去發(fā)現(xiàn)就是 獲取 PhoneWindow 的窗口屬性
?
?
第三部分
wm.addView(decor, l);
調(diào)用 WindowManagerImpl.addView 方法, 傳入 decorView 和 PhoneWindow 的窗口屬性.


?
?

1.3 WindowManagerImpl.addView(View, ViewGroup.LayoutParams)

WindowManagerImpl.java 90 行

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

又調(diào)用了 WindowManagerGlobal 的addView 方法, 并且傳入DecorView 與 params


?
?

1.4 WindowManagerGlobal.addView(View, ViewGroup.LayoutParams, Display, Window )

WindowManagerGlobal.java 278行

//管理所有Activity 的 ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//管理所有 Activity 的 DecorView
private final ArrayList<View> mViews = new ArrayList<View>();

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
    ...
   final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    ...
    synchronized (mLock) {
        ...
        ViewRootImpl root;
        ...
        //初始化 ViewRootImpl
        root = new ViewRootImpl(view.getContext(), display);
        //添加 DecorView 到 DecorView 集合
        mViews.add(view);
        //添加 ViewRootImpl 到集合
        mRoots.add(root);
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            ...
            throw e;
        }
    }
}

WindowManager 維護著所有 Activity 的 DecorView 和 ViewRootImpl .這里初始化了一個 ViewRootImpl 又使用 ViewRootImpl 調(diào)用了 setView, 也傳入了 DecorView 和經(jīng)過轉(zhuǎn)換的 WindowManager.LayoutParams.


?
?

1.5 ViewRootImpl.setView(View , WindowManager.LayoutParams , View)

ViewRootImpl.java 632 行

View mView;
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    //異步刷新View
    synchronized (this) {
        if (mView == null) {
            mView = view;
            ....
            requestLayout();
            ...
            view.assignParent(this);
            ...
        }
    }
}

首先把 DecorView 賦值給 ViewRootImpl 類成員變量 mView. 這里需要記住 mView 就是 DecorView.
然后調(diào)用了 ViewRootImpl 類 方法 requestLayout(), 請求對頁面進行布局, 對View 完成異步刷新, 在其內(nèi)部執(zhí)行 View 的繪制方法., 再去看 requestLayout()方法之前, 先看一下 view.assignParent(this); 這個方法將 ViewRootImpl 對象 this 作為參數(shù)調(diào)用了 View的 assignParent().

View.java 16834 行

void assignParent(ViewParent parent) {
    if (mParent == null) {
        mParent = parent;
    } else if (parent == null) {
        mParent = null;
    } else {
        throw new RuntimeException("view " + this + " being added, but"+ " it already has a parent");
    }
}
  • 參數(shù)是 ViewParent, 而 ViewRootImpl 實現(xiàn)了 ViewParent 接口, 所以在這里就將 DecorView 和 ViewRootImpl 綁定起來了.
  • 每個Activity 的根布局都是 DecorView, 而 DecorView 的 Parent 又是 ViewRootImpl, 所以在子 View 里執(zhí)行 invalidate() 之類的操作,需要循環(huán)找 parent 的時候, 最后都會走到 ViewRootImpl 里.

現(xiàn)在接著看 ViewRootImpl.requestLayout() 方法.


?
?

1.6 ViewRootImpl.requestLayout()

ViewRootImpl.java 1153 行

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

請求對頁面進行布局, 對View 完成異步刷新, 在其內(nèi)部執(zhí)行 View 的繪制方法.
checkThread(); 校驗當(dāng)前所在的線程
scheduleTraversals() , (當(dāng)我們自定義 View 調(diào)用 invalidate 的時候, 其實最后也是調(diào)用了這個方法), 這個方法是屏幕刷新的關(guān)鍵. 一起去一探究竟.


?
?

1.7 ViewRootImpl.scheduleTraversals()

Traversals /tr??v?rs(?)l/ 遍歷的意思
ViewRootImpl.java 1354 行

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

這里看到調(diào)用了 mChoreographer.postCallback 里面?zhèn)魅肓巳齻€參數(shù), 第二個參數(shù)是一個 Runnable 對象, 繼續(xù)跟蹤到這個 Runnable 中.
記住這個 boolean類型的變量mTraversalScheduled
記住這個方法 postSyncBarrier()
這兩個還有下面的 removeSyncBarrier() 將在以后另起一章來講解 Choreographer 的調(diào)用時機


?
?

1.8 ViewRootImpl.mTraversalRunnable

ViewRootImpl 6730 行

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

這個 Runnable 在 run 中調(diào)用了 doTraversal() 方法, 繼續(xù)跟蹤


?
?

1.9 ViewRootImpl.doTraversal()

ViewRootImpl.java 1377 行

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        ...
        //最最關(guān)鍵的方法
        performTraversals();
        ...
    }
}

boolean 類型變量 mTraversalScheduled 在1.7 中設(shè)置為 true, 并且調(diào)用了 postSyncBarrier(),
這里設(shè)置為了 false, 調(diào)用了 removeSyncBarrier().
現(xiàn)在進入最最最關(guān)鍵的方法 performTraversals()


?
?

1.10 ViewRootImpl.performTraversals() 從這里開始執(zhí)行三個流程.

ViewRootImpl.java 1576 行

private void performTraversals() {
    ...
    if (...) {
        ...
        if (...) {  
            if (...) {
                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);        
                 // Ask host how big it wants to be
                //測量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                             ...
                layoutRequested = true;
            }
        }
    } else {
       ...
    }
    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    if (didLayout) {
        //擺放
        performLayout(lp, mWidth, mHeight);
                ...
    }
        ...
    boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

    if (!cancelDraw && !newSurface) {
        ...
        //繪制
        performDraw();
    } else {
        ...
    }
        ...
}

performMeasure() 執(zhí)行測量
performLayout() 執(zhí)行擺放
performDraw() 執(zhí)行繪制
跟蹤到這里, 終于看到了這三個方法. 這個方法的邏輯很復(fù)雜, 每次都會根據(jù)一些狀態(tài)來判斷走哪個流程, 有時候可能只執(zhí)行某一個, 有時候可能三個都執(zhí)行或者兩個. 但是不管哪個流程, 都會遍歷一遍View 樹, 因此 View 的繪制是需要遍歷很多次 View 樹, 如果界面非常復(fù)雜, 耗時就會久一點. 當(dāng)然這三個流程在遍歷時, 也不一定都會遍歷View 樹, ViewGroup 在傳遞的時候, 還會根據(jù)響應(yīng)的狀態(tài)判斷是否繼續(xù)向下傳遞.

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
getRootMeasureSpec : 根據(jù) window 的布局參數(shù)計算出 root view 的 measure.
(可以理解為: 基于 phonewindow 的 layout parms, 算出 DecorView 的 measureSpec )

mWidth 和 mHeight 是 window (也就是 PhoneWindow) 的高度和寬度.
lp 是 window 的 LayoutParams 值. lp.width 和 lp.height 默認(rèn)都是 MATCH_PARENT,
getRootMeasureSpec() 中又調(diào)用了 makeMeasureSpec() 將 高度和寬度值與 MATCH_PARENT 傳入. 組合后返回一個采用32位存儲的整型值 measureSpec.

根據(jù)在 Android View 的繪制流程 - 開篇 MeasureSpec 中所學(xué)習(xí)的.
現(xiàn)在已經(jīng)得到了 DecorView 的 MeasureSpec了, 包含的模式與值分別如下.

MeasureSpec 模式 大小
childWidthMeasureSpec MeasureSpec.EXACTLY window 的寬度值
childHeightMeasureSpec MeasureSpec.EXACTLY window 的高度值

下一章將會開始學(xué)習(xí) performMeasure() 流程.


?
?

擴展知識

  • 知識點1

為什么我們在 Activity.onCreate 與 onResume 中獲取不到 View 寬高 ?
看過上面的流程, 是不是知道了, 打開一個Activity, 當(dāng)它的 onCreate 和 onResume 執(zhí)行完后, 才會將它的 DecorView 與新建的一個 ViewRootImpl 綁定起來, 同時開始執(zhí)行測量, 擺放, 繪制等流程. 執(zhí)行完測量后, 我們自己 View 中控件才能寬高等屬性. 所以, 都還沒有進行測量, 甚至連 ViewRootImpl 都沒創(chuàng)建,(在1.4中創(chuàng)建的) 怎么會有寬高呢.
還能得到一個信息是, Activity 界面的繪制, 是在 onResume 之后.

  • 知識點2

當(dāng)我們調(diào)用 View 的 invalidate() 方法, 執(zhí)行重繪的時候, 內(nèi)部也是要層層走到 ViewRootImpl 的 scheduleTraversals 方法里去. 然后這個方法會將遍歷繪制 View 樹的操作 preformTraversals() 封裝到 Runnable 中. 傳給 Chorerographer, 以當(dāng)前的時間戳放進一個 mCallbackQueue 隊列中, 然后調(diào)用了 native 層方法向底層注冊監(jiān)聽下一個屏幕刷新信號事件.

流程圖.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拼卵,一起剝皮案震驚了整個濱河市虹曙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌弟塞,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件即碗,死亡現(xiàn)場離奇詭異嗅绸,居然都是意外死亡,警方通過查閱死者的電腦和手機箍镜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來煎源,“玉大人色迂,你說我怎么就攤上這事∈窒” “怎么了歇僧?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長锋拖。 經(jīng)常有香客問我诈悍,道長,這世上最難降的妖魔是什么兽埃? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任侥钳,我火速辦了婚禮,結(jié)果婚禮上柄错,老公的妹妹穿的比我還像新娘舷夺。我一直安慰自己,他們只是感情好鄙陡,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著躏啰,像睡著了一般趁矾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上给僵,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天毫捣,我揣著相機與錄音,去河邊找鬼帝际。 笑死蔓同,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蹲诀。 我是一名探鬼主播斑粱,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼脯爪!你這毒婦竟也來了则北?” 一聲冷哼從身側(cè)響起矿微,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尚揣,沒想到半個月后涌矢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡快骗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年娜庇,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片方篮。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡名秀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恭取,到底是詐尸還是另有隱情泰偿,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布蜈垮,位于F島的核電站耗跛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏攒发。R本人自食惡果不足惜调塌,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惠猿。 院中可真熱鬧羔砾,春花似錦、人聲如沸偶妖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趾访。三九已至态秧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扼鞋,已是汗流浹背申鱼。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留云头,地道東北人捐友。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像溃槐,于是被迫代替她去往敵國和親匣砖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容