Android之Window和弹窗问题
版权声明:
本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《
阿里云开发者社区用户服务协议》和
《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写
侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
简介:
目录介绍
10.0.0.1 Window是什么?如何通过WindowManager添加Window(代码实现)?WindowManager的主要功能是什么?
10.0.0.2 Window概念解析?WindowSession的创建过程是怎样的?WindowSession的作用?Token的使用场景?
10.
目录介绍
- 10.0.0.1 Window是什么?如何通过WindowManager添加Window(代码实现)?WindowManager的主要功能是什么?
- 10.0.0.2 Window概念解析?WindowSession的创建过程是怎样的?WindowSession的作用?Token的使用场景?
- 10.0.0.3 Activity、View、Window三者之间的关系,Window有哪几种类型?
- 10.0.0.5 Activity的启动过程是怎样的?Activity创建和Dialog创建过程的异同?
- 10.0.0.6 如何处理快速连续点击了多次按钮时Toast就触发了多次而关闭不掉?
- 10.0.0.7 DecorView何时才被WindowManager真正添加到Window中?Window的addView源码分析?
- 10.0.0.8 Dialog的Window创建过程?为什么Dialog不能用Application的Context?
- 10.0.0.9 什么是DecorView?如何获取到DecorView?DecorView的职责是什么?DecorView如何被加载到Window中?
- 10.0.1.0 DecorView如何显示出来,为什么setContentView()设置的界面,为什么在onResume()之后才对用户可见呢?
- 10.0.1.1 什么是ViewRoot?ViewRoot属于View树的一份子吗?ViewRoot的工作流程是怎么样的?
- 10.0.1.2 吐司为何会出现内存泄漏?在Toast构造方法中创建NT对象是干什么用的?Toast是怎么show出来的?
- 10.0.1.3 连续吐司是如何确定吐司的先后顺序?为什么Toast执行show后过了一会儿就自动销毁?
- 10.0.1.4 如何理解普通应用的Toast显示数量是有限制的?为什么要判断是否是系统吐司?为何Activity销毁后Toast仍会显示?
- 10.0.1.5 为什么说Toast尽量用全局上下文?说一下Toast的显示和隐藏重点逻辑,说下你的理解?
- 10.0.1.6 Toast报错Unable to add window是什么意思?Toast运行在子线程会问题,在子线程或者service中能运行吗?
- 10.0.1.7 为什么建议用DialogFragment替代Dialog?如何定义DialogFragment样式?使用dialogFragment有何好处?
- 10.0.1.8 Dialog的Window创建过程是怎样的?为什么Dialog不能用Application的Context,说一下原因?
- 10.0.1.9 Dialog和Window有什么关系?Dialog的dismiss和cancel()方法都可销毁弹窗,它们有什么区别?
- 10.0.2.0 PopupWindow中不设置为什么必须设置宽高?PopupWindow和Dialog有什么区别?说下创建和销毁的大概流程?
- 10.0.2.1 Snackbar与吐司有何区别在哪里?Snackbar控件show时为何从下往上移出来?为什么显示在最下面?
- 10.0.2.2 说一下Snackbar和SnackbarManager类的设计有哪些奥妙的地方,如何处理消息的显示顺序?
好消息
- 博客笔记大汇总【15年10月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计500篇[近100万字],将会陆续发表到网上,转载请注明出处,谢谢!
- 链接地址:https://github.com/yangchong211/YCBlogs
- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
弹窗博客笔记汇总
-
02.Toast源码深度分析
- 最简单的创建,简单改造避免重复创建,show()方法源码分析,scheduleTimeoutLocked吐司如何自动销毁的,TN类中的消息机制是如何执行的,普通应用的Toast显示数量是有限制的,用代码解释为何Activity销毁后Toast仍会显示,Toast偶尔报错Unable to add window是如何产生的,Toast运行在子线程问题,Toast如何添加系统窗口的权限等等
-
03.DialogFragment源码分析
- 最简单的使用方法,onCreate(@Nullable Bundle savedInstanceState)源码分析,重点分析弹窗展示和销毁源码,使用中show()方法遇到的IllegalStateException分析
-
04.Dialog源码分析
- AlertDialog源码分析,通过AlertDialog.Builder对象设置属性,Dialog生命周期,Dialog中show方法展示弹窗分析,Dialog的dismiss销毁弹窗,Dialog弹窗问题分析等等
-
05.PopupWindow源码分析
- 显示PopupWindow,注意问题宽和高属性,showAsDropDown()源码,dismiss()源码分析,PopupWindow和Dialog有什么区别?为何弹窗点击一下就dismiss呢?
-
06.Snackbar源码分析
- 最简单的创建,Snackbar的make方法源码分析,Snackbar的show显示与点击消失源码分析,显示和隐藏中动画源码分析,Snackbar的设计思路,为什么Snackbar总是显示在最下面
-
07.弹窗常见问题
- DialogFragment使用中show()方法遇到的IllegalStateException,什么常见产生的?Toast偶尔报错Unable to add window,Toast运行在子线程导致崩溃如何解决?
-
08.Builder模式
- 你会发现,在这个弹窗封装库中,很多地方用到了builder模式,那么可以先了解下Builder模式使用场景,简单案例,Builder模式实际案例Demo展示,看看AlertDialog.Builder源代码如何实现,为什么AlertDialog要使用builder模式呢?builder模式优缺点分析。
10.0.0.1 Window是什么?如何通过WindowManager添加Window(代码实现)?WindowManager的主要功能是什么?
10.0.0.2 Window概念解析?WindowSession的创建过程是怎样的?WindowSession的作用?Token的使用场景?
-
Window概念解析?
- Window和View通过ViewRootImpl建立联系
- Window并不是实际存在的,而是以View的形式存在
- WindowManager的三个接口方法也是针对View的
- 实际使用中无法直接访问Window,必须通过WindowManager
- View是视图的呈现方式,但是不能单独存在,必须依附在Window这个抽象的概念上
- WMS把所有的用户消息发给View/ViewGroup,但是在View/ViewGroup处理消息的过程中,有一些操作是公共的, Window把这些公共行为抽象出来, 这就是Window。
-
WindowSession的创建过程是怎样的?
- 在WindowManager的addView中会创建ViewRootImpl,内部会通过WMS去获取WindowSession
- WindowSession的类型是IWindowSession,本身是Binder对象,真正实现类是Session

