从源码分析Dialog,PopWindow为啥需要用activity的context创建,而不能用application。
关系图
先来一张图展示,activity的结构和内容。可知 activity,phoneWindow,WindowManagerIMPL,viewRootIMPL是一对一的关系,WindowManagerIMPL内部维护一个全局WindowManagerGlobal对象。PhoneWIndow 中对视图的操作,都是通过WindowManagerIMPL代理实现,最终都是ViewRootImpl 和 WMS交互的完成的。
Dialog的创建
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
//可以看出来,dialog其实也支持主题的,就是个小型的activity,但是必须依附于activity,可以视为activity的子窗口,这个后面再说
if (themeResId == Resources.ID_NULL) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeResId = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
// 获取wms服务
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//创建phonewindow
final Window w = new PhoneWindow(mContext);
...
...省略
// 初始化phonewindow,注意后面两个参数为空
//关联WindowManager与新Window,特别注意第二个参数token为null,也就是说Dialog没有自己的token
//一个Window属于Dialog的话,那么该Window的mAppToken对象是null,
//mAppToken对象保存在windowState对象中,对应着这个窗口是否在AMS中有ActivityRecord记录。
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
appToken代表的是activity在AMS服务端的实体记录activityRecord,在ActivityThread内是以ibinder形式存在的,也就是远程通信对象,这个对象很重要,也注定dialog能否展示的关键。
我们来看下这个setWindowManager是怎么处理的:
也就是一个窗口实例会创建一个WindowManagerIMP的实例,一对一,activity一个对应一个,alertDIalog也对应一个。如果alerDialog的context使用的是activity的话,会获取到activity的WindowManagerIMP的实例,因为activity重写了这个方法。
由于传递的activity的context因此会调用activity重写的getSystemSevice方法,返回的其实是activity的mWIndowManager服务对象。
// 如果这个context是contextIMP对象的话,那么就是从app全局map中获取的单例,有兴趣自己去查看源码
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
可以看见Context的WindowManager对每个APP来说是一个全局单例的,而Activity的WindowManager是每个Activity都会新创建一个的(其实你从上面分析的两个实例化WindowManagerImpl的构造函数参数传递就可以看出来,Activity中Window的WindowManager成员在构造实例化时传入给WindowManagerImpl中mParentWindow成员的是当前Window对象,而ContextImpl的static块中单例实例化WindowManagerImpl时传入给WindowManagerImpl中mParentWindow成员的是null值),所以上面模拟苹果浮动小图标使用了Application的WindowManager而不是Activity的,原因就在于这里;使用Activity的WindowManager时当Activity结束时WindowManager就无效了,所以使用Activity的getSysytemService(WINDOW_SERVICE)获取的是Local的WindowManager。同时可以看出来Activity中的WindowManager.LayoutParams的type为TYPE_APPLICATION。
Dialog的show方法
public void show() {
if (!mCreated) {
dispatchOnCreate(null);
} else {
....
}
...
onStart();
mDecor = mWindow.getDecorView();
...
WindowManager.LayoutParams l = mWindow.getAttributes();
...
//这句话是关键
mWindowManager.addView(mDecor, l);
...
//发送handle消息,通知操作回调
sendShowMessage();
}
mWindowManager.addView(mDecor, l);这句话对应文章开头的对象结构图,调用链是:
mWindowManager.addView ===> windowManagerGlobal ===> 创建ViewRootImp ===>
ViewRootImp.setView ===> mWindowSession.addToDisplay 。
mWindowSession是viewRootImp的内部获取的WMS远程对象,用于跨进程调用WMS的方法。
这样就来到了WMS方法内部。
WMS对应窗口的管理
public int addWindow(Session session, IWindow client, int seq,
LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState) {
int[] appOp = new int[1];
int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
WindowState parentWindow = null;
...省略
synchronized (mGlobalLock) {
//这种情况一般不存在,只要正常流程displayContent 都是非空
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;
}
//displayContent对wms没有访问权限,这个只要是在application中应该都可以使用,不考虑
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;
}
// 已经缓存的windowmap中是否已经存在该iWindow对象,该对象是viewRootImp在跨进程中的binder对象,
// dialog多次添加的情况下,不处理,防止重复添加
if (mWindowMap.containsKey(client.asBinder())) {
Slog.w(TAG_WM, "Window " + client + " is already added");
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
// dialog窗口的类型是 APPLICATION_WINDOW 类型,这里不看,但是popwindow是该类型的
// 还有就是 surfaceview的窗口类型也是这种,会获取所依附的父窗口,也就是activity
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;
}
}
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;
}
...省略
上面步骤中,分析了一部分意外情况,还没到dialog的context是非activity的情况。
可以先说下这几个参数是啥:
Session session, //一个应用会创建一个这个对象,单例保存在viewRootImp中,用于和WMS通信
IWindow client, //客户端viewRootImp对象的内部类binder实例,传递给WMS,用于WMS远程调用
LayoutParams attrs, //客户端的windowManagerImp内部的布局对象
dialog的layoutParams是TYPE_APPLICATION因此不会执行到 SUB_WINDOW判断里面的内容。
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;
}
再往下看,getWindowToken是根据我们传递进来的attrs.token也就是客户端的IWindow对象,从map集合中获取windowToken对象,很显然,如果我们不是传递的activity的token,那么map集合中不可能存在iwindow对象,因为我们都没有保存过怎么可能存在这个对象。这样走走到了token==null的判断里面去了,我们上面说过alertDialog的窗口类型就是APPLICATION_WINDOW类型的,因此下面就会返回WindowManagerGlobal.ADD_BAD_APP_TOKEN;这个错误给我们的客户端。
viewRootIMP对于返回结果的处理
由于我的手机没展示错误就闪退了,只能看日志报错,所有上面日志的报错就对应着,viewrootIMP所抛出的异常错误,结果很明显了,就是application之所以不能创建dialog对象,是因为WMS服务端查询不到对应的windowToken对象,所有会报错误给客户端。
popWindow的创建过程
popWindow创建过程和AlertDialog差不多不赘述,来看下如何展示的:
可以看到需要外部传入一个view,然后获取这个view的windowToken,而创建的窗口类型是:
/**
* Window type: a panel on top of an application window. These windows
* appear on top of their attached window.
*/
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
上面看到的就是popupWindow的窗口类型,就是子窗口类型,如果我们使用的是或者不是activity的Token的话,我们按照这个思路去WMS寻找下答案。
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;
}
}
那么进入到这个if判断里面去,parentWindow是根据attrs.token来获取的,也就是这个Token对象对应的WindowState,WindowState对象是activity第一次添加窗口的时候创建的,成员变量是mAppToken,保存的是AMS中传递过来ActivityReord对象来进行创建的,因此如果我们的Token不是对应着AMS中的activity的话,那么这个parentWindow也就是个null,接着就会报异常给客户端,viewRootIMP中回对异常进行处理,看下是怎么处理的:
一样的和AlertDialog的异常是一样的。
偷老罗的一张图展示下,三个进程之间的关系,在我们的AMS的通知我们的ActivityThread进行activity的启动的时候,在AMS中相应把ActivityRecord对象传递给WMS中,在WMS中创建AppWindowToken对象,在Activity第一次添加窗口到WMS中的时候,会将viewRootIMP的iWindow对象,appWindowToken,作为成员变量,创建出windowState对象,这样我们接着在activity中启动AlertDialog的话,添加dialog的window到WMS中,判断这个token是否已经存在,显然如果是activity的Token肯定可以找得到,如果是application的Token那明显是空的,所以会报错。
参考:
https://blog.csdn.net/Luoshengyang/article/details/8275938
https://blog.csdn.net/yanbober/article/details/46361191
本文地址:https://blog.csdn.net/u012345683/article/details/107398229