前言

Dialog 是端上非常常见的弹框。 使用起来跟 Activity 有点类似。 我们也尝尝会跟 popwindow 进行对比。 我们先来看看 Dialog 的代码分析一下。

基本使用

1
2
3
4
5
6
7
8
9
class MyDialog(context: Context) : Dialog(context) {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(0)
}
}

MyDialog(this).show()

定义和使用很简单,也是我们经常使用的, 我们就不再多说了。

源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public Dialog(@NonNull Context context) {
this(context, 0, 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);
}

通过构造函数里可以看出来的几点。

  • 我们可以设置当前弹框自己的主题。
  • Dialog 自己持有 wms。
  • Dialog 自己持有 window, 即 PhoneWindow。

其他的先不说, 通过 PhoneWndow 及设置我们是不是有种熟悉的关键, 对, 就在 Android 事件源头分析中有, 还基本一致哈, 所以我们应该关心的有几点。

  • setCallback PhoneWindow 把事件可以回调回来给 Dialog。
  • PhoneWindw 在设置布局的时候肯定最外层也是 DecorView。 所以肯定是有自己的主题呢。

所以我们看看 Dialog 的 setContentView 方法。

1
2
3
public void setContentView(@LayoutRes int layoutResID) {
mWindow.setContentView(layoutResID);
}

这不是跟 Activity 一毛一样嘛, mWindow 是 PhoneWindow,setContentView 内部根据主题构造 DecorView, 然后把我们的布局设置到 content 布局中。
根据 Android 事件源头的分析, Activity 是在 ActivityThread 中 handleResumeActivity 中 WMS addView 把 DectorView 添加进去的。 那么 Dialog 呢, 它是在 show 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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();
}

show 里的代码没有删除任何代码, 因为简单所以直接全部复制了。 可以看到大致跟 Activity 中分析的基本一致, mWindowManager addView 了。 内部肯定也是 RootViewImpl 等去处理的。 这里就不再多分析了。 所以说 Dialog 的事件传递跟 Activity 的一致哈, 是单独的, 互不影响。
到这里我们就分析完成了。 很简单吧。 我们另外看看 Dialog 的生命周期吧。
在 shuw 方法中, 如果没有 mCreate 的话, 就执行 onCreate 方法。 所以说, 在没有调用 show 方法时, 是没有布局的话。 然后紧接着就会 onStart 生命周期的回调了。 那么有 onStart 肯定就有 onStop 对应吧, 不然就太鸡肋了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void dismissDialog() {
if (mDecor == null || !mShowing) {
return;
}

if (mWindow.isDestroyed()) {
Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
return;
}

try {
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;

sendDismissMessage();
}
}

看到了吧, 是在 dismiss dialog 时调用到啦。
看到 show 和 dismiss 我们可以发现一个点就是, 每次 show 的时候, 就会把布局添加进去, dismiss 都会把布局给移出。