android 二次绘制 layout,View的三次measure,两次layout和一次draw
我在《Android視圖結(jié)構(gòu)》這篇文章中已經(jīng)描述了Activity,Window和View在視圖架構(gòu)方面的關(guān)系。前天,我突然想到為什么在setContentView中能夠調(diào)用findViewById函數(shù)?View那時不是還沒有被加載,測量,布局和繪制啊。然后就搜索了相關(guān)的條目,發(fā)現(xiàn)findViewById只需要在inflate結(jié)束之后就可以。于是,我整理了Activity生命周期和View的生命周期的關(guān)系,并再次做一下總結(jié)。
為了節(jié)約你的時間,本篇文章的主要內(nèi)容為:
Activity的生命周期和它包含的View的生命周期的關(guān)系
Activity初始化時View為什么會三次measure,兩次layout但只一次draw?
ViewRoot的初始化過程
Activity的生命周期和View的生命周期
我通過一個簡單的demo來驗證Activity生命周期方法和View的生命周期方法的調(diào)用先后順序。請看如下截圖
在onCreate函數(shù)中,我們通常都調(diào)用setContentView來設(shè)置布局文件,此時Android系統(tǒng)就會讀取布局文件,但是視圖此時并沒有加載到Window上,并且也沒有進入自己的生命周期。
只有等到Activity進入resume狀態(tài)時,它所擁有的View才會加載到Window上,并進行測量,布局和繪制。所以我們會發(fā)現(xiàn)相關(guān)函數(shù)的調(diào)用順序是:
onResume(Activity)
onPostResume(Activity)
onAttachedToWindow(View)
onMeasure(View)
onMeasure(View)
onLayout(View)
onSizeChanged(View)
onMeasure(View)
onLayout(View)
onDraw(View)
大家會發(fā)現(xiàn),為什么onMeasure先調(diào)用了兩次,然后再調(diào)用onLayout函數(shù),最后還有在依次調(diào)用onMeasure,onLayout和onDraw函數(shù)呢?
ViewGroup的measure
大家應(yīng)該都知道,有些ViewGroup可能會讓自己的子視圖測量兩次。比如說,父視圖先讓每個子視圖自己測量,使用View.MeasureSpec.UNSPECIFIED,然后在根據(jù)每個子視圖希望得到的大小不超過父視圖的一些限制,就讓子視圖得到自己希望的大小,否則就用其他尺寸來重新測量子視圖。這一類的視圖有FrameLayout,RelativeLayout等。
在《Android視圖結(jié)構(gòu)》中,我們已經(jīng)知道Android視圖樹的根節(jié)點是DecorView,而它是FrameLayout的子類,所以就會讓其子視圖繪制兩次,所以onMeasure函數(shù)會先被調(diào)用兩次。
// FrameLayout的onMeasure函數(shù),DecorView的onMeasure會調(diào)用這個函數(shù)。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
.....
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
......
}
}
........
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
........
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
你以為到了這里就能解釋通View初始化時的三次measure,兩次layout卻只一次draw嗎?那你就太天真了!我們都知道,視圖結(jié)構(gòu)中不僅僅是DecorView是FrameLayout,還有其他的需要兩次measure子視圖的ViewGroup,如果每次都導(dǎo)致子視圖兩次measure,那效率就太低了。所以View的measure函數(shù)中有相關(guān)的機制來防止這種情況。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
// 當(dāng)FLAG_FORCE_LAYOUT位為1時,就是當(dāng)前視圖請求一次布局操作
//或者當(dāng)前當(dāng)前widthSpec和heightSpec不等于上次調(diào)用時傳入的參數(shù)的時候
//才進行從新繪制。
if (forceLayout || !matchingSize &&
(widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec)) {
......
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
......
}
源碼看到這里,我?guī)缀跏难蹨I掉下來!沒辦法,只能再想其他的方法來分析這個問題。
斷點調(diào)試大法好
為了分析函數(shù)調(diào)用的層級關(guān)系,我想到了斷點調(diào)試法。于是,我果斷在onMeasure和onLayout函數(shù)中設(shè)置了斷點,然后進行調(diào)試。
在《Android視圖架構(gòu)》一文中,我們知道ViewRoot是DecorView的父視圖,雖然它自己并不是一個View,但是它實現(xiàn)了ViewParent的接口,Android正是通過它來實現(xiàn)整個視圖系統(tǒng)的初始化工作。而它的performTraversals函數(shù)就是視圖初始化的關(guān)鍵函數(shù)。
對比兩次onMeasure被調(diào)用時的函數(shù)調(diào)用幀,我們可以輕易發(fā)現(xiàn)ViewRootImpl的performTraversals函數(shù)中直接和間接的調(diào)用了兩次performMeasure函數(shù),從而導(dǎo)致了View最開始的兩次measure過程。
然后在相同的performTraversals函數(shù)中會調(diào)用performLayout函數(shù),從而導(dǎo)致View進行一輪layout過程。
但是為什么這次performTraversals并沒有觸發(fā)View的draw過程呢?反而是View又將重新進行一輪measure,layout過程之后才進行draw。
兩次performTraversals
通過斷點調(diào)試,我們發(fā)現(xiàn)在View初始化的過程中,系統(tǒng)調(diào)用了兩次performTraversals函數(shù),第一次performTraversals函數(shù)導(dǎo)致了View的前兩次的onMeasure函數(shù)調(diào)用和第一次的onLayout函數(shù)調(diào)用。后一次的performTraversals函數(shù)導(dǎo)致了最后的onMeasure,onLayout,和onDraw函數(shù)的調(diào)用。但是,第二次performTraversals為什么會被觸發(fā)呢?我們研究一下其源碼就可知道。
private void performTraversals() {
......
boolean newSurface = false;
//TODO:決定是否讓newSurface為true,導(dǎo)致后邊是否讓performDraw無法被調(diào)用,而是重新scheduleTraversals
if (!hadSurface) {
if (mSurface.isValid()) {
// If we are creating a new surface, then we need to
// completely redraw it. Also, when we get to the
// point of drawing it we will hold off and schedule
// a new traversal instead. This is so we can tell the
// window manager about all of the windows being displayed
// before actually drawing them, so it can display then
// all at once.
newSurface = true;
.....
}
}
......
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
......
performDraw();
}
} else {
if (viewVisibility == View.VISIBLE) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
......
}
由源代碼可以看出,當(dāng)newSurface為真時,performTraversals函數(shù)并不會調(diào)用performDraw函數(shù),而是調(diào)用scheduleTraversals函數(shù),從而再次調(diào)用一次performTraversals函數(shù),從而再次進行一次測量,布局和繪制過程。
我們由斷點調(diào)試可以輕易看到,第一次performTraversals時的newSurface為真,而第二次時是假。
總結(jié)
雖然我已經(jīng)通過源碼得知View初始化時measure三次,layout兩次,draw一次的原因,但是Android系統(tǒng)設(shè)計時,為什么要將整個初始化過程設(shè)計成這樣?我卻還沒有明白,為什么當(dāng)Surface為新的時候,要推遲繪制,重新進行一輪初始化,這些可能都要涉及到Surface的相關(guān)內(nèi)容,我之后要繼續(xù)學(xué)習(xí)相關(guān)內(nèi)容!
總結(jié)
以上是生活随笔為你收集整理的android 二次绘制 layout,View的三次measure,两次layout和一次draw的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html 适配 android,Andr
- 下一篇: html中文字过长 自动隐藏,css 实