關(guān)于Activity,Window,View的關(guān)系一直有個(gè)模糊的印象,看別人的分析一般都這么理解Activity是管理Window,Window用來(lái)承載View,View是最終的視圖,也有說(shuō)Window的作用可有可無(wú)的,作用并不大的,并不是說(shuō)這些觀點(diǎn)有問(wèn)題,而是看了這么多后,會(huì)更迷惑,管理是怎么管理的,承載是怎么實(shí)現(xiàn)的,如果不自己根據(jù)源碼看一些,這些概念會(huì)一直是抽象的,遇到問(wèn)題還是沒(méi)法理解, 例如:
- 在Activity里調(diào)用
WindowManager.LayoutParams wl = new WindowManager.LayoutParams(); getWindowManager().addView(mView,wl)
和
LayoutParams wmParams =...
addContentView(mView,wmParams); //activity里的方法
這兩種方式背后的實(shí)現(xiàn)是怎樣的,有什么區(qū)別? - Dialog和PopupWindow的區(qū)別在哪里?為什么Dialog傳入application的Context會(huì)報(bào)錯(cuò)?
- ViewRootImpl是什么,一個(gè)Activity有多少個(gè)ViewRootImpl對(duì)象?
- 該怎樣理解Window?
Window的創(chuàng)建過(guò)程
之前一篇講解了Android 應(yīng)用點(diǎn)擊圖標(biāo)到Activity界面顯示的過(guò)程分析,接著分析界面的顯示過(guò)程來(lái)引入Activity中Window的創(chuàng)建,以及View的加載顯示過(guò)程。
在ActivityThread.performLaunchActivity中,創(chuàng)建Activity的實(shí)例,接著會(huì)調(diào)用Activity.attach()來(lái)初始化一些內(nèi)容,而Window對(duì)象就是在attach里進(jìn)行創(chuàng)建初始化賦值的塘辅。
Activity.attach
final void attach(...) {
...
mWindow = new PhoneWindow(this);
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();
...
}
Window.setWindowManager
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,boolean hardwareAccelerated) {
...
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
...
}
可看出Activity里新建一個(gè)PhoneWindow對(duì)象未辆。在Android中,Window是個(gè)抽象的概念,Android中Window的具體實(shí)現(xiàn)類(lèi)是PhoneWindow,Activity和Dialog中的Window對(duì)象都是PhoneWindow消请。
同時(shí)得到一個(gè)WindowManager對(duì)象,WindowManager是一個(gè)抽象類(lèi),這個(gè)WindowManager的具體實(shí)現(xiàn)實(shí)在WindowManagerImpl中,對(duì)比Context和ContextImpl。
每個(gè)Activity會(huì)有一個(gè)WindowManager對(duì)象,這個(gè)mWindowManager就是和WindowManagerService(WMS)進(jìn)行通信,也是WMS識(shí)別View具體屬于那個(gè)Activity的關(guān)鍵,創(chuàng)建時(shí)傳入IBinder 類(lèi)型的mToken回挽。
mWindow.setWindowManager(...,mToken, ...,...)
這個(gè)Activity的mToken,這個(gè)mToken是一個(gè)IBinder,WMS就是通過(guò)這個(gè)IBinder來(lái)管理Activity里的View镀娶。
接著在onCreate的setContentView中,
Activity.setContentView()
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
PhoneWindow.setContentView()
public void setContentView(int layoutResID) {
...
installDecor();
...
}
PhoneWindow.installDecor
private void installDecor() {
//根據(jù)不同的Theme,創(chuàng)建不同的DecorView,DecorView是一個(gè)FrameLayout
}
這時(shí)只是創(chuàng)建了PhoneWindow,和DecorView,但目前二者也沒(méi)有任何關(guān)系,產(chǎn)生利息的時(shí)刻是在ActivityThread.performResumeActivity中,再調(diào)用r.activity.performResume()奖蔓,調(diào)用r.activity.makeVisible,將DecorView添加到當(dāng)前的Window上赞草。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
WindowManager的addView的具體實(shí)現(xiàn)在WindowManagerImpl中,而WindowManagerImpl的addView又會(huì)調(diào)用WindowManagerGlobal.addView。
WindowManagerGlobal.addView
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
...
ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
...
}
這個(gè)過(guò)程創(chuàng)建一個(gè)ViewRootImpl,并將之前創(chuàng)建的DecoView作為參數(shù)闖入,以后DecoView的事件都由ViewRootImpl來(lái)管理了,比如DecoView上添加View,刪除View吆鹤。ViewRootImpl實(shí)現(xiàn)了ViewParent這個(gè)接口,這個(gè)接口最常見(jiàn)的一個(gè)方法是requestLayout()厨疙。
ViewRootImpl是個(gè)ViewParent,在DecoView添加的View時(shí),就會(huì)將View中的ViewParent設(shè)為DecoView所在的ViewRootImpl,View的ViewParent相同時(shí),理解為這些View在一個(gè)View鏈上疑务。所以每當(dāng)調(diào)用View的requestLayout()時(shí),其實(shí)是調(diào)用到ViewRootImpl沾凄,ViewRootImpl會(huì)控制整個(gè)事件的流程≈剩可以看出一個(gè)ViewRootImpl對(duì)添加到DecoView的所有View進(jìn)行事件管理撒蟀。
他們的關(guān)系可以用下面一張圖來(lái)大概表示,
注意mView和DecoView是同級(jí)關(guān)系,由不同ViewRootImpl控制,不在同一個(gè)View鏈上,之間沒(méi)有聯(lián)系。這個(gè)類(lèi)似于PopupWindow廊镜。
問(wèn)題解讀
第一個(gè)問(wèn)題
現(xiàn)在來(lái)看在Activity通過(guò) getWindowManager().addView(mView,wl)和 addContentView(mView,wmParams)的區(qū)別牙肝。第一種情況會(huì)調(diào)用到WindowManagerGlobal.addView,這時(shí)會(huì)創(chuàng)建一個(gè)新的ViewRootImpl,和原來(lái)的DecoView不在一條View鏈上,所以它們之間的任何一個(gè)調(diào)用requestLayout()不會(huì)影響到另一個(gè)唉俗。而addContentView(mView,wmParams)是直接將mView添加到DecoView中,會(huì)使ViewRootImpl鏈下的所以View重繪嗤朴。
第二個(gè)問(wèn)題
Dialog在創(chuàng)建時(shí)會(huì)新建一個(gè)PhoneWindow,同時(shí)也會(huì)使用DecoView作為這個(gè)PhoneWindow的根View,相當(dāng)于走了一遍Activity里創(chuàng)建PhoneWindow和DecoView的流程,而調(diào)用Dialog的show方法時(shí),類(lèi)似于ActivityThread.performResumeActivity,將DecoView添加到Window,同時(shí)創(chuàng)建管理DecoView鏈的RootViewImpl來(lái)管理DecoView。PopupWindow就和第一個(gè)問(wèn)題中 getWindowManager().addView(mView,wl)類(lèi)似了,只是創(chuàng)建一條新的View鏈和ViewRootImpl,并沒(méi)有創(chuàng)建新的Window虫溜。而Dialog通過(guò)非Activity的Context,如Application 和 Service,這是因?yàn)镈ialog通過(guò)傳入的Context來(lái)得到context里的mWindowManager(也就是WindowManagerImpl)與mToken,這是為了表明Dialog所屬的Activity,在Window.addView時(shí),需要這個(gè)mToken(IBinder對(duì)象),而Application 和 Service傳入的情況下Token是null雹姊。
第三個(gè)問(wèn)題
上面的分析可看出,ViewRootImpl是實(shí)際管理Window中所以View的類(lèi),每個(gè)Activity中ViewRootImpl數(shù)量取決于調(diào)用mWindowManager.addView的調(diào)用次數(shù)。
第四個(gè)問(wèn)題
Activity提供和WMS通信的Token(IBinder對(duì)象),DecoView結(jié)合ViewRootImpl來(lái)管理同一View鏈(有相同的ParentView的View,ViewRootImpl也就是ParentView)的所以View的事件,繪制等衡楞。那Window的意義在哪?雖然Window也就是PhoneWindow沒(méi)有具體做什么,但Window把Activity從View的一些創(chuàng)建,管理以及和ViewRootImpl的交互中脫離出來(lái),讓Activity與View盡量解耦,要不然這些工作都要放在Activity中午處理,Activity的任務(wù)就會(huì)變得更雜更重吱雏。為什么不能用一個(gè)ViewGroup比如DecoView來(lái)管理所有的View呢,因?yàn)橐粋€(gè)Activity可能有不止一條View鏈,總要有一個(gè)進(jìn)行管理的地方。View的意義就是將Activity和View的繁瑣工作中脫離出來(lái)瘾境。
總結(jié)
一句話總結(jié):Activity提供與AMS通信的Token(IBinder對(duì)象),創(chuàng)建Window為View提供顯示的地方,而具體的View管理任務(wù)由ViewRootImpl來(lái)完成歧杏。
讀別人的博客有個(gè)很大的好處是可以快速定位到關(guān)鍵點(diǎn),但具體想要真正的理解還需要自己趣深入研究。