Dialog中的Window添加过程解析
Dialog一直作为一种依附在Activity上下文才能存在的窗口视图,那是否可以逃脱Activity的上下文,采用其他Context存在呢?答案是肯定的,Dialog完全可以不依赖Activity上下文存在,这里我们说的只是非Activity的Context,不是不依赖Context
- Dialog的组成:
Dialog是一种承载Window的容器,而Window的唯一实现便是PhoneWindow,Dialog的setContentView就是将布局文件的id传给PhoneWindow,PhoneWindow通过该布局id解析然后创建一个DecorView,这是一个继承FrameLayout的ViewGroup,每个Window都有一个WindowManagerImpl,这里所说的是每个非子window类型的window,因为子window是依附于父window,父子共用一个WindowManagerImpl,普通的Dialog的WindowManagerImpl与Activity是共用的 - Dialog的创建代码:
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); // 获取windowmanager
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null); // phonewindow 与 Windowmanager绑定
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
再来看一下setContentView代码:
Dialog.java
public void setContentView(@LayoutRes int layoutResID) {
mWindow.setContentView(layoutResID);
}
PhoneWindow.java
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor(); // 创建DecorView与PhoneWindow绑定
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
以上也说明Dialog中视图的构建过程,其实就是Dialog持有一个Context、phonewindow、windowmanager,其中Context如果是Activity的话,因为Activity是集成ContextWrapTheme类,所以由Activity上下文构建的Dialog是和Activity的主题一样的。phonewindow持有视图解析后的View结构树DecorView,而WindowManager,如果是子窗口类型,便使用父窗口的windowmanager,如果是系统窗口,将独立创建一个windowmanager,其实每个WindowManager也是一个傀儡,真正执行View操作的的事WindowManagerGlobal,这个每个Application只有一个该单例对象,管理当前app的所有window以及view的跟新操作,可以看一下为什么WindowManager是个傀儡,这个类代码就几十行:
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
private IBinder mDefaultToken;
public WindowManagerImpl(Context context) {
this(context, null);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
return new WindowManagerImpl(displayContext, mParentWindow);
}
/**
* Sets the window token to assign when none is specified by the client or
* available from the parent window.
*
* @param token The default token to assign.
*/
public void setDefaultToken(IBinder token) {
mDefaultToken = token;
}
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
// Only use the default token if we don't have a parent window.
if (mDefaultToken != null && mParentWindow == null) {
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
// Only use the default token if we don't already have