-
WindowSession的作用?博客
- 表示一个Active Client Session
- 每个进程一般都有一个Session对象
- 用于WindowManager交互
-
Token的使用场景?
- Popupwindow的showAtLocation第一个参数需要传入View,这个View就是用来获取Token的。
- Android 5.0新增空间SnackBar同理也需要一个View来获取Token
-
Token是什么?
- 类型为IBinder,是一个Binder对象。
-
主要分两种Token:
- 指向Window的token: 主要是实现WmS和应用所在进程通信。
- 指向ActivityRecord的token: 主要是实现WmS和AmS通信的。
-
Activity中的Token
- ActivityRecord是AmS中用来保存一个Activity信息的辅助类。
- AMS中需要根据Token去找到对应的ActivityRecord。
10.0.0.3 Activity、View、Window三者之间的关系,Window有哪几种类型?
10.0.0.5 Activity的启动过程是怎样的?Activity的视图加载的源码分析?Activity创建和Dialog创建过程的异同?
-
Activity的启动过程是怎样的?
- 最终会由ActivityThread中的performLauchActivity来完成整个启动过程
- performLauchActivity内部会通过类加载器创建Activity的实例对象
- 并为Activity的实例对象调用attach方法,为其关联运行过程中所以来的上下文环境变量
- attch方法中,系统会创建Activity所属的Window对象,并为其设置回调接口
- Window对象的创建是通过PolicyManager的makeNewWindow方法实现。博客
- Activity实现了window的callback接口,因此外界状态改变时会回调Activity的方法(onAttachedToWindow、dispatchTouchEvent等等)
-
Activity的视图加载的源码分析
-
Dialog的Window创建过程
10.0.0.6 如何处理快速连续点击了多次按钮时Toast就触发了多次而关闭不掉?
-
使用中遇到的问题
- 例如:当点击有些按钮,需要吐司进行提示时;快速连续点击了多次按钮,Toast就触发了多次。可能导致Toast就长时间关闭不掉了。又或者我们其实已在进行其他操作了,应该弹出新的Toast提示,而上一个Toast却还没显示结束。博客
-
解决的办法
创建工具类:
/**
* 吐司工具类 避免点击多次导致吐司多次,最后导致Toast就长时间关闭不掉了
* @param context
* @param content
*/
private static Toast toast;
public static void showToast(Context context, String content) {
if (toast == null) {
toast = Toast.makeText(context.getApplicationContext(), content, Toast.LENGTH_SHORT);
} else {
toast.setText(content);
}
toast.show();
}
-
这样用的原理
- 先判断Toast对象是否为空,如果是空的情况下才会调用makeText()方法来去生成一个Toast对象,否则就直接调用setText()方法来设置显示的内容,最后再调用show()方法将Toast显示出来。由于不会每次调用的时候都生成新的Toast对象,因此刚才我们遇到的问题在这里就不会出现
10.0.0.7 DecorView何时才被WindowManager真正添加到Window中?Window的addView源码分析?
10.0.0.8 Dialog的Window创建过程?为什么Dialog不能用Application的Context?
10.0.0.9 什么是DecorView?如何获取到DecorView?DecorView的职责是什么?DecorView如何被加载到Window中?
10.0.1.0 DecorView如何显示出来,为什么setContentView()设置的界面,为什么在onResume()之后才对用户可见呢?
-
通过setContentView()设置的界面,为什么在onResume()之后才对用户可见呢?这就要从ActivityThread开始说起。
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//就是在这里调用了Activity.attach()呀,接着调用了Activity.onCreate()和Activity.onStart()生命周期,
//但是由于只是初始化了mDecor,添加了布局文件,还没有把
//mDecor添加到负责UI显示的PhoneWindow中,所以这时候对用户来说,是不可见的
Activity a = performLaunchActivity(r, customIntent);
......
if (a != null) {
//这里面执行了Activity.onResume()
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
if (!r.activity.mFinished && r.startsNotResumed) {
try {
r.activity.mCalled = false;
//执行Activity.onPause()
mInstrumentation.callActivityOnPause(r.activity);
}
}
}
}
-
重点看下handleResumeActivity(),在这其中,DecorView将会显示出来,同时重要的一个角色:ViewRoot也将登场。
- 这个方法里面会调用performResumeActivity方法,这个时候,Activity.onResume()已经调用了,但是现在界面还是不可见的
- 接着讲decorView添加进WindowManager了,但是这个时候,还是不可见的
- 最后执行makeVisible,执行了重要的操作,使得DecorView可见
-
当我们执行了Activity.makeVisible()方法之后,界面才对我们是可见的。博客
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());//将DecorView添加到WindowManager
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);//DecorView可见
}
- 到此DecorView便可见,显示在屏幕中。但是在这其中,
wm.addView(mDecor, getWindow().getAttributes());
起到了重要的作用,因为其内部创建了一个ViewRootImpl对象,负责绘制显示各个子View。
-
最后通过WindowManagerImpl的addView方法将DecorView加载出来
- 看到其中实例化了ViewRootImpl对象,然后调用其setView()方法。其中setView()方法经过一些列折腾,最终调用了performTraversals()方法,然后依照下图流程层层调用,完成绘制,最终界面才显示出来。

