try {
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
} finally {
}
…
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?");
…
}
throw new RuntimeException(
"Unable to add window – unknown error code " + res);
}
…
}
}
}
在这个方法中,我们找到了抛出异常的地方,当res的值为WindowManagerGlobal.ADD_BAD_APP_TOKEN或则WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN时,会抛出文章开头处的错误。 这个res是IWindowSession#addToDisplay
方法的返回值。IWindowSession是一个Binder接口,负责ViewRootImpl和WindowManagerService的通信,在ViewRootImpl对象创建时获取。在这里IWindowSession#addToDisplay
最终会调用WindowManagerService#addWindow方法:
// com.android.server.wm.WindowManagerService
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
…
synchronized(mWindowMap) {
…
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;
}
}
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
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;
}
…
}
…
return res;
}
通过这个方法,可以确定方法返回WindowManagerGlobal.ADD_BAD_APP_TOKEN或则WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN的条件,即Window的类型为子窗口时,parentWindow为空会返回WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN,当Window类型为APPLICATION_WINDOW时,当对应的WindowToken为空就会返回WindowManagerGlobal.ADD_BAD_APP_TOKEN
2. WindowToken是什么
在第一章节的最后一部分,我们引入了一个新概念:WindowToken,它和这个异常的判断息息相关,那么它是在哪里创建的呢?作用又是什么呢?
看下它的获取方式:
WindowToken getWindowToken(IBinder binder) {
return mTokenMap.get(binder);
}
先看下AppWindowToken的继承体系:
回顾一下Activity的启动流程,在Activity的启动过程中,会调用ActivityStack#startActivityLocked方法:
final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
boolean newTask, boolean keepCurTransition, ActivityOptions options) {
…
TaskRecord task = null;
if (!newTask) {
boolean startIt = true;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
task = mTaskHistory.get(taskNdx);
if (task.getTopActivity() == null) {
continue;
}
if (task == rTask) {
if (!startIt) {
if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to task "
- task, new RuntimeException(“here”).fillInStackTrace());
// zhangyulong 生成WindowContainer对象
r.createWindowContainer();
ActivityOptions.abort(options);
return;
}
break;
} else if (task.numFullscreen > 0) {
startIt = false;
}
}
}
…
}
在这个方法中会调用ActivityRecord#createWindowContainer方法,在这个方法里会创建AppWindowContainer的实例,进而创建AppWindowContainerController实例,在AppWindowContainerController中会创建AppWindowToken的实例,在创建实例时会调用AppWindowToken的构造方法。从上图AppWindowToken的继承关系图我们可以知道,AppWindowToken的父类是WindowToken,其构造方法如下:
// com.android.server.wm.WindowToken
WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
DisplayContent dc, boolean ownerCanManageAppTokens) {
mService = service;
token = _token;
windowType = type;
mPersistOnEmpty = persistOnEmpty;
mOwnerCanManageAppTokens = ownerCanManageAppTokens;
onDisplayChanged(dc);
}
看下onDisplayChanged方法:
// com.android.server.wm.onDisplayChanged
void onDisplayChanged(DisplayContent dc) {
dc.reParentWindowToken(this);
…
}
DisplayContent#reParentWindowToken会将该WindowToken加入到一个叫做mTokenMap的Map中,其定义为HashMap<IBinder, WindowToken> mTokenMap
. 而作为key的IBinder对象,就是我们在Activity启动流程中创建的AppToken对象。也就是说,每个AppToken会对应一个AppWindowToken,简单的示例图如下:
3. 问题产生的根本原因
要想探寻这个错误产生的根本原因,我们只需要寻确认在异常产生时,Window的Type和appToken的值是什么即可。
Window的Type是比较好确认的,在创建Dialog的Window时,便生成了其对应的WindowManager.LayoutParams
, 在其构造方法中已确定了Dialog对应Window的type:
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
而appToken的情况则稍显复杂,我们首先要确定这个appToken是从哪里获取的。
先看一下Dialog的构造方法:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
…
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
…
}
这个WindowManger是我们在show方法中用于执行addView的WindowManger。
那么接下来,我们就要确定在Application中启动Dialog和在Activity中启动Dialog这两种方式WindowManger的处理有什么不同就可以了。
首先看Activity的情况:
还是Dialog的构造方法,这个Context的实际类型是Activity的对象,而Activity重写了getSystemService方法:
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
“System services not available to Activities before onCreate()”);
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
;
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}