好記性不如爛筆頭盘榨。生活中多做筆記,不僅可以方便自己蟆融,還可以方便他人草巡。
(下面的源碼大部分是來自API 28)
1. View的知識前提
View的繪制是從上往下一層層迭代下來的:DecorView-->ViewGroup(--->ViewGroup)-->View,所以型酥,在學(xué)習(xí)view的繪制原理前山憨,我們來先看看DecorView。
1.1 DecorView的視圖結(jié)構(gòu)
Android 中 Activity 是作為應(yīng)用程序的載體存在弥喉,代表著一個完整的用戶界面郁竟,提供了一個窗口來繪制各種視圖。每個activity都對應(yīng)一個窗口window由境,這個窗口是PhoneWindow的實(shí)例枪孩,PhoneWindow對應(yīng)的布局是DecirView,是一個FrameLayout,DecorView內(nèi)部又分為兩部分蔑舞,一部分是ActionBar,另一部分是ContentParent嘹屯,即activity在setContentView對應(yīng)的布局攻询。
1.2 從源碼看DecorView
activity在啟動的時候都會在onCreate中執(zhí)行setContentView方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
以setContentView為切入點(diǎn),分析Activity州弟、PhoneWindow钧栖、DecorView、ActionBar和ContentParent的關(guān)系婆翔。
進(jìn)入setContentView
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
進(jìn)入activity的setContentView拯杠,發(fā)現(xiàn)里面調(diào)用的是getWindow()的setContentView(layoutResID)。
繼續(xù)看getWindow()
/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}
final void attach(...這里省略代碼) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
...這里省略代碼
}
從上面的源碼可以看出setContentView里面的是getWindow()其實(shí)是PhoneWindow啃奴,這也正如前面所說的潭陪,每個activity都對應(yīng)一個窗口window,這個窗口是PhoneWindow的實(shí)例最蕾。
繼續(xù)依溯,進(jìn)入PhoneWindow里面看看:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
PhoneWindow里面有一個DecorView對象mDecor,再看看setContentView方法
@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();
}
mContentParentExplicitlySet = true;
}
首次進(jìn)來mContentParent應(yīng)該是null瘟则,進(jìn)入installDecor()看看:
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
.....此處省略代碼
}
}
在installDecor里黎炉,mDecor=generateDecor(-1),再看看這個方法里是怎么生成DecorView的:
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
里面是直接new了一個DecorView醋拧。
看到這里慷嗜,PhoneWindow和DecorView的關(guān)系就一目了然了
那問題來了,DecorView是如何跟ActionBar和ContentParent關(guān)聯(lián)起來的呢丹壕?
繼續(xù)回頭看源碼的installDecor()方法:
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
.....此處省略代碼
}
}
發(fā)現(xiàn)mContentParent是通過generateLayout(mDecor)生成的庆械,那看看generateLayout方法:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
...此處省略代碼
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
...此處省略代碼
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...此處省略代碼
return contentParent;
}
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
generateLayout方法里面根據(jù)不同的配置初始化的代碼特別多,我省略了一些其他代碼
generateLayout方法里面雀费,mDecor加載了R.layout.screen_simple布局
<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>
可以看到干奢,R.layout.screen_simple是一個垂直的線性布局,上面的ViewStub就是APP的appBar盏袄,下面的FrameLayout的id為content忿峻!,activity所加載的xml頁面就是加載到這個布局里面
再看看mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)這個方法
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...此處省略代碼
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
從上面的方法來看辕羽,root這個View所代表的的就是 R.layout.screen_simple逛尚,然后DecorView調(diào)用addView將root加載到DecorView里面
奇怪了,只看到ContentParent初始化刁愿,沒看到ActionBar初始化按履?
再回頭看看Activity最開始的源碼:
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
原來ActionBar是在這里初始化的,看看initWindowDecorActionBar():
/**
* Creates a new ActionBar, locates the inflated ActionBarView,
* initializes the ActionBar with the view, and sets mActionBar.
*/
private void initWindowDecorActionBar() {
Window window = getWindow();
// Initializing the window decor can change window feature flags.
// Make sure that we have the correct set before performing the test below.
window.getDecorView();
if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
return;
}
mActionBar = new WindowDecorActionBar(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}
@RestrictTo({Scope.LIBRARY_GROUP})
public WindowDecorActionBar(View layout) {
assert layout.isInEditMode();
this.init(layout);
}
private void init(View decor) {
this.mOverlayLayout = (ActionBarOverlayLayout)decor.findViewById(id.decor_content_parent);
if (this.mOverlayLayout != null) {
this.mOverlayLayout.setActionBarVisibilityCallback(this);
}
this.mDecorToolbar = this.getDecorToolbar(decor.findViewById(id.action_bar));
this.mContextView = (ActionBarContextView)decor.findViewById(id.action_context_bar);
this.mContainerView = (ActionBarContainer)decor.findViewById(id.action_bar_container);
if (this.mDecorToolbar != null && this.mContextView != null && this.mContainerView != null) {
this.mContext = this.mDecorToolbar.getContext();
int current = this.mDecorToolbar.getDisplayOptions();
boolean homeAsUp = (current & 4) != 0;
if (homeAsUp) {
this.mDisplayHomeAsUpSet = true;
}
ActionBarPolicy abp = ActionBarPolicy.get(this.mContext);
this.setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp);
this.setHasEmbeddedTabs(abp.hasEmbeddedTabs());
TypedArray a = this.mContext.obtainStyledAttributes((AttributeSet)null, styleable.ActionBar, attr.actionBarStyle, 0);
if (a.getBoolean(styleable.ActionBar_hideOnContentScroll, false)) {
this.setHideOnContentScrollEnabled(true);
}
int elevation = a.getDimensionPixelSize(styleable.ActionBar_elevation, 0);
if (elevation != 0) {
this.setElevation((float)elevation);
}
a.recycle();
} else {
throw new IllegalStateException(this.getClass().getSimpleName() + " can only be used " + "with a compatible window decor layout");
}
}
ActionBar和ContentParent并非是添加到DecorView上去的滤钱,而是本身就存在于DecorView觉壶,
對于有ActionBar的activity,DecorView的默認(rèn)布局是screen_action_bar.xml件缸,里面就會包含ActionBar和ContentParent
對于沒有ActionBar的Activity铜靶,會根據(jù)Activity所帶的參數(shù)選擇decorView的默認(rèn)布局,例如screen_simple.xml
選擇DecorView的默認(rèn)布局的相關(guān)的判斷邏輯是installDecor方法中調(diào)用generateLayout完成的.
看到這里他炊,DecorView和ContentParent争剿、ActionBar的關(guān)系就一目了然了
1.3 DecorView建立與viewRootImpl的聯(lián)系
在ActivityThread的handleLaunchActivity中啟動Activity,當(dāng)onCreate()方法執(zhí)行完畢痊末,上面所述的DecorView創(chuàng)建動作也完畢了蚕苇,在handleLaunchActivity方法里會繼續(xù)調(diào)用到ActivityThread的handleResumeActivity方法,看看這個方法的源碼:
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
// TODO Push resumeArgs into the activity for consideration
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
...
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 (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
...
}
在handleResumeActivity方法中凿叠,獲取該activity所關(guān)聯(lián)的window對象涩笤,DecorView對象,以及windowManager對象幔嫂,而WindowManager是抽象類辆它,它的實(shí)現(xiàn)類是WindowManagerImpl,所以后面調(diào)用的是WindowManagerImpl的addView方法履恩,看看源碼:
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
}
mGlobal則是WindowManagerGlobal的一個實(shí)例锰茉,那么我們接著看WindowManagerGlobal的addView方法:
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); // 1
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); // 2
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
在上面的方法中,實(shí)例化了ViewRootImpl類切心,然后調(diào)用ViewRootImpl#setView方法飒筑,并把DecorView作為參數(shù)傳遞進(jìn)去,在這個方法內(nèi)部绽昏,會通過跨進(jìn)程的方式向WMS(WindowManagerService)發(fā)起一個調(diào)用协屡,從而將DecorView最終添加到Window上,在這個過程中全谤,ViewRootImpl肤晓、DecorView和WMS會彼此關(guān)聯(lián),最后通過WMS調(diào)用ViewRootImpl#performTraverals方法開始View的測量认然、布局补憾、繪制流程。(參考:Android View源碼解讀:淺談DecorView與ViewRootImpl
)
DecorView的相關(guān)知識就記錄到這卷员,下面開始view的繪制流程盈匾。
2. View的繪制流程
view繪制流程放在下一篇文章(http://www.reibang.com/p/68afe6c8501b
)