- 具体更加详细的过程,可以看10.DecorView介绍
10.0.1.1 什么是ViewRoot?ViewRoot属于View树的一份子吗?ViewRoot的工作流程是怎么样的?
-
什么是ViewRoot
- ViewRoot可能比较陌生,但是其作用非常重大。所有View的绘制以及事件分发等交互都是通过它来执行或传递的。
- ViewRoot对应ViewRootImpl类,它是连接WindowManagerService和DecorView的纽带,View的三大流程(测量(measure),布局(layout),绘制(draw))均通过ViewRoot来完成。
-
ViewRoot属于View树的一份子吗?
- ViewRoot并不属于View树的一份子。
- 从源码实现上来看,它既非View的子类,也非View的父类,但是,它实现了ViewParent接口,这让它可以作为View的名义上的父视图。RootView继承了Handler类,可以接收事件并分发,Android的所有触屏事件、按键事件、界面刷新等事件都是通过ViewRoot进行分发的。博客
-
下面结构图可以清晰的揭示四者之间的关系:
10.0.1.2 吐司为何会出现内存泄漏?在Toast构造方法中创建NT对象是干什么用的?Toast是怎么show出来的?
-
吐司为何会出现内存泄漏
- 原因在于:如果在 Toast 消失之前,Toast 持有了当前 Activity,而此时,用户点击了返回键,导致 Activity 无法被 GC 销毁, 这个 Activity 就引起了内存泄露。
-
在Toast构造方法中创建NT对象是干什么用的?
- TN是属于Toast内部一个私有静态类,它是通过aidl进行通信,主要作用是实现吐司的show和hide功能。
-
在构造方法中,创建了NT对象,那么有人便会问,NT是什么东西呢?看看NT的源码,可以发现NT实现了ITransientNotification.Stub,提到这个感觉是不是很熟悉,没错,在aidl中就会用到这个。
public Toast(Context context) {
mContext = context;
mTN = new TN();
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}
-
在TN类中,可以看到,实现了AIDL的show与hide方法
- TN是Toast内部的一个私有静态类,继承自ITransientNotification.Stub,ITransientNotification.Stub是出现在服务端实现的Service中,就是一个Binder对象,也就是对一个aidl文件的实现而已
@Override
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(0, windowToken).sendToTarget();
}
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
-
接着看下这个ITransientNotification.aidl文件
/** @hide */
oneway interface ITransientNotification {
void show();
void hide();
}
-
Toast是怎么show出来的?
- 通过AIDL(Binder)通信拿到NotificationManagerService的服务访问接口,然后把TN对象和一些参数传递到远程NotificationManagerService中去
- 当 Toast在show的时候,然后把这个请求放在 NotificationManager 所管理的队列中,并且为了保证 NotificationManager 能跟进程交互,会传递一个TN类型的Binder对象给NotificationManager系统服
- 然后通过service.enqueueToast方法,record是将Toast封装成ToastRecord对象,放入mToastQueue中。通过下面代码可以得知:通过isSystemToast判断是否为系统Toast。如果当前Toast所属的进程的包名为“android”,则为系统Toast。如果是系统Toast一定可以进入到系统Toast队列中,不会被黑名单阻止。
10.0.1.3 连续吐司是如何确定吐司的先后顺序?为什么Toast执行show后过了一会儿就自动销毁?
-
连续吐司是如何确定吐司的先后顺序?
-
为什么Toast执行show后过了一会儿就自动销毁?博客
- 回调了Toast的TN的show,当timeout可能就是hide呢。分析NotificationManagerService源码中的showNextToastLocked()的scheduleTimeoutLocked(record)源码,可以知道在NotificationManagerService通过handler延迟delay时间发送消息,然后通过callback调用hide,由于callback是TN中Binder的代理对象, 所以便可以调用到TN中的hide方法达到销毁吐司的目的。
- handleHide()源码如下所示,可知当销毁后先将view移除,然后在置空操作。
public void handleHide() {
if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
if (mView != null) {
// note: checking parent() just to make sure the view has
// been added... i have seen cases where we get here when
// the view isn't yet added, so let's try not to crash.
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeViewImmediate(mView);
}
mView = null;
}
}
10.0.1.4 如何理解普通应用的Toast显示数量是有限制的?为什么要判断是否是系统吐司?为何Activity销毁后Toast仍会显示?
-
如何理解普通应用的Toast显示数量是有限制的?
- 如何判断是否是系统吐司呢?如果当前Toast所属的进程的包名为“android”,则为系统Toast,或者调用isCallerSystem()方法
final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
- 接着看看isCallerSystem()方法源码,isCallerSystem的源码也比较简单,就是判断当前Toast所属进程的uid是否为SYSTEM_UID、0、PHONE_UID中的一个,如果是,则为系统Toast;如果不是,则不为系统Toast。
private static boolean isUidSystem(int uid) {
final int appid = UserHandle.getAppId(uid);
return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
}
private static boolean isCallerSystem() {
return isUidSystem(Binder.getCallingUid());
}
-
为什么要判断是否是系统吐司?
- 从源码可知:首先系统Toast一定可以进入到系统Toast队列中,不会被黑名单阻止。然后系统Toast在系统Toast队列中没有数量限制,而普通pkg所发送的Toast在系统Toast队列中有数量限制。
- 那么关于数量限制这个结果从何而来,大概是多少呢?查看将要入队的Toast是否已经在系统Toast队列中。这是通过比对pkg和callback来实现的。通过下面源码分析可知:只要Toast的pkg名称和tn对象是一致的,则系统把这些Toast认为是同一个Toast。
- 然后再看看下面这个源码截图,可知,非系统Toast,每个pkg在当前mToastQueue中Toast有总数限制,不能超过MAX_PACKAGE_NOTIFICATIONS,也就是50


