本文主要是介绍WindowManagerService的理解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
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() {@Overridepublic 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;...//保存了用于接受输入事件的inputManagermInputManager = 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) {//检查添加窗口的父窗口是否为nullattachedWindow = 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中取出已经注册的tokenWindowToken token = mTokenMap.get(attrs.token);AppWindowToken atoken = null;//如果看看已经注册的token没有if (token == null) {//如果是application 级别 activityif (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创建的tokentoken = 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);}}@Overridepublic 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对象的包装@Overridepublic 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();//窗口可见通知AMSvoid windowsVisible();//窗口不可见通知AMSvoid 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是整个系统所有窗口的一个全集
这篇关于WindowManagerService的理解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!