Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析
2015-07-17 15:59
736 查看
1 背景
之所以写这一篇博客的原因是因为之前有写过一篇《Android应用setContentView与LayoutInflater加载解析机制源码分析》, 然后有人在文章下面评论和微博私信中问我关于Android应用Activity、Dialog、PopWindow加载显示机制是咋回事,所以我就写一 篇文章来分析分析吧(本文以Android5.1.1 (API 22)源码为基础分析),以便大家在应用层开发时不再迷糊。PS一句:不仅有人微博私信我这个问题,还有人问博客插图这些是用啥画的,这里告诉大家。就是我,快来猛戳我
还记得之前《Android应用setContentView与LayoutInflater加载解析机制源码分析》这篇文章的最后分析结果吗?就是如下这幅图:
在那篇文章里我们当时重点是Activity的View加载解析xml机制分析,当时说到了Window的东西,但只是皮毛的分析了Activity相关的一些逻辑。(PS:看到这不清楚上面啥意思的建议先移步到《Android应用setContentView与LayoutInflater加载解析机制源码分析》,完事再回头继续看这篇文章。)当时给大家承诺过我们要从应用控件一点一点往下慢慢深入分析,所以现在开始深入,但是本篇的深入也只是仅限Window相关的东东,之后文章还会继续慢慢深入。
2 浅析Window与WindowManager相关关系及源码
通过上面那幅图可以很直观的看见,Android屏幕显示的就是Window和各种View,Activity在其中的作用主要是管理生命周期、建 立窗口等。也就是说Window相关的东西对于Android屏幕来说是至关重要的(虽然前面分析Activity的setContentView等原理 时说过一点Window,但那只是皮毛。),所以有必要在分析Android应用Activity、Dialog、PopWindow加载显示机制前再看 看Window相关的一些东西。2-1 Window与WindowManager基础关系
在分析Window与WindowManager之前我们先看一张图:接下来看一点代码,如下:
2-2 Activity窗口添加流程拓展
前面文章说过,ActivityThread类的performLaunchActivity方法中调运了activity.attach(…)方法进行初始化。如下是Activity的attach方法源码:接下来我们看下上面代码中的mWindow.setWindowManager(…)方法源码(PhoneWindow没有重写抽象Window的setWindowManager方法,所以直接看Window类的该方法源码),如下:
回过头继续看上面刚刚贴的Activity的attach方法代码,看见mWindow.setWindowManager方法传递的第一个参数 没?有人会想(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)这行代 码是什么意思,现在告诉你。
《Android应用Context详解及源码解析》一 文中第三部分曾经说过ActivityThread中创建了Acitivty(执行attach等方法)等东东,在创建这个Activity之前得到了 Context的实例。记不记得当时说Context的实现类就是ContextImpl吗?下面我们看下ContextImpl类的静态方法块,如下:
还记不记得《Android应用setContentView与LayoutInflater加载解析机制源码分析》一文2-6小节中说的,setContentView的实质显示是触发了Activity的resume状态,也就是触发了makeVisible方法,那我们再来看下这个方法,如下:
继续看makeVisible中调运的WindowManagerImpl的addView方法如下:
继续回到WindowManagerImpl的addView方法,分析可以看见WindowManagerImpl中有一个单例模式的 WindowManagerGlobal成员mGlobal,addView最终调运了WindowManagerGlobal的addView,源码如 下:
至此我们对上面背景中那幅图,也就是《Android应用setContentView与LayoutInflater加载解析机制源码分析》这篇文章总结部分的那幅图又进行了更深入的一点分析,其目的也就是为了下面分析Android应用Dialog、PopWindow、Toast加载显示机制做铺垫准备。
2-3 继续顺藤摸瓜WindowManager.LayoutParams类的源码
上面2-1分析Window与WindowManager基础关系时提到了WindowManager有一个静态内部类LayoutParams, 它继承于ViewGroup.LayoutParams,用于向WindowManager描述Window的管理策略。现在我们来看下这个类(PS:在 AD上也可以看见,自备梯子点我看AD的),如下:应用程序窗口。一般应用程序的窗口,比如我们应用程序的Activity的窗口。
子窗口。一般在Activity里面的窗口,比如对话框等。
系统窗口。系统的窗口,比如输入法,Toast,墙纸等。
同时还可以看见,WindowManager.LayoutParams里面窗口的type类型值定义是一个递增保留的连续增大数值,从注释可以看 出来其实就是窗口的Z-ORDER序列(值越大显示的位置越在上面,你需要将屏幕想成三维坐标模式)。创建不同类型的窗口需要设置不同的type值,譬如 上面拓展Activity窗口加载时分析的makeVisible方法中的Window默认属性的type=TYPE_APPLICATION。
既然说这个类很重要,那总得感性的体验一下重要性吧,所以我们先来看几个实例。
2-4 通过上面WindowManager.LayoutParams分析引出的应用层开发常用经典实例
有了上面分析相信你一定觉得WindowManager.LayoutParams还是蛮熟悉的,不信我们来看下。Part1:开发APP时设置Activity全屏常亮的一种办法(设置Activity也就是Activity的Window):
Part2:App开发中弹出软键盘时下面的输入框被软件盘挡住问题的解决办法:
在Activity中的onCreate中setContentView之前写如下代码:
省略了Activity的start与stop Service的按钮代码,直接给出了核心代码如下:
怎么样,通过最后这个例子你是不是就能体会到WindowManager.LayoutParams的Z-ORDER序列类型,值越大显示的位置越在上面。
2-5 总结Activity的窗口添加机制
有了上面这么多分析和前几篇的分析,我们对Activity的窗口加载再次深入分析总结如下:可以看见Context的WindowManager对每个APP来说是一个全局单例的,而Activity的WindowManager是每个 Activity都会新创建一个的(其实你从上面分析的两个实例化WindowManagerImpl的构造函数参数传递就可以看出来,Activity 中Window的WindowManager成员在构造实例化时传入给WindowManagerImpl中mParentWindow成员的是当前 Window对象,而ContextImpl的static块中单例实例化WindowManagerImpl时传入给 WindowManagerImpl中mParentWindow成员的是null值),所以上面模拟苹果浮动小图标使用了Application的 WindowManager而不是Activity的,原因就在于这里;使用Activity的WindowManager时当Activity结束时 WindowManager就无效了,所以使用Activity的getSysytemService(WINDOW_SERVICE)获取的是 Local的WindowManager。同时可以看出来Activity中的WindowManager.LayoutParams的type为 TYPE_APPLICATION。
好了,上面也说了不少了,有了上面这些知识点以后我们就来开始分析Android应用Activity、Dialog、PopWindow窗口显示机制。
3 Android应用Dialog窗口添加显示机制源码
3-1 Dialog窗口源码分析
写过APP都知道,Dialog是一系列XXXDialog的基类,我们可以new任意Dialog或者通过Activity提供的 onCreateDialog(……)、onPrepareDialog(……)和showDialog(……)等方法来管理我们的Dialog,但是究 其实质都是来源于Dialog基类,所以我们对于各种XXXDialog来说只用分析Dialog的窗口加载就可以了。如下从Dialog的构造函数开始分析:
回到Dialog的构造函数继续分析,在得到了WindowManager之后,程序又新建了一个Window对象(类型是PhoneWindow 类型,和Activity的Window新建过程类似);接着通过w.setCallback(this)设置Dialog为当前window的回调接 口,这样Dialog就能够接收事件处理了;接着把从Activity拿到的WindowManager对象关联到新创建的Window中。
至此Dialog的创建过程Window处理已经完毕,很简单,所以接下来我们继续看看Dialog的show与cancel方法,如下:
3-2 Dialog窗口加载总结
通过上面分析Dialog的窗口加载原理,我们总结如下图:从图中可以看出,Activity和Dialog共用了一个Token对象,Dialog必须依赖于Activity而显示(通过别的 context搞完之后token都为null,最终会在ViewRootImpl的setView方法中加载时因为token为null抛出异常),所 以Dialog的Context传入参数一般是一个存在的Activity,如果Dialog弹出来之前Activity已经被销毁了,则这个 Dialog在弹出的时候就会抛出异常,因为token不可用了。在Dialog的构造函数中我们关联了新Window的callback事件监听处理, 所以当Dialog显示时Activity无法消费当前的事件。
到此Dialog的窗口加载机制就分析完毕了,接下来我们说说应用开发中常见的一个诡异问题。
3-3 从Dialog窗口加载分析引出的应用开发问题
有了上面的分析我们接下来看下平时开发App初学者容易犯的几个错误。实现在一个Activity中显示一个Dialog,如下代码:
实现在一个Activity中显示一个Dialog,如下代码:
可以看见,Dialog的实质无非也是使用WindowManager的addView、updateViewLayout、removeView进行一些操作展示。
4 Android应用PopWindow窗口添加显示机制源码
PopWindow实质就是弹出式菜单,它与Dialag不同的地方是不会使依赖的Activity组件失去焦点(PopupWindow弹出后可 以继续与依赖的Activity进行交互),Dialog却不能这样。同时PopupWindow与Dialog另一个不同点是PopupWindow是 一个阻塞的对话框,如果你直接在Activity的onCreate等方法中显示它则会报错,所以PopupWindow必须在某个事件中显示地或者是开 启一个新线程去调用。说这么多还是直接看代码吧。
4-1 PopWindow窗口源码分析
依据PopWindow的使用,我们选择最常用的方式来分析,如下先看其中常用的一种构造函数:接着继续回到showAsDropDown方法看看第三步,如下源码:
到此可以发现,PopWindow的实质无非也是使用WindowManager的addView、updateViewLayout、 removeView进行一些操作展示。与Dialog不同的地方是没有新new Window而已(也就没法设置callback,无法消费事件,也就是前面说的PopupWindow弹出后可以继续与依赖的Activity进行交互 的原因)。
到此PopWindw的窗口加载显示机制就分析完毕了,接下来进行总结与应用开发技巧提示。
4-2 PopWindow窗口源码分析总结及应用开发技巧提示
通过上面分析可以发现总结如下图:可以看见,PopWindow完全使用了Activity的Window与WindowManager,相对来说比较简单容易记理解。
再来看一个开发技巧:
如果设置了PopupWindow的background,则点击Back键或者点击PopupWindow以外的区域时PopupWindow就 会dismiss;如果不设置PopupWindow的background,则点击Back键或者点击PopupWindow以外的区域 PopupWindow不会消失。
5 Android应用Toast窗口添加显示机制源码
5-1 基础知识准备
在开始分析这几个窗口之前需要脑补一点东东,我们从应用层开发来直观脑补,这样下面分析源码时就不蛋疼了。如下是一个我们写的两个应用实现 Service跨进程调用服务ADIL的例子,客户端调运远程Service的start与stop方法控制远程Service的操作。Android系统中的应用程序都运行在各自的进程中,进程之间是无法直接交换数据的,但是Android为开发者提供了AIDL跨进程调用Service的功能。其实AIDL就相当于双方约定的一个规则而已。
先看下在Android Studio中AIDL开发的工程目录结构,如下:
由于AIDL文件中不能出现访问修饰符(如public),同时AIDL文件在两个项目中要完全一致而且只支持基本类型,所以我们定义的AIDL文件如下:
ITestService.aidl
5-2 Toast窗口源码分析
我们常用的Toast窗口其实和前面分析的Activity、Dialog、PopWindow都是不同的,因为它和输入法、墙纸类似,都是系统窗口。我们还是按照最常用的方式来分析源码吧。
我们先看下Toast的静态makeText方法吧,如下:
当我们有了这个Toast对象之后,
可以通过show方法来显示出来,如下看下show方法源码:
再回到上面分析的show()方法中可以看到,我们的Toast是传给远程的NotificationManagerService管理的,为了 NotificationManagerService回到我们的应用程序(回调),我们需要告诉NotificationManagerService 我们当前程序的Binder引用是什么(也就是TN)。是不是觉得和上面例子有些不同,这里感觉Toast又充当客户端,又充当服务端的样子,实质就是一 个回调过程而已。
继续来看Toast中的show方法的service.enqueueToast(pkg, tn, mDuration);语句,service实质是远程的NotificationManagerService,所以enqueueToast方法就是 NotificationManagerService类的,如下:
现在我们就回到Toast的TN类再看看这个show与hide方法,如下:
那我们重点关注一下handleShow与handleHide方法,如下:
5-3 Toast窗口源码分析总结及应用开发技巧
经过上面的分析我们总结如下:通过上面分析及上图直观描述可以发现,之所以Toast的显示交由远程的NotificationManagerService管理是因为 Toast是每个应用程序都会弹出的,而且位置和UI风格都差不多,所以如果我们不统一管理就会出现覆盖叠加现象,同时导致不好控制,所以Google把 Toast设计成为了系统级的窗口类型,由NotificationManagerService统一队列管理。
在我们开发应用程序时使用Toast注意事项:
通过分析TN类的handler可以发现,如果想在非UI线程使用Toast需要自行声明Looper,否则运行会抛出Looper相关的异常;UI线程不需要,因为系统已经帮忙声明。
在使用Toast时context参数尽量使用getApplicationContext(),可以有效的防止静态引用导致的内存泄漏。
有时候我们会发现Toast弹出过多就会延迟显示,因为上面源码分析可以看见Toast.makeText是一个静态工厂方法,每次调用这 个方法都会产生一个新的Toast对象,当我们在这个新new的对象上调用show方法就会使这个对象加入到 NotificationManagerService管理的mToastQueue消息显示队列里排队等候显示;所以如果我们不每次都产生一个新的 Toast对象(使用单例来处理)就不需要排队,也就能及时更新了。
6 Android应用Activity、Dialog、PopWindow、Toast窗口显示机制总结
可以看见上面无论Acitivty、Dialog、PopWindow、Toast的实质其实都是如下接口提供的方法操作:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0615/3044.html
相关文章推荐
- android ndk编译getevent
- 【Android Studio探索之路系列】之五:Android Studio项目创建
- 修改Android签名证书keystore的密码、别名alias以及别名密码
- android 中一个工程引用另一个工程
- android自定义style
- android引入第三方jar包后打包报错
- 【Android学习】Android工程资源命名禁忌
- Android仿qq下拉刷新及向左滑动列表----PullToRefresh, SwipeMenuListView开源项目整合
- Android开发模板------ViewPager(二)FragmentPagerAdapter的简介
- android LayoutInflater 的使用
- 另一篇关于 Serializable 和 Parcelable 对比的文章
- Android开发之PagerAdapter的使用
- 背景音乐实现功能
- android EditText默认问题
- Android Studio 上如何使用LogCat
- 完全自定义Android对话框AlertDialog的实现(系统源码)
- min3d引擎使用指南(Android)<二>
- android studio 之BaseAdapter
- android getContentLength()函数返回为-1的问题
- apk打包之用纯命令行打包apk(android4.4亲测可用)