本篇从Dialog的创建来看一看Android的窗口是如何添加的,
写个Demo,简单的例子创建一个Dialog,点击button,Dialog显示出来
public class MainActivity extends Activity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
final AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("Test")
.setMessage("hello world")
.setIcon(R.drawable.ic_launcher_background)
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
builder.show();
}
});
}
}
显示出来就是这样的:
AlertDialog
AlertDialog通过内部的builder设置Dialog的各种属性,如title,message,点击事件等,全部存入AlertController.AlertParams对象,AlertParams是一个类似LayoutParams的类,存储这窗口的各种属性
public class AlertDialog extends Dialog implements DialogInterface {
......
public static class Builder {
@UnsupportedAppUsage
private final AlertController.AlertParams P;
public Builder(Context context) {
this(context, resolveDialogTheme(context, Resources.ID_NULL));
}
public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
}
public Context getContext() {
return P.mContext;
}
public Builder setTitle(@StringRes int titleId) {
P.mTitle = P.mContext.getText(titleId);
return this;
}
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
public Builder setCustomTitle(View customTitleView) {
P.mCustomTitleView = customTitleView;
return this;
}
public Builder setMessage(@StringRes int messageId) {
P.mMessage = P.mContext.getText(messageId);
return this;
}
public Builder setMessage(CharSequence message) {
P.mMessage = message;
return this;
}
public Builder setIcon(@DrawableRes int iconId) {
P.mIconId = iconId;
return this;
}
public Builder setIcon(Drawable icon) {
P.mIcon = icon;
return this;
}
public Builder setIconAttribute(@AttrRes int attrId) {
TypedValue out = new TypedValue();
P.mContext.getTheme().resolveAttribute(attrId, out, true);
P.mIconId = out.resourceId;
return this;
}
public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) {
P.mPositiveButtonText = P.mContext.getText(textId);
P.mPositiveButtonListener = listener;
return this;
}
public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
P.mPositiveButtonText = text;
P.mPositiveButtonListener = listener;
return this;
}
public Builder setNegativeButton(@StringRes int textId, final OnClickListener listener) {
P.mNegativeButtonText = P.mContext.getText(textId);
P.mNegativeButtonListener = listener;
return this;
}
public Builder setNegativeButton(CharSequence text, final OnClickListener listener) {
P.mNegativeButtonText = text;
P.mNegativeButtonListener = listener;
return this;
}
public Builder setNeutralButton(@StringRes int textId, final OnClickListener listener) {
P.mNeutralButtonText = P.mContext.getText(textId);
P.mNeutralButtonListener = listener;
return this;
}
public Builder setNeutralButton(CharSequence text, final OnClickListener listener) {
P.mNeutralButtonText = text;
P.mNeutralButtonListener = listener;
return this;
}
public Builder setCancelable(boolean cancelable) {
P.mCancelable = cancelable;
return this;
}
public Builder setOnCancelListener(OnCancelListener onCancelListener) {
P.mOnCancelListener = onCancelListener;
return this;
}
public Builder setOnDismissListener(OnDismissListener onDismissListener) {
P.mOnDismissListener = onDismissListener;
return this;
}
public Builder setOnKeyListener(OnKeyListener onKeyListener) {
P.mOnKeyListener = onKeyListener;
return this;
}
public Builder setItems(@ArrayRes int itemsId, final OnClickListener listener) {
P.mItems = P.mContext.getResources().getTextArray(itemsId);
P.mOnClickListener = listener;
return this;
}
......
}
show()
从show方法开始
public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}
create
AlertDialog的创建
public AlertDialog create() {
final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
看下AlertDialog构造方法
AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
//调用父类Dialog的构造方法
super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
createContextThemeWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
//创建AlertController
mAlert = AlertController.create(getContext(), this, getWindow());
}
看下AlertController的构造方法
这里面就是加载Dialog的布局,样式等,我们创建的最简单的Dialog,全部采用默认值
protected AlertController(Context context, DialogInterface di, Window window) {
......
final TypedArray a = context.obtainStyledAttributes(null,
R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
//默认的Dialog的布局文件为R.layout.alert_dialog
mAlertDialogLayout = a.getResourceId(
R.styleable.AlertDialog_layout, R.layout.alert_dialog);
//默认为0
mButtonPanelSideLayout = a.getResourceId(
R.styleable.AlertDialog_buttonPanelSideLayout, 0);
......
//showTitle为true
mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);
a.recycle();
//Feature为FEATURE_NO_TITLE
window.requestFeature(Window.FEATURE_NO_TITLE);
}
看下Dialog的构造方法,大概就是创建window对象,设置回调可以接受window的事件进行处理
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
//这里传进来的是false
if (createContextThemeWrapper) {
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 {
//注意,这里的context传的是Activity
mContext = context;
}
//从Activity里获取WindowManagr,获取到的是和Activity同一个WindowManager
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//创建PhoneWindow
final Window w = new PhoneWindow(mContext);
mWindow = w;
//给窗口设置回调,Dialog会实现Window中的回调方法,
//window接受到事件之后就可以传递给Dialog处理
//这点和Activity添加到Window的流程一样
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
//注意下这个方法,设置WindowManager,后面两个参数为空
//token为空
w.setWindowManager(mWindowManager, null, null);
//调整位置,处于中间
w.setGravity(Gravity.CENTER);
//Handler类,处理事件的
mListenersHandler = new ListenersHandler(this);
}
以上需要重点注意的是Dialog的WindowMnaager和Activity共用同一个,虽然Dialog自身也通过setWindowManager方法创建了WindowManager,但是并没有将赋值给自身成员变量mWindowManager而是用的Activity的WindowManager
Activity.getSystemService
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
Window.setWindowManager
注意Dialog的token为空,Android创建任何窗口都必须要有token,关于窗口和token的更介绍可以看下Android窗口与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);
}
//通过Activity的WindowManager创建了WindowManagerImpl
//mWindowManager是Dialog的WindowManagerImpl和Activity的
//WindowManagerImpl不是同一个
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
createLocalWindowManager
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
最后主要是创建了WindowManagerImpl类,这里创建的是Dialog中的WindowManagerImpl,传入的是Dialog的PhoneWindow
好了接着回到AlertDialog的create方法,看下P.apply方法
public AlertDialog create() {
final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
//设置Dialog数据
P.apply(dialog.mAlert);
//设置dialog是否可以取消
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
//设置点击事件
dialog.setOnCancelListener(P.mOnCancelListener);
//设置dismiss事件
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
P.apply
很明显就是给Dailog设置具体数据,有setTitle,setIcon,setButton等
public void apply(AlertController dialog) {
if (mCustomTitleView != null) {
dialog.setCustomTitle(mCustomTitleView);
} else {
if (mTitle != null) {
dialog.setTitle(mTitle);
}
if (mIcon != null) {
dialog.setIcon(mIcon);
}
if (mIconId != 0) {
dialog.setIcon(mIconId);
}
if (mIconAttrId != 0) {
dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
}
}
if (mMessage != null) {
dialog.setMessage(mMessage);
}
if (mPositiveButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
mPositiveButtonListener, null);
}
if (mNegativeButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
mNegativeButtonListener, null);
}
if (mNeutralButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
mNeutralButtonListener, null);
}
到这里Dialog已经创建完毕,接着再回到Dialog的show方法
public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}
dialog.show()
调用父类Dialog的show方法。
public void show() {
//mShowing默认值为false
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;
//mCreated默认值为false,保证onCreate只调用一次
if (!mCreated) {
//这里面就是调用Dialog的onCreate方法
dispatchOnCreate(null);
} else {
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
//调用onStart
onStart();
//获取DecorView,DecorView就是窗口的顶层布局
mDecor = mWindow.getDecorView();
//有ActionBar的情况
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();
......
mWindowManager.addView(mDecor, l);
......
mShowing = true;
sendShowMessage();
}
从上面流程看和Activity有点类似,也有onCreate,onStart生命周期,我们看一下AlertDialog的onCreate
onCreate
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlert.installContent();
}
AlertController.installContent
public void installContent(AlertParams params) {
//给Dailog的布局文件设置数据
params.apply(this);
installContent();
}
public void installContent() {
int contentView = selectContentView();
mWindow.setContentView(contentView);
setupView();
}
selectContentView
返回默认布局mAlertDialogLayout,mAlertDialogLayout再AlertController构造方法中赋值为默认值R.layout.alert_dialog,这就是默认Dialog的布局文件,后面会调用setContentView添加到DecorView中
private int selectContentView() {
if (mButtonPanelSideLayout == 0) {
return mAlertDialogLayout;
}
if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
return mButtonPanelSideLayout;
}
// TODO: use layout hint side for long messages/lists
return mAlertDialogLayout;
}
现在只是获取了Dialog默认的布局文件,还没有填充数据,如title,message,icon等,setupView这个方法会获取布局中的控件并将数据设置进去
setupView
一系列的findViewId获取了布局文件的控件之后调用setXXX设置数据
private void setupView() {
final View parentPanel = mWindow.findViewById(R.id.parentPanel);
final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
// Install custom content before setting up the title or buttons so
// that we can handle panel overrides.
final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
setupCustomContent(customPanel);
final View customTopPanel = customPanel.findViewById(R.id.topPanel);
final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
// Resolve the correct panels and remove the defaults, if needed.
final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
setupContent(contentPanel);
setupButtons(buttonPanel);
setupTitle(topPanel);
......
.....
}
mWindow.setContentView()
这完全和Activity的setContentView流程一样
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//创建DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
......
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
......
} else {
//加载Dialog的默认布局到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//Dialog中的onContentChanged实现为空
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
installDecor
主要看这里面两个核心方法,创建DecorView和创建contentView
private void installDecor() {
......
if (mDecor == null) {
mDecor = generateDecor(-1);
.....
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
......
}
}
generateDecor
new了一个DecorView
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
generateLayout
这个方法代码比较多,主要就是根据主题theme设置对应的xml布局文件以及Feature到DecorView中,并将mContentParent和id为ID_ANDROID_CONTENT(com.android.internal.R.id.content)的ViewGroup
绑定,我们Dialog没有设置特别的主题
protected ViewGroup generateLayout(DecorView decor) {
......
layoutResource = R.layout.screen_simple;
//这个方法会通过mLayoutInflater解析layoutResource,
//并添加到DecorView中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
......
return contentParent;
}
可以看到DecorView添加的布局文件,就是一个垂直LinearinLayout,包含一个ActionBar和id为content的FrameLayout,Activity的setContentView就是将我们自己创建的布局设置到content中
screen_simple.xml
<LinearinLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
总结下getDecorView:
1.创建DecorView,并根据指定的Theme加载对应布局到DecorView中,默认的布局就是就是一个垂直LinearinLayout,包含一个ActionBar和content
2.获取DecorView加载的布局中id为R.id.content的FrameLayout赋值给contentParent
再回到setContentView中,DecorView和contentParent创建完之后将Dialog的默认布局加载到contentParent中,现在的DecorView已经拥有了可以显示的布局了
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//创建DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
......
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
......
} else {
//加载Dialog的默认布局到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//Dialog中的onContentChanged实现为空
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
接着再回到dialog.show(),onCreate方法调用之后Dialog的布局已经加载到了DecorView中
public void show() {
//mShowing默认值为false
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;
//mCreated默认值为false,保证onCreate只调用一次
if (!mCreated) {
//这里面就是调用Dialog的onCreate方法
dispatchOnCreate(null);
} else {
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
//调用onStart,AlertDialog没有实现
onStart();
//获取DecorView,DecorView就是窗口的顶层布局
mDecor = mWindow.getDecorView();
//有ActionBar的情况
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);
}
//获取窗口的LayoutParams
WindowManager.LayoutParams l = mWindow.getAttributes();
......
mWindowManager.addView(mDecor, l);
......
mShowing = true;
sendShowMessage();
}
getAttributes
private final WindowManager.LayoutParams mWindowAttributes =
new WindowManager.LayoutParams();
public final WindowManager.LayoutParams getAttributes() {
return mWindowAttributes;
}
注意,在创建Dialog的时候是new的一个WindowManager.LayoutParams,我们看下WindowManager.LayoutParams的构造方法,窗口的type是TYPE_APPLICATION,并不是子窗口
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
mWindowManager.addView
接着调用addView将Dialog窗口显示出来,前面说了Dialog用的是Activity的WindowManager,所以这里调用的是Activity的WindowManager的addView方法
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
限于篇幅本篇就讲到这里,本篇主要介绍了Dialog如何一步一步加载到DecorView中的,其实Dialog布局文件的加载和Activity的布局加载几乎一样的,都是onCreate中加载布局文件,之后的流程都一样,到PhoneWindow中创建DecorView并将具体布局文件设置进DecorView的id为content的FrameLayout中
需要特别注意的是Dialog和Activity共用同一个WindowManager,此WindowManager在Activity的attach方法中创建
具体Window如何添加即显示的,下一篇再继续从addView开始分析Window如何一步一步添加的