一 前言
在谈到这个话题的时候,脑海里面千头万绪,因为它涉及到了方方面面的知识… 比如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
02 |
* Start of window types that represent normal application windows. |
04 |
public static final int FIRST_APPLICATION_WINDOW = 1 ; |
09 |
* End of types of application windows. |
11 |
public static final int LAST_APPLICATION_WINDOW = 99 ; |
子窗口 1000~1999
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. |
07 |
public static final int FIRST_SUB_WINDOW = 1000 ; |
12 |
* End of types of sub-windows. |
14 |
public static final int LAST_SUB_WINDOW = 1999 ; |
系统窗口 2000~2999
02 |
* Start of system-specific window types. These are not normally |
03 |
* created by applications. |
05 |
public static final int FIRST_SYSTEM_WINDOW = 2000 ; |
11 |
* End of types of system windows. |
13 |
public static final int LAST_SYSTEM_WINDOW = 2999 ; |
创建不同类型的窗口,在窗口属性,也就是WindowManager.LayoutParams对象,设置不同的type值。这点很重要的。因为wms会针对不用的type值做不同的处理。
四 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. 指向ActivityRecord里面appToken的IApplicationToken对象
文件路径:
frameworks/base/services/java/com/android/server/am/ActivityRecord.java
代码:
1 |
static class Token extends IApplicationToken.Stub { |
然后它的对象定义在代码就是:
1 |
final class ActivityRecord { |
3 |
final IApplicationToken.Stub appToken; // window manager token |
ActivityRecord的话是Activity在ams里面的记录缓存,也就是每启动一个Activity,都会在ams里面用一个ActivityRecord对象储存起来,一个名字叫mHistory的列表。
代码路径:
frameworks/base/services/java/com/android/server/am/ActivityStack.java
代码:
2 |
* The back history of all previous (and possibly still |
3 |
* running) activities. It contains HistoryRecord objects. |
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 { |
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, |
app代表的是ProcessRecord对象,表示一个进程,其实就是客户端进程啦。它的thread对象是一个Binder对象,用来ams和客户端进程通信用的。那么,上面那行代码的意思就是通过它的Binder对象,向目标进程发送一个启动Activity的命令,同时把ActivityRecrod的appToken一起传输了过去。
那么,我们来看,这个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(); |
12 |
queueOrSendMessage(H.LAUNCH_ACTIVITY, r); |
这里的话,它首先是生成了一个ActivityClientRecord对象,顾名思义,就是客户端的Activity记录,然后把传入过来的ActivityRecord对象里面的属性赋给ActivityClientRecord对象,其中就包括从ActivityRecord里面来的token对象;然后发送一条LAUNCH_ACTIVITY消息。
看看这条消息做了什么….
还是在ActivityThread文件里面
1 |
case LAUNCH_ACTIVITY: { |
3 |
ActivityClientRecord r = (ActivityClientRecord)msg.obj; |
5 |
r.packageInfo = getPackageInfoNoCheck( |
6 |
r.activityInfo.applicationInfo, r.compatInfo); |
7 |
handleLaunchActivity(r, null ); |
很明显,它是把刚才新建的ActivityClientRecord对象从Message里面取出来,然后给它的一些属性继续赋值,再调用handleLaunchActivity(r, null)方法。
Ok….继续看…
1 |
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { |
3 |
Activity a = performLaunchActivity(r, customIntent); |
继续调用performLaunchActivity(r, customIntent); 这个方法返回了一个Activity对象,这个就是我们要启动的Activity了。看看里面做了什么…
01 |
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { |
04 |
Activity activity = null ; |
06 |
java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); |
07 |
activity = mInstrumentation.newActivity( |
08 |
cl, component.getClassName(), r.intent); |
10 |
} catch (Exception e) { |
15 |
if (activity != null ) { |
16 |
Context appContext = createBaseContextForActivity(r, activity); |
17 |
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); |
18 |
Configuration config = new Configuration(mCompatConfiguration); |
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); |
这段代码,主要首先用反射机制,把我们配置好的activity对象实例化出来,然后如果成功(activity != null),就调用activity.attch(xxxx)方法,当然不可避免的,会把我们从ams传入过来的token对象一起传输过去。
继续往下面看…
代码路径:
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); |
09 |
mFragments.attachActivity( this , mContainer, null ); |
11 |
mWindow = PolicyManager.makeNewWindow( this ); |
12 |
mWindow.setCallback( this ); |
14 |
mUiThread = Thread.currentThread(); |
16 |
mMainThread = aThread; |
17 |
mInstrumentation = instr; |
20 |
mWindow.setWindowManager( |
21 |
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE), |
22 |
mToken, mComponent.flattenToString(), |
23 |
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0 ); |
这段代码里面做了很多初始化的操作,比如创建了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
代码:
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) { |
10 |
mWindow.setWindowManager( |
11 |
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE), |
12 |
mToken, mComponent.flattenToString(), |
13 |
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0 ); |
这其实就是刚才上面讲的代码啦,看看mWindow.setWindowManager(xxx)这个方法吧,第2个参数是Activity的mToken对象。
那看看里面做了什么…
代码路径:
frameworks/base/core/java/android/view/Window.java
代码:
01 |
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, |
02 |
boolean hardwareAccelerated) { |
05 |
mHardwareAccelerated = hardwareAccelerated |
06 |
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false ); |
08 |
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); |
10 |
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager( this ); |
嗯…把传入进来的IBinder appToken对象赋值给Window的mAppToken对象…
所以,如果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) { |
04 |
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); |
05 |
Window w = PolicyManager.makeNewWindow(mContext); |
08 |
w.setWindowManager(mWindowManager, null , null ); |
从上面的代码可以看到,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
代码:
1 |
public void addView(View view, ViewGroup.LayoutParams params, |
2 |
Display display, Window parentWindow) { |
4 |
if (parentWindow != null ) { |
5 |
parentWindow.adjustLayoutParamsForSubWindow(wparams); |
这个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
代码:
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(); |
08 |
wp.token = decor.getWindowToken(); |
14 |
if (wp.token == null ) { |
15 |
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; |
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
代码:
1 |
public ViewRootImpl(Context context, Display display) { |
5 |
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this , mHandler, this ); |
从上面代码看到,我们新建了一个mAttachInfo对象,参数
mWindowSession: 就是访问wms的Binder接口
mWindow: wms回调应用程序的Binder接口
xxxx
然后看ViewRootImpl的performTraversals(xxx)方法
1 |
private void performTraversals() { |
3 |
host.dispatchAttachedToWindow(attachInfo, 0 ); |
host对象嘛,就是一个View了,上面那个方法会调用ViewGroup. dispatchAttachedToWindow(xxx)方法。
至于为什么会调用到ViewGroup里面,可以看下View和ViewGroup的关系,这里就不多了。
那么,来看看,ViewGroup怎么实现这个方法的。
代码路径:
frameworks/base/core/java/android/view/ViewGroup.java
代码:
01 |
void dispatchAttachedToWindow(AttachInfo info, int visibility) { |
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)); |
这里嘛,主要就是找到这个ViewGroup的所有子视图,然后挨个分发,调用它们的dispatchAttachedToWindow(xxx)方法。
看看View里面怎么实现这个dispatchAttachedToWindow(xxx)方法的吧…
1 |
void dispatchAttachedToWindow(AttachInfo info, int visibility) { |
把从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
代码:
1 |
public void setContentView(View view) { |
2 |
getWindow().setContentView(view); |
这个会调用到PhoneWindow的setContentView(xxx)里面,这里用PhoneWindow,顾名思义,就是为手机设计的Window实现类,如果以后要是让android支持其他设备的话,在这里就可以为那种设备新建一个XXXWindow..
代码路径:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java
代码:
01 |
public void setContentView(View view, ViewGroup.LayoutParams params) { |
02 |
if (mContentParent == null ) { |
05 |
mContentParent.removeAllViews(); |
07 |
mContentParent.addView(view, params); |
08 |
final Callback cb = getCallback(); |
09 |
if (cb != null && !isDestroyed()) { |
10 |
cb.onContentChanged(); |
这里的话,它会构造出一个mDecorView,然后把传入的view放到 mDecorView下面。也就是mDecorView是个壳子,里面包含了我们要显示的内容。
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) { |
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(); |
首先,取得window对应的mDecorView,然后赋值给Activity的mDecor…这样,Activity的mDecorView也就包含我们想显示的内容了..
然后,再看Activity. makeVisible()方法
代码路径:
frameworks/base/core/java/android/app/Activity.java
代码:
3 |
ViewManager wm = getWindowManager(); |
4 |
wm.addView(mDecor, getWindow().getAttributes()); |
7 |
mDecor.setVisibility(View.VISIBLE); |
这里的话,调用wm.addView(xxx)方法,第一个参数就是上面我们设置的mDecor这个view啦!第二个参数getWindow().getAttributes(),看看怎么来的
代码路径:
frameworks/base/core/java/android/view/Window.java
代码;
1 |
public final WindowManager.LayoutParams getAttributes() { |
2 |
return mWindowAttributes; |
5 |
// The current window attributes. |
6 |
private final WindowManager.LayoutParams mWindowAttributes = |
7 |
new WindowManager.LayoutParams(); |
然后看看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; |
嗯,这里设置了type属性,是个应用窗口的属性啦~~~
回到开始,我们来看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); |
继续…
代码路径:
frameworks/base/core/java/android/view/WindowManagerGlobal.java
代码:
01 |
public void addView(View view, ViewGroup.LayoutParams params, |
02 |
Display display, Window parentWindow) { |
04 |
root = new ViewRootImpl(view.getContext(), display); |
06 |
view.setLayoutParams(wparams); |
11 |
mRoots = new ViewRootImpl[ 1 ]; |
12 |
mParams = new WindowManager.LayoutParams[ 1 ]; |
14 |
index = mViews.length + 1 ; |
15 |
Object[] old = mViews; |
16 |
mViews = new View[index]; |
17 |
System.arraycopy(old, 0 , mViews, 0 , index- 1 ); |
19 |
mRoots = new ViewRootImpl[index]; |
20 |
System.arraycopy(old, 0 , mRoots, 0 , index- 1 ); |
22 |
mParams = new WindowManager.LayoutParams[index]; |
23 |
System.arraycopy(old, 0 , mParams, 0 , index- 1 ); |
29 |
mParams[index] = wparams; |
32 |
// do this last because it fires off messages to start doing things |
34 |
root.setView(view, wparams, panelParentView); |
这里主要是创建ViewRootImple对象,一个添加到wms实现的View对应一个ViewRootImpl对象。
然后这里有3个数组
2 |
mRoots = new ViewRootImpl[ 1 ]; |
3 |
mParams = new WindowManager.LayoutParams[ 1 ]; |
它保存了当前应用程序添加的所有的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
代码:
01 |
Dialog(Context context, int theme, boolean createContextThemeWrapper) { |
02 |
if (createContextThemeWrapper) { |
04 |
TypedValue outValue = new TypedValue(); |
05 |
context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme, |
07 |
theme = outValue.resourceId; |
09 |
mContext = new ContextThemeWrapper(context, theme); |
14 |
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); |
15 |
Window w = PolicyManager.makeNewWindow(mContext); |
18 |
w.setWindowManager(mWindowManager, null , null ); |
首先,它把外部传入的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
代码:
1 |
public Object getSystemService(String name) { |
2 |
if (WINDOW_SERVICE.equals(name)) { |
4 |
} else if (SEARCH_SERVICE.equals(name)) { |
8 |
return super .getSystemService(name); |
这里重写只针对两种服务,一个是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
代码:
04 |
WindowManager.LayoutParams l = mWindow.getAttributes(); |
08 |
mWindowManager.addView(mDecor, l); |
首先是取得mWindow.getAttributes();在上面已经讲过,它的实际是:
代码路径:
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; |
这个方法的第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
代码:
3 |
case TYPE_INPUT_METHOD: |
这三种类型对应的分别是Toas,输入法,墙纸
接下来看看,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); |
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); |
11 |
result.mDuration = duration; |
这个方法构造了一个Toast,然后把要显示的文本放到这个View里面,然后把View,以及Toast的持续时间,都封装到一个result对象里面,然后返回…
2. 然后我们来看Toast.show()方法
02 |
if (mNextView == null ) { |
03 |
throw new RuntimeException( "setView must have been called" ); |
06 |
INotificationManager service = getService(); |
07 |
String pkg = mContext.getPackageName(); |
09 |
tn.mNextView = mNextView; |
12 |
service.enqueueToast(pkg, tn, mDuration); |
13 |
} catch (RemoteException e) { |
这个方法首先判断我们刚才创建的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
代码:
02 |
// ============================================================================ |
03 |
public void enqueueToast(String pkg, ITransientNotification callback, int duration) |
07 |
synchronized (mToastQueue) { |
08 |
int callingPid = Binder.getCallingPid(); |
09 |
long callingId = Binder.clearCallingIdentity(); |
12 |
record = new ToastRecord(callingPid, pkg, callback, duration); |
13 |
mToastQueue.add(record); |
14 |
index = mToastQueue.size() - 1 ; |
15 |
keepProcessAliveLocked(callingPid); |
19 |
showNextToastLocked(); |
这里主要是新建一个ToastRecord对象record,然后把这个对象放置到一个mToastQueue队列里面..
最后,调用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); |
06 |
record.callback.show(); |
07 |
scheduleTimeoutLocked(record, false ); |
09 |
} catch (RemoteException e) { |
首先呢,把record取出来,然后调用
record.callback.show();
这个callback其实就是一个TN 对象啦,就是我们从应用程序传过来滴,不过我们暂且不看它的实现,继续看下一行:
scheduleTimeoutLocked(record, false);
代码:
1 |
private void scheduleTimeoutLocked(ToastRecord r, boolean immediate) |
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); |
这里,主要是根据我们toast设置的时间长短(Toast.length_show/Toast.length_long)设置一个延迟时间,如果是short的话,就延迟2s,如果是long的话,就延迟3.5s,这也可以看出,toast的持续时间是多少秒。
设置好延迟时间之后,发送一个消息MESSAGE_TIMEOUT,那我们再看看怎么处理这个消息的。
它其实辗转反侧之后,会调用到:
01 |
private void cancelToastLocked( int index) { |
02 |
ToastRecord record = mToastQueue.get(index); |
04 |
record.callback.hide(); |
05 |
} catch (RemoteException e) { |
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 |
13 |
showNextToastLocked(); |
首先会调用callback.hide()方法,也就是到了指定时间,通知客户端去取消toast,然后再show下一个toast
好吧,我们再来反过头看看这个TN.show()方法怎么实现的.
代码路径:
frameworks/base/core/java/android/widget/Toast.java
代码:
1 |
public void handleShow() { |
3 |
if (mView != mNextView) { |
5 |
mWM.addView(mView, mParams); |
这里辗转反侧之后,会调用这里,也是调用mWM.addView(xxx)方法来向wms请求添加view。不过由于Toast发送到wms的参数是没有Token值。不过没关系wms不会检查Toast的token值。
这也是为什么Toast其实不会依赖弹出它的Activity的原因。
最后是NotificationManagerService通知TN去消失Toast,实现都差不多
最后总结下为什么Toast要采用NotificationManagerService来管理Toast吧
1. 因为Toast是每个应用程序都会弹出的,而且位置都差不多,那么如果不统一管理的话,就会出现覆盖现象。
时间: 2024-10-04 13:04:38