-
为何Activity销毁后Toast仍会显示
- 记得以前昊哥问我,为何toast在activity销毁后仍然会弹出呢,我毫不思索地说,因为toast是系统级别的呀。那么是如何实现的呢,我就无言以对呢……今天终于可以回答呢!
- 还是回到NotificationManagerService类中的enqueueToast方法中,直接查看keepProcessAliveIfNeededLocked(callingPid)方法。这段代码的意思是将当前Toast所在进程设置为前台进程,这里的mAm = ActivityManager.getService(),调用了setProcessImportant方法将当前pid的进程置为前台进程,保证不会系统杀死。这也就解释了为什么当我们finish当前Activity时,Toast还可以显示,因为当前进程还在执行。

10.0.1.5 为什么说Toast尽量用全局上下文?说一下Toast的显示和隐藏重点逻辑,说下你的理解?
10.0.1.6 Toast报错Unable to add window是什么意思?Toast运行在子线程会问题,在子线程或者service中能运行吗?
-
Toast偶尔报错Unable to add window
-
报错日志,是不是有点眼熟呀?更多可以看我的开源项目:https://github.com/yangchong211
android.view.WindowManager$BadTokenException
Unable to add window -- token android.os.BinderProxy@7f652b2 is not valid; is your activity running?
-
查询报错日志是从哪里来的
-
发生该异常的原因
- 这个异常发生在Toast显示的时候,原因是因为token失效。通常情况下,一般是不会出现这种异常。但是由于在某些情况下, Android进程某个UI线程的某个消息阻塞。导致 TN 的 show 方法 post 出来 0 (显示) 消息位于该消息之后,迟迟没有执行。这时候,NotificationManager 的超时检测结束,删除了 WMS 服务中的 token 记录。删除 token 发生在 Android 进程 show 方法之前。这就导致了上面的异常。
- 测试代码。模拟一下异常的发生场景,其实很容易,只需要这样做就可以出现上面这个问题
Toast.makeText(this,"潇湘剑雨-yc",Toast.LENGTH_SHORT).show();
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
-
解决办法,目前见过好几种,思考一下那种比较好……
-
哪些情况会发生该问题?
- UI 线程执行了一条非常耗时的操作,比如加载图片等等,就类似上面用 sleep 模拟情况
- 进程退后台或者息屏了,系统为了减少电量或者某种原因,分配给进程的cpu时间减少,导致进程内的指令并不能被及时执行,这样一样会导致进程看起来”卡顿”的现象
- 当TN抛出消息的时候,前面有大量的 UI 线程消息等待执行,而每个 UI 线程消息虽然并不卡顿,但是总和如果超过了 NotificationManager 的超时时间,还是会出现问题
-
Toast运行在子线程问题
new Thread(new Runnable() {
@Override
public void run() {
ToastUtils.showRoundRectToast("潇湘剑雨-杨充");
}
}).start();
- 报错日志如下所示:

