一、概述
Android系統視窗管理是由WindowManagerService負責實作的.WindowManagerService(後面簡稱WMS)的代碼位于
frameworks/base/services/java/com/android/server/wm/WindowManagerService.java.
什麼是視窗?
視窗就是螢幕上的一塊矩形區域,可以顯示UI和與使用者互動.常見的比如:Dialog,Activity界面,狀态欄、Toast界面.站在系統的角度來說,
視窗其實是一個Surface(畫布).一個螢幕有多個視窗,而這多個視窗的布局和順序以及視窗動畫是由WMS管理的,然後由一個叫SurfaceFlinger的服務來對多個畫布内容混合和顯示出來.
WMS和SurfaceFlinger的關系如下圖
圖中的Z軸大小就是不同視窗顯示的順序,在Android裡叫Z-order.SurfaceFlinger将多塊Surface的内容按照Z-order進行混合并輸出到FrameBuffer(幀緩沖).
二、WMS的啟動
和AMS、PMS一樣,WMS也是在SystemServer的initAndLoop方法裡啟動的.
主要有3個階段:
1.建立WMS
2.做顯示準備工作
3.SystemServer啟動之後通知WMS
先看第一個階段
1.建立WMS
wm = WindowManagerService.main(context, power, display, inputManager, wmHandler, factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL, !firstBoot, onlyCore);
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
建立WMS執行個體,然後往ServiceManager注冊WMS.
看下main方法
public static WindowManagerService main(final Context context, final PowerManagerService pm, final DisplayManagerService dm, final InputManagerService im, final Handler wmHandler, final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore) {
final WindowManagerService[] holder = new WindowManagerService[1];
wmHandler.runWithScissors(new Runnable() {
@Override
public void run() {
holder[0] = new WindowManagerService(context, pm, dm, im, haveInputMethods, showBootMsgs, onlyCore);
}
}, 0);
return holder[0];
}
建立了一個WMS類型的數組,大小為1,然後new一個WMS執行個體,指派給該數組,最後傳回這個數組.
調用了WMS的有參構造函數
private WindowManagerService(Context context, PowerManagerService pm, DisplayManagerService displayManager, InputManagerService inputManager, boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) {
......
......
//建立視窗動畫執行個體
mAnimator = new WindowAnimator(this);
//在UI線程中初始化WindowManagerPolicy
initPolicy(UiThread.getHandler());
// 添加自己到Watchdog中
Watchdog.getInstance().addMonitor(this);
......
......
主要是建立視窗動畫,在UI線程中初始化WindowManagerPolicy,添加自己到Watchdog中.
這裡介紹下WindowManagerPolicy,WindowManagerPolicy是一個接口,它隻有一個實作類PhoneWindowManager.
它相當于WMS的中介,會為WMS處理視窗資訊.比如計算視窗尺寸等.
2.做顯示準備工作
public void displayReady() {
//顯示預設的尺寸、像素等配置
displayReady(Display.DEFAULT_DISPLAY);
synchronized (mWindowMap) {
//擷取螢幕
final DisplayContent displayContent = getDefaultDisplayContentLocked();
//讀取螢幕尺寸和像素等資訊
readForcedDisplaySizeAndDensityLocked(displayContent);
mDisplayReady = true;
}
......
......
}
會先配置預設的尺寸、像素等顯示資訊.然後擷取螢幕,讀取螢幕尺寸和像素等資訊.這裡要介紹下DisplayContent和mWindowMap.
DisplayContent是一塊螢幕.用清單儲存的.
SparseArray<DisplayContent> mDisplayContents = new SparseArray<DisplayContent>(2);
DisplayContent會根據視窗的位置顯示出視窗,屬于同一個DisplayContent的視窗就顯示在同一個螢幕裡.
mWindowMap一個HashMap,儲存了所有視窗的狀态資訊.
鍵為IBinder,值為WindowToken.
WindowToken是窗密碼牌的意思,用來标示該視窗的類别.視窗有Activity、InputMethod、Wallpaper以及Dream這幾種.不同的類别對應不同的WindowToken.
3.SystemServer啟動之後通知WMS
try {
wm.systemReady();
} catch (Throwable e) {
reportWtf("making Window Manager Service ready", e);
}
調用的是WindowManagerPolicy的systemReady方法
public void systemReady() {
mPolicy.systemReady();
}
三、視窗的添加和删除
WMS主要的功能如下
1. 視窗的添加和删除
2. 視窗的顯示和隐藏控制
3. Z-order順序管理
4. 焦點視窗和焦點應用的管理
5. 輸入法視窗管理和牆紙視窗管理
6. 視窗動畫管理
7. 系統消息收集和分發
這裡暫時隻介紹Activity視窗的添加.
AMS啟動一個應用時,會在服務端生成一個AppWindowToken,AppWindowToken繼承自WindowToken,标示為Activity的WindowToken.
Activity在請求WMS添加視窗時,提供這個AppWindowToken給WMS.
添加視窗的方法是addWindow.看代碼吧.
public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, InputChannel outInputChannel) {
......
......
//通過WindowManagerPolicy檢查權限,是否能添加視窗
int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
boolean reportNewConfig = false;
//添加子視窗的父視窗的視窗狀态
WindowState attachedWindow = null;
//子視窗的視窗狀态
WindowState win = null;
long origId;
//視窗類型,比如Toast、StatusBar
final int type = attrs.type;
synchronized (mWindowMap) {
//螢幕設定未初始化
if (!mDisplayReady) {
throw new IllegalStateException("Display has not been initialialized");
}
//如果重複添加了
if (mWindowMap.containsKey(client.asBinder())) {
Slog.w(TAG, "Window " + client + " is already added");
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
//如果是子視窗
if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
//傳回該子視窗在WMS對應的父視窗
attachedWindow = windowForClientLocked(null, attrs.token, false);
//如果父視窗不存在
if (attachedWindow == null) {
Slog.w(TAG, "Attempted to add window with token that is not a window: " + attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
// 若取得的父視窗也是子視窗,則列印:添加了錯誤的子視窗
if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
Slog.w(TAG, "Attempted to add window with token that is a sub-window: " + attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
}
if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
Slog.w(TAG, "Attempted to add private presentation window to a non-private display. Aborting.");
return WindowManagerGlobal.ADD_PERMISSION_DENIED;
}
boolean addToken = false;
//取出WindowToken
WindowToken token = mTokenMap.get(attrs.token);
if (token == null) {
// 對于子視窗來說,WmS中必須有對應的Token才能添加
if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
Slog.w(TAG, "Attempted to add application window with unknown token " + attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
// 如果是内置的輸入方法視窗,WmS中必須有對應的Token才能添加
if (type == TYPE_INPUT_METHOD) {
Slog.w(TAG, "Attempted to add input method window with unknown token " + attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
// 牆紙視窗,WmS中必須有對應的Token才能添加
if (type == TYPE_WALLPAPER) {
Slog.w(TAG, "Attempted to add wallpaper window with unknown token " + attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (type == TYPE_DREAM) {
Slog.w(TAG, "Attempted to add Dream window with unknown token " + attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
// 建立視窗
token = new WindowToken(this, attrs.token, -1, false);
addToken = true;
}
//Activity類型視窗
else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
AppWindowToken atoken = token.appWindowToken;
// appWindowToken值為空則列印資訊
if (atoken == null) {
Slog.w(TAG, "Attempted to add window with non-application token " + token + ". Aborting.");
return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
}
// 試圖使用存在的應用token添加視窗
else if (atoken.removed) {
Slog.w(TAG, "Attempted to add window with exiting application token " + token + ". Aborting.");
return WindowManagerGlobal.ADD_APP_EXITING;
}
// 對于内置的輸入方法視窗,token的windowType值要等于TYPE_INPUT_METHOD
if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) {
// No need for this guy!
if (localLOGV)
Slog.v(TAG, "**** NO NEED TO START: " + attrs.getTitle());
return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
}
}
//輸入法類型視窗
else if (type == TYPE_INPUT_METHOD) {
if (token.windowType != TYPE_INPUT_METHOD) {
Slog.w(TAG, "Attempted to add input method window with bad token " + attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}
//桌面類型視窗
else if (type == TYPE_WALLPAPER) {
if (token.windowType != TYPE_WALLPAPER) {
Slog.w(TAG, "Attempted to add wallpaper window with bad token " + attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}
//DREAM類型視窗
else if (type == TYPE_DREAM) {
if (token.windowType != TYPE_DREAM) {
Slog.w(TAG, "Attempted to add Dream window with bad token " + attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}
//建立視窗
win = new WindowState(this, session, client, token, attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
//調整子視窗尺寸
mPolicy.adjustWindowParamsLw(win.mAttrs);
......
......
// 如果要添加Token,添加Token添加到WmS中
if (addToken) {
mTokenMap.put(attrs.token, token);
}
// 将視窗添加到Session中
win.attach();
......
......
}
Session表示一個用戶端和服務端的互動會話。一般來說不同的應用通過不同的會話來和WindowManagerService互動,但是處于同一個程序的不同應用通過同一個Session來互動。
結束語:關于WMS管理視窗的機制要遠比我分析的要複雜,能力有限,我隻能介紹這麼多了.