为什么Application不能作为Dialog的Context
作者:互联网
结论:
用activity作为context参数的dialog创建的windowManager是有token的,用service和application作为参数的windowManager的是没有token,所以会崩溃,这样做的好处是防止当你已经关闭页面了,或者已经打开其他app了,这个时候弹出一个操作弹窗,防止误操作。
源码分析
首先我们看dialog的构造方法的代码片段
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
注意这里的mWindowManager,是通过context.getSystemService(Context.WINDOW_SERVICE);我们看这个方法在activity的实现
activity的getSystemService
//Activity#
@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);
}
再看mWindowManager的在哪里赋值的
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
//···
//注释1
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
//注释2
mWindowManager = mWindow.getWindowManager();
先看注释1处,注意这里的mWindow是phoneWindow,而且这个phonewindow的token不为空
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated;
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
//再看
//WindowManagerImpl#createLocalWindowManager
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
可以看到创建了一个WindowManagerImpl,并且把PhoneWindow作为参数赋值给了WindowManagerImpl#mParentWindow,敲重点,这里的 WindowManagerImpl的mParentWindow是activity的phonewindow,而这个mParentWindow是有token的
我们再看Application的getSystemService
//先看Application的构造方法
public Application() {
super(null);
}
//application中的实现是在ContextWrapper中
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
@Override
public Object getSystemService(String name) {
return mBase.getSystemService(name);
}
//Context#getSystemService
public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
可以看到Application的mBase是null,所以getSystemService返回的也是null,但是在dialog的构造方法里w.setWindowManager(mWindowManager, null, null);,而这个方法会创建一个WindowManagerImpl,类比刚才的activity,只不过这个时候传进来的window是没有token的,也就是此时的WindowManagerImpl#mParentWindow是没有token的。
至此,windowManager的来源分析清楚。
dialog#show()
public void show() {
//······
mDecor = mWindow.getDecorView();
//······
WindowManager.LayoutParams l = mWindow.getAttributes();
//······
mWindowManager.addView(mDecor, l);
//······
}
我们看mWindowManager.addView(mDecor, l);,第一部分我们已经分析了这个mWindowManager的创建(是WindowManagerImpl)。我们直接看WindowManagerImpl#addView
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
//接着看WindowManagerGlobal#addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//······
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//注释1
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;
}
}
}
注意看注释1,因为这两种方式我们都是有window的,所以parentWindow != null成立,我们看parentWindow.adjustLayoutParamsForSubWindow(wparams);注意此时调用的是parentWindow是我们通过context.getSystemService(Context.WINDOW_SERVICE)获取的,也就是WindowManagerImpl.mParentWindow。
//Window#adjustLayoutParamsForSubWindow
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
CharSequence curTitle = wp.getTitle();
if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
if (wp.token == null) {
View decor = peekDecorView();
if (decor != null) {
wp.token = decor.getWindowToken();
}
}
//``````
} else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
//``````
} else {
/注释1
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
//``````
}
//``````
}
注意此时的wp.token的来源,是在Dialog.show()的时候取得mWindow.getAttributes(),此时的mWindow的token的空的,因此会调用注释1,而这个类又是我们第一部分讲的WindowManagerImpl.mParentWindow,所以当context时activity的时候,mAppToken不为null。
标签:Dialog,WindowManagerImpl,WindowManager,Application,token,Context,mWindow,null,ge 来源: https://blog.csdn.net/qq_31302233/article/details/115347382