-
子线程中吐司的正确做法,代码如下所示
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
ToastUtils.showRoundRectToast("潇湘剑雨-杨充");
Looper.loop();
}
}).start();
-
得出的结论
- Toast也可以在子线程执行,不过需要手动提供Looper环境的。
- Toast在调用show方法显示的时候,内部实现是通过Handler执行的,因此自然是不阻塞Binder线程,另外,如果addView的线程不是Loop线程,执行完就结束了,当然就没机会执行后续的请求,这个是由Hanlder的构造函数保证的。可以看看handler的构造函数,如果Looper==null就会报错,而Toast对象在实例化的时候,也会为自己实例化一个Hanlder,这就是为什么说“一定要在主线程”,其实准确的说应该是 “一定要在Looper非空的线程”。博客
10.0.1.7 为什么建议用DialogFragment替代Dialog?如何定义DialogFragment样式?使用dialogFragment有何好处?
-
为什么建议用DialogFragment替代Dialog
- Android比较推荐采用DialogFragment实现对话框,它完全能够实现Dialog的所有需求,并且还能复用Fragment的生命周期管理,被后台杀死后,可以恢复重建。
- 在手机配置变化导致 Activity 需要重新创建时,例如旋转屏幕,基于 DialogFragment 的对话框将会由 FragmentManager 自动重建,然而基于 Dialog 实现的对话框却没有这样的能力。
-
如何定义DialogFragment样式
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (local == BOTTOM) {
setStyle(DialogFragment.STYLE_NO_TITLE, R.style.BottomDialog);
} else if (local == CENTER || local == TOP) {
setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog);
}
}
-
创建theme主题样式,并且进行设置
- 设置样式,以DialogFragment为例,只需要在onCreate中setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog)即可。
- 注意,CenterDialog中可以设置弹窗的动画效果。
-
注意一下style常量,这里只是展示常用的。
STYLE_NORMAL:会显示一个普通的dialog
STYLE_NO_TITLE:不带标题的dialog
STYLE_NO_FRAME:无框的dialog
STYLE_NO_INPUT:无法输入内容的dialog,即不接收输入的焦点,而且触摸无效。
-
注意动画设置如下所示
<style name="CenterDialog" parent="@android:style/Theme.Dialog">
<item name="android:windowTitleStyle">@null</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowAnimationStyle">@style/CenterDialogAnimationStyle</item>
<item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
</style>
-
使用dialogFragment有何好处?
- DialogFragment是继承Fragment,具有Fragment的生命周期,本质上说就是Fragment,只是其内部还有一个dialog而已。你既可以当它是Dialog使用,也可以把它作为Fragment使用。
- onCreateView可以加载客户化更高的对话框,onCreateDialog加载系统AlertDialog类型对话框比较合适。
- DialogFragmnet对话框横屏时对话框不会关闭,因为DailogFragment有Fragment属性,会在屏幕发生变化时重新创建DialogFragment。博客
- setStyle的调用点,要放在onCreateView前,一般是放在onCreat方法中执行,否则,设置的style和theme将不起作用!setStyle中,style的参数是不可以相互一起使用的,只能用一个,如果还不满足你使用,可以通过设置theme来满足。
10.0.1.8 Dialog的Window创建过程是怎样的?为什么Dialog不能用Application的Context,说一下原因?
10.0.1.9 Dialog和Window有什么关系?Dialog的dismiss和cancel()方法都可销毁弹窗,它们有什么区别?
-
Dialog和Window有什么关系?
- 看源码可知在Dialog的构造方法中直接直接构造了一个PhoneWindow,并赋值给Dialog的成员变量mWindow,从这里可以看出其实Dialog和Activity的显示逻辑都是类似的,都是通过对应的Window变量来实现窗口的加载与显示的。然后我们执行了一些Window对象的初始化操作,比如设置回调函数为本身,然后调用Window类的setWindowManager方法,并传入了WindowManager。然后创建一个对话框监听handler对象。
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;
}
//创建一个Context
mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
//获取一个WindowManager对象
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//创建一个Window对象
final Window w = new PhoneWindow(mContext);
//将Window对象w赋值给mWindow
mWindow = w;
//为Windowd对象设置回调,并且它本身实现了这些回调函数
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
//为Window对象设置WindowManager对象
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
//创建一个对话框监听Handler
mListenersHandler = new ListenersHandler(this);
}
-
Dialog的dismiss和cancel()方法都可销毁弹窗,它们有什么区别?
- 调用alertDialog.cancel()或者alertDialog.dismiss()都可以达到销毁弹窗的效果。
- 如果没有设置setOnCancelListener事件,则两个方法是等效的。为什么这样说呢?
- 首先看一下Dialog的cannel方法的具体实现:可以看到方法体中,若当前Dialog没有取消,并且设置了取消message,则调用Message.obtain(mCancelMessage).sendToTarget()。而这个mCancelMessage则是在setOnCancelListener方法中创建的。调用的是设置的OnCancelListener的onCancel方法,也就是说调用dialog.cancel方法时首先会判断dialog是否调用了setOnCancelListener若设置了,则先调用OnCancelListener的onCancel方法,然后再次执行dismiss方法,若我们没有为Dialog.Builder设置OnCancelListener那么cancel方法和dismiss方法是等效的。博客
public void cancel() {
if (!mCanceled && mCancelMessage != null) {
mCanceled = true;
// Obtain a new message so this dialog can be re-used
Message.obtain(mCancelMessage).sendToTarget();
}
dismiss();
}
public void setOnCancelListener(final OnCancelListener listener) {
if (listener != null) {
mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);
} else {
mCancelMessage = null;
}
}
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;
}
}
}
-
dismiss方法主要是做了什么?
- 可以看到,这里首先判断当前线程的Looper是否是主线程的Looper(由于mHandler是在主线程中创建的,所以mHandler.getLooper返回的是主线程中创建的Looper对象),若是的话,则直接执行dismissDialog()方法,否则的话,通过mHandler发送异步消息至主线程中,简单来说就是判断当前线程是否是主线程,若是主线程则执行dismissDialog方法否则发送异步消息。而这里的异步消息最终也是调用的dismissDialog方法
-
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
10.0.2.0 PopupWindow中不设置为什么必须设置宽高?PopupWindow和Dialog有什么区别?说下创建和销毁的大概流程?
10.0.2.1 Snackbar与吐司有何区别在哪里?Snackbar控件show时为何从下往上移出来?为什么显示在最下面?
10.0.2.2 说一下Snackbar和SnackbarManager类的设计有哪些奥妙的地方,如何处理消息的显示顺序?
其他介绍
01.关于博客汇总链接
02.关于我的博客