Android4.0窗口机制和创建过程分析
来源:互联网 发布:刘双军网络二胡教学15 编辑:程序博客网 时间:2024/05/01 18:35
原文链接
一 前言
在谈到这个话题的时候,脑海里面千头万绪,因为它涉及到了方方面面的知识… 比如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呢?
如下表:
代码路径类名变量frameworks/base/core/java/android/app/Activity.java
Activity.java
IBinder mTokenframeworks/base/core/java/android/view/Window.java
Window.java
IBinder mAppTokenframeworks/base/core/java/android/view/WindowManager.java
WindowManager.LayoutParams IBinder tokenframeworks/base/core/java/android/view/ViewRootImpl.java
ViewRootImpl
View.AttachInfo mAttacheInfoframeworks/base/core/java/android/view/View.java
View
View.AttachInfo mAttacheInfoframeworks/base/core/java/android/view/View.java
View.attachInfoIBinder mWindowToken
IBinder mPanelParentWindowToken
IWindow mWindow
下面,对上面这些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是每个应用程序都会弹出的,而且位置都差不多,那么如果不统一管理的话,就会出现覆盖现象。
0 0
- Android4.0窗口机制和创建过程分析
- Android4.0窗口机制和创建过程分析
- Android4.0窗口机制和创建过程分析
- Android4.0窗口机制和创建过程分析
- 系统入门(11):Android4.0窗口机制和创建过程分析
- Android4.0窗口机制token分析以及activitiy, dialog, toast 窗口创建过程分析
- Android4.0窗口机制token分析以及activitiy, dialog, toast 窗口创建过程分析
- Android4.4 Framework分析——Activity窗口的创建过程(一)
- 窗口创建和销毁过程
- android4.0.1 webkit dom tree 创建过程分析
- android4.0.1 webkit render tree创建过程分析
- Android4.2.2多媒体架构MediaPlay的创建过程分析(一)
- Android4.2.2多媒体架构MediaPlay的创建过程分析(一)
- Android4.4 Framework分析——startService的创建过程
- Android4.2.2多媒体架构MediaPlay的创建过程分析(一)
- Android4.0(Phone)来电过程分析
- Android4.2.2 SurfaceFlinger之Layer和Bufferqueue的创建过程
- Android4.4 Camera callback注册和回调过程分析
- Jquery中易混淆的选择器
- Android应用权限管理总结
- 时间to_date,层级查询 --工作备忘2016/1/8
- 图的遍历 DFS(深度优先),BFS(广度优先)
- gradle老问题
- Android4.0窗口机制和创建过程分析
- Android实现静默安装与卸载
- Hadoop经典案例Spark实现(六)——求最大的K个值并排序
- java中的Cipher类
- Parcelable 复杂对象,对象列表等
- tcpdump filters for HTTP GET & HTTP POST
- 在iPhone5以上机器显示iPhone4尺寸的launchImage
- 算法洗脑系列(8篇)——第二篇 递归思想
- <九度 OJ>题目1526:朋友圈