前面說到,應用程序添加窗口時,會在本地創建一個ViewRoot,然后通過IPC(進程間通信)調用WmS的Session的addWindow請求WmS創建窗口,下面來看看addWindow方法。
addWindow方法定義在frameworks/base/services/java/com.android.server.WindowManagerService.java中,其代碼如下所示:
public int addWindow(Session session, IWindow client,
WindowManager.LayoutParams attrs, int viewVisibility,
Rect outContentInsets, InputChannel outInputChannel) {
// 是否有添加權限
int res = mPolicy.checkAddPermission(attrs);
if (res != WindowManagerImpl.ADD_OKAY) {
return res;
}
boolean reportNewConfig = false;
WindowState attachedWindow = null;
WindowState win = null;
synchronized(mWindowMap) {
// Instantiating a Display requires talking with the simulator,
// so don't do it until we know the system is mostly up and
// running.
// 是否存在顯示設置
if (mDisplay == null) {
// 若不存在,則獲取系統設置
WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
mDisplay = wm.getDefaultDisplay();
mInitialDisplayWidth = mDisplay.getWidth();
mInitialDisplayHeight = mDisplay.getHeight();
// 將Display存放到InputManager中
mInputManager.setDisplaySize(0, mInitialDisplayWidth, mInitialDisplayHeight);
reportNewConfig = true;
}
// 是否重復添加
if (mWindowMap.containsKey(client.asBinder())) {
Slog.w(TAG, "Window " + client + " is already added");
return WindowManagerImpl.ADD_DUPLICATE_ADD;
}
// 是否子窗口
if (attrs.type >= FIRST_SUB_WINDOW && attrs.type <= LAST_SUB_WINDOW) {
// 若為子窗口
// 返回WmS中存在的對應父窗口,若不存在則返回null
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 WindowManagerImpl.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 WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN;
}
}
boolean addToken = false;
// 在WmS中尋找對應的WindowToken
WindowToken token = mTokenMap.get(attrs.token);
if (token == null) {
if (attrs.type >= FIRST_APPLICATION_WINDOW
&& attrs.type <= LAST_APPLICATION_WINDOW) {
// 對于子窗口來說,WmS中必須有對應的Token才能添加
Slog.w(TAG, "Attempted to add application window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerImpl.ADD_BAD_APP_TOKEN;
}
if (attrs.type == TYPE_INPUT_METHOD) {
// 如果是內置的輸入方法窗口,WmS中必須有對應的Token才能添加
Slog.w(TAG, "Attempted to add input method window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerImpl.ADD_BAD_APP_TOKEN;
}
if (attrs.type == TYPE_WALLPAPER) {
// 墻紙窗口,WmS中必須有對應的Token才能添加
Slog.w(TAG, "Attempted to add wallpaper window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerImpl.ADD_BAD_APP_TOKEN;
}
// 創建窗口
token = new WindowToken(attrs.token, -1, false);
addToken = true;
} else if (attrs.type >= FIRST_APPLICATION_WINDOW
&& attrs.type <= LAST_APPLICATION_WINDOW) {
// token不為null且是應用窗口
AppWindowToken atoken = token.appWindowToken;
if (atoken == null) {
// appWindowToken值不能為空
Slog.w(TAG, "Attempted to add window with non-application token "
+ token + ". Aborting.");
return WindowManagerImpl.ADD_NOT_APP_TOKEN;
} else if (atoken.removed) {
// 試圖使用存在的應用token添加窗口
Slog.w(TAG, "Attempted to add window with exiting application token "
+ token + ". Aborting.");
return WindowManagerImpl.ADD_APP_EXITING;
}
if (attrs.type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) {
// No need for this guy!
// 窗口類型不能是應用啟動時顯示的窗口
if (localLOGV) Slog.v(
TAG, "**** NO NEED TO START: " + attrs.getTitle());
return WindowManagerImpl.ADD_STARTING_NOT_NEEDED;
}
} else if (attrs.type == TYPE_INPUT_METHOD) {
// 對于內置的輸入方法窗口,token的windowType值要等于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 WindowManagerImpl.ADD_BAD_APP_TOKEN;
}
} else if (attrs.type == TYPE_WALLPAPER) {
// 對于墻紙窗口,token的windowType值要等于TYPE_WALLPAPER
if (token.windowType != TYPE_WALLPAPER) {
Slog.w(TAG, "Attempted to add wallpaper window with bad token "
+ attrs.token + ". Aborting.");
return WindowManagerImpl.ADD_BAD_APP_TOKEN;
}
}
// 創建窗口
win = new WindowState(session, client, token,
attachedWindow, attrs, viewVisibility);
if (win.mDeathRecipient == null) {
// Client has apparently died, so there is no reason to
// continue.
// 客戶端已被銷毀,所以沒必要繼續
Slog.w(TAG, "Adding window client " + client.asBinder()
+ " that is dead, aborting.");
return WindowManagerImpl.ADD_APP_EXITING;
}
// 如果是Toast,則此窗口不能夠接收input事件
mPolicy.adjustWindowParamsLw(win.mAttrs);
// 判斷添加的窗口是單例還是多例
res = mPolicy.prepareAddWindowLw(win, attrs);
if (res != WindowManagerImpl.ADD_OKAY) {
// 是多例則直接返回
return res;
}
// 如果輸出的Channel,也即Pipe中的讀通道為空
if (outInputChannel != null) {
// 創建通道
String name = win.makeInputChannelName();
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
win.mInputChannel = inputChannels[0];
inputChannels[1].transferToBinderOutParameter(outInputChannel);
// 在InputManager中注冊通道
mInputManager.registerInputChannel(win.mInputChannel);
}
// From now on, no exceptions or errors allowed!
res = WindowManagerImpl.ADD_OKAY;
// 重置當前線程的IPC的ID
final long origId = Binder.clearCallingIdentity();
// 從上述代碼中得出是否要添加Token,若是則添加Token添加到WmS中
if (addToken) {
mTokenMap.put(attrs.token, token);
mTokenList.add(token);
}
// 將窗口添加到Session中
win.attach();
// 窗口信息添加到WmS中
mWindowMap.put(client.asBinder(), win);
if (attrs.type == TYPE_APPLICATION_STARTING &&
token.appWindowToken != null) {
// 對于應用啟動時顯示的窗口,設置token
token.appWindowToken.startingWindow = win;
}
boolean imMayMove = true;
if (attrs.type == TYPE_INPUT_METHOD) {
// 內置的輸入方法窗口
mInputMethodWindow = win;
addInputMethodWindowToListLocked(win);
imMayMove = false;
} else if (attrs.type == TYPE_INPUT_METHOD_DIALOG) {
// 內置的輸入方法對話框窗口
mInputMethodDialogs.add(win);
addWindowToListInOrderLocked(win, true);
adjustInputMethodDialogsLocked();
imMayMove = false;
} else {
// 其他窗口
addWindowToListInOrderLocked(win, true);
if (attrs.type == TYPE_WALLPAPER) {
mLastWallpaperTimeoutTime = 0;
adjustWallpaperWindowsLocked();
} else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
adjustWallpaperWindowsLocked();
}
}
win.mEnterAnimationPending = true;
// 獲取系統窗口區域的insets
mPolicy.getContentInsetHintLw(attrs, outContentInsets);
if (mInTouchMode) {
// 用戶直接觸摸的窗口
res |= WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE;
}
if (win == null || win.mAppToken == null || !win.mAppToken.clientHidden) {
// 應用窗口
res |= WindowManagerImpl.ADD_FLAG_APP_VISIBLE;
}
boolean focusChanged = false;
if (win.canReceiveKeys()) {
// 窗口需要按鍵事件
// 更新焦點,將窗口信息寫入了InputDispatcher
focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS);
if (focusChanged) {
imMayMove = false;
}
}
if (imMayMove) {
// 若需要鎖定的話,移動輸入方法窗口
moveInputMethodWindowsIfNeededLocked(false);
}
assignLayersLocked();
// Don't do layout here, the window must call
// relayout to be displayed, so we'll do it there.
//dump();
if (focusChanged) {
finishUpdateFocusedWindowAfterAssignLayersLocked();
}
if (localLOGV) Slog.v(
TAG, "New client " + client.asBinder()
+ ": window=" + win);
if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked()) {
reportNewConfig = true;
}
}
// sendNewConfiguration() checks caller permissions so we must call it with
// privilege. updateOrientationFromAppTokens() clears and resets the caller
// identity anyway, so it's safe to just clear & restore around this whole
// block.
final long origId = Binder.clearCallingIdentity();
if (reportNewConfig) {
sendNewConfiguration();
}
Binder.restoreCallingIdentity(origId);
return res;
}
有些東西還沒摸明白,后面深入學習后再補一下。
上文還說到,addWindow會將窗口信息寫入InputDispatcher,其實在addWindow代碼中有體現:
if (win.canReceiveKeys()) {
// 窗口需要按鍵事件
// 更新焦點,在這里,將窗口信息寫入了InputDispatcher
focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS);
if (focusChanged) {
imMayMove = false;
}
} ? ? ? ? 至于如何寫入InputDispatcher,下文分析。