Android中的窗口概念:屏幕上一块用于绘制各种UI元素并可以响应用户输入的一块矩形区域。从原理上来看,窗口的概念是独自占有一个Surface实例的显示区域。如Dialog、Activity的界面、壁纸、状态栏以及Toast等都是窗口。
上述对窗口的概念性描述,提取出来主要有以下几点信息:
用于绘制UI元素
响应用户输入
独自占有一个surface实例
- Surface是一块画布,应用通过canvas或者openGL在上面作画
- 作画后通过SurfaceFlinger将多块Surface的内容按照Z-order进行混合并输出到FrameBuffer,从而将Android的页面显示给用户。
每个窗口都有一块Surface用于显示自己的ui,必然需要一个角色对窗口进行统一管理,这个时候,WMS应运而生。WMS为所有窗口分配Surface,掌管z-order以及位置、尺寸、窗口的进场出场动画,并且还是输入系统的中转站。本文的分析角度:
- 布局系统:计算管理窗口的位置和层次
- 动画系统:根据布局系统的计算渲染窗口动画
一个窗口的创建流程大概如下:
1. 客户端通过WindowManagerGlobal获取IWindowSession,通过IWindowSession来向WMS发起添加请求。
2. 初始化WindowManager.LayoutParam,指定响应的type,当type大于System级别的时候,不需要token,WMS会创建一个token,而type小于system级别的窗口,是必须要求有token带过来给WMS的。WMS根据这个type来对窗口的z-order进行排序。
3. 向was申请relayout,所谓重新布局,是根据窗口新的属性去调整其Surface的相关属性,或者重新创建一个Surface。向wms添加一个窗口,仅仅是将它在wms中进行注册。只有经过了relayout后,窗口才拥有了wms为其分配的画布,有了画布,窗口才能进行绘制工作。
4. 之后,窗口的绘制过程如下:
– 通过surface.lock()函数获取可以在surface上作画的canvas
– 使用canvas进行绘图
– 通过unlockCanvasAndPost提交绘制结果
说到底,Window的本质其实就是一块Surface画布.
于是,根据对Surface的操作类型可以将Android的显示系统分为3个层次。
1. UI框架层,负责View空间的布局、绘制、事件分发、响应
2. WMS管理窗口Surface的布局与次序
3. SurfaceFlinger 将WMS维护的窗口Surface按照一定的次序混合后显示到屏幕上。
说了那么多,我们来看看窗口的核心管理WMS的构成。
– WMS也属于系统服务,都是由SystemServer来启动,代码位于SystemServer.java中:
//这里需要加入SystemServer启动WMS的相关代码,最后进入到WMS.Main函数中
- 1
//可以看到,SystemServer启动WMS的过程是将WSM的构造函数所需要的相关参数传入main方法
//由main方法来调用HandlerThread的handler将构造函数丢入looper中进行wms的实例化
public static WindowManagerService main(final Context context,
final InputManagerService im,
final boolean haveInputMethods, final boolean showBootMsgs,
final boolean onlyCore) {
final WindowManagerService[] holder = new WindowManagerService[1];
//那么这里的runWithScissor到底起了什么作用呢?
DisplayThread.getHandler().runWithScissors(new Runnable() {
@Override
public void run() {
holder[0] = new WindowManagerService(context, im,
haveInputMethods, showBootMsgs, onlyCore);
}
}, 0);
return holder[0];
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
//这个方法位于android.os.Handler.java类,是一个@hide方法
//同时这个方法是一个阻塞的方法,会阻塞目前县城直到返回,这也就
//说,return的时候在wms线程会被此方法阻塞,直到wms完成实例化
//,也可以理解为,main方法return后,holder可能仍然为null,
//但是wms线程中,所有消息都需要等待这个实例化完成
public final boolean runWithScissors(final Runnable r, long timeout) {
if (r == null) {
throw new IllegalArgumentException("runnable must not be null");
}
if (timeout < 0) {
throw new IllegalArgumentException("timeout must be non-negative");
}
//由于 wms 的 Main()方法调用
//如果调用此方法的looper和handler的looper相同,则直接运行返回
if (Looper.myLooper() == mLooper) {
r.run();
return true;
}
//如果不是则新建一个阻塞的runnable来进入msgqueue里面
BlockingRunnable br = new BlockingRunnable(r);
return br.postAndWait(this, timeout);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
接下来,看wms的构造函数,所有参数都是SystemServer传入:
private WindowManagerService(Context context, InputManagerService inputManager,
boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) {
mContext = context;
mHaveInputMethods = haveInputMethods;
mAllowBootMessages = showBootMsgs;
mOnlyCore = onlyCore;
...
//保存了用于接受输入事件的inputManager
mInputManager = inputManager; // Must be before createDisplayContentLocked.
...
//保存屏幕相关信息
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
mDisplays = mDisplayManager.getDisplays();
for (Display display : mDisplays) {
createDisplayContentLocked(display);
}
...
//掌管所有window的动画
mAnimator = new WindowAnimator(this);
//初始化WindowManagerPolicy成员
initPolicy();
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
SurfaceControl.openTransaction();
try {
createWatermarkInTransaction();
} finally {
SurfaceControl.closeTransaction();
}
showEmulatorDisplayOverlayIfNeeded();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
至此,WMS的初始化工作已经完成,那么初始化工作有哪些重要的成员变量被初始化呢?
- mInputManager : 输入系统服务,用于管理每个窗口的输入事件管道(InputManager)以及向通道上派发事件。
- mChoreographer ,从显示子系统获取VSYNC同步事件,从而来在合适的实际通知渲染动作,避免在渲染的过程中因为发生屏幕重绘而导致画面撕裂。WMS使用这个对象驱动所有的窗口动画,屏幕旋转动画,墙纸动画的渲染。
- mAnimator,WindowAnimator实例,在Choreographer的驱动下逐个渲染所有动画。
- mPolicy,WindowManagerPolicy的实现,目前只有PhoneWindowManager一个实现类。mPolicy定义了很多窗口相关的策略,是wms的首席顾问,当WMS要做任何事情的时候,都会向policy求助。例如,过塑wms某一个类型的window 的zOrder是多少,帮助wms矫正不合理的窗口属性,为wms监听屏幕旋转状态。预处理一些系统按键事件,例如(home back键都在这里实现默认行为),所以mPolicy是一个非常重要的成员变量。
- mDisplayContents ,一个DisplayContent列表,4.2支持基于Wi-Fi Display的多屏幕输出,而一个DisplayContent描述了一块可以绘制窗口的屏幕。每个DisplayContent都用一个整形变量做为其ID。
- mTokenMap,一个hash map,保存了所有显示令牌(类型为WindowToken),用于窗口管理。一个窗口必须有一个显示令牌,mAppTokens保存了所有的Activity令牌(WindowToken的子类AppWindowToken),mExitingTokens则保存了正在退出过程中的显示令牌,其中mAppTokens列表是有序的,他与AMS中的mHistory的顺序保持一致,反应了系统中activity的顺序。
- mWindowMap,也是一个HashMap,保存了所有窗口的状态信息,用于窗口管理。状态信息的类型为WindowState。实际上,所有被add过的window的状态都会保存在这个map中,与mTokenMap一样,mWindowMap也有衍生的子集,mPendingRemove保存了退出动画播放完成即将被移除的窗口,mLosingFocus保存了失去输入焦点的窗口。在DisplayContent中,也有一个window列表,这个列表以order为顺序存储了所有window。
- mSessions,一个List,元素为Session,这个列表保存了所有当前向WMS发起请求的客户端进程唯一的IWindowSession对象,注意,这里的Session也是进程唯一的。
- mRotation,是一个int变量。保存了当前手机的旋转状态。
接下来,我们从WindowManagerService的addWindow()函数开始讨论WMS的工作流程:
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
int[] appOp = new int[1];
//首先,检查权限
int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
boolean reportNewConfig = false;
//当为某个窗口添加子窗口的时候,attachedWindow用来保存父窗口的实例
WindowState attachedWindow = null;
long origId;
final int type = attrs.type;
synchronized(mWindowMap) {
if (!mDisplayReady) {
throw new IllegalStateException("Display has not been initialialized");
}
// 获取窗口要添加的displayContent
// 在添加窗口时,必须的通过displayId参数指定添加哪个DisplayContent,如果没有指定,session会替Window添加DefaultDisplay
// 也就是手机屏幕
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent == null) {
Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
+ displayId + ". Aborting.");
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
if (!displayContent.hasAccess(session.mUid)) {
Slog.w(TAG_WM, "Attempted to add window to a display for which the application "
+ "does not have access: " + displayId + ". Aborting.");
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
//已经添加了窗口
if (mWindowMap.containsKey(client.asBinder())) {
Slog.w(TAG_WM, "Window " + client + " is already added");
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
// 如果是type是子窗口
if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) { //检查添加窗口的父窗口是否为null attachedWindow = windowForClientLocked(null, attrs.token, false); if (attachedWindow == null) { Slog.w(TAG_WM, "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_WM, "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_WM, "Attempted to add private presentation window to a non-private display. Aborting."); return WindowManagerGlobal.ADD_PERMISSION_DENIED; }
boolean addToken = false;
//从tokenMap中取出已经注册的token
WindowToken token = mTokenMap.get(attrs.token);
AppWindowToken atoken = null;
//如果看看已经注册的token没有
if (token == null) {
//如果是application 级别 activity
if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { Slog.w(TAG_WM, "Attempted to add application window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; }
if (type == TYPE_INPUT_METHOD) { Slog.w(TAG_WM, "Attempted to add input method window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; }
if (type == TYPE_VOICE_INTERACTION) { Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; }
if (type == TYPE_WALLPAPER) { Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; }
if (type == TYPE_DREAM) { Slog.w(TAG_WM, "Attempted to add Dream window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; }
if (type == TYPE_QS_DIALOG) { Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; }
if (type == TYPE_ACCESSIBILITY_OVERLAY) { Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; }
//如果说以上的情况,都new一个token,最后一个参数是false,表明是wms创建的token
token = new WindowToken(this, attrs.token, -1, false);
//说明token是临时创建的
addToken = true;
//如果说token不为null,则说明已经被注册过了 ,看一下activity级别的讨论,这里要求级别也是application的
//并且atoken是appWindowToken
} else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { atoken = token.appWindowToken; if (atoken == null) { Slog.w(TAG_WM, "Attempted to add window with non-application token " + token + ". Aborting."); return WindowManagerGlobal.ADD_NOT_APP_TOKEN; } else if (atoken.removed) { Slog.w(TAG_WM, "Attempted to add window with exiting application token " + token + ". Aborting."); return WindowManagerGlobal.ADD_APP_EXITING; }
if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) { // No need for this guy! if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v( TAG_WM, "**** 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_WM, "Attempted to add input method window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; }
} else if (type == TYPE_VOICE_INTERACTION) { if (token.windowType != TYPE_VOICE_INTERACTION) { Slog.w(TAG_WM, "Attempted to add voice interaction 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_WM, "Attempted to add wallpaper window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; }
} else if (type == TYPE_DREAM) { if (token.windowType != TYPE_DREAM) { Slog.w(TAG_WM, "Attempted to add Dream window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; }
} else if (type == TYPE_ACCESSIBILITY_OVERLAY) { if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) { Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; }
} else if (type == TYPE_QS_DIALOG) { if (token.windowType != TYPE_QS_DIALOG) { Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; }
} else if (token.appWindowToken != null) {
Slog.w(TAG_WM, "Non-null appWindowToken for system window of type=" + type);
// It is not valid to use an app token with other system types; we will
// instead make a new token for it (as if null had been passed in for the token).
attrs.token = null;
token = new WindowToken(this, null, -1, false);
addToken = true;
}
//这个windowState维护了所有窗口的信息
WindowState win = new WindowState(this, session, client, token,
attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
if (win.mDeathRecipient == null) {
// Client has apparently died, so there is no reason to
// continue.
Slog.w(TAG_WM, "Adding window client " + client.asBinder()
+ " that is dead, aborting.");
return WindowManagerGlobal.ADD_APP_EXITING;
}
if (win.getDisplayContent() == null) {
Slog.w(TAG_WM, "Adding window to Display that has been removed.");
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
//windowPolicy粉墨登场
mPolicy.adjustWindowParamsLw(win.mAttrs);
win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
res = mPolicy.prepareAddWindowLw(win, attrs);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
final boolean openInputChannels = (outInputChannel != null
&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
if (openInputChannels) {
win.openInputChannel(outInputChannel);
}
// From now on, no exceptions or errors allowed!
res = WindowManagerGlobal.ADD_OKAY;
if (excludeWindowTypeFromTapOutTask(type)) { displayContent.mTapExcludedWindows.add(win); }
origId = Binder.clearCallingIdentity();
//刚才被wms临时创建的token被加入到tokenMap中
if (addToken) {
mTokenMap.put(attrs.token, token);
}
win.attach();
//然后将WindowState加入到map中
mWindowMap.put(client.asBinder(), win);
if (win.mAppOp != AppOpsManager.OP_NONE) {
int startOpResult = mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(),
win.getOwningPackage());
if ((startOpResult != AppOpsManager.MODE_ALLOWED) &&
(startOpResult != AppOpsManager.MODE_DEFAULT)) {
win.setAppOpVisibilityLw(false);
}
}
if (type == TYPE_APPLICATION_STARTING && token.appWindowToken != null) { token.appWindowToken.startingWindow = win; if (DEBUG_STARTING_WINDOW) Slog.v (TAG_WM, "addWindow: " + token.appWindowToken + " startingWindow=" + win); }
boolean imMayMove = true;
if (type == TYPE_INPUT_METHOD) { win.mGivenInsetsPending = true; mInputMethodWindow = win; addInputMethodWindowToListLocked(win); imMayMove = false; } else if (type == TYPE_INPUT_METHOD_DIALOG) { mInputMethodDialogs.add(win); addWindowToListInOrderLocked(win, true); moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true)); imMayMove = false; } else { addWindowToListInOrderLocked(win, true); if (type == TYPE_WALLPAPER) { mWallpaperControllerLocked.clearLastWallpaperTimeoutTime(); displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) { displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } else if (mWallpaperControllerLocked.isBelowWallpaperTarget(win)) { // If there is currently a wallpaper being shown, and // the base layer of the new window is below the current // layer of the target window, then adjust the wallpaper. // This is to avoid a new window being placed between the // wallpaper and its target. displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; }
}
// If the window is being added to a task that's docked but non-resizeable,
// we need to update this new window's scroll position when it's added.
win.applyScrollIfNeeded();
// If the window is being added to a stack that's currently adjusted for IME,
// make sure to apply the same adjust to this new window.
win.applyAdjustForImeIfNeeded();
if (type == TYPE_DOCK_DIVIDER) { getDefaultDisplayContentLocked().getDockedDividerController().setWindow(win); }
final WindowStateAnimator winAnimator = win.mWinAnimator;
winAnimator.mEnterAnimationPending = true;
winAnimator.mEnteringAnimation = true;
// Check if we need to prepare a transition for replacing window first.
if (atoken != null && !prepareWindowReplacementTransition(atoken)) {
// If not, check if need to set up a dummy transition during display freeze
// so that the unfreeze wait for the apps to draw. This might be needed if
// the app is relaunching.
prepareNoneTransitionForRelaunching(atoken);
}
if (displayContent.isDefaultDisplay) {
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
final Rect taskBounds;
if (atoken != null && atoken.mTask != null) {
taskBounds = mTmpRect;
atoken.mTask.getBounds(mTmpRect);
} else {
taskBounds = null;
}
if (mPolicy.getInsetHintLw(win.mAttrs, taskBounds, mRotation,
displayInfo.logicalWidth, displayInfo.logicalHeight, outContentInsets,
outStableInsets, outOutsets)) {
res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
}
} else {
outContentInsets.setEmpty();
outStableInsets.setEmpty();
}
if (mInTouchMode) {
res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
}
if (win.mAppToken == null || !win.mAppToken.clientHidden) {
res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
}
mInputMonitor.setUpdateInputWindowsNeededLw();
boolean focusChanged = false;
if (win.canReceiveKeys()) {
focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
false /*updateInputWindows*/);
if (focusChanged) {
imMayMove = false;
}
}
if (imMayMove) {
moveInputMethodWindowsIfNeededLocked(false);
}
mLayersController.assignLayersLocked(displayContent.getWindowList());
// Don't do layout here, the window must call
// relayout to be displayed, so we'll do it there.
if (focusChanged) {
mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
}
mInputMonitor.updateInputWindowsLw(false /*force*/);
if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
+ client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));
if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) {
reportNewConfig = true;
}
if (attrs.removeTimeoutMilliseconds > 0) {
mH.sendMessageDelayed(
mH.obtainMessage(H.WINDOW_REMOVE_TIMEOUT, win),
attrs.removeTimeoutMilliseconds);
}
}
if (reportNewConfig) {
sendNewConfiguration();
}
Binder.restoreCallingIdentity(origId);
return res;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
接下来,我们来理解WindowToken的重要作用:
WindowToken类位于:com.android.server.wm.WindowToken.java,
/** * Container of a set of related windows in the window manager. Often this * is an AppWindowToken, which is the handle for an Activity that it uses * to display windows. For nested windows, there is a WindowToken created for * the parent window to manage its children. */
//先看类说明,WindowToken是一个位于WindowManager中相互关联的Window的结合。
//对于一个activity来说,WindowToken是一个AppWindowToken的句柄(WindowToken的一个子类),用来显示各种window。对于嵌套的window来说,会创建一个WindowToken来给父window管理儿子。
class WindowToken {
// The window manager!
final WindowManagerService service;
// The actual token.
final IBinder token;
// The type of window this token is for, as per WindowManager.LayoutParams.
final int windowType;
// Set if this token was explicitly added by a client, so should
// not be removed when all windows are removed.
final boolean explicit;
// For printing.
String stringName;
// If this is an AppWindowToken, this is non-null.
AppWindowToken appWindowToken;
// All of the windows associated with this token.
final WindowList windows = new WindowList();
// Is key dispatching paused for this token?
boolean paused = false;
// Should this token's windows be hidden?
boolean hidden;
// Temporary for finding which tokens no longer have visible windows.
boolean hasVisible;
// Set to true when this token is in a pending transaction where it
// will be shown.
boolean waitingToShow;
// Set to true when this token is in a pending transaction where its
// windows will be put to the bottom of the list.
boolean sendingToBottom;
WindowToken(WindowManagerService _service, IBinder _token, int type, boolean _explicit) {
service = _service;
token = _token;
windowType = type;
explicit = _explicit;
}
void removeAllWindows() {
for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
WindowState win = windows.get(winNdx);
if (DEBUG_WINDOW_MOVEMENT) Slog.w(TAG_WM, "removeAllWindows: removing win=" + win);
win.mService.removeWindowLocked(win);
}
windows.clear();
}
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("windows="); pw.println(windows);
pw.print(prefix); pw.print("windowType="); pw.print(windowType);
pw.print(" hidden="); pw.print(hidden);
pw.print(" hasVisible="); pw.println(hasVisible);
if (waitingToShow || sendingToBottom) {
pw.print(prefix); pw.print("waitingToShow="); pw.print(waitingToShow);
pw.print(" sendingToBottom="); pw.print(sendingToBottom);
}
}
@Override
public String toString() {
if (stringName == null) {
StringBuilder sb = new StringBuilder();
sb.append("WindowToken{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" "); sb.append(token); sb.append('}');
stringName = sb.toString();
}
return stringName;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- WindowToken将同一个应用组件的窗口组织在一起,应用组件可以是Activity,InputMethod,WallPaper以及Dream。在WMS管理窗口的过程中,用WindowToken指代一个应用组件。例如在进行窗口ZOrder的排序中,属于同一个WindowToken的窗口会安排在一起。在其中定义的一些属性会影响到所有属于这个windowToken的窗口。
- WindowToken具有令牌的作用,这是WMS管理应用组件的一个手段。WindowToken由应用组件或其管理者负责向WMS生命并持有。应用组件在需要新的窗口时,必须提供WindowToken以表明自己的身份,并且窗口的类型必须与所持有的WindowToken类型一致。从前面代码看到,System级别窗口不需要提供Token,会由WMS隐式的创建一个WindowToken。那么是不是所有人都能够添加一个系统窗口呢?可以看到addWindow刚开始mPolicy就对应用进行了permission的检查,也就是客户端持有SYSTEM_ALERT_WINDOW等呼出系统窗口的权限,才能创建系统窗口。
那么,应用组件在创建一个窗口的时候必须指定的WindowToken,到底该如何声明呢?
回到WMS的addWindowToken()函数中,
//可以看到,token其实是一个Binder对象的包装
@Override
public void addWindowToken(IBinder token, int type) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"addWindowToken()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized(mWindowMap) {
//去map中找token,以binder对象为键,WindowToken为值
WindowToken wtoken = mTokenMap.get(token);
if (wtoken != null) {
Slog.w(TAG_WM, "Attempted to add existing input method token: " + token);
return;
}
//创建一个windowToken,放入map中
wtoken = new WindowToken(this, token, type, true);
mTokenMap.put(token, wtoken);
if (type == TYPE_WALLPAPER) {
mWallpaperControllerLocked.addWallpaperToken(wtoken);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
从addWindowToken()这个函数可以看到,WindowToken有两层含义。
– 对显示组件(客户端)而言,token是一个任意的binder实例,对客户端而言,token仅仅是一个创建窗口的令牌,没有其他含义。
– 对WMS来说,WindowToken是一个WindowToken的实例,包装了客户端一侧的binder实例,并且以这个token为键,以WindowToken为值,保存了token。mTokenMap是否已经保存了该binder实例和对应的WindowToken,标志着客户端一侧的binder token是否已经在WMS声明。
- WMS通过token来管理各个窗口,那么最重要的Activity的token是如何管理的呢,首先Activity的token使用方式与WallPaper和InputMethod类似的,但是扩展了更多的内容。对此,WMS创建了一个继承WindowToken的AppWindowToken来对Activity进行描述和管理。既然AppWindowToken是为Activity服务的,那么其声明也自然是在ActivityManagerService中,具体位置在ActivityStack.validateAppTokensLocked()中,代码如下:
final void validateAppTokensLocked() {
mValidateAppTokens.clear();
mValidateAppTokens.ensureCapacity(numActivities());
final int numTasks = mTaskHistory.size();
for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
TaskRecord task = mTaskHistory.get(taskNdx);
final ArrayList<ActivityRecord> activities = task.mActivities;
if (activities.isEmpty()) {
continue;
}
TaskGroup group = new TaskGroup();
group.taskId = task.taskId;
mValidateAppTokens.add(group);
final int numActivities = activities.size();
for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
group.tokens.add(r.appToken);
}
}
//WMS对appTokens进行更新
mWindowManager.validateAppTokens(mStackId, mValidateAppTokens);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
下面一段代码是AppToken的aidl类,可以看出,实际的binder对象是由这个AIDL文件生成的stub类,分别有一些方法供给WMS进行回调
interface IApplicationToken {
//窗口完成初次绘制
void windowsDrawn();
//窗口可见通知AMS
void windowsVisible();
//窗口不可见通知AMS
void windowsGone();
//窗口没能按时完成输入事件的处理
boolean keyDispatchingTimedOut(String reason);
//从AMS处获取界定ANR的事件
long getKeyDispatchingTimeout();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
AMS通过ActivityRecord表示一个Activity,而ActivityRecord的appToken在其构造函数时被创建,类型为IApplicationToken.Stub,就是上述AIDL的stub类,所以每个Activity有自己的appToken,而WMS接受AMS对Application的Binder对象appToken粘结了AMS的ActivityRecord与WMS的AppWindowToken,只要给定一个ActivityRecord,都可以通过appToken在WMS中找到对应的AppWindowToken,从而使得AMS拥有了操纵Activity窗口绘制的能力。例如,当AMS认为一个Activity需要被隐藏的时候,以Activity对应的ActivityRecord所拥有的appToken为参数调用WMS的setAppVisibility()函数,此函数通过appToken找到appdWindowToken(通过mAppWindowMap),然后将这个token的窗口隐藏。
- 理解WindowState:
从WindowManagerService.addWindow()函数的实现看到,当WMS添加一个窗口时,WMS会为其创建一个WindowState。WindowState表示一个窗口的所有实行,所以他是WMS中事实上的窗口。
类似于WindowToken,WindowState在显示组件一侧也有对应的类型:IWindow.Stub 。这个借口提供了窗口管理相关的通知回调,如尺寸的变化、焦点的变化等。WindowState被保存到mWindowMap中,键值为IWindow的Bp端。mWindowMap是整个系统所有窗口的一个全集