前言
在Activity
的啟動過程中馋艺,通常我們在onCreate
生命周期中調(diào)用setContentView
方法設(shè)置布局文件(即xml文件)躲叼,似乎這樣就完成了布局文件對應(yīng)的View
的繪制及顯示亲轨,那么背后具體的原理是什么呢醇坝?
?? Activity onCreate生命周期
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
?? 布局文件
setContentView(R.layout.activity_main);
}
很明顯布局文件對應(yīng)的View
的繪制和顯示是伴隨著Activity
的生命周期而進行的扰柠。在Activity
的啟動過程中,ActivityThread
類中的handleLaunchActivity
藐吮,performLaunchActivity
溺拱,handleResumeActivity
這3個主要的方法完成了Activity
的創(chuàng)建到啟動工作,完成了Activity
的onCreate谣辞、onStart迫摔、onResume
這三個生命周期的執(zhí)行。
具體的一個Activity啟動過程可以參考Android Activity生命周期泥从,啟動模式句占,啟動過程詳解
讓我們伴隨著Activity
的生命周期執(zhí)行過程,來具體進行分析和相關(guān)源碼解讀躯嫉。先來張總結(jié)本文的圖:
Activity啟動過程
我們按照Activity
的啟動過程中涉及到的ActivityThread
類三個方法來逐步說明纱烘,首先來看handleLaunchActivity
方法。
1. ActivityThread#handleLaunchActivity()方法:調(diào)用performLaunchActivity()和handleResumeActivity()方法
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
// ?? 此方法中完成 Activity 生命周期的 onCreate 和 onStart 方法
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
...
// ?? 此方法中完成 Activity 生命周期的 onResume 方法
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
...
}
...
}
handleLaunchActivity()
方法中主要調(diào)用了performLaunchActivity()
方法和handleResumeActivity()
方法祈餐,performLaunchActivity()
中完成onCreate
和onStart
回調(diào)擂啥;handleResumeActivity()
中完成onResume
回調(diào)。
2. ActivityThread#performLaunchActivity()方法:執(zhí)行onCreate昼弟、onStart生命周期
performLaunchActivity()
方法中主要做的工作包括:
- 1?? 創(chuàng)建
Activity
實例:調(diào)用mInstrumentation.newActivity
方法來創(chuàng)建Activity
對象; - 2?? 創(chuàng)建
Application
對象奕筐,如果已經(jīng)創(chuàng)建就不再創(chuàng)建舱痘,一個進程只有一個Application
對象; - 3?? 調(diào)用
Activity
的attach
方法离赫,在其中創(chuàng)建了PhoneWindow
對象(有關(guān)PhoneWindow
對象下文再慢慢解釋)芭逝; - 4?? 調(diào)用了
Instrumentation
的callActivityOnCreate
方法,從而間接執(zhí)行了onCreate
生命周期渊胸; - 5?? 調(diào)用了
Activity
的performStart
方法旬盯,從而間接執(zhí)行了onStart
生命周期。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
// ?? 通過反射創(chuàng)建 Activity 實例
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
// ?? 通過反射構(gòu)建 Application翎猛,如果已經(jīng)構(gòu)建則不會重復(fù)構(gòu)建胖翰,一個進程只能有一個 Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
...
// ?? 在這里實例化了 PhoneWindow,并將該 Activity 設(shè)置為 PhoneWindow 的 Callback 回調(diào)切厘;
// 初始化 WindowManager
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config);
...
// ?? 調(diào)用了 Activity 的 performCreate 方法萨咳,間接調(diào)用了 Activity 的 onCreate 方法
mInstrumentation.callActivityOnCreate(activity, r.state);
// ?? 調(diào)用 Activity 的 onStart 方法
if (!r.activity.mFinished) {
activity.performStart();
r.stopped = false;
}
...
}
}
performLaunchActivity()
方法的前兩步工作:Activity
對象和Application
對象的創(chuàng)建過程,這里不再具體描述疫稿,可以參考Android Activity生命周期培他,啟動模式鹃两,啟動過程詳解。
下面重點看下attach()
方法舀凛,其中創(chuàng)建了PhoneWindow
對象俊扳,而PhoneWindow
是Window
的一個子類(其實也是唯一子類)。
要想理解PhoneWindow
的作用猛遍,肯定要先知道Window
這個概念馋记。
Window:視圖容器
Window代表一個窗口,是視圖的容器螃壤。Android中的視圖是以View樹的形式組織的抗果,而View樹必須依附在Window上才能工作,一個Window對應(yīng)著一個View樹奸晴。Activity并不負責(zé)視圖控制冤馏,它只是控制生命周期和處理事件,真正控制視圖的是Window寄啼。
啟動Activity時會創(chuàng)建一個Window逮光,顯示Dialog時也會創(chuàng)建一個Window,而顯示Toast時也會創(chuàng)建Window墩划,因此Activity內(nèi)部可以有多個Window涕刚。由于View的測量、布局乙帮、繪制只是在View樹內(nèi)進行的杜漠,因此一個Window內(nèi)View的改動不會影響到另一個Window。Window是一個抽象類察净,它只有一個實現(xiàn)類PhoneWindow
驾茴,也就是說在PhoneWindow中完成了Window的真正工作。
- 作者:青霉素
- 鏈接:https://juejin.im/post/5d00cc9fe51d4510624f97b4
- 來源:掘金
Activity#attach()方法:實例化PhoneWindow
介紹完Window
和PhoneWindow
后氢卡,再回到attach
方法锈至。其中主要做了以下兩步工作:
- 1?? 實例化PhoneWindow:直接new一個
PhoneWindow
對象; - 2?? 為Activity的Window設(shè)置WindowManager:Android 中對 Window 的管理都是通過
WindowManager
來完成的译秦,創(chuàng)建PhoneWindow
之后還會為該 Window 對象設(shè)置WindowManager
峡捡,WindowManager
是一個接口,繼承ViewManager
接口筑悴,從這里也能看出對 Window 的操作其實就是對 View 的操作们拙。WindowManager
的實現(xiàn)類是WindowMangerImpl
。具體關(guān)于 Window和WindowManager
的關(guān)系下文再說阁吝。
- 作者:任教主來也
- 鏈接:http://www.reibang.com/p/a7e9797094eb
- 來源:簡書
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) {
...
// ?? 實例化 PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
...
// ?? 為 Activity 的 Window 設(shè)置 WindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager();
...
}
Activity#onCreate生命周期:調(diào)用setContentView方法(設(shè)置Activity對應(yīng)的布局)
在performLaunchActivity()
方法的第4??步中睛竣,調(diào)用了Instrumentation
的callActivityOnCreate
方法,源碼較為簡單求摇,如下:
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}
// ?? 然后調(diào)用 Activity 的 performCreate 方法
final void performCreate(Bundle icicle) {
performCreate(icicle, null);
}
// ?? 執(zhí)行 onCreate 生命周期
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
....
if (persistentState != null) {
onCreate(icicle, persistentState);
} else {
onCreate(icicle);
}
......
}
Activity#setContentView方法
在onCreate
方法中射沟,我們通過setContentView
來加載我們定義的布局文件殊者。Activity的源碼中提供了三個重載的setContentView方法,如下:
// ?? 最常用的 setContentView 方法
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
這三種方法都先調(diào)用了getWindow()
的setContentView
方法验夯,而getWindow()
方法返回的正是之前創(chuàng)建的PhoneWindow
對象猖吴;然后調(diào)用Activity的initWindowDecorActionBar
方法。
// ?? Activity 中 Window 成員變量
private Window mWindow;
public Window getWindow() {
// ?? 這個 mWindow 是在 attach 方法中設(shè)置的挥转;
// ?? mWindow 是一個 PhoneWindow 對象
return mWindow;
}
所以最終調(diào)用了PhoneWindow
中的setContentView
方法海蔽。
PhoneWindow#setContentView方法
PhoneWindow
中也有三個對應(yīng)的重載setContentView方法,下面以參數(shù)為布局文件
id(layoutResID)的方法進行說明绑谣,其中大概做了以下幾步工作:
- 1?? 初始化:首先判斷
mContentParent
是否為null党窜,如果為 null,則調(diào)用installDecor()
方法初始化mContentParent
借宵,此方法后續(xù)會說明幌衣;
mContentParent 用來裝xml布局文件解析出來的view樹,是一個FrameLayout壤玫,后文還會繼續(xù)說明其作用豁护。
- 2?? 填充布局:默認情況下會將設(shè)置的布局文件解析為View樹,并添加到
mContentParent
中欲间; - 3?? 通知Activity布局改變:通過相關(guān)回調(diào)通知Activity布局已發(fā)改變楚里。
public void setContentView(int layoutResID) {
// ?? 首先判斷 mContentParent 是否為 null,如果是第一次調(diào)用猎贴,則調(diào)用 installDecor() 方法
if (mContentParent == null) {
??
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
// 否則判斷是否設(shè)置 FEATURE_CONTENT_TRANSITIONS Window屬性(默認false)班缎,
// 如果沒有就移除該 mContentParent 內(nèi)所有的所有子View;
mContentParent.removeAllViews();
}
// ?? 填充布局
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext()););
transitionTo(newScene);
} else {
// 將xml資源文件通過 LayoutInflater 轉(zhuǎn)換為 View 樹
// 并且添加至 mContentParent 視圖中
??
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
// ?? 通知 Activity 布局改變
// 獲取 Callback她渴,在 Activity attach 方法中通過 setCallback 設(shè)置
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
// onContentChanged 是個空方法达址,當(dāng) Activity 的布局改動時,
// 即 setContentView() 或者 addContentView() 方法執(zhí)行完畢時就會調(diào)用該方法
??
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
PhoneWindow
的setContentView
方法將我們想要顯示的View添加到mContentParent
中惹骂,而mContentParent
又是由installDecor
方法得來苏携,下面我們關(guān)注此方法做瞪。
DecorView:窗口頂層視圖
再介紹installDecor()
方法之前对粪,我們需要介紹一個重要的概念:DecorView。
installDecor()
方法中重點完成了兩個對象的初始化装蓬,一個是mDecor
對象著拭,另一個就是之前說的mContentParent
對象,這兩個對象的定義如下:
// ?? 是一個 DecorView 對象牍帚,而 DecorView 是窗口頂層視圖
DecorView mDecor;
// ?? 是一個 ViewGroup 儡遮,本質(zhì)上是一個 FrameLayout
ViewGroup mContentParent;
這里讓我們先回顧下已經(jīng)提到的概念,Activity(調(diào)用setContentView
設(shè)置布局)暗赶,PhoneWindow
(setContentView中完成 mContentParent 初始化鄙币,填充布局等)肃叶,這兩個與視圖有關(guān)的概念并未真正直接承載視圖,而承載視圖的便是 DecorView 對象十嘿,即此處的 mDecor
因惭,DecorView
繼承自 FrameLayout
,如下所示:
public class DecorView extends FrameLayout
DecorView
既然是一個FrameLayout绩衷,那么肯定也有其對應(yīng)的布局文件蹦魔,只不過是系統(tǒng)默認設(shè)置的布局文件,由于在不同的Activity主題情況下咳燕,系統(tǒng)默認的 DecorView 對應(yīng)的布局不一樣勿决,我們這里以其中一種舉例說明:
// ?? screen_simple.xml 為例
<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
?? 對應(yīng) mContentParent
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>
那么mDecor
和 mContentParent
之間是什么關(guān)系呢? DecorView
分為兩部分招盲,一部分是 ActionBar
(有的Activity Theme情況下沒有ActionBar)低缩,另一部分就是 mContentParent
(即上圖中id為content的FrameLayout)。
那么Activity宪肖、PhoneWindow表制、DecorView三者之間有什么關(guān)系呢?見下圖所示:
PhoneWindow#installDecor方法
介紹完DecorView
后控乾,我們再回頭介紹installDecor
方法的具體工作么介,其中主要做了以下幾步工作:
- 1?? 初始化
mDecor
:調(diào)用generateDecor
方法來創(chuàng)建mDecor
對象; - 2?? 初始化
mContentParent
:調(diào)用generateLayout
方法創(chuàng)建mContentParent
對象蜕衡;
private void installDecor() {
...
// ?? 初始化 mDecor
if (mDecor == null) {
// 如果 mDecor 為空壤短,則生成一個 DecorView 對象
mDecor = generateDecor(-1);
...
} else {
// 將 PhoneWindow 設(shè)置給 mDecor
mDecor.setWindow(this);
}
if (mContentParent == null) {
// ?? 初始化 mContentParent
mContentParent = generateLayout(mDecor);
...
}
}
// ?? generateDecor:初始化 DecorView
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
// ?? generateLayout:根據(jù)窗口的風(fēng)格,為 DecorView 選擇對應(yīng)的布局文件
protected ViewGroup generateLayout(DecorView decor) {
// 獲取窗口屬性
TypedArray a = getWindowStyle();
...
int layoutResource;
int features = getLocalFeatures();
// ?? 根據(jù)設(shè)定好的 features 值選擇不同的窗口布局文件慨仿,得到 layoutResource 值久脯,前文中曾以 screen_simple 舉例
// 注意:此處還有很多分支判斷代碼省略了
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
}
...
// ?? 把選中的窗口布局文件解析成 View 樹,并添加到 DecorView 中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// ?? 指定 contentParent 值镰吆,對應(yīng)的是布局文件中 id 為 content 的 View
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
// ?? 返回 contentParent帘撰,賦值給 mContentParent,所以 mContentParent 對應(yīng)的是 R.id.content
return contentParent;
}
介紹完installDecor
方法万皿,我們再來看看PhoneWindow.setContentView
方法中的第2??步工作:填充布局摧找,即默認情況下會將我們設(shè)置的布局文件解析為 View 樹,并添加到 mContentParent
中牢硅,現(xiàn)在我們應(yīng)該理解的更加清楚了蹬耘。
3. ActivityThread#handleResumeActivity()方法:執(zhí)行onResume生命周期
執(zhí)行完onCreate、onStart
生命周期后减余,來到了handleResumeActivity()
方法中執(zhí)行onResume
生命周期综苔,有一點需要注意的是,到目前為止,我們也僅是生成了一個Activity
如筛,一個PhoneWindow
堡牡,一個DecorView
,而并未真正的將我們需要顯示的內(nèi)容和Android系統(tǒng)進行交互杨刨,以進行View繪制悴侵,而onResume
是我們啟動Activity過程中的生命周期的最后一步,那我們有理由猜想有關(guān)繪制的執(zhí)行應(yīng)該在此生命周期中執(zhí)行拭嫁。此方法主要工作包括以下幾步:
- 1?? 執(zhí)onResume生命周期:調(diào)用
performResumeActivity
來間接執(zhí)行 Activity 的onResume
生命周期可免; - 2?? 獲取Activity的Window和DecorView:獲取這兩個變量賦值給相關(guān)變量,同時暫時使
DecorView
不可見做粤; - 3?? 獲取 WindowManager:在
Activity.attach()
方法中浇借,我們?yōu)锳ctivity設(shè)置了WindowManager
; - 4?? WindowManager添加DecorView:調(diào)用
WindowManager.addView
方法為Window添加DecorView
怕品; - 5?? 使得 DecorView 可見:調(diào)用
Activity.makeVisible
方法使得DecorView
重新可見妇垢。
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
// ?? 在其內(nèi)部間接執(zhí)行 Activity 的 onResume 方法,此時界面還不可見
ActivityClientRecord r = performResumeActivity(token, clearHide, reason);
...
final Activity a = r.activity;
...
// ?? 獲取 Window肉康,DecorView 等對象
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
// ?? 使 DecorView 不可見
decor.setVisibility(View.INVISIBLE);
// ?? 獲取 WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
if (a.mVisibleFromClient && !a.mWindowAdded) {
...
// ?? WindowManager 添加 DecorView闯估,此時依然不可見
wm.addView(decor, l);
...
}
...
if (r.activity.mVisibleFromClient) {
// ?? 使得 DecorView 可見
r.activity.makeVisible();
}
...
}
// ?? Activity.makeVisible方法:使得 DecorView 可見
void makeVisible() {
...
mDecor.setVisibility(View.VISIBLE);
}
可見到目前為止,還是沒有相關(guān)直接繪制View的操作吼和,但對DecorView
的操作有好幾處涨薪,重點是第4??步,其中調(diào)用WindowManager
的addView
方法添加DecorView炫乓。我們之前解釋過View必須依附于Window才能顯示刚夺,而Android 中對 Window 的管理都是通過 WindowManager 來完成的,那相關(guān)的繪制操作應(yīng)該就是在此方法中了末捣。
在理解addView
方法之前侠姑,我們先來看看WindowManager
的初始化過程,即Activity.attach
方法中調(diào)用的setWindowManager
方法:
// ?? Window.setWindowManager 方法
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
...
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
// ?? WindowManagerImpl.createLocalWindowManager 方法
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
可見此處WindowManager
它的真正實現(xiàn)是WindowManagerImpl
箩做,而WindowManagerImpl
也并沒有真正實現(xiàn)ViewManager
接口的三大操作(addView莽红,updateViewLayout,removeView)邦邦,而是交給了WindowManagerGlobal
安吁。
// ?? WindowManagerImpl.addView 方法
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
?? WindowManagerGlobal對象
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
mGlobal
對象的初始化如下,就是 WindowManagerGlobal
的單例模式圃酵。
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
WindowManagerGlobal#addView方法
下面再來看看 WindowManagerGlobal.addView
方法柳畔,主要做了兩步工作:
- 1?? 初始化ViewRootImpl:
ViewRootImpl
是視圖層次的最頂層馍管,連接WindowManagerService和DecorView的紐帶郭赐, 同時 ViewRootImpl 的內(nèi)容大部分都是 WindowManagerGlobal 類的內(nèi)部實現(xiàn)細節(jié); - 2?? 調(diào)用 ViewRootImpl.setView 方法:調(diào)用此方法來完成真正的添加 View 操作。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
// ?? ViewRootImpl 對象
ViewRootImpl root;
synchronized (mLock) {
...
// ?? 初始化 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
...
// ?? 調(diào)用 ViewRootImpl.setView 方法
root.setView(view, wparams, panelParentView);
}
}
ViewRootImpl#setView方法
真正完成添加 View 的操作是此方法:
// ?? ViewRootImpl.setView 方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
??
requestLayout();
...
??
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
...
?? 將 DecorView 的 parent 設(shè)置為 ViewRootImpl
view.assignParent(this);
...
}
此方法中做的工作主要包括以下兩個方面:
- 1?? requestLayout()方法:調(diào)用View的測量捌锭,布局俘陷,繪制三個流程;
- 2?? mWindowSession.addToDisplay()方法:mWindowSession 是一個aidl观谦,ViewRootImpl 利用它來和 WindowManagerService 進行跨進程交互拉盾,這里先不過多介紹有關(guān)
WindowManagerService
內(nèi)容,只簡單介紹下其作用豁状,如下捉偏。
WindowManagerService(WMS)的作用有很多:
- 窗口的管理者:負責(zé)窗口的啟動、添加和刪除泻红,另外窗口的大小和層級也是由WMS進行管理夭禽;
- 事件的管理和派發(fā)工作:通過對窗口的觸摸從而產(chǎn)生觸摸事件,InputManagerService(IMS)會對觸摸事件進行處理谊路,它會尋找一個最合適的窗口來處理觸摸反饋信息讹躯,WMS是窗口的管理者,因此缠劝,WMS“理所應(yīng)當(dāng)”的成為了事件的中轉(zhuǎn)站潮梯。
- Surface管理:窗口并不具備有繪制的功能,因此每個窗口都需要有一塊Surface來供自己繪制惨恭。為每個窗口分配Surface是由WMS來完成的秉馏。
我們重點看看 requestLayout()
方法,源碼如下:
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// ?? 檢查是不是主線程
checkThread();
mLayoutRequested = true;
// ?? view繪制三大流程入口
scheduleTraversals();
}
}
熟悉View繪制流程的同學(xué)應(yīng)該知道脱羡,當(dāng)View的大小沃饶、形狀發(fā)生了變化的時候,可以調(diào)用此方法來進行重新繪制轻黑,此方法會從View樹重新進行一次測量糊肤、布局、繪制這三個流程氓鄙。此方法主要做了兩步工作:
- 1?? 檢查當(dāng)前線程:如果調(diào)用此方法的現(xiàn)場不是主線程馆揉,那么就在
checkThread()
方法中拋出異常,這一點與我們"通常意義"上所說的:只能在主線程更新UI相吻合抖拦,具體checkThread()方法稍后寫明升酣; - 2?? 調(diào)用 scheduleTraversals 方法:此方法后續(xù)完成了View的三大流程(測量(measure),布局(layout)态罪,繪制(draw))噩茄,具體分析見后續(xù) 。
再看下 checkThread()
方法:
void checkThread() {
??
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
mThread
的初始化是在 ViewRootImpl
的構(gòu)造函數(shù)中完成的:
// ViewRootImpl 構(gòu)造函數(shù)
public ViewRootImpl(Context context, Display display) {
...
??
mThread = Thread.currentThread();
...
}
回憶下前文复颈, ViewRootImpl 的初始化是在 WindowManagerGlobal.addView 方法中完成的绩聘,因此mThread 肯定對應(yīng)的是主線程,因為 ActivityThread.handleLaunchActivity 方法就是在主線程中執(zhí)行的,而其中并未切換過線程凿菩。因此如果我們在子線程中更新UI机杜,那么最終會走到 requestLayout 方法進行重繪制,但此時會發(fā)現(xiàn) mThread(主線程) 和 Thread.currentThread()(子線程)不是同一個線程衅谷,那么便會拋出異常椒拗。
再來說說 ViewRootImpl ,其作用非常重大获黔。所有View的繪制以及事件分發(fā)等交互都是通過它來執(zhí)行或傳遞的蚀苛。View的三大流程(測量(measure),布局(layout)玷氏,繪制(draw))均通過ViewRootImpl來完成枉阵。Android的所有觸屏事件、按鍵事件预茄、界面刷新等事件都是通過ViewRootImpl進行分發(fā)的兴溜。
- 作者:Ruheng
- 鏈接:http://www.reibang.com/p/8766babc40e0
- 來源:簡書
從源碼實現(xiàn)上來看,ViewRooImpl 既非View的子類耻陕,也非View的父類拙徽,但是,它實現(xiàn)了ViewParent接口诗宣,這讓它可以作為View的名義上的父視圖膘怕。
在 ViewRootImpl.setView
方法中調(diào)用了 View.assignParent
方法:
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
}
...
}
View是以View樹組織的,每個View都有其Parent召庞,DecorView作為最頂層的View岛心,其Parent被設(shè)置為ViewRootImpl對象。這樣它可以作為View的名義上的父視圖篮灼,實質(zhì)上完成了View的更新忘古,繪制等工作。
View類中也有requestLayout
方法:用于 View 的位置诅诱,大小髓堪、形狀發(fā)生了變化的時候進行調(diào)用。當(dāng)一個子 View 調(diào)用此方法時娘荡,便會令 View 樹重新進行一次測量干旁、布局、繪制這三個流程炮沐,具體源碼如下:
// ?? View#requestLayout:
public void requestLayout() {
...
if (mParent != null && !mParent.isLayoutRequested()) {
// ?? 調(diào)用父容器的 `requestLayout` 方法
mParent.requestLayout();
}
...
}
此方法中最重要的是調(diào)用mParent.requestLayout
方法争群,是向父容器請求布局,即調(diào)用父容器的 requestLayout
方法大年,此方法沿著View樹向上傳遞换薄,最終來到了 DecorView#requestLayout中玉雾,而DecorView是頂層View,其mParent便是ViewRootImpl专控,所以子View的requestLayout方法,經(jīng)過層層傳遞遏餐,最終會被ViewRootImpl 接收并處理伦腐。
那具體的 scheduleTraversals
方法中主要完成了 View 的三大流程,本文暫且不進行分析失都。
總結(jié)
通過以上可以知道柏蘑,Activity就像個控制器,不負責(zé)視圖部分粹庞。Window像個承載器咳焚,裝著內(nèi)部視圖。DecorView是個頂層視圖庞溜,是所有View的最外層布局革半。ViewRootImpl像個連接器,負責(zé)溝通流码,通過硬件的感知(分發(fā)事件又官,繪制View內(nèi)容)來通知視圖,進行用戶之間的交互漫试。
- 作者:Ruheng
- 鏈接:http://www.reibang.com/p/8766babc40e0
- 來源:簡書
其實本文還存在兩個問題沒有說明:
- 1?? WindowManagerService具體原理:ViewRootImpl 和 WindowManagerService 進行跨進程交互的背后原理是什么六敬,怎么做到View的真正顯示?
- 2?? View繪制的三大流程:View 測量驾荣,布局外构,繪制的詳細流程是什么?
這些分析后續(xù)陸續(xù)進行說明播掷。