1 前言
以前开始接触编码最开始纠结的一个问题就是Dialog,当时感觉好混乱啊!老早就想研究下dialog的问题。这次就研究研究Dialog的问题。相对于Toast来说,Dialog是一种比较重要的提示。对于我们来说第一反应来说是非常简单,但是我们思考他的实现的原理。他是在PhoneWindow上添加一个界面忽然就感觉不是那么简单了,这里我们一起来看看到底他是如何实现的。
2 代码分析
Dialog的使用一般非常简单,符合我们最常规的操作。如下:
Dialog dialog = new Dialog(MainActivity.this);
dialog.setContentView(R.layout.dialog_main);
dialog.show();
这里们就可以建立一个内容太是dialog_main的dialog,那么我们就跟踪着这段代码来研究研究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 == 0) {
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.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
这里有三个构造函数,前两个没啥好说的。至于第三个。我们来好好看看。从是一行到20行是获取context的,这里我们往往都没有设置resourceid所以他会搜索我们当前工程的theme然后从中找到reshource的id是R.attr.dialogTheme的内容,新建一个mcontext,其实这里仅仅是设置了mContext的resource这里就不在多说了。接着向下看,我们见到获取一个WindowManager的实例,开始new出PhoneWindow然后对其进行必要的设置,最后出现一个ListenersHandler,这里我们先不管这个,一会会好好介绍的。
这里构造函数阅读完成,我们开始研究setview()函数,这里我们知道有两个函数,看过源码的小伙伴估计发现所有的问题不是在dialog完成的,都是通过PhoneWindow完成的,这里我们不必要纠结实现细节,以后有机会我们好好研究PhoneWindow源码,这里我们只用知道吧view给设置好了,接着我们进入我们最关键的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 {
}
}
前十行是判断当前的dialog是否已经处于显示状态或者已经隐藏,如果是就直接显示。第15行的dispatchOnCreate()函数最终仅仅是调用了omcreat()函数这是个空函数是为了复写dialog的一个函数,如果你要自定义dialog直接复写oncreat()函数,添加自己的view就好了。onstart()函数仅仅对mActionBar做了适当的控制,因为你的dialog在前台的时候mActionBar是不可以工作的,这代码就是在这里完成的。下面就是获取phonewidow的mDecor和new出一个mActionBar,然后根据WindowManager.LayoutParams这个类的l来控制显示方式(这里代码功能我都了解,但是实现细节跟其他类有很大的关系,并且这里问题比较复杂,就不给大家解释了。
最后一段代码setshowmessage是个比较有意思的函数,他发送一个message给开始构造函数里初始化的mListenersHandler,这里我还是给大家解释下,要想知道这个messager发给谁。我们就找到在哪里赋值,立马我们定位到setOnShowListener()函数中,当我们设置OnShowListener时,我们就给messager赋值,弱不赋值,在setshowmessage就不发消息,这里我们很容易找到是发给mListenersHandler 的。我们来看下这个mListenersHandler
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;
}
}
}
这里的dialog也就是这个WeakReference是为了维护自己的生命周期。在ListenersHandler 构造函数中调用了传递了自己。当自己胃空时,,这里dialog也自动在释放内存。也就是不在调用监听事件。这里一共三个事件,每个处理也许你们会比较奇怪,但是这里慢慢看下就会很容易的发现他们都是调用三个监听类中的函数。然后把自己给作为参数返回,这里我们可以找到其他两个监听时间。然后观察他们的函数,这里不再做解释。dialog如何消失的。
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
忽然感觉是不是很奇怪??那么我们看看到底这个是干啥的。首先这个if语句是判什么的,Looper.myLooper()是获取调用dismiss()函数线程的looper,mhandler.getlooper是获取mhandler的looper,那么我们很容易发现我们mhandler是真个类初始化时候初始化的也就是初始化的线程的looper。这里就非常明显了、如果在同一个线程中调用dismiss函数。直接调用dismissDialog()。如果不是同一个线程。我们通过初始化的mHandler来在初始化dialog的线程中调用dismissDialog()。这里我们看下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();
}
}
比较容易理解。对mWindowManager移除view,关闭mWindow的panels。然后调用为复写而声明的onstop()函数,然后就是处理监听事件。至此基本框架书写完毕。但是研究源码的童鞋发现里面几个有几个全局变量没用到。这里感觉其中每个都是比价复杂的东西。并且我水平有限,就不在过多的研究了,
3 总结
个人感觉这篇博客写的很奇怪。因为是在我书写过程中发现问题每一个都相当复杂。没有找到重点。但是我对于dialog的书写的想法有这么强烈。我就写了这篇博客。这里有不好的地方大家理解。或者提问。