From
51CTO
一 前言
在谈到这个话题的时候,脑海里面千头万绪,因为它涉及到了方方面面的知识… 比如Activity管理,窗口添加,Token权限验证等等…
既然这么复杂,那么我们就复杂的问题简单化,可以分成下面几个步骤进行讲解。
1. Android里面窗口这个概念的分析。
2. Android里面窗口的类型
3. Android窗口功能相关的token值
4. Android里面Activity窗口添加流程分析
5. Dialog窗口的添加流程分析
6. Toast窗口的流程分析
二 Android里面窗口是什么
1. 对用户来说,窗口就是手机屏幕,包括下面的那些home, back按键,状态栏等等。
2. 对于Activity来说,窗口就是除开系统状态栏,系统按键的屏幕区域,因此它有window之类的概念
3. 对于wms来说,它没有什么窗口的概念,它能接受的只是一个个view而已。
也就是Activity这里还有Window这个概念,但在wms那里,已经没有window的概念了。
这个也许就是google和windows的区别了,在windows操作系统里面,window是个非常重要的概念;但是在google这里,window就不是那么重要了,更多重要的责任已经转移到了View这么个概念的身上。
有点像两个人在斗气,你把window概念看的那么重,我就偏偏看不起它~~
View不仅可以显示视图,也就是用户看到的界面;还可以分发事件等等。
三 Android窗口类型
窗口类型主要分成3类
1. 应用程序窗口
顾名思义,就是一般应用程序的窗口,比如我们应用程序的Activity的窗口
2. 子窗口
一般在Activity里面的窗口,比如TabActivity
3. 系统窗口
系统的窗口,比如输入法,Toast,墙纸等等…
看WindowManager.LayoutParams里面关于各种窗口的type类型定义,type还有个含义,就是窗口的z-order, 值越大,显示的位置越在上面, 如下:
文件路径:
frameworks/base/core/java/android/view/WindowManager.java
应用程序窗口type范围 1~99
子窗口 1000~1999
系统窗口 2000~2999
创建不同类型的窗口,在窗口属性,也就是WindowManager.LayoutParams对象,设置不同的type值。这点很重要的。因为wms会针对不用的type值做不同的处理。
四 Android窗口功能相关的token值
Token值听起来有点像令牌之类的东西,貌似是权限的作用,其实,它在我们今天要讨论的东西里面,它就是一个Binder对象。Binder对象,应该不会陌生,是android里面实现跨进程通信的重要机制…
那么,在窗口创建这个部分里面,主要有哪些Binder呢?
1. W对象,它是wms访问应用程序的接口
文件路径:
frameworks/base/core/java/android/view/ViewRootImpl.java
代码:
2. 指向ActivityRecord里面appToken的IApplicationToken对象
文件路径:
frameworks/base/services/java/com/android/server/am/ActivityRecord.java
代码:
然后它的对象定义在代码就是:
ActivityRecord的话是Activity在ams里面的记录缓存,也就是每启动一个Activity,都会在ams里面用一个ActivityRecord对象储存起来,一个名字叫mHistory的列表。
代码路径:
frameworks/base/services/java/com/android/server/am/ActivityStack.java
代码:
不过这个东西不是我们今天要讲的重点,以后有机会可以分析分析。
那么,在android窗口管理里面究竟有哪些相关的Token呢?
如下表:
下面,对上面这些token一一进行讲解
1. Activity的mToken
它的值其实就是ActivityRecord里面的mAppToken值
Ok…还是看代码吧,讲再多,也不如来点代码实在,从ams启动Activity开始
代码路径:
frameworks/base/services/java/com/android/server/am/ActivityStack.java
代码:
app代表的是ProcessRecord对象,表示一个进程,其实就是客户端进程啦。它的thread对象是一个Binder对象,用来ams和客户端进程通信用的。那么,上面那行代码的意思就是通过它的Binder对象,向目标进程发送一个启动Activity的命令,同时把ActivityRecrod的appToken一起传输了过去。
那么,我们来看,这个scheduleLaunchActivity方法做了什么
代码路径:
frameworks/base/core/java/android/app/ActivityThread.java
代码:
这里的话,它首先是生成了一个ActivityClientRecord对象,顾名思义,就是客户端的Activity记录,然后把传入过来的ActivityRecord对象里面的属性赋给ActivityClientRecord对象,其中就包括从ActivityRecord里面来的token对象;然后发送一条LAUNCH_ACTIVITY消息。
看看这条消息做了什么….
还是在ActivityThread文件里面
很明显,它是把刚才新建的ActivityClientRecord对象从Message里面取出来,然后给它的一些属性继续赋值,再调用handleLaunchActivity(r, null)方法。
Ok….继续看…
继续调用performLaunchActivity(r, customIntent); 这个方法返回了一个Activity对象,这个就是我们要启动的Activity了。看看里面做了什么…
这段代码,主要首先用反射机制,把我们配置好的activity对象实例化出来,然后如果成功(activity != null),就调用activity.attch(xxxx)方法,当然不可避免的,会把我们从ams传入过来的token对象一起传输过去。
继续往下面看…
代码路径:
frameworks/base/core/java/android/app/Activity.java
代码:
这段代码里面做了很多初始化的操作,比如创建了window对象,它表示当前Activity对应的window对象,一般一个Activity对应一个Window对象。
然后调用mWindow.setCallBack(this),就是把Activity设置成Window的回调对象,因为Activity实现了Window的回调接口。这样Activity就可以接受Window的回调事件了。
还有设置mMainThread,也就是当前应用程序的主线程
当然了,也包括设置mToken值为ActivityRecord的appToken。
所以说,Activity的mToken值就是ams里面ActivityRecord的appToken值。
2. Window的mAppToken
注意,这里说的Window是指window对象,不是一个窗口,因为对于wms来说,一个窗口,就是一个view而已,和window对象没有半毛钱的关系。一般一个Activity对应一个Window对象,但是一个Window对象不一定对应一个Activity对象,比如,有可能对应一个Dialog。
当Window属于某个Activity时,它的mAppToken值就是Activity的token,如果是Dialog的话,它的mAppToken值应该为null
下面,我们从代码的角度分析,如果window属于Activity的话,它的mAppToken变量怎么被赋值为Activity的token。
继续上面的Activity.attach(xxx)看
代码路径:
frameworks/base/core/java/android/app/Activity.java
代码:
这其实就是刚才上面讲的代码啦,看看mWindow.setWindowManager(xxx)这个方法吧,第2个参数是Activity的mToken对象。
那看看里面做了什么…
代码路径:
frameworks/base/core/java/android/view/Window.java
代码:
嗯…把传入进来的IBinder appToken对象赋值给Window的mAppToken对象…
所以,如果Window属于某个Activity的话,它的mAppToken就是Activity的mToken对象。
那为什么Dialog的Window对象的mAppToken是null呢?来段代码分析下。
这个,得从Dialog的构造函数说起了
代码路径:
frameworks/base/core/java/android/app/Dialog.java
代码:
从上面的代码可以看到,Dialog在创建的时候,其实也是创建了一个Window对象,然后调用
w.setCallback(this);//用来接收window的事件分发
这和Activity一样,所以,从这点来说,Dialog和Activity并没有什么区别。
然后调用w.setWindowManager(xxx)方法,从上面Activity的attach方法我们可以知道,这个w.setWindowManager(xxx)方法有个重要作用就是设置Window对象的mAppToken对象,就是它的第2个参数…
那么,在这里,在Dialog里面, 它的第2个参数是null….
也就是,如果一个Window属于Dialog的话,那么该Window的mAppToken对象是null….
3. WindowManager.LayoutParams中的token
正是人如其名啦!这里的token就像的它的类名一样,是wms添加窗口(其实就是个view啦)的时候,指定的参数。
它有3中情况,和我们android里面定义的窗口类型有关
a. 第一种,是应用程序窗口,如果这样,那么token对应就是Activity里面的mToken
b. 第二种,子窗口,那么token就是父窗口的W对象
c. 第三种,系统窗口,那么这个token一般就是null了…
比较抽象,对不对?怎么办呐?!!
“翠花,上源代码!!” -_^
代码路径:
frameworks/base/core/java/android/view/WindowManagerGlobal.java
代码:
这个parentWindow.adjustLayoutParamsForSubWindow(wparams);方法里面的重要一步就是给token设置值。不过在这以前要判断parentWindow是否为null
i. 如果是应用程序窗口的话,这个parentWindow就是activity的window
ii. 如果是子窗口的话,这个parentWindow就是activity的window
iii. 如果是系统窗口的话,那个parentWindow就是null
so,,,, 待我们进入这个方法里面看看…
代码路径:
frameworks/base/core/java/android/view/Window.java
代码:
Ok…如果是子窗口,也就是WindowManager.LayoutParams对象的type参数是属于
if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW)
然后给wp参数的token赋值
wp.token = decor.getWindowToken();
这里赋值的是父窗口的W对象
如果是应用程序窗口,走的是else分支
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
mContainer表示父窗口,比如TabActivity,一般应用程序窗口的话,mContainer为null,也就是mAppToken,就是Activity的mToken对象。
4. ViewRootImpl 和View的mAttachInfo
其实ViewRootImpl和View里面的mAttachInfo是一个东西,为什么这么说呢…得从代码说起呀!
首先看ViewRootImpl的构造函数
代码路径:
frameworks/base/core/java/android/view/ViewRootImpl.java
代码:
从上面代码看到,我们新建了一个mAttachInfo对象,参数
mWindowSession: 就是访问wms的Binder接口
mWindow: wms回调应用程序的Binder接口
xxxx
然后看ViewRootImpl的performTraversals(xxx)方法
host对象嘛,就是一个View了,上面那个方法会调用ViewGroup. dispatchAttachedToWindow(xxx)方法。
至于为什么会调用到ViewGroup里面,可以看下View和ViewGroup的关系,这里就不多了。
那么,来看看,ViewGroup怎么实现这个方法的。
代码路径:
frameworks/base/core/java/android/view/ViewGroup.java
代码:
这里嘛,主要就是找到这个ViewGroup的所有子视图,然后挨个分发,调用它们的dispatchAttachedToWindow(xxx)方法。
看看View里面怎么实现这个dispatchAttachedToWindow(xxx)方法的吧…
把从ViewRootImpl里面创建的mAttachInfo对象,赋值给View的mAttachInfo。
那么经过上面的步骤,我们可以理解为:
1. 在ViewRootImpl里面创建了一个mAttachInfo对象
2. 调用ViewRootImpl的performTraversals(xxx)方法,把mAttachInfo分发给根节点View
3. 根节点View,其实是一个ViewGroup,它会把接受到的mAttachInfo逐个分发给它下面的View,这样,整个View视图系统里面的mAttachInfo和ViewRootImpl的mAttachInfo就是一个东东了…
接下来,终于看最后一个关于View.AttachInfo这个东东了
5. 在上面的表格里面,最后面说道View.AttachInfo有三个变量
IBinder mWindowToken;
IBinder mPanelParentWindowToken;
IWindow mWindow;
下面来说说这个几个变量代表什么
mWindowToken 代表的是W对象,也就是wms和应用程序交互的Binder接口
mPanelParentWindowToken 如果该窗口时子窗口,那么该值就是父窗口的W对象,如果mWindowToken不为空,则说明没有父窗口…嗯,和mWindowToken有点相对的意思。
mWindow, 其实和mWindowToken功能差不多,因为mWindowToken可以通过mWinow得到:
mWinowToken = mWinow.asBinder();
也许,是为了调用方便,管他呢….
Ok….至此,窗口有关的Token对象说明的差不多了。
下面,我们来看Activity窗口是怎么被添加到显示的…
四 Activity窗口添加流程
这个要说起来,话就长了… 但是也不能不说,是吧~~ 从哪里开始呢?说个大家熟悉的地方吧!
从Activity的onCreate(xxx)方法的setContentView(View view) 开始!
代码路径:
california_td_new/frameworks/base/core/java/android/app/Activity.java
代码:
这个会调用到PhoneWindow的setContentView(xxx)里面,这里用PhoneWindow,顾名思义,就是为手机设计的Window实现类,如果以后要是让android支持其他设备的话,在这里就可以为那种设备新建一个XXXWindow..
代码路径:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java
代码:
这里的话,它会构造出一个mDecorView,然后把传入的view放到 mDecorView下面。也就是mDecorView是个壳子,里面包含了我们要显示的内容。
Ok…这一步的话,我们把我们想要显示的view放到了window里面的mDecorView里面。
那么继续往下面看,看哪里呢?看Activity要resume的时候,做了什么…
代码路径:
frameworks/base/core/java/android/app/ActivityThread.java
代码:
首先,取得window对应的mDecorView,然后赋值给Activity的mDecor…这样,Activity的mDecorView也就包含我们想显示的内容了..
然后,再看Activity. makeVisible()方法
代码路径:
frameworks/base/core/java/android/app/Activity.java
代码:
这里的话,调用wm.addView(xxx)方法,第一个参数就是上面我们设置的mDecor这个view啦!第二个参数getWindow().getAttributes(),看看怎么来的
代码路径:
frameworks/base/core/java/android/view/Window.java
代码;
然后看看WindowManager.LayoutParams()怎么实现的..
代码路径:
/frameworks/base/core/java/android/view/WindowManager.java
代码:
嗯,这里设置了type属性,是个应用窗口的属性啦~~~
回到开始,我们来看wm.addView(xxx)怎么实现的..
代码路径:
frameworks/base/core/java/android/view/WindowManagerImpl.java
代码:
继续…
代码路径:
frameworks/base/core/java/android/view/WindowManagerGlobal.java
代码:
这里主要是创建ViewRootImple对象,一个添加到wms实现的View对应一个ViewRootImpl对象。
然后这里有3个数组
它保存了当前应用程序添加的所有的View对象,已经相对应的ViewRootImpl对象和添加时使用的WindowManager.LayoutParams属性对象。
最后,调用ViewRootImpl.setView(xxx)正是准备通过mSession向wms发送添加窗口请求。
从这里也可以看出,对于wms来说,它添加的都是view,和window没有半毛钱关系..或许叫ViewManagerService更恰当~~?
五 Dialog窗口的添加流程
其实我相信,Google的程序在处理Dialog和Activity的关系的时候肯定会头疼,因为他们会面临这样一个问题:
1. Dialog必须依附Activity存在,比如Dialog的构造函数一般有个Context变量,这个Context一般是个Activity。那么如果在Dialog弹出来之前,这个Activity已经被销毁了,那么这个Dialog在弹出的时候就会遇到问题,会报错。
所以,Dialog必须依附于它所关联的Activity!
2. Dialog和它所关联的Activity必须区分开,比如在事件分发的时候。如果Activity上面有Dialog存在的话,这个时候用户按back键,Activity是不应该受到这个事件的,只能由Dialog收到并且处理。
所以,从这个角度来分析的话,Activity和Dialog又要区别对待。
那么,Google程序员到底做了什么,以至于让这两者如此统一又分离呢?
欲知后事如何,且听下面分解~~~
上面我们已经就Dialog和Activity的统一和分离的矛盾性做出了分析,那么,Google的程序员是怎么解决这个问题的呢?
他们的办法是Activity和Dialog共用一个Token对象,这样,Dialog就必须依附于Activity而存在了。
然后它们彼此又有不同的Window对象,ViewRootImple对象,W对象,这样和wms建立的事件分发管道就独立于Activity和wms的管道了。这样就能实现Dialog和Activity在事件这块是区别对待的。
Ok….我们来看看Dialog是怎么实现这个功能的。
上代码!!!
先看Dialog的构造函数
代码路径:
frameworks/base/core/java/android/app/Dialog.java
代码:
首先,它把外部传入的context对象缓存起来,这个context一般是个Activity,这点很重要!!
然后调用context.getSystemService(Context.WINDOW_SERVICE),那么由于Dialog对应的context变量是个Activity,所以,它会调用到Activity的getSystemService(xxx)方法里面。
这是关键,各位一定要理解了…
Ok…看看Activity里面怎么重写个getSystemService(xxxx)方法的
代码路径:
frameworks/base/core/java/android/app/Activity.java
代码:
这里重写只针对两种服务,一个是windowService,还有一个SearchService,如果是windowService的话,就返回此Activity的mWindowManager对象。
Ok…那么返回,继续看Dialog的构造函数
然后把从Activity返回的mWindowManager对象缓存起来,记住哦,这个mWindowManager和Activity里面是一样的。
然后调用
Window w = PolicyManager.makeNewWindow(mContext);
新建了一个Window对象,这确确实实是新建了Window对象,类型是PhoneWindow类型,这也是和Activity事件区分开来的关键。
再调用
w.setCallback(this);
这是设置Dialog为当前window的回调接口,这也是Dialog能够接受到按键事件的原因,从这一点看,Dialog和Activity并没有什么区别。
Ok..Dialog的构造函数介绍完毕之后,然后来看看Dialog的弹出方法show()
代码路径:
frameworks/base/core/java/android/app/Dialog.java
代码:
首先是取得mWindow.getAttributes();在上面已经讲过,它的实际是:
代码路径:
frameworks/base/core/java/android/view/WindowManager.java
代码:
这个方法的第2行就是给它的type类型设置值为TYPE_APPLICATION;所以,这也说明,普通的Dialog,默认是一个应用程序窗口。
Ok…继续上面的show()方法看…
mWindowManager.addView(mDecor, l);
这个方法是调用WindowManager.addView(xxx)方法,意图就是把一个View添加到windowManager里面去..
不过不要忘记的是,这里的mWindowManager是Activity 的mWindowManager。
这里不是很想再写出来了,不过就是因为Dialog使用Activity的mWindowManager,而WindowManager里面有个Window变量,当然更重要的是Window变量里面有个mAppToken值,那么既然Dialog和Activity共享一个mWindowManager,那么它们也就可以共享一个mAppToken值,只不过Dialog和Activity的Window对象不同。
这种设计的作用和实现方式在上面也已经分析过..
六 Toast窗口的添加流程
Toast窗口的话和我们的Activity以及Dialog都是不同的,它是属于系统窗口..
一般来说,android系统是不允许应用程序添加系统窗口的,但是有三种情况例外,是哪三种窗口呢?
在wms添加窗口时有这样的检查:
代码路径:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
代码:
这三种类型对应的分别是Toas,输入法,墙纸
接下来看看,Toast是怎么实现的呢?
1. 首先来看看Toast的makeText(xxx)方法
代码路径:
frameworks/base/core/java/android/widget/Toast.java
代码:
这个方法构造了一个Toast,然后把要显示的文本放到这个View里面,然后把View,以及Toast的持续时间,都封装到一个result对象里面,然后返回…
2. 然后我们来看Toast.show()方法
这个方法首先判断我们刚才创建的View是不是为null,如果为null,就抛出一个异常.
如果不是,那么构造一个TN对象mTN,这个TN是什么东西呢?看看它的实现:
private static class TN extends ITransientNotification.Stub {
….
}
很明显,它是一个Binder对象,Binder嘛,用来跨进程调用的啦!那这里为什么要弄出这么个东西呢?
这是因为我们的Toast都是传给NotificationManagerService管理的,那么为了NotificationManagerService回到我们的应用程序,必须告诉NotificationManagerService,我们应用程序的Binder引用是什么。
果不其然,首先它会拿到NotificationManagerService的服务访问接口
INotificationManager service = getService();
然后调用
service.enqueueToast(pkg, tn, mDuration);
这里,它把TN对象,以及这个Toast的延续时间告诉了NotificationManagerService,那么为什么要把mDuration这个持续时间告诉NotificationManagerService呢?
这是便于NotificationManagerService在指定的时间内回调我们应用程序,通知我们该去dismiss咱们的Toast了。
于是,我们看看NotificationManagerService怎么实现这个enqueueToast(xxxx)方法的.
代码路径:
frameworks/base/services/java/com/android/server/NotificationManagerService.java
代码:
这里主要是新建一个ToastRecord对象record,然后把这个对象放置到一个mToastQueue队列里面..
最后,调用showNextToastLocked()方法,准备弹出Toast
继续看…
首先呢,把record取出来,然后调用
record.callback.show();
这个callback其实就是一个TN 对象啦,就是我们从应用程序传过来滴,不过我们暂且不看它的实现,继续看下一行:
scheduleTimeoutLocked(record, false);
代码:
这里,主要是根据我们toast设置的时间长短(Toast.length_show/Toast.length_long)设置一个延迟时间,如果是short的话,就延迟2s,如果是long的话,就延迟3.5s,这也可以看出,toast的持续时间是多少秒。
设置好延迟时间之后,发送一个消息MESSAGE_TIMEOUT,那我们再看看怎么处理这个消息的。
它其实辗转反侧之后,会调用到:
首先会调用callback.hide()方法,也就是到了指定时间,通知客户端去取消toast,然后再show下一个toast
好吧,我们再来反过头看看这个TN.show()方法怎么实现的.
代码路径:
frameworks/base/core/java/android/widget/Toast.java
代码:
这里辗转反侧之后,会调用这里,也是调用mWM.addView(xxx)方法来向wms请求添加view。不过由于Toast发送到wms的参数是没有Token值。不过没关系wms不会检查Toast的token值。
这也是为什么Toast其实不会依赖弹出它的Activity的原因。
最后是NotificationManagerService通知TN去消失Toast,实现都差不多
最后总结下为什么Toast要采用NotificationManagerService来管理Toast吧
1. 因为Toast是每个应用程序都会弹出的,而且位置都差不多,那么如果不统一管理的话,就会出现覆盖现象。
写的好累…
一 前言
在谈到这个话题的时候,脑海里面千头万绪,因为它涉及到了方方面面的知识… 比如Activity管理,窗口添加,Token权限验证等等…
既然这么复杂,那么我们就复杂的问题简单化,可以分成下面几个步骤进行讲解。
1. Android里面窗口这个概念的分析。
2. Android里面窗口的类型
3. Android窗口功能相关的token值
4. Android里面Activity窗口添加流程分析
5. Dialog窗口的添加流程分析
6. Toast窗口的流程分析
二 Android里面窗口是什么
1. 对用户来说,窗口就是手机屏幕,包括下面的那些home, back按键,状态栏等等。
2. 对于Activity来说,窗口就是除开系统状态栏,系统按键的屏幕区域,因此它有window之类的概念
3. 对于wms来说,它没有什么窗口的概念,它能接受的只是一个个view而已。
也就是Activity这里还有Window这个概念,但在wms那里,已经没有window的概念了。
这个也许就是google和windows的区别了,在windows操作系统里面,window是个非常重要的概念;但是在google这里,window就不是那么重要了,更多重要的责任已经转移到了View这么个概念的身上。
有点像两个人在斗气,你把window概念看的那么重,我就偏偏看不起它~~
View不仅可以显示视图,也就是用户看到的界面;还可以分发事件等等。
三 Android窗口类型
窗口类型主要分成3类
1. 应用程序窗口
顾名思义,就是一般应用程序的窗口,比如我们应用程序的Activity的窗口
2. 子窗口
一般在Activity里面的窗口,比如TabActivity
3. 系统窗口
系统的窗口,比如输入法,Toast,墙纸等等…
看WindowManager.LayoutParams里面关于各种窗口的type类型定义,type还有个含义,就是窗口的z-order, 值越大,显示的位置越在上面, 如下:
文件路径:
frameworks/base/core/java/android/view/WindowManager.java
应用程序窗口type范围 1~99
01 | /** |
02 | * Start of window types that represent normal application windows. |
03 | */ |
04 | public static final int FIRST_APPLICATION_WINDOW = 1 ; |
05 |
06 | …. |
07 |
08 | /** |
09 | * End of types of application windows. |
10 | */ |
11 | public static final int LAST_APPLICATION_WINDOW = 99 ; |
01 | /** |
02 | * Start of types of sub-windows. The {@link #token} of these windows |
03 | * must be set to the window they are attached to. These types of |
04 | * windows are kept next to their attached window in Z-order, and their |
05 | * coordinate space is relative to their attached window. |
06 | */ |
07 | public static final int FIRST_SUB_WINDOW = 1000 ; |
08 |
09 | ….. |
10 |
11 | /** |
12 | * End of types of sub-windows. |
13 | */ |
14 | public static final int LAST_SUB_WINDOW = 1999 ; |
01 | /** |
02 | * Start of system-specific window types. These are not normally |
03 | * created by applications. |
04 | */ |
05 | public static final int FIRST_SYSTEM_WINDOW = 2000 ; |
06 |
07 | ….. |
08 |
09 |
10 | /** |
11 | * End of types of system windows. |
12 | */ |
13 | public static final int LAST_SYSTEM_WINDOW = 2999 ; |
四 Android窗口功能相关的token值
Token值听起来有点像令牌之类的东西,貌似是权限的作用,其实,它在我们今天要讨论的东西里面,它就是一个Binder对象。Binder对象,应该不会陌生,是android里面实现跨进程通信的重要机制…
那么,在窗口创建这个部分里面,主要有哪些Binder呢?
1. W对象,它是wms访问应用程序的接口
文件路径:
frameworks/base/core/java/android/view/ViewRootImpl.java
代码:
1 | static class W extends IWindow.Stub { |
2 | …. |
3 | } |
文件路径:
frameworks/base/services/java/com/android/server/am/ActivityRecord.java
代码:
1 | static class Token extends IApplicationToken.Stub { |
2 | …. |
3 | } |
1 | final class ActivityRecord { |
2 | … |
3 | final IApplicationToken.Stub appToken; // window manager token |
4 | … |
5 | } |
代码路径:
frameworks/base/services/java/com/android/server/am/ActivityStack.java
代码:
1 | /** |
2 | * The back history of all previous (and possibly still |
3 | * running) activities. It contains HistoryRecord objects. |
4 | */ |
5 | final ArrayList<ActivityRecord> mHistory = new ArrayList<ActivityRecord>(); |
那么,在android窗口管理里面究竟有哪些相关的Token呢?
如下表:
代码路径 | 类名 | 变量 |
frameworks/base/core/java/android/app/Activity.java | Activity.java | IBinder mToken |
frameworks/base/core/java/android/view/Window.java | Window.java | IBinder mAppToken |
frameworks/base/core/java/android/view/WindowManager.java | WindowManager.LayoutParams | IBinder token |
frameworks/base/core/java/android/view/ViewRootImpl.java | ViewRootImpl | View.AttachInfo mAttacheInfo |
frameworks/base/core/java/android/view/View.java | View | View.AttachInfo mAttacheInfo |
frameworks/base/core/java/android/view/View.java | View.attachInfo | IBinder mWindowToken IBinder mPanelParentWindowToken IWindow mWindow |
下面,对上面这些token一一进行讲解
1. Activity的mToken
它的值其实就是ActivityRecord里面的mAppToken值
Ok…还是看代码吧,讲再多,也不如来点代码实在,从ams启动Activity开始
代码路径:
frameworks/base/services/java/com/android/server/am/ActivityStack.java
代码:
01 | final boolean realStartActivityLocked(ActivityRecord r, |
02 | ProcessRecord app, boolean andResume, boolean checkConfig) |
03 | throws RemoteException { |
04 | …. |
05 | app.thread.scheduleLaunchActivity( new Intent(r.intent), r.appToken, |
06 | System.identityHashCode(r), r.info, |
07 | new Configuration(mService.mConfiguration), |
08 | r.compat, r.icicle, results, newIntents, !andResume, |
09 | mService.isNextTransitionForward(), profileFile, profileFd, |
10 | profileAutoStop); |
11 |
12 | …. |
13 | } |
那么,我们来看,这个scheduleLaunchActivity方法做了什么
代码路径:
frameworks/base/core/java/android/app/ActivityThread.java
代码:
01 | // we use token to identify this activity without having to send the |
02 | // activity itself back to the activity manager. (matters more with ipc) |
03 | public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, |
04 | ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, |
05 | Bundle state, List<ResultInfo> pendingResults, |
06 | List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, |
07 | String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { |
08 | ActivityClientRecord r = new ActivityClientRecord(); |
09 |
10 | r.token = token; |
11 | … |
12 | queueOrSendMessage(H.LAUNCH_ACTIVITY, r); |
13 | } |
看看这条消息做了什么….
还是在ActivityThread文件里面
1 | case LAUNCH_ACTIVITY: { |
2 | …. |
3 | ActivityClientRecord r = (ActivityClientRecord)msg.obj; |
4 |
5 | r.packageInfo = getPackageInfoNoCheck( |
6 | r.activityInfo.applicationInfo, r.compatInfo); |
7 | handleLaunchActivity(r, null ); |
8 | … |
9 | } break ; |
Ok….继续看…
1 | private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { |
2 | …. |
3 | Activity a = performLaunchActivity(r, customIntent); |
4 | …. |
5 | } |
01 | private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { |
02 | …. |
03 |
04 | Activity activity = null ; |
05 | try { |
06 | java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); |
07 | activity = mInstrumentation.newActivity( |
08 | cl, component.getClassName(), r.intent); |
09 | …. |
10 | } catch (Exception e) { |
11 | …. |
12 | } |
13 | ….. |
14 |
15 | if (activity != null ) { |
16 | Context appContext = createBaseContextForActivity(r, activity); |
17 | CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); |
18 | Configuration config = new Configuration(mCompatConfiguration); |
19 | ….. |
20 | activity.attach(appContext, this , getInstrumentation(), r.token, |
21 | r.ident, app, r.intent, r.activityInfo, title, r.parent, |
22 | r.embeddedID, r.lastNonConfigurationInstances, config); |
23 | } |
24 |
25 | …. |
26 | } |
继续往下面看…
代码路径:
frameworks/base/core/java/android/app/Activity.java
代码:
01 | final void attach(Context context, ActivityThread aThread, |
02 | Instrumentation instr, IBinder token, int ident, |
03 | Application application, Intent intent, ActivityInfo info, |
04 | CharSequence title, Activity parent, String id, |
05 | NonConfigurationInstances lastNonConfigurationInstances, |
06 | Configuration config) { |
07 | attachBaseContext(context); |
08 |
09 | mFragments.attachActivity( this , mContainer, null ); |
10 | |
11 | mWindow = PolicyManager.makeNewWindow( this ); |
12 | mWindow.setCallback( this ); |
13 | …. |
14 | mUiThread = Thread.currentThread(); |
15 | |
16 | mMainThread = aThread; |
17 | mInstrumentation = instr; |
18 | mToken = token; |
19 | …. |
20 | mWindow.setWindowManager( |
21 | (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), |
22 | mToken, mComponent.flattenToString(), |
23 | (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0 ); |
24 | …. |
25 | } |
然后调用mWindow.setCallBack(this),就是把Activity设置成Window的回调对象,因为Activity实现了Window的回调接口。这样Activity就可以接受Window的回调事件了。
还有设置mMainThread,也就是当前应用程序的主线程
当然了,也包括设置mToken值为ActivityRecord的appToken。
所以说,Activity的mToken值就是ams里面ActivityRecord的appToken值。
2. Window的mAppToken
注意,这里说的Window是指window对象,不是一个窗口,因为对于wms来说,一个窗口,就是一个view而已,和window对象没有半毛钱的关系。一般一个Activity对应一个Window对象,但是一个Window对象不一定对应一个Activity对象,比如,有可能对应一个Dialog。
当Window属于某个Activity时,它的mAppToken值就是Activity的token,如果是Dialog的话,它的mAppToken值应该为null
下面,我们从代码的角度分析,如果window属于Activity的话,它的mAppToken变量怎么被赋值为Activity的token。
继续上面的Activity.attach(xxx)看
代码路径:
frameworks/base/core/java/android/app/Activity.java
代码:
01 | final void attach(Context context, ActivityThread aThread, |
02 | Instrumentation instr, IBinder token, int ident, |
03 | Application application, Intent intent, ActivityInfo info, |
04 | CharSequence title, Activity parent, String id, |
05 | NonConfigurationInstances lastNonConfigurationInstances, |
06 | Configuration config) { |
07 | …. |
08 | mToken = token; |
09 | …. |
10 | mWindow.setWindowManager( |
11 | (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), |
12 | mToken, mComponent.flattenToString(), |
13 | (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0 ); |
14 | …. |
15 | } |
那看看里面做了什么…
代码路径:
frameworks/base/core/java/android/view/Window.java
代码:
01 | public void setWindowManager(WindowManager wm, IBinder appToken, String appName, |
02 | boolean hardwareAccelerated) { |
03 | mAppToken = appToken; |
04 | mAppName = appName; |
05 | mHardwareAccelerated = hardwareAccelerated |
06 | || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false ); |
07 | if (wm == null ) { |
08 | wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); |
09 | } |
10 | mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager( this ); |
11 | } |
所以,如果Window属于某个Activity的话,它的mAppToken就是Activity的mToken对象。
那为什么Dialog的Window对象的mAppToken是null呢?来段代码分析下。
这个,得从Dialog的构造函数说起了
代码路径:
frameworks/base/core/java/android/app/Dialog.java
代码:
01 | Dialog(Context context, int theme, boolean createContextThemeWrapper) { |
02 | ….. |
03 |
04 | mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); |
05 | Window w = PolicyManager.makeNewWindow(mContext); |
06 | mWindow = w; |
07 | w.setCallback( this ); |
08 | w.setWindowManager(mWindowManager, null , null ); |
09 | …. |
10 | } |
w.setCallback(this);//用来接收window的事件分发
这和Activity一样,所以,从这点来说,Dialog和Activity并没有什么区别。
然后调用w.setWindowManager(xxx)方法,从上面Activity的attach方法我们可以知道,这个w.setWindowManager(xxx)方法有个重要作用就是设置Window对象的mAppToken对象,就是它的第2个参数…
那么,在这里,在Dialog里面, 它的第2个参数是null….
也就是,如果一个Window属于Dialog的话,那么该Window的mAppToken对象是null….
3. WindowManager.LayoutParams中的token
正是人如其名啦!这里的token就像的它的类名一样,是wms添加窗口(其实就是个view啦)的时候,指定的参数。
它有3中情况,和我们android里面定义的窗口类型有关
a. 第一种,是应用程序窗口,如果这样,那么token对应就是Activity里面的mToken
b. 第二种,子窗口,那么token就是父窗口的W对象
c. 第三种,系统窗口,那么这个token一般就是null了…
比较抽象,对不对?怎么办呐?!!
“翠花,上源代码!!” -_^
代码路径:
frameworks/base/core/java/android/view/WindowManagerGlobal.java
代码:
1 | public void addView(View view, ViewGroup.LayoutParams params, |
2 | Display display, Window parentWindow) { |
3 | …. |
4 | if (parentWindow != null ) { |
5 | parentWindow.adjustLayoutParamsForSubWindow(wparams); |
6 | } |
7 | …. |
8 | } |
i. 如果是应用程序窗口的话,这个parentWindow就是activity的window
ii. 如果是子窗口的话,这个parentWindow就是activity的window
iii. 如果是系统窗口的话,那个parentWindow就是null
so,,,, 待我们进入这个方法里面看看…
代码路径:
frameworks/base/core/java/android/view/Window.java
代码:
01 | void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) { |
02 | CharSequence curTitle = wp.getTitle(); |
03 | if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && |
04 | wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { |
05 | if (wp.token == null ) { |
06 | View decor = peekDecorView(); |
07 | if (decor != null ) { |
08 | wp.token = decor.getWindowToken(); |
09 | } |
10 | } |
11 | ….. |
12 | } |
13 | } else { |
14 | if (wp.token == null ) { |
15 | wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; |
16 | } |
17 | ….. |
18 | } |
19 | …. |
20 | } |
if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW)
然后给wp参数的token赋值
wp.token = decor.getWindowToken();
这里赋值的是父窗口的W对象
如果是应用程序窗口,走的是else分支
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
mContainer表示父窗口,比如TabActivity,一般应用程序窗口的话,mContainer为null,也就是mAppToken,就是Activity的mToken对象。
4. ViewRootImpl 和View的mAttachInfo
其实ViewRootImpl和View里面的mAttachInfo是一个东西,为什么这么说呢…得从代码说起呀!
首先看ViewRootImpl的构造函数
代码路径:
frameworks/base/core/java/android/view/ViewRootImpl.java
代码:
1 | public ViewRootImpl(Context context, Display display) { |
2 | super (); |
3 |
4 | …. |
5 | mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this , mHandler, this ); |
6 | ….. |
7 |
8 | } |
mWindowSession: 就是访问wms的Binder接口
mWindow: wms回调应用程序的Binder接口
xxxx
然后看ViewRootImpl的performTraversals(xxx)方法
1 | private void performTraversals() { |
2 | …. |
3 | host.dispatchAttachedToWindow(attachInfo, 0 ); |
4 | …. |
5 | } |
至于为什么会调用到ViewGroup里面,可以看下View和ViewGroup的关系,这里就不多了。
那么,来看看,ViewGroup怎么实现这个方法的。
代码路径:
frameworks/base/core/java/android/view/ViewGroup.java
代码:
01 | void dispatchAttachedToWindow(AttachInfo info, int visibility) { |
02 | …. |
03 | final int count = mChildrenCount; |
04 | final View[] children = mChildren; |
05 | for ( int i = 0 ; i < count; i++) { |
06 | final View child = children[i]; |
07 | child.dispatchAttachedToWindow(info, |
08 | visibility | (child.mViewFlags&VISIBILITY_MASK)); |
09 | } |
10 | } |
看看View里面怎么实现这个dispatchAttachedToWindow(xxx)方法的吧…
1 | void dispatchAttachedToWindow(AttachInfo info, int visibility) { |
2 | mAttachInfo = info; |
3 | …. |
4 | } |
那么经过上面的步骤,我们可以理解为:
1. 在ViewRootImpl里面创建了一个mAttachInfo对象
2. 调用ViewRootImpl的performTraversals(xxx)方法,把mAttachInfo分发给根节点View
3. 根节点View,其实是一个ViewGroup,它会把接受到的mAttachInfo逐个分发给它下面的View,这样,整个View视图系统里面的mAttachInfo和ViewRootImpl的mAttachInfo就是一个东东了…
接下来,终于看最后一个关于View.AttachInfo这个东东了
5. 在上面的表格里面,最后面说道View.AttachInfo有三个变量
IBinder mWindowToken;
IBinder mPanelParentWindowToken;
IWindow mWindow;
下面来说说这个几个变量代表什么
mWindowToken 代表的是W对象,也就是wms和应用程序交互的Binder接口
mPanelParentWindowToken 如果该窗口时子窗口,那么该值就是父窗口的W对象,如果mWindowToken不为空,则说明没有父窗口…嗯,和mWindowToken有点相对的意思。
mWindow, 其实和mWindowToken功能差不多,因为mWindowToken可以通过mWinow得到:
mWinowToken = mWinow.asBinder();
也许,是为了调用方便,管他呢….
Ok….至此,窗口有关的Token对象说明的差不多了。
下面,我们来看Activity窗口是怎么被添加到显示的…
四 Activity窗口添加流程
这个要说起来,话就长了… 但是也不能不说,是吧~~ 从哪里开始呢?说个大家熟悉的地方吧!
从Activity的onCreate(xxx)方法的setContentView(View view) 开始!
代码路径:
california_td_new/frameworks/base/core/java/android/app/Activity.java
代码:
1 | public void setContentView(View view) { |
2 | getWindow().setContentView(view); |
3 | … |
4 | } |
代码路径:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java
代码:
01 | public void setContentView(View view, ViewGroup.LayoutParams params) { |
02 | if (mContentParent == null ) { |
03 | installDecor(); |
04 | } else { |
05 | mContentParent.removeAllViews(); |
06 | } |
07 | mContentParent.addView(view, params); |
08 | final Callback cb = getCallback(); |
09 | if (cb != null && !isDestroyed()) { |
10 | cb.onContentChanged(); |
11 | } |
12 | } |
Ok…这一步的话,我们把我们想要显示的view放到了window里面的mDecorView里面。
那么继续往下面看,看哪里呢?看Activity要resume的时候,做了什么…
代码路径:
frameworks/base/core/java/android/app/ActivityThread.java
代码:
01 | final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, |
02 | boolean reallyResume) { |
03 |
04 | …. |
05 |
06 | r.window = r.activity.getWindow(); |
07 | View decor = r.window.getDecorView(); |
08 | decor.setVisibility(View.INVISIBLE); |
09 | ViewManager wm = a.getWindowManager(); |
10 | WindowManager.LayoutParams l = r.window.getAttributes(); |
11 | a.mDecor = decor; |
12 |
13 | …. |
14 | } |
然后,再看Activity. makeVisible()方法
代码路径:
frameworks/base/core/java/android/app/Activity.java
代码:
1 | void makeVisible() { |
2 | if (!mWindowAdded) { |
3 | ViewManager wm = getWindowManager(); |
4 | wm.addView(mDecor, getWindow().getAttributes()); |
5 | mWindowAdded = true ; |
6 | } |
7 | mDecor.setVisibility(View.VISIBLE); |
8 | } |
代码路径:
frameworks/base/core/java/android/view/Window.java
代码;
1 | public final WindowManager.LayoutParams getAttributes() { |
2 | return mWindowAttributes; |
3 | } |
4 |
5 | // The current window attributes. |
6 | private final WindowManager.LayoutParams mWindowAttributes = |
7 | new WindowManager.LayoutParams(); |
代码路径:
/frameworks/base/core/java/android/view/WindowManager.java
代码:
1 | public LayoutParams() { |
2 | super (LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); |
3 | type = TYPE_APPLICATION; |
4 | format = PixelFormat.OPAQUE; |
5 | } |
回到开始,我们来看wm.addView(xxx)怎么实现的..
代码路径:
frameworks/base/core/java/android/view/WindowManagerImpl.java
代码:
1 | public void addView(View view, ViewGroup.LayoutParams params) { |
2 | mGlobal.addView(view, params, mDisplay, mParentWindow); |
3 | } |
代码路径:
frameworks/base/core/java/android/view/WindowManagerGlobal.java
代码:
01 | public void addView(View view, ViewGroup.LayoutParams params, |
02 | Display display, Window parentWindow) { |
03 | …. |
04 | root = new ViewRootImpl(view.getContext(), display); |
05 |
06 | view.setLayoutParams(wparams); |
07 |
08 | if (mViews == null ) { |
09 | index = 1 ; |
10 | mViews = new View[ 1 ]; |
11 | mRoots = new ViewRootImpl[ 1 ]; |
12 | mParams = new WindowManager.LayoutParams[ 1 ]; |
13 | } else { |
14 | index = mViews.length + 1 ; |
15 | Object[] old = mViews; |
16 | mViews = new View[index]; |
17 | System.arraycopy(old, 0 , mViews, 0 , index- 1 ); |
18 | old = mRoots; |
19 | mRoots = new ViewRootImpl[index]; |
20 | System.arraycopy(old, 0 , mRoots, 0 , index- 1 ); |
21 | old = mParams; |
22 | mParams = new WindowManager.LayoutParams[index]; |
23 | System.arraycopy(old, 0 , mParams, 0 , index- 1 ); |
24 | } |
25 | index--; |
26 |
27 | mViews[index] = view; |
28 | mRoots[index] = root; |
29 | mParams[index] = wparams; |
30 | } |
31 |
32 | // do this last because it fires off messages to start doing things |
33 | try { |
34 | root.setView(view, wparams, panelParentView); |
35 | } |
36 | … |
37 | } |
然后这里有3个数组
1 | mViews = new View[ 1 ]; |
2 | mRoots = new ViewRootImpl[ 1 ]; |
3 | mParams = new WindowManager.LayoutParams[ 1 ]; |
最后,调用ViewRootImpl.setView(xxx)正是准备通过mSession向wms发送添加窗口请求。
从这里也可以看出,对于wms来说,它添加的都是view,和window没有半毛钱关系..或许叫ViewManagerService更恰当~~?
五 Dialog窗口的添加流程
其实我相信,Google的程序在处理Dialog和Activity的关系的时候肯定会头疼,因为他们会面临这样一个问题:
1. Dialog必须依附Activity存在,比如Dialog的构造函数一般有个Context变量,这个Context一般是个Activity。那么如果在Dialog弹出来之前,这个Activity已经被销毁了,那么这个Dialog在弹出的时候就会遇到问题,会报错。
所以,Dialog必须依附于它所关联的Activity!
2. Dialog和它所关联的Activity必须区分开,比如在事件分发的时候。如果Activity上面有Dialog存在的话,这个时候用户按back键,Activity是不应该受到这个事件的,只能由Dialog收到并且处理。
所以,从这个角度来分析的话,Activity和Dialog又要区别对待。
那么,Google程序员到底做了什么,以至于让这两者如此统一又分离呢?
欲知后事如何,且听下面分解~~~
上面我们已经就Dialog和Activity的统一和分离的矛盾性做出了分析,那么,Google的程序员是怎么解决这个问题的呢?
他们的办法是Activity和Dialog共用一个Token对象,这样,Dialog就必须依附于Activity而存在了。
然后它们彼此又有不同的Window对象,ViewRootImple对象,W对象,这样和wms建立的事件分发管道就独立于Activity和wms的管道了。这样就能实现Dialog和Activity在事件这块是区别对待的。
Ok….我们来看看Dialog是怎么实现这个功能的。
上代码!!!
先看Dialog的构造函数
代码路径:
frameworks/base/core/java/android/app/Dialog.java
代码:
01 | Dialog(Context context, int theme, boolean createContextThemeWrapper) { |
02 | if (createContextThemeWrapper) { |
03 | if (theme == 0 ) { |
04 | TypedValue outValue = new TypedValue(); |
05 | context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme, |
06 | outValue, true ); |
07 | theme = outValue.resourceId; |
08 | } |
09 | mContext = new ContextThemeWrapper(context, theme); |
10 | } else { |
11 | mContext = context; |
12 | } |
13 |
14 | mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); |
15 | Window w = PolicyManager.makeNewWindow(mContext); |
16 | mWindow = w; |
17 | w.setCallback( this ); |
18 | w.setWindowManager(mWindowManager, null , null ); |
19 | …. |
20 |
21 | } |
然后调用context.getSystemService(Context.WINDOW_SERVICE),那么由于Dialog对应的context变量是个Activity,所以,它会调用到Activity的getSystemService(xxx)方法里面。
这是关键,各位一定要理解了…
Ok…看看Activity里面怎么重写个getSystemService(xxxx)方法的
代码路径:
frameworks/base/core/java/android/app/Activity.java
代码:
1 | public Object getSystemService(String name) { |
2 | if (WINDOW_SERVICE.equals(name)) { |
3 | return mWindowManager; |
4 | } else if (SEARCH_SERVICE.equals(name)) { |
5 | ensureSearchManager(); |
6 | return mSearchManager; |
7 | } |
8 | return super .getSystemService(name); |
9 | } |
Ok…那么返回,继续看Dialog的构造函数
然后把从Activity返回的mWindowManager对象缓存起来,记住哦,这个mWindowManager和Activity里面是一样的。
然后调用
Window w = PolicyManager.makeNewWindow(mContext);
新建了一个Window对象,这确确实实是新建了Window对象,类型是PhoneWindow类型,这也是和Activity事件区分开来的关键。
再调用
w.setCallback(this);
这是设置Dialog为当前window的回调接口,这也是Dialog能够接受到按键事件的原因,从这一点看,Dialog和Activity并没有什么区别。
Ok..Dialog的构造函数介绍完毕之后,然后来看看Dialog的弹出方法show()
代码路径:
frameworks/base/core/java/android/app/Dialog.java
代码:
01 | public void show() { |
02 | …. |
03 |
04 | WindowManager.LayoutParams l = mWindow.getAttributes(); |
05 | …. |
06 |
07 | try { |
08 | mWindowManager.addView(mDecor, l); |
09 | …. |
10 | } finally { |
11 | } |
12 | } |
代码路径:
frameworks/base/core/java/android/view/WindowManager.java
代码:
1 | public LayoutParams() { |
2 | super (LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); |
3 | type = TYPE_APPLICATION; |
4 | format = PixelFormat.OPAQUE; |
5 | } |
Ok…继续上面的show()方法看…
mWindowManager.addView(mDecor, l);
这个方法是调用WindowManager.addView(xxx)方法,意图就是把一个View添加到windowManager里面去..
不过不要忘记的是,这里的mWindowManager是Activity 的mWindowManager。
这里不是很想再写出来了,不过就是因为Dialog使用Activity的mWindowManager,而WindowManager里面有个Window变量,当然更重要的是Window变量里面有个mAppToken值,那么既然Dialog和Activity共享一个mWindowManager,那么它们也就可以共享一个mAppToken值,只不过Dialog和Activity的Window对象不同。
这种设计的作用和实现方式在上面也已经分析过..
六 Toast窗口的添加流程
Toast窗口的话和我们的Activity以及Dialog都是不同的,它是属于系统窗口..
一般来说,android系统是不允许应用程序添加系统窗口的,但是有三种情况例外,是哪三种窗口呢?
在wms添加窗口时有这样的检查:
代码路径:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
代码:
1 | switch (type) { |
2 | case TYPE_TOAST: |
3 | case TYPE_INPUT_METHOD: |
4 | case TYPE_WALLPAPER: |
5 | |
6 | break ; |
接下来看看,Toast是怎么实现的呢?
1. 首先来看看Toast的makeText(xxx)方法
代码路径:
frameworks/base/core/java/android/widget/Toast.java
代码:
01 | public static Toast makeText(Context context, CharSequence text, int duration) { |
02 | Toast result = new Toast(context); |
03 |
04 | LayoutInflater inflate = (LayoutInflater) |
05 | context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
06 | View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null ); |
07 | TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); |
08 | tv.setText(text); |
09 | |
10 | result.mNextView = v; |
11 | result.mDuration = duration; |
12 |
13 | return result; |
14 | } |
2. 然后我们来看Toast.show()方法
01 | public void show() { |
02 | if (mNextView == null ) { |
03 | throw new RuntimeException( "setView must have been called" ); |
04 | } |
05 |
06 | INotificationManager service = getService(); |
07 | String pkg = mContext.getPackageName(); |
08 | TN tn = mTN; |
09 | tn.mNextView = mNextView; |
10 |
11 | try { |
12 | service.enqueueToast(pkg, tn, mDuration); |
13 | } catch (RemoteException e) { |
14 | // Empty |
15 | } |
16 | } |
如果不是,那么构造一个TN对象mTN,这个TN是什么东西呢?看看它的实现:
private static class TN extends ITransientNotification.Stub {
….
}
很明显,它是一个Binder对象,Binder嘛,用来跨进程调用的啦!那这里为什么要弄出这么个东西呢?
这是因为我们的Toast都是传给NotificationManagerService管理的,那么为了NotificationManagerService回到我们的应用程序,必须告诉NotificationManagerService,我们应用程序的Binder引用是什么。
果不其然,首先它会拿到NotificationManagerService的服务访问接口
INotificationManager service = getService();
然后调用
service.enqueueToast(pkg, tn, mDuration);
这里,它把TN对象,以及这个Toast的延续时间告诉了NotificationManagerService,那么为什么要把mDuration这个持续时间告诉NotificationManagerService呢?
这是便于NotificationManagerService在指定的时间内回调我们应用程序,通知我们该去dismiss咱们的Toast了。
于是,我们看看NotificationManagerService怎么实现这个enqueueToast(xxxx)方法的.
代码路径:
frameworks/base/services/java/com/android/server/NotificationManagerService.java
代码:
01 | // Toasts |
02 | // ============================================================================ |
03 | public void enqueueToast(String pkg, ITransientNotification callback, int duration) |
04 | { |
05 | …. |
06 |
07 | synchronized (mToastQueue) { |
08 | int callingPid = Binder.getCallingPid(); |
09 | long callingId = Binder.clearCallingIdentity(); |
10 | try { |
11 | …… |
12 | record = new ToastRecord(callingPid, pkg, callback, duration); |
13 | mToastQueue.add(record); |
14 | index = mToastQueue.size() - 1 ; |
15 | keepProcessAliveLocked(callingPid); |
16 | } |
17 | …. |
18 | if (index == 0 ) { |
19 | showNextToastLocked(); |
20 | } |
21 | } |
22 | … |
23 | } |
24 | } |
最后,调用showNextToastLocked()方法,准备弹出Toast
继续看…
01 | private void showNextToastLocked() { |
02 | ToastRecord record = mToastQueue.get( 0 ); |
03 | while (record != null ) { |
04 | if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback); |
05 | try { |
06 | record.callback.show(); |
07 | scheduleTimeoutLocked(record, false ); |
08 | return ; |
09 | } catch (RemoteException e) { |
10 | …. |
11 | } |
12 | } |
13 | } |
record.callback.show();
这个callback其实就是一个TN 对象啦,就是我们从应用程序传过来滴,不过我们暂且不看它的实现,继续看下一行:
scheduleTimeoutLocked(record, false);
代码:
1 | private void scheduleTimeoutLocked(ToastRecord r, boolean immediate) |
2 | { |
3 | Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r); |
4 | long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY); |
5 | mHandler.removeCallbacksAndMessages(r); |
6 | mHandler.sendMessageDelayed(m, delay); |
7 | } |
设置好延迟时间之后,发送一个消息MESSAGE_TIMEOUT,那我们再看看怎么处理这个消息的。
它其实辗转反侧之后,会调用到:
01 | private void cancelToastLocked( int index) { |
02 | ToastRecord record = mToastQueue.get(index); |
03 | try { |
04 | record.callback.hide(); |
05 | } catch (RemoteException e) { |
06 | …. |
07 | } |
08 | …. |
09 | if (mToastQueue.size() > 0 ) { |
10 | // Show the next one. If the callback fails, this will remove |
11 | // it from the list, so don't assume that the list hasn't changed |
12 | // after this point. |
13 | showNextToastLocked(); |
14 | } |
15 | } |
好吧,我们再来反过头看看这个TN.show()方法怎么实现的.
代码路径:
frameworks/base/core/java/android/widget/Toast.java
代码:
1 | public void handleShow() { |
2 | … |
3 | if (mView != mNextView) { |
4 | …. |
5 | mWM.addView(mView, mParams); |
6 | …. |
7 | } |
8 | } |
这也是为什么Toast其实不会依赖弹出它的Activity的原因。
最后是NotificationManagerService通知TN去消失Toast,实现都差不多
最后总结下为什么Toast要采用NotificationManagerService来管理Toast吧
1. 因为Toast是每个应用程序都会弹出的,而且位置都差不多,那么如果不统一管理的话,就会出现覆盖现象。
写的好累…