我们都知道创建一个对话框dialog的时候使用的上下文对象context必须是Activity,而不能是Application,在Android中Context是一个抽象类,ContextWrapper,ContextThemeWrapper,Activity,Services,Application都是其直接或间接子类,该文主要是从框架层的角度分析下为啥必须是Activity
1:报错信息
先来看一个错误示例,在Dialog的构造方法中传入一个Application的上下文环境。看看程序是否报错:
Dialog dialog = new Dialog(getApplication());
TextView textView = new TextView(this);
textView.setText("创建Application的Dialog");
dialog.setContentView(textView);
dialog.show();
运行程序,程序不出意外的崩溃了,我们来看下报错信息:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
at android.view.ViewRootImpl.setView(ViewRootImpl.java:517)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:301)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:215)
at android.view.WindowManagerImpl$CompatModeWrapper.addView(WindowManagerImpl.java:140)
从上面的报错信息我们可以看到以下信息:
Unable to add window -- token null is not for an application
2:token到底是什么东西
token中文的意思就是令牌的意思,其本质上是一个binder对象,我们在跨进程通信过程中充当的是一个安全校验的作用,另外在wms端,token还是窗口分组的一句,也就是说相同token的窗口会放在一起进行组织和管理,我们在启动一个activity的时候会在ams端创建一个token对象,应用层的activity在ams端对应的对象是y一个ActivityRecord对象,在ActivityRecord的构造方法中,会创建一个token对象,在为该activity创建窗口的过程中会将该token对象从ams传递到wms端,当然并不是所有的窗口在向wms申请创建窗口的过程中都需要显式的向wms表明token,部分系统窗口wms会为其创建token对象
3:创建对话框的流程
Dialog有两个公开的构造方法
public Dialog(@NonNull Context context) {
this(context, 0, true);
}
public Dialog(@NonNull Context context, @StyleRes int themeResId) {
this(context, themeResId, true);
}
最终都会调到
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == ResourceId.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;
}
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);
mListenersHandler = new ListenersHandler(this);
}
从上面的实现有两个关键点
1:获取windowmanager对象
2:为对话框dialog的window设置windowmanager
其中第一步就涉及到context对象,如果该context是applicaition类型,我们分析下getSystemService的过程
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
我们看下getSystemService的实现
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
接着分析
/**
* Gets a system service from a given context.
*/
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
这里是通过serviceFetcher从一个Hashmap对象中获取的
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}
找到其实现的地方
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
我们找到WindowManagerImpl的构造方法
public WindowManagerImpl(Context context) {
this(context, null);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
可以看到此时将其全局变量mparentwindow设置为null,至此其windowmanager便创建完成
4:对话框的显示流程
一个对话框显示是通过调用show方法来完成
public void show() {
if (mShowing) {
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
mCanceled = false;
if (!mCreated) {
dispatchOnCreate(null);
} else {
// Fill the DecorView in on any configuration changes that
// may have occured while it was removed from the WindowManager.
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
onStart();
mDecor = mWindow.getDecorView();
if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
final ApplicationInfo info = mContext.getApplicationInfo();
mWindow.setDefaultIcon(info.icon);
mWindow.setDefaultLogo(info.logo);
mActionBar = new WindowDecorActionBar(this);
}
WindowManager.LayoutParams l = mWindow.getAttributes();
boolean restoreSoftInputMode = false;
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
l.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
restoreSoftInputMode = true;
}
mWindowManager.addView(mDecor, l);
if (restoreSoftInputMode) {
l.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
}
mShowing = true;
sendShowMessage();
}
可以看出其是通过第一步生成的windowmanager添加decorview到窗口管理器的
进入到windowmanagerImpl的addview方法
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) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
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;
}
}
其中有很重要的一步
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
如果parentwindow不为空的话,则进行相关的操作
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();
}
}
if (curTitle == null || curTitle.length() == 0) {
final StringBuilder title = new StringBuilder(32);
if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) {
title.append("Media");
} else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY) {
title.append("MediaOvr");
} else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
title.append("Panel");
} else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) {
title.append("SubPanel");
} else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL) {
title.append("AboveSubPanel");
} else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) {
title.append("AtchDlg");
} else {
title.append(wp.type);
}
if (mAppName != null) {
title.append(":").append(mAppName);
}
wp.setTitle(title);
}
} else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
// We don't set the app token to this system window because the life cycles should be
// independent. If an app creates a system window and then the app goes to the stopped
// state, the system window should not be affected (can still show and receive input
// events).
if (curTitle == null || curTitle.length() == 0) {
final StringBuilder title = new StringBuilder(32);
title.append("Sys").append(wp.type);
if (mAppName != null) {
title.append(":").append(mAppName);
}
wp.setTitle(title);
}
} else {
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
if ((curTitle == null || curTitle.length() == 0)
&& mAppName != null) {
wp.setTitle(mAppName);
}
}
if (wp.packageName == null) {
wp.packageName = mContext.getPackageName();
}
if (mHardwareAccelerated ||
(mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
wp.flags |= FLAG_HARDWARE_ACCELERATED;
}
}
这个方法主要是根据窗口的类型来调整一些相关的参数,我们找到dialog的类型type:
WindowManager.LayoutParams l = mWindow.getAttributes();
找到
public final WindowManager.LayoutParams getAttributes() {
return mWindowAttributes;
}
初始化为
private final WindowManager.LayoutParams mWindowAttributes =
new WindowManager.LayoutParams();
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
类型为Type_application
因此会走最后一个else语句
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
if ((curTitle == null || curTitle.length() == 0)
&& mAppName != null) {
wp.setTitle(mAppName);
}
}
if (wp.packageName == null) {
wp.packageName = mContext.getPackageName();
}
if (mHardwareAccelerated ||
(mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
wp.flags |= FLAG_HARDWARE_ACCELERATED;
}
这里就会对dialog的token赋值,如果执行了上面的语句就不会导致token为null,这样便可以不会因为token为null导致其无法添加
5:为什么activity不会造成token为null
我们都知道应用程序段的activity是在performLaunchActivity的过程中创建的,当activity对象创建成功之后便会执行器attach方法,
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
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);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
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());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());
enableAutofillCompatibilityIfNeeded();
}
我们看下其获取windowmanager的过程,进入到window的setWindowManager方法中
public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
setWindowManager(wm, appToken, appName, false);
}
/**
* Set the window manager for use by this Window to, for example,
* display panels. This is <em>not</em> used for displaying the
* Window itself -- that must be done by the client.
*
* @param wm The window manager for adding new windows.
*/
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
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);
}
我们可以发现parentWindow不为null,为activity对应的PhoneWindow对象,这样其parentWindow便不为null,并且将activity的token对象赋值给dialog,在wms端分组管理的时候被放在同一组中进行管理
小结:添加一个窗口的过程最终都是通过windowmanager的addView方法添加到窗口管理器的,由wms为其分配surface,应用程序在surface上绘制自己的内容,最终通过surfacefligner合成渲染到屏幕上,在添加窗口的过程中wms会进行一系列的检查和校验:权限,窗口类型,token值的校验,windowmanager只是一个接口,其实现类为WindowmanagerImpl,WindowManagerImpl中最终干活的是WindowmanagerGlobal,最终的入口就是addView方法,如果创建dialog的过程中使用的不是activity作为上下文对象,则造成addView的过程中mparentWindow为null,造成不会调用adjustLayoutParamsForSubWindow,从而dialog在添加窗口的过程中没有token值,在wms端的addWindow方法中校验失败
网友评论