結論:
用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,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/271598.html
標籤:其他
上一篇:常用C++代碼技巧
下一篇:android 多屏異屏顯
