基于 Android API 26 Platform 源碼
寫作背景
在上一篇探究Android View 繪制流程掷贾,Xml 文件到 View 對(duì)象的轉(zhuǎn)換過程我們了解了setContentView(resId)
如何把 xml 文件轉(zhuǎn)換成 Java 中的 View 對(duì)象。本篇文章在此基礎(chǔ)上繼續(xù)探究荣茫,View 是如何展示到 Activity 上的想帅。
很多 Android 開發(fā)者都知道一個(gè)事情
當(dāng) Activity 執(zhí)行 onResume() 方法后,代表 Activity 顯示到前臺(tái)
這句話很短啡莉,但是背后隱藏了多少方法的調(diào)用呢港准?下面我們將一層一層的剝開源碼尋找真相。
先從 setContentView(resId) 入手
先說(shuō)明一下咧欣,從 Android 的 Launcher 上點(diǎn)擊應(yīng)用的 Icon 的啟動(dòng)過程比較復(fù)雜浅缸,本人仍在學(xué)習(xí)。如果想了解如何啟動(dòng)一個(gè) Activity 的過程可以參考Android Launcher 啟動(dòng) Activity 的工作過程该押,這里我們只從關(guān)注 Activity 中的 View 顯示出來(lái)疗杉。所以直接從 Activity 的一些方法入手。
在 Activity 的 onCreate(savedInstanceState)
中調(diào)用 setContentView(resId)
,而setContentView(resId)
則會(huì)調(diào)用 PhoneWindow.setContentView(layoutResID)
源碼并不是太長(zhǎng)
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
這里忽略轉(zhuǎn)場(chǎng)動(dòng)畫
和一些回調(diào)相關(guān)的邏輯代碼后如下
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
mContentParent.requestApplyInsets();
其中 mContentParent 是一個(gè) ViewGroup
引用
private ViewGroup mContentParent;
這樣開代碼比較簡(jiǎn)單明了
1. 判斷 mContentParent 是否為空烟具,如果為空?qǐng)?zhí)行 installDecor()
2. 如果 mContentParent 不為空梢什,清除 mContentParent 的所有子 View
3. 把傳入的布局文件轉(zhuǎn)換為 View 對(duì)象添加到 mContentParent
分析 installDecor()
然后我們?cè)倏聪?installDecor()
,因?yàn)樵创a比較長(zhǎng)朝聋,我們分成幾個(gè)部分解讀
第一部分
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
這幾行代碼最重要的是調(diào)用了方法 generateDecor()
其實(shí)就是創(chuàng)建一個(gè) DecorView
嗡午。這里是不是能想到探究Android View 繪制流程,Canvas 的由來(lái)中最后的那張圖冀痕,我們做個(gè)類似的截圖截個(gè)圖
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new TextView(getApplicationContext()));
}
@Override
protected void onResume() {
super.onResume();
}
}
我們看到一個(gè) Activity 頁(yè)面最底層的 View 就是我們剛看到的 DecorView
第二部分
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
……
}
這里看到了對(duì) mContentParent
的賦值操作荔睹,調(diào)用了 generateLayout(mDecor)
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
//設(shè)置 Windows Style ,title 言蛇、action_bar 僻他、設(shè)置鍵盤彈出方式之類的屬性
//……
//……
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
……
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
……
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
……
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
……
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
……
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//……
//……
mDecor.finishChanging();
return contentParent;
}
這里把 generateLayout(mDecor)
做了很大的簡(jiǎn)化,大部分都是設(shè)置一些窗體屬性腊尚,軟鍵盤彈出方式之類的東西吨拗。我們關(guān)心的 View 相關(guān)的就以下幾行
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
mDecor.finishChanging();
layoutResource
是什么呢?我們隨便選擇一個(gè) R.layout.screen_simple
在 AndroidSdk 中搜到這個(gè)文件婿斥,內(nèi)容如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
這個(gè)時(shí)候再回到我們剛的那個(gè)截圖劝篷,我們找到了第二層內(nèi)容 LinearLayout
的來(lái)源,這一層LinearLayout
包含兩個(gè)部分
1. id 為 action_mode_bar_stub 的 ViewStub ,用來(lái)設(shè)置 actionBar 之類的
2. id 為 android.R.id.content 的 FrameLayout民宿。里面會(huì)存放我們?cè)?Activity.setContentView(resId) 傳入的文件布局
然后再看下最后 mDecor.finishChanging()
public void finishChanging() {
mChanging = false;
drawableChanged();
}
private void drawableChanged() {
if (mChanging) {
return;
}
//……
//……
requestLayout();
invalidate();
//……
//……
}
根據(jù)我們對(duì) View 的了解娇妓,requestLayout()
和 invalidate()
會(huì)引發(fā) View 的重新布局和重新繪制,難道這個(gè)時(shí)候就繪制 View 了活鹰。 這不科學(xué)
而事實(shí)上哈恰,這個(gè)真的不科學(xué)。此時(shí)并不會(huì)執(zhí)行繪制和計(jì)算志群。 原因是此時(shí)的 View 還沒有和 ViewRootImpl 關(guān)聯(lián)上 蕊蝗。留個(gè)懸念,這個(gè)我們?cè)诤竺娴恼鹿?jié)會(huì)講解赖舟。
第三部分
第三部分就是第二部分省略的代碼,代碼特別長(zhǎng),這里也縮減一下夸楣。
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
//……
} else {
mTitleView = (TextView)findViewById(R.id.title);
//……
}
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}
// Only inflate or create a new TransitionManager if the caller hasn't
// already set a custom one.
if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
//……
}
這里簡(jiǎn)單的歸納一下代碼做的事情
1. 設(shè)置 title
2. 設(shè)置背景色
3. 處理 FEATURE_ACTIVITY_TRANSITIONS 屬性
requestLayout()
和 invalidate()
源碼追蹤
requestLayout()
和 invalidate()
的源碼都在 View 類里面
先看 requestLayout()
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
我們看到此時(shí)的 View 會(huì)調(diào)用 mParent.requestLayout()
宾抓。mParent
會(huì)是 ViewGroup
嗎?我們看下聲明變量的地方
protected ViewParent mParent;
然后再搜下mParent
賦值的地方豫喧,發(fā)現(xiàn)只有一處
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");
}
}
那接下來(lái)就看 assignParent(parent)
被誰(shuí)調(diào)用了石洗,發(fā)現(xiàn) View
中只有聲明,沒有調(diào)用紧显。所以我們就去 ViewGroup
看看讲衫。發(fā)現(xiàn)也只有一處調(diào)用
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
……
// tell our children
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
……
}
順著這個(gè)方法追溯一下,如下圖
這時(shí)候我們又疑問了:
DecorView 的 mParent 是誰(shuí)呢?涉兽?招驴?
答案只有一個(gè),是 NULL
我們剛說(shuō)了 mDecor.finishChanging()
不會(huì)執(zhí)行繪制和計(jì)算相枷畏。 原因是此時(shí)的 View 還沒有和 ViewRootImpl 關(guān)聯(lián)上 别厘。
先看 invalidate()
public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
……
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
……
}
}
我們又在跟蹤 invalidate()
方法時(shí)發(fā)現(xiàn)了 p.invalidateChild(this, damage)
這里似乎又是一層一層的向上迭代。為了確保拥诡,我們?nèi)タ聪?ViewGroup 的 invalidateChild()
public final void invalidateChild(View child, final Rect dirty) {
……
ViewParent parent = this;
if (attachInfo != null) {
……
do {
……
parent = parent.invalidateChildInParent(location, dirty);
……
}
} while (parent != null);
}
}
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
……
return mParent;
}
return null;
}
所以和 requestLayout()
一樣層層追溯触趴,又到了 DecorView
中。我們可以準(zhǔn)確的說(shuō) DecorView
的 mParent
其實(shí)是 ViewRootImpl
渴肉。但是怎么證明呢冗懦??仇祭?
DecorView
和 ViewRootImpl
的關(guān)系
本文開盤就已經(jīng)說(shuō)了 當(dāng) Activity 執(zhí)行 onResume() 方法后披蕉,代表 Activity 顯示到前臺(tái),這是為什么呢前塔?
我們都是 Activity
的由 ActivityManager
管理嚣艇,Activity 頁(yè)面的操作必須在主線程中,而主線程就是 ActivityThread 华弓。在 ActivityThread 的源碼中食零,找到了一個(gè) H
類,該類繼承 Handler
寂屏。在 H
的 handleMessage(Message msg)
發(fā)現(xiàn)以下代碼
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
……
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
handleResumeActivity((IBinder) msg.obj, true, msg.arg1 != 0, true);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
……
}
然后看下 handleResumeActivity
final void handleResumeActivity(IBinder token,
……
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
……
}
這里我們看到了 DecorView
被添加到了 ViewManager
之中贰谣。
ViewManager
只是一個(gè)接口,它的實(shí)現(xiàn)類為 WindowManagerImpl
迁霎。在 WindowManagerImpl
我查找 addView()
方法
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
這里的 mGlobal
又是 WindowManagerGlobal
的實(shí)例吱抚。所有我們又要跳轉(zhuǎn)到 WindowManagerGlobal.addView()
。
O__O "… 這時(shí)千萬(wàn)別放棄考廉,勝利就在眼前秘豹,同志們要堅(jiān)持往下看啊。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
……
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
……
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} ……
}
然后再看下 ViewRootImpl.setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
……
view.assignParent(this);
}
}
}
親人安痢既绕!終于看到 root.setView(view, wparams, panelParentView),我們上面一直說(shuō)的 View 和 ViewRootImpl 的關(guān)系終于在這關(guān)聯(lián)上了涮坐。為了更清晰一點(diǎn)我們畫一個(gè)時(shí)序圖
ViewRootImpl
繪制 View
現(xiàn)在進(jìn)入了本文的壓軸部分凄贩,View 繪制的核心源碼。
通過以上的講解袱讹,我們也知道要去找 ViewRootImpl
的 requestLayout()
和 invalidateChildInParent()
方法
ViewRootImpl.requestLayout()
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
scheduleTraversals()
又是什么鬼
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
這里我們看到了一個(gè)任務(wù) mTraversalRunnable
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
mTraversalRunnable
是一個(gè) Runnable 的子類
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
這個(gè)時(shí)候我們又要去看下 doTraversal()
的源碼疲扎。
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
最后我們找到了 performTraversals()
方法, 注意 performTraversals() 里面有重大內(nèi)容該方法很長(zhǎng)(真的是特別長(zhǎng)),我們這里看一下簡(jiǎn)化后的
private void performTraversals() {
……
if (!mStopped || mReportNextDraw) {
……
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
……
}
……
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
……
}
……
performDraw();
……
}
看到了 performMeasure
椒丧、 performLayout
壹甥、 performDraw
這里就不用多說(shuō)了吧。也就解釋了為啥 View 的繪制順序是 measure -> layout -> draw
了吧
ViewRootImpl.invalidateChildInParent()()
這里我們不啰嗦太多瓜挽,直接上源碼
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
……
invalidateRectOnScreen(dirty);
return null;
}
private void invalidateRectOnScreen(Rect dirty) {
……
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
看到這里就不用多說(shuō)了盹廷,下面的執(zhí)行順序 ViewRootImpl.requestLayout()
已經(jīng)分析過了。
這個(gè)時(shí)候大家再看下網(wǎng)上很多分析 requestLayout() 和 invalidate() 方法區(qū)別的久橙,大家可以去先去查一下俄占,等后面有時(shí)間我也會(huì)寫一篇分析這兩個(gè)方法區(qū)別的文章。
View 到底什么時(shí)候繪制到屏幕上淆衷?
通過以上分析我們知道
1. setContentView() 只是把 View 添加到 DecorView 上
2. onResume() 中 ViewRootImpl 和 DecorView 做了關(guān)聯(lián)
3. requestLayout() 和 invalidate() 會(huì)觸發(fā) ViewRootImpl 繪制 View
但是缸榄!setContentView() 中調(diào)用了 requestLayout() 和 invalidate() 不會(huì)觸發(fā)繪制,我們上面只講了 onResume() 中 ViewRootImpl 和 DecorView 做了關(guān)聯(lián) 祝拯。到底什么時(shí)候又調(diào)用了 requestLayout() 或者 invalidate() 甚带??佳头?
往上翻我們發(fā)現(xiàn)在 ViewRootImpl.setView() 中有一個(gè) requestLayout
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
……
requestLayout();
……
view.assignParent(this);
……
}
}
}
但是鹰贵!居然在 view.assignParent(this)
這尼瑪逗我吧!
我們?cè)诨仡^看下 requestLayout()
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
這里重點(diǎn)看一下這句
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
了解 Android Handler Looper 都知道 postSyncBarrier 是創(chuàng)建一個(gè)障礙康嘉,阻止后面的 Message 對(duì)象被執(zhí)行碉输。那這里也就解決了我剛剛的疑問, 雖然request()
在 view.assignParent(this) 之前被調(diào)用亭珍,但是會(huì)被阻塞敷钾。 doTraversal() 執(zhí)行的時(shí)候 DecorView 和 ViewRootImpl 已經(jīng)關(guān)聯(lián)了
這里留個(gè)坑
我沒有找到 ViewRootImpl 怎么執(zhí)行到 removeSyncBarrier(mTraversalBarrier)
的代碼组底。
總結(jié)
對(duì)以上內(nèi)容做個(gè)總結(jié)
1. View 在 Activity 的 onCreate() 方法中通過 setContentView() 方法添加到 Activity 的 DecorView 上
2. 此時(shí) ViewRootImpl 和 DecorView 沒有關(guān)聯(lián)上轴咱,不會(huì)繪制 View
3. 在 Activity 的 onResume() 方法執(zhí)行后尔艇,DecorView 會(huì)被添加帶 ViewRootImpl 中惧眠。然后執(zhí)行 requestlayout()