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