时间:2021-07-12 13:28:48 | 栏目:Android代码 | 点击:次
最近在改bug的时候发现在windowManager.addView的时候会发生莫名其妙的崩溃,那个崩溃真的是让你心态爆炸,潜心研究了两天window相关的东西,虽然不是很深奥的东西,本人也只是弄清楚了window的添加逻辑,在此分享给大家:
一、悬浮窗的概念
在android中,无论我们的app界面,还是系统桌面,再或者是手机下方的几个虚拟按键和最上方的状态栏,又或者是一个吐司。。。我们所看到的所有界面,都是由一个个悬浮窗口组成的。
但是这些窗口有不同的级别:
基于上述三种,android把悬浮窗划分成三个级别,并通过静态int型变量来表示:
/** * Start of system-specific window types. These are not normally * created by applications. **/ public static final int FIRST_SYSTEM_WINDOW = 2000; /** * End of types of system windows. **/ public static final int LAST_SYSTEM_WINDOW = 2999;
2000~2999:在系统级别的悬浮窗范围内,一般我们要想创建是需要申请权限。
public static final int FIRST_SUB_WINDOW = 1000; /** * End of types of sub-windows. **/ public static final int LAST_SUB_WINDOW = 1999;
1000~1999:子窗口级别的悬浮窗,他如果想要创建必须在一个父窗口下。
public static final int TYPE_BASE_APPLICATION = 1; public static final int LAST_APPLICATION_WINDOW = 99;
1~99:应用程序级别的悬浮窗,作为每个应用程序的基窗口。
在每段的范围内都有众多个窗口类型,这个具体就不说了,因为太多了根本说不完。。
但是说了这么半天,悬浮窗到底是个啥东西,可能这个名词听得很多,但是仔细想想android中用到的哪个控件还是哪个类叫悬浮窗?没有吧,那么View总该知道吧(不知道别说你是做android的)
其实说白了悬浮窗就是一个被包裹的view。因为除了一个view他还有很多的属性:长宽深度,类型,证书等等东西,只是属性很多而且属性之间的依赖关系有一些复杂而已。简单的来说可以这么理解。
二、WindowManager介绍
上面简单介绍了悬浮窗的概念,而WindowManager是对悬浮窗进行操作的一个媒介。
WindowManager是一个接口,他是继承了ViewManager接口中的三个方法:
public interface ViewManager { public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); }
windowManage暴露给我们的只是这个三个方法,真的是简单粗暴,但是很实用。
这三个方法看名字就知道含义了,增删改嘛,就不多说啦。
而在上面提到的对于悬浮窗的三种分类,也是WindowManager的内部类:WindowManager.LayoutParams,关于LayoutParams是什么在这里就不多说了。这不是我们的重点。
我们平时想要添加一个悬浮窗,就会使用第一个方法:
WindowManager windowManager = getWindowManager(); windowManager.addView(.....);
我们在getWindowManager获取的类,实际上是WindowManager的是WindowManager的实现类:WindowManagerImpl。接下来我们走一下添加悬浮窗的流程。
三、悬浮窗添加流程
入口肯定是从自己的addView中,上面说到了WindowManager的实现类是WindowManagerImpl,来看一下:
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); }
这里有两步:第一步是给layoutparams设置一个默认的令牌(就是token这个属性,至于这个干什么的等会再说)
private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) { // 设置条件:有默认令牌,而且不是子窗口级别的悬浮窗 if (mDefaultToken != null && mParentWindow == null) { if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } // 如果没有令牌就设置默认令牌 final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (wparams.token == null) { wparams.token = mDefaultToken; } } }
然后调用了mGlobal的addView:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { /**进行一系列判空操作。。。**/ if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { // If there's no parent, then hardware acceleration for this view is // set from the application's hardware acceleration setting. final Context context = view.getContext(); if (context != null && (context.getApplicationInfo().flags & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } ViewRootImpl root; root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true); } throw e; } } }
看到WindowManagerGLobal中有三个属性: mViews、mRoots、mParams,可以大胆猜测这个类中保存了我们进程中的所有视图以及相关属性。在这里主要关注一下ViewRootImpl的这个实例对象root,接下来的会走进root的setView中。
ViewRootImpl的setView方法内容有点多,我这里就截取关键的两部分:
1.
int res; /** = WindowManagerImpl.ADD_OKAY; **/ try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mWinFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
创建了一个名为res的int类型变量,他要获取到的是悬浮窗添加的结果:成功或者失败。
2.
if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; mAdded = false; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); switch (res) { case WindowManagerGlobal.ADD_BAD_APP_TOKEN: case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?"); case WindowManagerGlobal.ADD_NOT_APP_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not for an application"); case WindowManagerGlobal.ADD_APP_EXITING: throw new WindowManager.BadTokenException( "Unable to add window -- app for token " + attrs.token + " is exiting"); case WindowManagerGlobal.ADD_DUPLICATE_ADD: throw new WindowManager.BadTokenException( "Unable to add window -- window " + mWindow + " has already been added"); case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED: // Silently ignore -- we would have just removed it // right away, anyway. return; case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON: throw new WindowManager.BadTokenException("Unable to add window " + mWindow + " -- another window of type " + mWindowAttributes.type + " already exists"); case WindowManagerGlobal.ADD_PERMISSION_DENIED: throw new WindowManager.BadTokenException("Unable to add window " + mWindow + " -- permission denied for window typ + mWindowAttributes.type); case WindowManagerGlobal.ADD_INVALID_DISPLAY: throw new WindowManager.InvalidDisplayException("Unable to add window " + mWindow + " -- the specified display can not be found"); case WindowManagerGlobal.ADD_INVALID_TYPE: throw new WindowManager.InvalidDisplayException("Unable to add window " + mWindow + " -- the specified window type " + mWindowAttributes.type + " is not valid"); } throw new RuntimeException( "Unable to add window -- unknown error code " + res); }
第二部分是res返回失败的所有情况,在添加成功的时候res为OKAY,而非OKAY的情况就是上述情况。
接下来来看一下添加悬浮窗的操作,就是1中mWindowSession.addToDisplay。mWindowSession类型如下:
final IWindowSession mWindowSession;
在这里其实用到了aidl跨进程通信,最终执行该方法的类是Session:
@Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel); }
这个mService就是一个关键了系统类――WindowMamagerService(WMS)。到了这里我们简单过一下思路:在addView之后,通过WindowManagerGlobal进行一些相关配置,传入ViewRootImpl,再通过aidl方式发送给WMS系统服务。
可能有小伙伴会疑惑。好端端的为什么要用aidl实现?最开始本人也有这个疑惑,但是后来想了想所有的窗口无论系统窗口还是第三方app,窗口都是要通过一个类去进行添加允许判断,这里使用aidl是在合适不过的了。我们接着看一下WMS的addWindow方法:
这个addWindow方法又是一段超长的代码,所以也就不全粘,说一下他的简单流程吧,主要是分为三步:权限判断、条件筛选、添加窗口
WMS的addWindow方法:
int res = mPolicy.checkAddPermission(attrs, appOp); if (res != WindowManagerGlobal.ADD_OKAY) { return res; }
首先进行一个权限判断,
final WindowManagerPolicy mPolicy;
WindowManagerPolicy的实现类是PhoneWindowManagerPolicy,看一下他的实现:
又是小一百行的代码,我们拆开来看:
//排除不属于三种类型悬浮窗范围内的type //很明显的三段排除。 if (!((type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) || (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) || (type >= FIRST_SYSTEM_WINDOW && type <= LAST_SYSTEM_WINDOW))) { return WindowManagerGlobal.ADD_INVALID_TYPE; } //不是系统级别的悬浮窗直接满足条件 if (type < FIRST_SYSTEM_WINDOW || type > LAST_SYSTEM_WINDOW) { return ADD_OKAY; } //以下几种不是系统警告类型的系统弹窗,会满足条件,除此之外的使用默认判断的方式 if (!isSystemAlertWindowType(type)) { switch (type) { case TYPE_TOAST: outAppOp[0] = OP_TOAST_WINDOW; return ADD_OKAY; case TYPE_DREAM: case TYPE_INPUT_METHOD: case TYPE_WALLPAPER: case TYPE_PRESENTATION: case TYPE_PRIVATE_PRESENTATION: case TYPE_VOICE_INTERACTION: case TYPE_ACCESSIBILITY_OVERLAY: case TYPE_QS_DIALOG: // The window manager will check these. return ADD_OKAY; } return mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW) == PERMISSION_GRANTED ? ADD_OKAY : ADD_PERMISSION_DENIED; }
后面的几段代码会频繁出现最后的这段代码:mContext.checkCallingOrSelfPermission,具体实现的类是ContextFixture:
@Override public int checkCallingOrSelfPermission(String permission) { if (mPermissionTable.contains(permission) || mPermissionTable.contains(PERMISSION_ENABLE_ALL)) { logd("checkCallingOrSelfPermission: " + permission + " return GRANTED"); return PackageManager.PERMISSION_GRANTED; } else { logd("checkCallingOrSelfPermission: " + permission + " return DENIED"); return PackageManager.PERMISSION_DENIED; } }
这里会使用默认权限判断的方式,要么允许对应权限,要么就是拥有全部权限,否则就会返回DENIED。
这个说完接着回到checkPermission方法。
//对于系统进程直接满足允许 final int callingUid = Binder.getCallingUid(); if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) { return ADD_OKAY; }
说实话下面这一段代码我看的不是很明白,只是看到了这里对8.0之后做了版本限制,直接使用默认检查方式。
ApplicationInfo appInfo; try { appInfo = mContext.getPackageManager().getApplicationInfoAsUser( attrs.packageName, 0 /* flags */, UserHandle.getUserId(callingUid)); } catch (PackageManager.NameNotFoundException e) { appInfo = null; } if (appInfo == null || (type != TYPE_APPLICATION_OVERLAY && appInfo.targetSdkVersion >= O)) { return (mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW) == PERMISSION_GRANTED) ? ADD_OKAY : ADD_PERMISSION_DENIED; }
这段是要从PackageManager中获取ApplicationInfo,如果获取失败会抛出NameNotFound异常。所以下面的判断是在异常的时候使用默认权限处理方式。
最后还以一步检查操作,关系不大就不看了。到这里checkPermission方法就结束了。
权限检查的步骤已经结束,接着就是根据上述获取到的结果进行条件筛选。
if (res != WindowManagerGlobal.ADD_OKAY) { return res; }
首先在权限检查的步骤获取权限失败,那么会直接返回,不会执行条件筛选的步骤。而真正的条件筛选步骤代码也是很多,我这里直接粘过来然后说了。
//111111111111111 if (!mDisplayReady) { throw new IllegalStateException("Display has not been initialialized"); } final DisplayContent displayContent = getDisplayContentOrCreate(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) && !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) { 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; } //22222222222222 if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) { parentWindow = windowForClientLocked(null, attrs.token, false); if (parentWindow == 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 (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW && parentWindow.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; } } //333333333333333 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; } //444444444444444 AppWindowToken atoken = null; final boolean hasParent = parentWindow != null; // Use existing parent window token for child windows since they go in the same token // as there parent window so we can apply the same policy on them. WindowToken token = displayContent.getWindowToken( hasParent ? parentWindow.mAttrs.token : attrs.token); // If this is a child window, we want to apply the same type checking rules as the // parent window type. final int rootType = hasParent ? parentWindow.mAttrs.type : type; boolean addToastWindowRequiresToken = false; if (token == null) { if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= 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 (rootType == 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 (rootType == 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 (rootType == TYPE_WALLPAPER) { Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (rootType == TYPE_DREAM) { Slog.w(TAG_WM, "Attempted to add Dream window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (rootType == 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 (rootType == 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; } if (type == TYPE_TOAST) { // Apps targeting SDK above N MR1 cannot arbitrary add toast windows. if (doesAddToastWindowRequireToken(attrs.packageName, callingUid, parentWindow)) { Slog.w(TAG_WM, "Attempted to add a toast window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } final IBinder binder = attrs.token != null ? attrs.token : client.asBinder(); final boolean isRoundedCornerOverlay = (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0; token = new WindowToken(this, binder, type, false, displayContent, session.mCanAddInternalSystemWindow, isRoundedCornerOverlay); } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) { atoken = token.asAppWindowToken(); 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; } else if (type == TYPE_APPLICATION_STARTING && atoken.startingWindow != null) { Slog.w(TAG_WM, "Attempted to add starting window to token with already existing" + " starting window"); return WindowManagerGlobal.ADD_DUPLICATE_ADD; } } else if (rootType == 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 (rootType == 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 (rootType == 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 (rootType == 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 (rootType == 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_TOAST) { // Apps targeting SDK above N MR1 cannot arbitrary add toast windows. addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName, callingUid, parentWindow); if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) { Slog.w(TAG_WM, "Attempted to add a toast 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.asAppWindowToken() != null) { Slog.w(TAG_WM, "Non-null appWindowToken for system window of rootType=" + rootType); // 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, client.asBinder(), type, false, displayContent, session.mCanAddInternalSystemWindow); } //5555555555555 final WindowState win = new WindowState(this, session, client, token, parentWindow, appOp[0], seq, attrs, viewVisibility, session.mUid, session.mCanAddInternalSystemWindow); 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; } final boolean hasStatusBarServicePermission = mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE) == PackageManager.PERMISSION_GRANTED; mPolicy.adjustWindowParamsLw(win, win.mAttrs, hasStatusBarServicePermission); 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); } //666666666666666 if (type == TYPE_TOAST) { if (!getDefaultDisplayContentLocked().canAddToastWindowForUid(callingUid)) { Slog.w(TAG_WM, "Adding more than one toast window for UID at a time."); return WindowManagerGlobal.ADD_DUPLICATE_ADD; } if (addToastWindowRequiresToken || (attrs.flags & LayoutParams.FLAG_NOT_FOCUSABLE) == 0 || mCurrentFocus == null || mCurrentFocus.mOwnerUid != callingUid) { mH.sendMessageDelayed( mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win), win.mAttrs.hideTimeoutMilliseconds); } }
这里讲筛选部分大体分成这么几个步骤:
在代码中对应的步骤有明确的标注,而具体的代码大多只是一些判断,所以在感觉没有细说的必要了。
在条件筛选完成之后,剩下的类型都是符合添加的类型,从现在开始就开始对不同的type进行不同的添加。经过多到加工后,将OKAY返回。
如果能从添加窗口的步骤返回,就说明一定是OKAY的。那么我们可以一步步跳回层层调用的代码,最终在ViewRootImpl中,对没有添加成功的抛出异常。
if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; mAdded = false; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); switch (res) { case WindowManagerGlobal.ADD_BAD_APP_TOKEN: case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?"); case WindowManagerGlobal.ADD_NOT_APP_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not for an application"); case WindowManagerGlobal.ADD_APP_EXITING: throw new WindowManager.BadTokenException( "Unable to add window -- app for token " + attrs.token + " is exiting"); case WindowManagerGlobal.ADD_DUPLICATE_ADD: throw new WindowManager.BadTokenException( "Unable to add window -- window " + mWindow + " has already been added"); case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED: // Silently ignore -- we would have just removed it // right away, anyway. return; case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON: throw new WindowManager.BadTokenException("Unable to add window " + mWindow + " -- another window of type " + mWindowAttributes.type + " already exists"); case WindowManagerGlobal.ADD_PERMISSION_DENIED: throw new WindowManager.BadTokenException("Unable to add window " + mWindow + " -- permission denied for window type " + mWindowAttributes.type); case WindowManagerGlobal.ADD_INVALID_DISPLAY: throw new WindowManager.InvalidDisplayException("Unable to add window " + mWindow + " -- the specified display can not be found"); case WindowManagerGlobal.ADD_INVALID_TYPE: throw new WindowManager.InvalidDisplayException("Unable to add window " + mWindow + " -- the specified window type " + mWindowAttributes.type + " is not valid"); } throw new RuntimeException( "Unable to add window -- unknown error code " + res); }
对于OKAY的,在ViewRootImpl中会做一些其他的操作,反正我是没看懂-。=、
四、小结
到这里WMS的添加悬浮窗口的流程差不多就过了一遍了。可能有些地方说的不是很细,大家下来可以关注一下个别几个点。整个过程有这么几个需要强调的地方。
如果在添加悬浮窗的时候使用了不同的type,可能会发生异常:本人拿了一个8.0的手机,分别对窗口type设置为OVERLAY和ERROR。因为ERROR类型是被弃用的,我发现使用ERROR会抛出异常,而OVERLAY不会。同样的拿了一个6.0的手机添加ERROR类型就没有异常抛出,肯定是上述的问题导致的,但是具体在哪一块我还没有找到,因为整个流程的出口太多了-。=。
此外在WindowManagerGlobal.addView方法中,有一个地方:
if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else {
这个方法是对于有子窗口类型的证书处理,网上查了一下该方法在四点几、六点几和8.0是不同的,也就是说对证书的处理方式变化了,这里本人还没有细看,有兴趣的盆友可以研究一下然后评论交流一番。
总结