前言
對于大部分Android開發(fā)而言界面都是我們開發(fā)必不可少的環(huán)節(jié)抵屿,但是大部分人對界面的理解還只停留在id為content的根布局中粱玲,我們自己的布局外到底套著多少布局诸尽?為什么在onCreate中無法通過getWidth()和 getMeasuredWidth()拿到view的尺寸蠢箩?為什么View不能在子線程中更新劳翰,但是在onResume中和onResume之前可以在子線程中更新敦锌?這些疑問在閱讀完本篇文章之后都會解開答案。
注意:下面的內(nèi)容是建立在繼承AppCompatActivity的基礎(chǔ)上佳簸,Activity的結(jié)構(gòu)類似但是簡單一點(diǎn)
由于代碼量太大乙墙,也為了方便閱讀颖变,下面的代碼都是刪減版,只有核心代碼
界面的構(gòu)成
廢話不多說听想,直接上圖:
這是我通過Android Studio的Layout Inspector抓取的手機(jī)上界面的構(gòu)成圖腥刹,看上去是不是有點(diǎn)懵B,別著急下面還有一張:
這張圖里我用箭頭加文字描述了每個(gè)部分是什么汉买,注意我由從外到內(nèi)的方式標(biāo)了序號衔峰,按照我的序號看,而且每個(gè)層次我用的相同的顏色框起來的蛙粘。簡單介紹一下(上面的序號對應(yīng)下面的序號):
- 從圖上看DecorView是最外層的布局垫卤,這么說對也不對,因?yàn)镈ecorView是由PhoneWindow持有的出牧,你可以說最外層是PhoneWindow穴肘,但是PhoneWindow不是繼承View的,又不算是布局舔痕,所以也可以說最外層的布局是DecorView梢褐,這里不用糾結(jié),知道這么一個(gè)情況就行了赵讯。DecorView下有statusBarBackground、navigationBarBackground和LinearLayout三個(gè)直接子布局耿眉。
- statusBarBackground是手機(jī)的狀態(tài)欄边翼,我們經(jīng)常需要適配的沉浸式狀態(tài)欄就是這里。
- navigationBarBackground是手機(jī)的導(dǎo)航欄鸣剪,也就是下面的虛擬按鍵组底,這個(gè)有的手機(jī)有,有的沒有筐骇,現(xiàn)在的全面屏手機(jī)基本都會有這個(gè)選項(xiàng)债鸡,但是大部分人用的是手勢操作,一般不用去適配這個(gè)铛纬。
- Linearlayout下面包著一個(gè)FrameLayout厌均,F(xiàn)rameLayout下面又包著一個(gè)decor_content_parent,decor_content_parent下面有action_bar_container和content兩個(gè)直接子布局告唆。
- action_bar_container就是系統(tǒng)自帶的標(biāo)題欄了棺弊,里面是一個(gè)ToolBar,但是我們一般不用擒悬。
- content可能是最多人了解的一個(gè)布局了模她,他是我們自己布局的父布局,setContentView(R.layout.activity_main)中的R.layout.activity_main就是被添加到這個(gè)布局中了懂牧。
- RelativeLayout是我們自己的布局侈净,是我寫的。button是我添加的一個(gè)按鈕。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Button" />
</RelativeLayout>
這是原始的布局文件畜侦。
界面的構(gòu)成講完了元扔,其實(shí)也沒有那么復(fù)雜,以前在網(wǎng)上搜這個(gè)知識點(diǎn)夏伊,沒有一個(gè)講的全面的摇展,果然還是要靠自己。
界面的創(chuàng)建
這里就來到愉(cao)快(dan)的閱讀源碼時(shí)間了溺忧,想了解界面的創(chuàng)建過程只能去讀源碼咏连。我先大致把流程總結(jié)一下,后面我們在看源碼鲁森,這樣會好理解一點(diǎn)祟滴。
界面的創(chuàng)建涉及Activity的兩個(gè)生命周期,在onCreate中創(chuàng)建視圖歌溉,在onResume中展示視圖垄懂。
在onCreate的過程中會創(chuàng)建PhoneWindow、WindowManager和DecorView痛垛,視圖在解析之后添加到DecorView中草慧,DecorView會保存在PhoneWindow中。
在onResume中DecorView會被WindowManager的ViewRootImpl進(jìn)行繪制匙头,也就是調(diào)用view的三大方法onMeasure漫谷、onLayout、onDraw蹂析。
簡單來說就是這么個(gè)流程舔示,但是里面的細(xì)節(jié)很多,看下面的內(nèi)容建議你先沖杯咖啡电抚。
onCreate-視圖創(chuàng)建過程
故事的開頭從performLaunchActivity開始惕稻,這是創(chuàng)建Activity開始的類,onCreate方法就是在這里調(diào)用的蝙叛。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
// 將目標(biāo)Activity的類文件加載到內(nèi)存中俺祠,并創(chuàng)建一個(gè)實(shí)例。由于所有Activity組件都是從Activity類
// 繼承下來的借帘,因此锻煌,我們就可以將前面創(chuàng)建的Activity組件保存在Activity對象activity中
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
// 創(chuàng)建Application對象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
// 使用ContextImpl對象appContext和ActivityClientRecord對象r來初始化Activity對象activity
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
// 調(diào)用成員變量mInstrumentation的callActivityOnCreate方法將Activity對象activity啟動起來,
// 會調(diào)用Activity的onCreate方法
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
return activity;
}
PhoneWindow創(chuàng)建
這里創(chuàng)建了Activity并且調(diào)用它的attach方法綁定了很多對象姻蚓,最后調(diào)用Activity的onCreate方法宋梧,attach方法綁定的對象中有一個(gè)window對象,我們看一下他的attach方法:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
// 初始化PhoneWindow對象(可以看到每一個(gè)Activity都有一個(gè)PhoneWindow)
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
// 創(chuàng)建WindowManager(實(shí)現(xiàn)類是WindowManagerImpl狰挡,另外一個(gè)是創(chuàng)建Dialog時(shí))
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
}
WindowManager創(chuàng)建
可以看到這里通過Window對象創(chuàng)建了PhoneWindow對象捂龄,接著設(shè)置PhoneWindow的各種參數(shù)释涛,其中最重要的是WindowManager對象,我們看一下他的setWindowManager方法:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
// 創(chuàng)建WindowManager的實(shí)現(xiàn)類WindowManagerImpl
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
因?yàn)閃indowManager是系統(tǒng)服務(wù)倦沧,這里通過getSystemService獲取了這個(gè)對象唇撬,然后又通過他的createLocalWindowManager方法又創(chuàng)建了一個(gè)WindowManager對象,我們看一下createLocalWindowManager方法:
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
這里直接new了一個(gè)WindowManagerImpl對象返回展融,這里我們也就知道WindowManager的實(shí)現(xiàn)類是WindowManagerImpl窖认,這樣的話每個(gè)Activity都會有一個(gè)WindowManager對象,這樣怎么統(tǒng)一管理呢告希?其實(shí)WindowManager只是一個(gè)代理類扑浸,真正管事的是WindowManager中的WindowManagerGlobal:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
可以看出WindowManagerGlobal是一個(gè)單例類,這樣即使有多個(gè)WindowManager對象最終也只有一個(gè)WindowManagerGlobal燕偶。我們看一下WindowManagerImpl的addView方法:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
其實(shí)就是調(diào)用WindowManagerGlobal的addView方法喝噪。
這里我們也講一下WindowManagerGlobal這個(gè)類,它里面緩存了一個(gè)app所有的DecorView和ViewRootImpl:
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
從這里也能看出他是真正對Window管理的類指么,他還有兩個(gè)非常重要的方法getWindowManagerService()和getWindowSession() :
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
try {
if (sWindowManagerService != null) {
ValueAnimator.setDurationScale(
sWindowManagerService.getCurrentAnimatorScale());
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowManagerService;
}
}
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
這里是跨進(jìn)程通信相關(guān)的東西了酝惧,getWindowManagerService獲取的是WindowManagerService,這是系統(tǒng)用來管理窗口的類伯诬。而getWindowSession是調(diào)用WindowManagerService的openSession方法獲取的Session晚唇,這個(gè)Session是干嘛的呢?其實(shí)他類似于WindowManagerImpl和WindowManagerGlobal的關(guān)系盗似,WindowManagerImpl的方法都是調(diào)用WindowManagerGlobal來實(shí)現(xiàn)的缺亮,而Session的方法其本上都是調(diào)用的WindowManagerService的方法,這里貼出來幾個(gè):
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
}
@Override
public void setInsets(IWindow window, int touchableInsets,
Rect contentInsets, Rect visibleInsets, Region touchableArea) {
mService.setInsetsWindow(this, window, touchableInsets, contentInsets,
visibleInsets, touchableArea);
}
@Override
public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
mService.getWindowDisplayFrame(this, window, outDisplayFrame);
}
其中的mService就是WindowManagerService桥言。
DecorView創(chuàng)建
到這里PhoneWindow和WindowManager都已經(jīng)創(chuàng)建完成,下面就到了DecorView的創(chuàng)建葵礼,這一步在onCreate方法的setContentView中号阿。
public void setContentView(@LayoutRes int layoutResID) {
// getWindow獲取的是PhoneWindow,所以這里是調(diào)用的PhoneWindow的setContentView方法
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
這里的getWindow方法獲取的就是在attach中創(chuàng)建的PhoneWindow對象鸳粉,我們看一下PhoneWindow的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.
// 根據(jù)layout的id加載一個(gè)布局扔涧,然后通過findViewById(R.id.content)加載出布局中id為content
// 的FrameLayout賦值給mContentParent,并且將該view添加到mDecor(DecorView)中
if (mContentParent == null) {// 第一次是空
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
// 沒有過度效果届谈,并且不是第一次setContentView枯夜,那么要先移除盛放setContentView傳遞進(jìn)來
// 的View的父容器中的所有子view
mContentParent.removeAllViews();
}
// 窗口是否需要過度顯示
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {// 不需要過度,加載id為layoutResID的視圖并且添加到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
// 繪制視圖
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
這個(gè)方法的信息量很大艰山,首先如果是第一次調(diào)用該方法會先調(diào)用installDecor方法創(chuàng)建DecorView湖雹,然后通過LayoutInflater.inflate方法將我們自己的布局加到mContentParent中,這個(gè)mContentParent是在installDecor方法中初始化的曙搬,其實(shí)就是id為content的布局摔吏。
installDecor方法
private void installDecor() {
mForceDecorInstall = false;
// 繼承FrameLayout鸽嫂,是窗口頂級視圖,也就是Activity顯示View的根View征讲,包含一個(gè)TitleView和一個(gè)ContentView
if (mDecor == null) {// 首次為空
// 創(chuàng)建DecorView(FrameLayout)
mDecor = generateDecor(-1);
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {// 第一次setContentView時(shí)為空
// 這個(gè)mContentParent就是后面從系統(tǒng)的frameworks\base\core\res\res\layout\目錄下加載出來
// 的layout布局(這個(gè)Layout布局加載完成后會添加到mDecor(DecorView)中)中的一個(gè)id為content的
// FrameLayout控件据某,這個(gè)FrameLayout控件用來盛放setContentView傳遞進(jìn)來的View
mContentParent = generateLayout(mDecor);
// 背景
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}
}
}
這里還不是創(chuàng)建DecorView的具體地方,而是通過generateDecor去創(chuàng)建诗箍,在DecorView創(chuàng)建完成之后會通過generateLayout方法用被創(chuàng)建的DecorView對象去構(gòu)造mContentParent癣籽,這兩個(gè)就是我們想要的了。
我們先來看DecorView的創(chuàng)建:
generateDecor
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) {// 從Activity的setContentView方法調(diào)用則為true
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {// 系統(tǒng)進(jìn)程時(shí)沒有Application的context滤祖,所以就用現(xiàn)有的context
context = getContext();
} else {// 應(yīng)用有application的Context
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
// 設(shè)置主題
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
這里就是創(chuàng)建DecorView的地方筷狼,是通過new一個(gè)DecorView對象,DecorView的構(gòu)造函數(shù)會把當(dāng)前的PhoneWindow對象傳過去氨距。
接著看mContentParent的創(chuàng)建:
generateLayout
/**
* 因?yàn)閟etContentView要初始化ActionBar桑逝,所以設(shè)置NoTittle要在setContentView之前
* <p>
* 這里返回的是frameworks\base\core\res\res\layout\目錄下layout布局中id為content的FrameLayout布局
* 用來放置setContentView中傳遞進(jìn)來的View
* <p>
* 可能的Layout:
* R.layout.screen_swipe_dismiss
* R.layout.screen_title_icons
* R.layout.screen_progress
* R.layout.screen_custom_title
* R.layout.screen_title
* R.layout.screen_simple_overlay_action_mode
* R.layout.screen_simple
*/
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
// 根據(jù)Window的屬性調(diào)用相應(yīng)的requestFeature
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);
}
// 獲取Window的各種屬性來設(shè)置flag和參數(shù)
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
false)) {
setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
& (~getForcedWindowFlags()));
}
// Inflate the window decor.
// 根據(jù)之前的flag和feature來加載一個(gè)layout資源到DecorView中,并把可以作為容器的View返回
// 這個(gè)layout布局文件在frameworks\base\core\res\res\layout\目錄下
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 {
/**
* 這個(gè)就是screen_simple.xml布局中的代碼俏让,后面的就是id為content的FrameLayout布局
*
* <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>
*/
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
// 根據(jù)layoutResource(布局id)加載系統(tǒng)中布局文件(Layout)并添加到DecorView中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// contentParent是用來添加Activity中布局的父布局(FrameLayout)楞遏,并帶有相關(guān)主題樣式,就是上面
// 提到的id為content的FrameLayout首昔,返回后會賦值給PhoneWindow中的mContentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
// 設(shè)置mDecor背景之類
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;
}
這個(gè)方法首先加載了一個(gè)布局文件寡喝,這個(gè)布局文件是系統(tǒng)提供的,不同的參數(shù)會加載不同的布局勒奇,創(chuàng)建界面的布局是R.layout.screen_simple预鬓,看一下這個(gè)布局:
<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è)LinearLayout,對照界面構(gòu)成圖的第二層赊颠,然后是一個(gè)ViewStub格二,這個(gè)東西其實(shí)是以前的ActionBar,現(xiàn)在我們系統(tǒng)都是用ToolBar竣蹦,我們開發(fā)甚至連ToolBar都不想用顶猜,所以這個(gè)基本沒用,和ViewStub平級的是一個(gè)FrameLayout痘括,他的id是content长窄,這里就有點(diǎn)奇怪了,這和上面的界面構(gòu)成圖不一樣呀纲菌,明明還有好幾個(gè)層級呢挠日,其實(shí)這是我們繼承AppCompatActivity造成的,原始加載的布局確實(shí)是這一個(gè)翰舌,只是在加載完之后AppCompatDelegateImpl替換了content嚣潜,替換成了下面的布局:
<androidx.appcompat.widget.ActionBarOverlayLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/decor_content_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<include layout="@layout/abc_screen_content_include"/>
<androidx.appcompat.widget.ActionBarContainer
android:id="@+id/action_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
style="?attr/actionBarStyle"
android:touchscreenBlocksFocus="true"
android:gravity="top">
<androidx.appcompat.widget.Toolbar
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:navigationContentDescription="@string/abc_action_bar_up_description"
style="?attr/toolbarStyle"/>
<androidx.appcompat.widget.ActionBarContextView
android:id="@+id/action_context_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:theme="?attr/actionBarTheme"
style="?attr/actionModeStyle"/>
</androidx.appcompat.widget.ActionBarContainer>
</androidx.appcompat.widget.ActionBarOverlayLayout>
不信你Activity改成繼承原來的Activity,這時(shí)候布局就會恢復(fù)原樣
拿到布局之后就會調(diào)用DecorView的onResourcesLoaded方法將布局添加到DecorView中:
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
final View root = inflater.inflate(layoutResource, null);
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) root;
initializeElevation();
}
這里使用的是inflate加載布局椅贱,然后直接addview郑原。
根布局加載完成之后會從根布局中里取id為content的子布局返回賦值給mContentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
到這里界面創(chuàng)建的過程講完了唉韭,下面該講顯示了,不對呀犯犁?還有statusBarBackground和navigationBarBackground呢属愤?沒有看到他們的創(chuàng)建呀,別急他們在后面呢酸役,他們并沒有被寫進(jìn)布局里住诸,而是在動態(tài)添加的。他們的添加過程在onResume過程里涣澡。
onResume-視圖顯示過程
視圖的顯示是從ActivityThread的handleResumeActivity方法中開始的贱呐,這里也是調(diào)用onResume調(diào)用的地方。
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
// 獲取DecorView入桂,此處r.window是PhoneWindow
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
// ViewManager是WindowManagerImpl
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) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 將DecorView添加到窗口中奄薇,調(diào)用的是WindowManagerImpl中的addView方法
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
if (r.activity.mVisibleFromClient) {
// 顯示Activity
r.activity.makeVisible();
}
}
}
首先performResumeActivity方法里沒有上面需要關(guān)注的,就是這里調(diào)用了onResume方法抗愁,這里就不把代碼貼出來了馁蒂,從這里也知道為什么在onCreate中無法通過getWidth()和 getMeasuredWidth()拿到view的尺寸了,因?yàn)檎{(diào)用onResume方法的時(shí)候view還沒開始繪制呢蜘腌。后面會 獲取Activity之前創(chuàng)建的DecorView并設(shè)置他的Visibility為View.INVISIBLE沫屡,然后在拿到Activity的WindowManager,這里是用的ViewManager 接收撮珠,因?yàn)閃indowManager是繼承ViewManager的沮脖,然后調(diào)用WindowManager的addView方法將DecorView添加進(jìn)去。最后調(diào)用Activity的makeVisible方法設(shè)置DecorView為View.VISIBLE芯急。
上面的方法主要就是WindowManager的addView方法勺届,從前面WindowManager的創(chuàng)建我們可以知道他的實(shí)現(xiàn)類是WindowManagerImpl,那就是調(diào)用到下面的代碼:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerImpl又會調(diào)用到WindowManagerGlobal的addView方法:
/**
* 窗口顯示時(shí)調(diào)用娶耍,添加視圖到窗口進(jìn)行顯示(Activity顯示或者Dialog顯示)
*
* @param view 要顯示的視圖(Layout布局)
* @param params 布局參數(shù)
* @param display 顯示參數(shù)對象(大小免姿、分辨率等)
* @param parentWindow 要顯示的窗口(Activity的PhoneWindow或者Dialog的PhoneWindow)
*/
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// 初始化ViewRootImpl
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);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
這里首先創(chuàng)建了一個(gè)ViewRootImpl,后面調(diào)用了他的setView方法并將DecorView傳進(jìn)去伺绽,看一下這個(gè)方法:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
try {
// 將mWindow發(fā)送給WindowManagerService,注冊進(jìn)去嗜湃,以后WMS接收按鍵奈应、Config等改變就會
// 通知mWindow,mWindow是一個(gè)Binder對象购披,其接收到消息就會做相應(yīng)處理杖挣,比如發(fā)送給主線程
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
} catch (RemoteException e) {
throw new RuntimeException("Adding window failed", e);
}
}
}
}
首先調(diào)用了requestLayout方法,這個(gè)方法就是繪制的主要方法刚陡。后面又通過跨進(jìn)程的方式調(diào)用了WindowManagerService的addToDisplay方法將window信息和回調(diào)傳過去惩妇。
先來看一下requestLayout方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 檢測線程株汉,如果不是在主線程拋出異常
checkThread();
// 標(biāo)記已經(jīng)處于強(qiáng)制布局的過程中了
mLayoutRequested = true;
// 往Handler發(fā)送一個(gè)重新布局和重繪的請求
scheduleTraversals();
}
}
這里有一個(gè)很重要的checkThread方法,顧名思義就是檢查線程是否是主線程的歌殃,我們確認(rèn)一下:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
這里拿mThread和當(dāng)前線程比較乔妈,如果不同就拋異常,mThread是在ViewRootImpl的構(gòu)造函數(shù)中賦值的
mThread = Thread.currentThread()
因?yàn)閂iewRootImpl是在主線程創(chuàng)建的所以mThread被賦的值就是主線程氓皱。
這里就解釋了為什么在onResume和onResume之前在子線程更新ui沒有拋異常的原因了路召。
檢查完線程之后會通過scheduleTraversals方法發(fā)送一個(gè)布局請求:
void scheduleTraversals() {
// 當(dāng)mTraversalScheduled為false,也就是沒有重繪請求或者沒有未執(zhí)行完的重繪時(shí)才開始重繪
if (!mTraversalScheduled) {
// 一旦開始重回此處設(shè)置為True波材,當(dāng)執(zhí)行完畢后調(diào)用unscheduleTraversals函數(shù)股淡,
// 重新設(shè)置為false,避免同時(shí)存在多次繪制
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 將消息放入消息處理器中廷区,最終調(diào)用doTraversal方法
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mTraversalRunnable就是我們需要執(zhí)行的消息:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 執(zhí)行View繪制流程
performTraversals();
}
}
這里就到了performTraversals方法唯灵,這是執(zhí)行繪制的最終方法也是最復(fù)雜的方法(有八百多行代碼),單講它就要一篇文章隙轻,我們就不仔細(xì)研究它了埠帕,講一下大概流程:
private void performTraversals() {
// 調(diào)用每一個(gè)View的dispatchAttachedToWindow方法
host.dispatchAttachedToWindow(mAttachInfo, 0);
// 通知View樹觀察者,窗口(Window)被添加了
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
// 1.第一步:測量
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 2.第二步:布局
performLayout(lp, mWidth, mHeight);
// 3.第三步:繪制
performDraw();
}
八百多行的代碼被我壓縮成這幾行大脉,首先是調(diào)用DecorView的 dispatchAttachedToWindow方法搞监,后面會調(diào)用ViewTreeObserver的回調(diào),這里也有很多東西镰矿,放開了講就太多了琐驴,以后再說吧。在接著就是dispatchApplyInsets秤标,前面說的狀態(tài)欄就在這里添加的绝淡,再往后就是三大流程了。我們先說狀態(tài)欄是怎么被添加的苍姜。
首先我們說的這個(gè)狀態(tài)欄有兩層牢酵,系統(tǒng)有一個(gè)顯示時(shí)間通知等等的view,而我們添加的狀態(tài)欄是它之下的一個(gè)帶有顏色方塊view而已衙猪,他只是填充在界面上馍乙,并不會影響系統(tǒng)的view。
從界面構(gòu)成圖我們可以看到狀態(tài)欄是在DecorView下面的垫释,說明狀態(tài)欄是DecorView中添加的丝格,如果你在DecorView中搜索statusbar最終應(yīng)該會找到下面的代碼:
public static final ColorViewAttributes STATUS_BAR_COLOR_VIEW_ATTRIBUTES =
new ColorViewAttributes(SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
com.android.internal.R.id.statusBarBackground,
FLAG_FULLSCREEN);
public static final ColorViewAttributes NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES =
new ColorViewAttributes(
SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT,
Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
com.android.internal.R.id.navigationBarBackground,
0 /* hideWindowFlag */);
這里是狀態(tài)欄和導(dǎo)航欄的屬性,被定義在這里棵譬,他們只在一個(gè)地方使用了:
private final ColorViewState mStatusColorViewState =
new ColorViewState(STATUS_BAR_COLOR_VIEW_ATTRIBUTES);
private final ColorViewState mNavigationColorViewState =
new ColorViewState(NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES);
我們看一下ColorViewState這個(gè)類:
private static class ColorViewState {
View view = null;
int targetVisibility = View.INVISIBLE;
boolean present = false;
boolean visible;
int color;
final ColorViewAttributes attributes;
ColorViewState(ColorViewAttributes attributes) {
this.attributes = attributes;
}
}
里面保存了一個(gè)狀態(tài)欄的所有信息显蝌,view參數(shù)其實(shí)就是我們要添加到布局中的東西,也可以稱他為狀態(tài)欄订咸。
那么在哪里創(chuàng)建的呢曼尊?ViewRootImpl的dispatchApplyInsets會去調(diào)用DecorView的dispatchApplyWindowInsets方法酬诀,接著調(diào)用onApplyWindowInsets方法:
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
final WindowManager.LayoutParams attrs = mWindow.getAttributes();
mFloatingInsets.setEmpty();
if ((attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0) {
// For dialog windows we want to make sure they don't go over the status bar or nav bar.
// We consume the system insets and we will reuse them later during the measure phase.
// We allow the app to ignore this and handle insets itself by using
// FLAG_LAYOUT_IN_SCREEN.
if (attrs.height == WindowManager.LayoutParams.WRAP_CONTENT) {
mFloatingInsets.top = insets.getSystemWindowInsetTop();
mFloatingInsets.bottom = insets.getSystemWindowInsetBottom();
insets = insets.inset(0, insets.getSystemWindowInsetTop(),
0, insets.getSystemWindowInsetBottom());
}
if (mWindow.getAttributes().width == WindowManager.LayoutParams.WRAP_CONTENT) {
mFloatingInsets.left = insets.getSystemWindowInsetTop();
mFloatingInsets.right = insets.getSystemWindowInsetBottom();
insets = insets.inset(insets.getSystemWindowInsetLeft(), 0,
insets.getSystemWindowInsetRight(), 0);
}
}
mFrameOffsets.set(insets.getSystemWindowInsets());
insets = updateColorViews(insets, true /* animate */);
insets = updateStatusGuard(insets);
if (getForeground() != null) {
drawableChanged();
}
return insets;
}
這里進(jìn)過一系列計(jì)算之后會調(diào)用updateColorViews方法:
WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
boolean navBarToRightEdge = isNavBarToRightEdge(mLastBottomInset, mLastRightInset);
boolean navBarToLeftEdge = isNavBarToLeftEdge(mLastBottomInset, mLastLeftInset);
int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset, mLastLeftInset);
updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
mWindow.mNavigationBarColor, mWindow.mNavigationBarDividerColor, navBarSize,
navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge,
0 /* sideInset */, animate && !disallowAnimate, false /* force */);
boolean statusBarNeedsRightInset = navBarToRightEdge
&& mNavigationColorViewState.present;
boolean statusBarNeedsLeftInset = navBarToLeftEdge
&& mNavigationColorViewState.present;
int statusBarSideInset = statusBarNeedsRightInset ? mLastRightInset
: statusBarNeedsLeftInset ? mLastLeftInset : 0;
updateColorViewInt(mStatusColorViewState, sysUiVisibility,
calculateStatusBarColor(), 0, mLastTopInset,
false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset,
animate && !disallowAnimate,
mForceWindowDrawsStatusBarBackground);
return insets;
}
這里兩次調(diào)用了updateColorViewInt方法,而且傳入的參數(shù)分別是狀態(tài)欄和導(dǎo)航欄的ColorViewState骆撇,我們要找的應(yīng)該就在這瞒御,我們到這個(gè)方法里看看:
private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin,
boolean animate, boolean force) {
View view = state.view;
if (view == null) {
if (showView) {
state.view = view = new View(mContext);
setColor(view, color, dividerColor, verticalBar, seascape);
view.setTransitionName(state.attributes.transitionName);
view.setId(state.attributes.id);
visibilityChanged = true;
view.setVisibility(INVISIBLE);
state.targetVisibility = VISIBLE;
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(resolvedWidth, resolvedHeight,
resolvedGravity);
if (seascape) {
lp.leftMargin = sideMargin;
} else {
lp.rightMargin = sideMargin;
}
addView(view, lp);
updateColorViewTranslations();
}
}
}
在這里會創(chuàng)建狀態(tài)欄View并設(shè)置他的顏色、id等屬性艾船,最后通過addView添加到DecorView中葵腹。
最后就是三大流程了。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
try {
// host是DecorView屿岂,安排自己的位置
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
無非就是調(diào)用view的measure践宴、layout和draw方法,performDraw方法我沒有貼出來爷怀,因?yàn)槔锩嫔婕傲擞布L制軟件繪制等等的東西嵌套的有點(diǎn)多阻肩,總之最終還是調(diào)用draw方法。到這里界面就繪制完成了运授,還記的前面performResumeActivity中調(diào)用的Activity的makeVisible方法嗎烤惊,經(jīng)過這一系列的調(diào)用之后最終視圖的展示出來。
總結(jié)
- PhoneWindow:Window的子類吁朦,主要負(fù)責(zé)了DecorView的創(chuàng)建和setContentView中布局文件的解析和添加柒室。
- DecorView:界面的根布局,是一個(gè)繼承FrameLayout的ViewGroup逗宜。
- WindowManager:管理一個(gè)app的所有Window雄右,緩存著所有的Window和ViewRootImpl,會把View的繪制請求轉(zhuǎn)發(fā)到ViewRootImpl中纺讲。
- ViewRootImpl:真正實(shí)現(xiàn)繪制的地方擂仍,所有View的根(雖然他是所有View的根,但是他并不是一個(gè)View熬甚,只是實(shí)現(xiàn)了ViewParent接口)逢渔,
DecorView也在他的管轄之下,所有的繪制請求都會到這里處理乡括。