构造函数入手
public Dialog(Context context) {
this(context, 0, true);
}
public Dialog(Context context, int theme) {
this(context, theme, true);
}
Dialog(Context context, int theme, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (theme == 0) {
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
outValue, true);
theme = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, theme);
} else {
mContext = context;
}
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Window w = PolicyManager.makeNewWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
对外提供了两个构造函数,最终都是调用了具有三个参数的构造函数:
createContextThemeWrapper默认传递过来的就是true,所以会进入if (createContextThemeWrapper) {}内部
theme:假如调用了的只有一个参数的构造函数,会默认传递0进来,这会使得Dialog最终使用系统提供的dialogTheme作为主题
创建了一个Window对象,并在本地保存了一个mWindowManager的引用
show()方法
接下来,看下它的显示过程(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);
}
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();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
nl.copyFrom(l);
nl.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
l = nl;
}
try {
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
} finally {
}
}
这里边有几个步骤:
1. mCreated默认值为false,于是会调用dispatchOnCreate(null);方法,进入该方法
// internal method to make sure mcreated is set properly without requiring
// users to call through to super in onCreate
void dispatchOnCreate(Bundle savedInstanceState) {
if (!mCreated) {
onCreate(savedInstanceState);
mCreated = true;
}
}
主要做了两个操作:
- 调用onCreate方法
- 将mCreated标志设置为true
再进入onCreate方法查看
/**
* Similar to {@link Activity#onCreate}, you should initialize your dialog
* in this method, including calling {@link #setContentView}.
* @param savedInstanceState If this dialog is being reinitalized after a
* the hosting activity was previously shut down, holds the result from
* the most recent call to {@link #onSaveInstanceState}, or null if this
* is the first time.
*/
protected void onCreate(Bundle savedInstanceState) {
}
这是个空方法,官方给的解析原意大概是:
类似与Activity的onCreate方法,你应该在这个方法里调用setContentView设置需要显示的视图和初始化Dialog
所以,我们在使用Dialog时,需要在这个重写该方法,并调用setContentView设置视图
那setContentView内部做了什么操作呢?
public void setContentView(int layoutResID) {
mWindow.setContentView(layoutResID);
}
public void setContentView(View view) {
mWindow.setContentView(view);
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
mWindow.setContentView(view, params);
}
最终是调用了Window的setContentView方法
/**
* Set the screen content to an explicit view. This view is placed
* directly into the screen's view hierarchy. It can itself be a complex
* view hierarchy.
*
* <p>Note that calling this function "locks in" various characteristics
* of the window that can not, from this point forward, be changed: the
* features that have been requested with {@link #requestFeature(int)},
* and certain window flags as described in {@link #setFlags(int, int)}.
*
* @param view The desired content to display.
* @param params Layout parameters for the view.
*/
public abstract void setContentView(View view, ViewGroup.LayoutParams params);
大概原意:将view的内容设置给屏幕显示,这个视图直接添加到屏幕的视图层次结构中
- 调用onStart();
- 通过mDecor = mWindow.getDecorView();获取根视图
- 设置Window的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);
}
- 显示
try {
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
} finally {
}
这里主要有三个操作:
- 通过WindowManager将根视图Add进去并显示
- 将mShowing标识设置为true
- 调用sendShowMessage方法(下边再进行分析)
以上就是Dialog创建和显示过程,那消失过程又是如何实现的呢?
dismiss()方法
@Override
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
这个方法主要是做了一个线程的判断工作,保证了线程安全:
- 假如调用该方法的线程与mHandler所在的线程是一致的,则直接调用dismissDialog()方法
- 不一致,则调用了mHandler.post(mDismissAction);,查看mDismissAction
private final Runnable mDismissAction = new Runnable() {
public void run() {
dismissDialog();
}
};
这是一个Runnable接口,内部实现也是调用了dismissDialog()方法。通过mHandler.post方法使得切换到mHandler所在的线程执行dismissDialog()
这里就牵扯到了一个重要的知识点:
通过上篇的Loop、Handler、MessageQueue和Message源码分析,我们知道假如在子线程创建Handler,需要先调用Looper.prepare()方法。而从private final Handler mHandler = new Handler();知道mHandler作为Dialog的一个私有变量,所以假如要在子线程中使用Dialog,需要这样使用:
// 例子
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Dialog dialog = new Dialog(MainActivity.this);
dialog.setTitle("ffdfdfdf");
dialog.show();
Looper.loop();
}
}).start();
回到主题,继续查看dismissDialog()方法
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();
}
}
- 这里是Dialog消失的处理方法,主要在于mWindowManager.removeViewImmediate(mDecor),进入查看:
/**
* Special variation of {@link #removeView} that immediately invokes
* the given view hierarchy's {@link View#onDetachedFromWindow()
* View.onDetachedFromWindow()} methods before returning. This is not
* for normal applications; using it correctly requires great care.
*
* @param view The view to be removed.
*/
public void removeViewImmediate(View view);
大概原意:这是removeView的特别版本,会立马调用给定的视图层次结构的onDetachedFromWindow()方法,然后再返回
2. 主要做一些收尾工作:将mDecor置为空,调用onStop()方法,设置标识mShowing为false。
3. 调用sendDismissMessage()方法
sendShowMessage和sendDismissMessage分析
我们上边的分析时有涉及到,在show()方法的最后调用了sendShowMessage()方法;在dismiss()方法的最后调用了sendDismissMessage()方法。这两个方法的作用是什么,我们接下来分析
sendShowMessage
private void sendShowMessage() {
if (mShowMessage != null) {
// Obtain a new message so this dialog can be re-used
Message.obtain(mShowMessage).sendToTarget();
}
}
当mShowMessage不为空时,再调用sendToTarget()方法。那mShowMessage是什么时候赋值的呢?在Dialog进行全文搜索,可以发现:
/**
* Sets a listener to be invoked when the dialog is shown.
* @param listener The {@link DialogInterface.OnShowListener} to use.
*/
public void setOnShowListener(OnShowListener listener) {
if (listener != null) {
mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);
} else {
mShowMessage = null;
}
}
假如调用了setOnShowListener方法,并且传入的参数不为空,则对mShowMessage进行赋值,将一个SHOW类型的消息赋值个它。
再回到sendShowMessage方法,在这里调用了sendToTarget的方法。这是使用了Handler进行消息分发处理,那我们进入到mListenersHandler分析,该类是一个Handler对象
private static final class ListenersHandler extends Handler {
private WeakReference<DialogInterface> mDialog;
public ListenersHandler(Dialog dialog) {
mDialog = new WeakReference<DialogInterface>(dialog);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DISMISS:
((OnDismissListener) msg.obj).onDismiss(mDialog.get());
break;
case CANCEL:
((OnCancelListener) msg.obj).onCancel(mDialog.get());
break;
case SHOW:
((OnShowListener) msg.obj).onShow(mDialog.get());
break;
}
}
}
可以看到,在case SHOW的分支,调用了OnShowListener的onShow方法:
case SHOW:
((OnShowListener) msg.obj).onShow(mDialog.get());
可以看到整个流程的目的就是为了用户可以设置OnShowListener,在dialog显示时获得回调,做其他处理
sendDismissMessage
整个流程与sendShowMessage相似,这里不再描述
总结
通过上边的代码分析,我们可以有以下几个结论:
- 假如使用只有一个参数的构造函数,则会使用系统提供的默认主题来创建Dialog
- 在onCreate()方法中对Dialog进行初始化,并调用setContentView()方法为Dialog设置视图
- 在子线程中使用Dialog时,需要先调用Looper.prepare()方法,因为Dialog内部使用了Handler
- 假如需要监听Dialog的显示和消失,可以通过方法setOnShowListener()和setOnDismissListener()方法
- 假如需要在Dialog启动时进行其他的操作,可以重写onStart()方法;假如需要在其消失时进行其他的操作,可以重写onStop()方法