本片文章講解:UI繪製流程-源碼講解(基於API28的源碼分析)
文章大綱:
part1:View是如何被添加到屏幕窗口上
part2:View的繪製流程
本片先講part1:View是如何被添加到屏幕窗口上,part2:放在下一篇文章講解。
part1:View是如何被添加到屏幕窗口上
首先,MainActivity中調用了setContentView(R.layout.activity_main);方法,我們跟進去,來到了Activity的源碼當中:
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback{
//省略....
/**
* 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();
}
//省略....
}
其中getWindow()方法返回了Window對象:
class Activity{
/**
* 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;
}
}
繼續查找Window這個類,發現類註釋上面有這樣一段話:
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
//...
}
The only existing implementation of this abstract class is android.view.PhoneWindow,也就是說PhoneWindow是Window類的唯一繼承實現。那麼我們可以找到PhoneWindow類的setContentView方法了:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//....
@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();
}
}
//....
}
在PhoneWindow類中有兩個比較重要的方法installDecor()和mLayoutInflater.inflate(layoutResID, mContentParent)。接下來我們分別說一下這個兩個方法的作用:
installDecor方法:
class PhoneWinow{
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();//注意這裏 方法①
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//注意這裏 方法②
}
}
}
這裏先提一句,就是在installDecor方法裏面有一個mContentParent = generateLayout(mDecor);這裏給mContentParent賦值之後,回到setContentView方法中也有一個mContentParent對象,調用的方法爲 mLayoutInflater.inflate(layoutResID, mContentParent),這兩個mContentParent是同一個對象。
然後我們跟進到generateDecor方法裏面:
class PhoneWindow{
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());
}
}
實際上就是生成了一個DecorView對象,我們看看DecorView到底是個什麼?
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
//...
}
可以發現DecorView 實際上是一個FrameLayout,因此installDecor方法就是生成了一個DecorView容器對象,接下來分析:
mContentParent = generateLayout(mDecor);方法
class PhoneWindow{
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
//...
a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
if (DEBUG) Log.d(TAG, "Min width minor: " + mMinWidthMinor.coerceToString()
+ ", major: " + mMinWidthMajor.coerceToString());
if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedWidthMajor,
mFixedWidthMajor);
}
if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {
if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedWidthMinor,
mFixedWidthMinor);
}
//...
// Inflate the window decor.
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;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} 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);//注意這裏 ③
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
registerSwipeCallbacks(contentParent);
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
if (getContainer() == null) {
final Drawable background;
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
mDecor.setWindowBackground(background);
final Drawable frame;
if (mFrameResource != 0) {
frame = getContext().getDrawable(mFrameResource);
} else {
frame = null;
}
mDecor.setWindowFrame(frame);
mDecor.setElevation(mElevation);
mDecor.setClipToOutline(mClipToOutline);
if (mTitle != null) {
setTitle(mTitle);
}
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;//注意這裏 ④
}
}
這個方法狠多,我們只撿重點的說幾個:
方法①處的含義爲,layoutResource的字段賦值是依舊features等字段,這裏我們以最後一個else爲例,給layoutResource 賦值:
layoutResource = R.layout.screen_simple;
找到screen_simple佈局:
<?xml version="1.0" encoding="utf-8"?>
<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>
注意一下FrameLayout的id是@android:id/content,後面會用到。
所以總結一下方法①的作用:根據設置的主題、features等字段的不同找到不同的對應佈局給layoutResource賦值。
接下來進到方法②的分析,即:mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);,mDecor就是前面生成的DecorView對象,跟進到onResourcesLoaded方法裏面去:
class DecorView{
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
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();
}
}
這個方法有三處需要注意的,方法①:final View root = inflater.inflate(layoutResource, null);
意思就是根據上面賦值的layoutResource然後inflate成一個View對象名稱爲root,方法②③addView方法,含義就是將生成的root這個View添加到DecorView裏面,DecorView是一個FragmeLayout對象。
到此我們也知道了onResourcesLoaded的作用就是將找到的layoutResource佈局生成View然後添加到DecorView身上。
接下來回去繼續分析PhoneWindow類中的generatorLayout方法中第二處:ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
ID_ANDROID_CONTENT是Window類中的定義的一個靜態常量:
class Window{
/**
* 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;
}
我們可以發現這個id就是之前我們在佈局screen_simple裏面FrameLayout的id!
所以通過findViewById方法之後找到了contentParent容器,然後返回到調用之處。回到調用之處:
class PhoneWinow{
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();//注意這裏 方法①
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//注意這裏 方法②
}
}
}
繼續回到installDecor調用之處:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//....
@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();
}
}
//....
}
此時installDecor方法走完,mContentParent也賦值完畢,mContentParent就等於佈局screen_simple裏面FrameLayout!
接下來走方法②: mLayoutInflater.inflate(layoutResID, mContentParent);這個就比較好理解了,即將傳進來的佈局layoutResID加入到mContengParent裏面,即screen_simple佈局中的FrameLayout容器裏面!
以上就是View是如何被添加到屏幕窗口全部分析,接下來我們總結一下:
首先,系統會創建一個頂層佈局容器DecorView,它是一個ViewGroup,繼承FrameLayout,是PhoneWindow持有的一個實例,它(PhoneWindow)是所有應用程序的頂層View,在系統內部初始化。
當DecorView初始化完成之後,系統會根據應用程序的主題特性去加載一個基礎容器,比如dart actionbar、noactionbar等主題加載的基礎容器也是不同的。但無論如何這個基礎容器裏面一定會有一個com.android.internal.R.id.content的容器,這個容器就是FragmeLayout。
而我們開發者通過setContentView(R.layout.activity_main)的XML佈局文件就是通過解析之後添加到上面的FragmeLayout中。
接下來我們用不同類的形式再彙總說一下:
涉及到的類如下:
MainActivity:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/**
* 加載XML佈局activity_main
*/
setContentView(R.layout.activity_main);
}
}
Activity:
public class Activity {
/**
* MainActivity的setContentView方法會調到這裏來
* @param layoutResID 佈局id
*/
public void setContentView(@LayoutRes int layoutResID) {
//getWindow()返回PhoneWindow對象,實際會走到PhoneWindow.setContentView方法中
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
/**
* 返回Window對象,實際是PhoneWindow對象
* @return Window對象
*/
public Window getWindow() {
return mWindow;
}
}
Window:
/**
* PhoneWindow是Window的唯一實現
*/
public abstract class Window {
/**
* 這個id是佈局文件中FragmeLayout中的id
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
}
PhoneWindow:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
/**
* MainActivity.setContentView-->Activity.setContentView-->PhoneWindow.setContentView
*
* @param layoutResID 傳入的佈局id,此處可以理解爲R.layout.activity_main
*/
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
/**
* 這個方法作用:
* 1.生成DecorView對象(DecorView是繼承FrameLayout)
* 2.初始化mContentParent對象
*/
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 {
/**
*這個方法作用:
* 將傳進來的佈局加載到mContentParent容器中
*/
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
/**
* 裏面有兩個重要的方法:
* 1. generateDecor:如果mDecor爲空,則生成DecorView
* 2. generateLayout:
*/
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//生成DecorView,DecorView是繼承FrameLayout
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);
}
}
/**
* 生成DecorView 對象
*/
protected DecorView generateDecor(int featureId) {
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());
}
/**
*找相應的加載佈局,加載到DecorView中,然後返回佈局中FragmeLayout對象
* @param decor
* @return 佈局中id爲ID_ANDROID_CONTENT的容器
*/
protected ViewGroup generateLayout(DecorView decor) {
TypedArray a = getWindowStyle();
//省略...
//解析屬性值
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
requestFeature(FEATURE_ACTION_BAR);
}
if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
requestFeature(FEATURE_ACTION_MODE_OVERLAY);
}
// 根據features和屬性的不同,來給layoutResource賦值,實際就是找相應的加載佈局
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if {
//省略...
}else{
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
//將找到的佈局layoutResource加入到DecorView中去
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//找到layoutResource佈局中id爲ID_ANDROID_CONTENT的控件,然後返回
ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
//省略...
return contentParent;
}
}
DecorView:
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
/**
* 根據傳入的layoutResource生成View,然後將它加入到DecorView裏面
*
* @param inflater 佈局生成器
* @param layoutResource 佈局id
*/
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
//根據layoutResource生成view對象
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
//將生成的佈局View加入到DecorView中
mDecorCaptionView.addView(root, new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
//將生成的佈局View加入到DecorView中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
}
最後畫一個圖來說一下:
相信看到這裏,大家都應該明白View視圖是如何被添加到屏幕上了。