1、概述
這篇文章主要講Window、WindowManager、WindowManagerService三者之間的關(guān)系及其運(yùn)行機(jī)制??偟膩碚fWindow表示的是一種抽象的功能集合,具體實(shí)現(xiàn)為PhoneWindow。WindowManager是外界訪問Window的入口,對Window的訪問必須通過WindowManager,而WindowManger和WindowManagerService的交互是一個(gè)IPC過程。Android中所有的視圖都是通過Window來呈現(xiàn)的,不管是Activity、Dialog、Toast,他們的視圖都是附加在Window上的。Window是一個(gè)抽象概念,每一個(gè)Window都對應(yīng)著一個(gè)View和一個(gè)ViewRootImpl,Window和View通過ViewRootImpl來建立聯(lián)系。View才是Window存在的實(shí)體,可以理解為WindowManager中的addView()方法,即為add一個(gè)Window。
如上圖所示我們平時(shí)的View由一個(gè)根view和一個(gè)Window綁定,然后這一個(gè)Window統(tǒng)一被WindowManagerService所管理。這些被WindowManagerService所管理的Window按照一定的次序和位置通過SurfaceFlinger顯示到最終屏幕上。上圖里面沒有提到WindowManager ,WindowManager 在其中扮演的是這么一個(gè)角色,用戶想添加更新或者移除Window是通過調(diào)用WindowManager 的方法來操作。而用戶的這些操作指令,從WindowManager 通過IPC傳遞到WindowManagerService,最后由WindowManagerService來統(tǒng)籌安排這些Window的布局和次序。接下來詳細(xì)講解一下其中的運(yùn)作機(jī)制。
2、Window
Window表示一個(gè)窗口的意思。其實(shí)不管是Activity、Dialog、還是Toast他們的視圖實(shí)際上都是附加在Window上的。Window 有三種類型,分別是應(yīng)用 Window、子 Window 和系統(tǒng) Window。應(yīng)用類 Window 對應(yīng)一個(gè) Acitivity,子 Window 不能單獨(dú)存在,需要依附在特定的父 Window 中,比如常見的一些 Dialog 就是一個(gè)子 Window。系統(tǒng) Window是需要聲明權(quán)限才能創(chuàng)建的 Window,比如 Toast 和系統(tǒng)狀態(tài)欄都是系統(tǒng) Window。
Window 是分層的,每個(gè) Window 都有對應(yīng)的 z-ordered,層級大的會(huì)覆蓋在層級小的 Window 上面,這和 HTML 中的 z-index 概念是完全一致的。在三種 Window 中,應(yīng)用 Window 層級范圍是 1~99,子 Window 層級范圍是 1000~1999,系統(tǒng) Window 層級范圍是 2000~2999,這些層級范圍對應(yīng)著 WindowManager.LayoutParams 的 type 參數(shù),如果想要 Window 位于所有 Window 的最頂層,那么采用較大的層級即可,很顯然系統(tǒng) Window 的層級是最大的,當(dāng)我們采用系統(tǒng)層級時(shí),需要聲明權(quán)限。
Window是一個(gè)抽象概念,每一個(gè)Window都對應(yīng)著一個(gè)View和一個(gè)ViewRootImpl,Window和View通過ViewRootImpl來建立聯(lián)系。如下圖所示:
其中ViewRootImpl負(fù)責(zé)對View的渲染,具體的如何操作的請看我的這篇文章:
3、WindowManager
WindowManager是整個(gè)窗口管理機(jī)制里面的樞紐,也是這邊重點(diǎn)要講的。WindowManager實(shí)現(xiàn)了ViewManager,這個(gè)接口定義了我們對于Window的基本操作:
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
這三個(gè)方法其實(shí)就是 WindowManager 對外提供的主要功能,即添加 View、更新 View 和刪除 View。看一個(gè)通過 WindowManager 添加 Window 的例子:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button floatingButton = new Button(MainActivity.this);
floatingButton.setText("button");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
0, 0,
PixelFormat.TRANSPARENT
);
// flag 設(shè)置 Window 屬性
layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
// type 設(shè)置 Window 類別(層級)
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
layoutParams.gravity = Gravity.CENTER;
WindowManager windowManager = getWindowManager();
windowManager.addView(floatingButton, layoutParams);
}
}
如下效果:
代碼中并沒有調(diào)用 Activity 的 setContentView 方法,而是直接通過 WindowManager 添加 Window,并將其type設(shè)置為TYPE_APPLICATION_OVERLAY。表示在所有應(yīng)用之上。由效果圖可以看到,這個(gè)Button并不是在MainActivity對應(yīng)的那個(gè)View里面,而是一個(gè)類似獨(dú)立的存在。其實(shí)從這里可以感受到真正承載View的其實(shí)是Window,同時(shí)也可以猜出,之所以Activity會(huì)對應(yīng)一個(gè)頁面是因?yàn)锳ctivity持有Window從而來持有View,對于就窗口顯示來說,Activity 其實(shí)不是必須存在的。比如我們常用的Toast ,它其實(shí)就沒有與之對應(yīng)的Activity,而是類似于上述Button產(chǎn)生的方式,產(chǎn)生在界面上的。接下來我們用源碼的角度看下Activity中是如何創(chuàng)建Window的。
3.1 Activity 的 Window 創(chuàng)建過程
Activity的啟動(dòng)過程請參見我的這篇文章:Android Activity啟動(dòng)過程-從桌面點(diǎn)擊圖標(biāo)到調(diào)用Activity的OnCreate 。在Activity啟動(dòng)過程中ActivityThread會(huì)調(diào)用performLaunchActivity()這個(gè)函數(shù),這個(gè)函數(shù)里面會(huì)經(jīng)過層層深入會(huì)調(diào)用Activity的OnCreate()方法,而在performLaunchActivity()中調(diào)用OnCreate()方法前會(huì)調(diào)用Activity的attach()方法,今天我們要關(guān)注的就是這個(gè)方法:
// ActivityThread.class
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) {
...
// 創(chuàng)建PhoneWindow并設(shè)置其回調(diào)接口
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
// 將該Window和WindowManager綁定
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
...
// 設(shè)置管理Activity的Window的WindowManager
mWindowManager = mWindow.getWindowManager();
...
}
在attach()這個(gè)方法中創(chuàng)建了Activity的Window,同時(shí)為該Window綁定WindowManager,將這個(gè)WindowManager傳給Activity的成員變量mWindowManager。同時(shí)我們可以看到Window 的具體實(shí)現(xiàn)類是PhoneWindow。
進(jìn)去setWindowManager()方法中看一下:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
...
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
該方法里面看到如果傳入的wm 是空的調(diào)用將其賦值(WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
這里其實(shí)是獲取了WindowManagerService,當(dāng)然由于WindowManagerService和activity的應(yīng)用不在一個(gè)進(jìn)程里,這里是通過Binder通信獲取的一個(gè)WindowManagerService代理。進(jìn)程間Binder通信機(jī)制請看我的這篇文章:Android Binder進(jìn)程間通信機(jī)制。
獲取完WindowManagerService代理后通過它來創(chuàng)建出一個(gè)真正要用的WindowManager并賦值給。即這句代碼:
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
我們來看一下createLocalWindowManager()這個(gè)函數(shù):
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
這個(gè)函數(shù)很簡單,只是創(chuàng)建了一個(gè)WindowManagerImpl對象。從這里可以看到WindowManager真正的實(shí)現(xiàn)類是WindowManagerImpl。
到此我們對Activity的attach()方法分析的差不多了,這個(gè)方法主要做了創(chuàng)建出Activity的Window對象,并且獲取了管理該Window的WindowManager。而此時(shí)其實(shí)并沒有將Activity對應(yīng)的View附屬到這個(gè)Window中。而將這個(gè)View附屬到Window的代碼其實(shí)就是我們在OnCreate() 中常調(diào)用的setContentView(int layoutResID):
// Activity.class
public void setContentView(int layoutResID){
getWindow().setContentView(layoutResID);
...
}
該方法調(diào)用最終調(diào)用的是getWindow()的setContentView(layoutResID),而getWindow()獲取的就是在attach()中創(chuàng)建的PhoneWindow,我們再進(jìn)去看一下:
// PhoneWindow.class
public void setContentView(int layoutResID) {
...
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
···
}
這里如果沒有DecorView 就創(chuàng)建一個(gè),DecorView 是 Activity 中的頂級 View,是一個(gè) FrameLayout,一般來說它的內(nèi)部包含標(biāo)題欄和內(nèi)容欄,但是這個(gè)會(huì)隨著主題的變化而改變,不管怎么樣,內(nèi)容欄是一定存在的,并且有固定id:”android.R.id.content”。而內(nèi)容欄里面就是將用戶寫的layout放進(jìn)去,通過這行代碼:
mLayoutInflater.inflate(layoutResID, mContentParent);
之后再回調(diào)onContentChanged()通知Activity 視圖已經(jīng)發(fā)生改變。此時(shí)就將View和Window關(guān)聯(lián)了起來。不過現(xiàn)在任然沒有將對應(yīng)的畫面展示到手機(jī)屏幕上。因?yàn)榇藭r(shí)還沒講Window加入到WindowManager 中,更別提提交給WindowManagerService來統(tǒng)籌安排展示了。
Window加入到WindowManager這一過程要在調(diào)用完Acitivy的onResume()方法后,之后會(huì)調(diào)用Activity的makeVisible():
// Activity.class
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
這個(gè)函數(shù)里首先判斷Window是否已經(jīng)添加到WindowManager中,沒有的話取出在剛剛attach()方法中創(chuàng)建的WindowManager,將DecorView加入進(jìn)去,這里的DecorView其實(shí)就是Window所持有那個(gè)。然后再將DecorView設(shè)置為顯示狀態(tài),來顯示我們的布局。
至此才正真將Window加入到WindowManager中。接下來我們看一下WindowManager里面的幾個(gè)核心方法。
3.2 WindowManager的核心方法
在實(shí)際使用中無法直接訪問 Window,對 Window 的訪問必須通過 WindowManager。WindowManager 提供的三個(gè)接口方法 addView、updateViewLayout 以及 removeView 都是針對 View 的,而這些View都被其對應(yīng)的Window所持有,所以上面這些操作實(shí)際上相當(dāng)于對Window 的操作,WindowManager 是一個(gè)接口,它的真正實(shí)現(xiàn)由上文可知是 WindowManagerImpl類。
看一下WindowManagerImpl的這三個(gè)方法:
@Override
public void addView(View view, ViewGroup.LayoutParams params){
...
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params){
...
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view){
...
mGlobal.removeView(view, false);
}
這3個(gè)方法都交給了mGlobal去實(shí)現(xiàn),而mGlobal是一個(gè)WindowManagerGlobal類。我們這邊看一下WindowManagerGlobal的addView()方法,updateViewLayout()方法和removeView()方法原理類似,只是一個(gè)用來添加View,另兩個(gè)用來更新和移除view。
// WindowManagerGlobal.class
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
...
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
...
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
...
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
}
}
}
在 WindowManagerGlobal 內(nèi)部有如下幾個(gè)集合比較重要
private final ArrayListmViews = new ArrayList();
private final ArrayListmRoots = new ArrayList();
private final ArrayListmParams = new ArrayList();
private final ArraySetmDyingViews = new ArraySet();
其中 mViews 存儲(chǔ)的是所有 Window 所對應(yīng)的 View,mRoots 存儲(chǔ)的是所有 Window 所對應(yīng)的 ViewRootImpl,mParams 存儲(chǔ)的是所有 Window 所對應(yīng)的布局參數(shù),mDyingViews 存儲(chǔ)了那些正在被刪除的 View 對象,或者說是那些已經(jīng)調(diào)用了 removeView 方法但是操作刪除還未完成的 Window 對象。在addView ()方法中將這些相關(guān)對象添加到對應(yīng)集合中。在最后調(diào)用root.setView(view, wparams, panelParentView);方法,而root一個(gè)ViewRootImpl類。我們到setView()方法中看一下:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();
...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
...
}
}
}
setView()中會(huì)調(diào)用一個(gè)很重要的方法requestLayout(),其主要是來刷新頁面的。具體詳情請看我的這篇文章:Android UI刷新機(jī)制。我們今天要關(guān)注的是這個(gè)方法:
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
mWindowSession 的類型是 IWindowSession,它是一個(gè) Binder 對象,真正的實(shí)現(xiàn)類是 Session,這也就是之前提到的 IPC 調(diào)用的位置。也就是說在這里,完成了WindowManager和WindowManagerService 的通信,將Window 信息傳給了WindowManagerService:
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams, attrs, int viewVisibility,
int displayId, Rect outContentInsets, InputChannel outInputChannel){
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outInputChannel);
}
4、WindowManagerService
上文已經(jīng)通過WindowManagerService 的代理調(diào)用了其addWindow()方法,該方法非常復(fù)雜。主要的操作就是將傳來的Window 根據(jù)它的參數(shù),尤其是其type,保存起來。方便SurfaceFlinger渲染。
WindowManagerService服務(wù)大致按照以下方式來控制哪些窗口需要顯示的以及要顯在哪里:
① 由于每一個(gè)Activity窗口的大小都等于屏幕的大小,因此,只要對每一個(gè)Activity窗口設(shè)置一個(gè)不同的Z軸位置,然后就可以使得位于最上面的,即當(dāng)前被激活的Activity窗口,才是可見的。
② 每一個(gè)子窗口的Z軸位置都比它的父窗口大,但是大小要比父窗口小,這時(shí)候Activity窗口及其所彈出的子窗口都可以同時(shí)顯示出來。
③ 對于非全屏Activity窗口來說,它會(huì)在屏幕的上方留出一塊區(qū)域,用來顯示狀態(tài)欄。這塊留出來的區(qū)域稱對于屏幕來說,稱為裝飾區(qū)(decoration),而對于Activity窗口來說,稱為內(nèi)容邊襯區(qū)(Content Inset)。
④ 輸入法窗口只有在需要的時(shí)候才會(huì)出現(xiàn),它同樣是出現(xiàn)在屏幕的裝飾區(qū)或者說Activity窗口的內(nèi)容邊襯區(qū)的。
⑤ 對于壁紙窗口,它出現(xiàn)需要壁紙的Activity窗口的下方,這時(shí)候要求Activity窗口是半透明的,這樣就可以將它后面的壁紙窗口一同顯示出來。
⑥ 兩個(gè)Activity窗口在切換過程,實(shí)際上就是前一個(gè)窗口顯示退出動(dòng)畫而后一個(gè)窗口顯示開始動(dòng)畫的過程,而在動(dòng)畫的顯示過程,窗口的大小會(huì)有一個(gè)變化的過程,這樣就導(dǎo)致前后兩個(gè)Activity窗口的大小不再都等于屏幕的大小,因而它們就有可能同時(shí)都處于可見的狀態(tài)。事實(shí)上,Activity窗口的切換過程是相當(dāng)復(fù)雜的,因?yàn)榧磳⒁@示的Activity窗口可能還會(huì)被設(shè)置一個(gè)啟動(dòng)窗口(Starting Window)。一個(gè)被設(shè)置了啟動(dòng)窗口的Activity窗口要等到它的啟動(dòng)窗口顯示了之后才可以顯示出來。
同時(shí)在Android系統(tǒng)中,WindowManagerService服務(wù)是通過一個(gè)實(shí)現(xiàn)了WindowManagerPolicy接口的策略類來計(jì)算一個(gè)窗口的位置和大小的。例如,在Phone平臺上,這個(gè)策略類就是PhoneWindowManager。這樣做的好處就是對于不同的平臺實(shí)現(xiàn)不同的策略類來達(dá)到不同的窗口控制模式。
5、總結(jié)
總的來說,整個(gè)流程大致如下:
① 創(chuàng)建Window,將View和ViewRootImpl同Window綁定。
② WindowManager的addView()、updateViewLayout() 和 removeView()方法操作Window。
③ 將WindowManager這些操作方法轉(zhuǎn)移給WindowManagerGobal來調(diào)用。
④ 調(diào)用與Window綁定的ViewRootImpl的setView()方法。
⑤ setView()方法里面會(huì)通過mWindowSession這個(gè)Binder對象將Window傳給WindowManagerService。
⑥ WindowManagerService來管理各個(gè)Window的大小和顯示位置,來讓SurfaceFlinger渲染。