Android4.0窗口机制token分析以及activitiy, dialog, toast 窗口创建过程分析

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
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;
子窗口 1000~1999
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;
系统窗口 2000~2999
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;
创建不同类型的窗口,在窗口属性,也就是WindowManager.LayoutParams对象,设置不同的type值。这点很重要的。因为wms会针对不用的type值做不同的处理。


四  Android窗口功能相关的token值
Token值听起来有点像令牌之类的东西,貌似是权限的作用,其实,它在我们今天要讨论的东西里面,它就是一个Binder对象。Binder对象,应该不会陌生,是android里面实现跨进程通信的重要机制…
那么,在窗口创建这个部分里面,主要有哪些Binder呢?

1.    W对象,它是wms访问应用程序的接口
文件路径:
frameworks/base/core/java/android/view/ViewRootImpl.java
代码:
1static class W extends IWindow.Stub {
2        ….
3}
2.    指向ActivityRecord里面appToken的IApplicationToken对象
文件路径:
frameworks/base/services/java/com/android/server/am/ActivityRecord.java
代码:
1static class Token extends IApplicationToken.Stub {
2        ….
3}
然后它的对象定义在代码就是:
1final class ActivityRecord {
2        
3        final IApplicationToken.Stub appToken; // window manager token
4
5}
ActivityRecord的话是Activity在ams里面的记录缓存,也就是每启动一个Activity,都会在ams里面用一个ActivityRecord对象储存起来,一个名字叫mHistory的列表。
代码路径:
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     */
5final 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.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
代码:
01final 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}
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();
09 
10            r.token = token;
11            
12            queueOrSendMessage(H.LAUNCH_ACTIVITY, r);
13        }
这里的话,它首先是生成了一个ActivityClientRecord对象,顾名思义,就是客户端的Activity记录,然后把传入过来的ActivityRecord对象里面的属性赋给ActivityClientRecord对象,其中就包括从ActivityRecord里面来的token对象;然后发送一条LAUNCH_ACTIVITY消息。
看看这条消息做了什么….

还是在ActivityThread文件里面
1case 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;
很明显,它是把刚才新建的ActivityClientRecord对象从Message里面取出来,然后给它的一些属性继续赋值,再调用handleLaunchActivity(r, null)方法。
Ok….继续看…
1private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
2        ….
3        Activity a = performLaunchActivity(r, customIntent);
4        ….
5}
继续调用performLaunchActivity(r, customIntent); 这个方法返回了一个Activity对象,这个就是我们要启动的Activity了。看看里面做了什么…
01private 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    }
这段代码,主要首先用反射机制,把我们配置好的activity对象实例化出来,然后如果成功(activity != null),就调用activity.attch(xxxx)方法,当然不可避免的,会把我们从ams传入过来的token对象一起传输过去。
继续往下面看…
代码路径:
frameworks/base/core/java/android/app/Activity.java
代码:
01final 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}
这段代码里面做了很多初始化的操作,比如创建了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
代码:
01final 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}
这其实就是刚才上面讲的代码啦,看看mWindow.setWindowManager(xxx)这个方法吧,第2个参数是Activity的mToken对象。
那看看里面做了什么…

代码路径:
frameworks/base/core/java/android/view/Window.java
代码:
01public 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    }
嗯…把传入进来的IBinder appToken对象赋值给Window的mAppToken对象…
所以,如果Window属于某个Activity的话,它的mAppToken就是Activity的mToken对象。

那为什么Dialog的Window对象的mAppToken是null呢?来段代码分析下。
这个,得从Dialog的构造函数说起了
代码路径:
frameworks/base/core/java/android/app/Dialog.java
代码:
01Dialog(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    }
从上面的代码可以看到,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
代码:
1public void addView(View view, ViewGroup.LayoutParams params,
2            Display display, Window parentWindow) {
3        ….
4        if (parentWindow != null) {
5            parentWindow.adjustLayoutParamsForSubWindow(wparams);
6        }
7        ….
8}
这个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
代码:
01void 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}
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
代码:
1public ViewRootImpl(Context context, Display display) {
2        super();
3 
4        ….
5        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
6        …..
7 
8}
从上面代码看到,我们新建了一个mAttachInfo对象,参数
mWindowSession:  就是访问wms的Binder接口
mWindow: wms回调应用程序的Binder接口
xxxx

然后看ViewRootImpl的performTraversals(xxx)方法
1private void performTraversals() {
2        ….
3        host.dispatchAttachedToWindow(attachInfo, 0);
4        ….
5}
host对象嘛,就是一个View了,上面那个方法会调用ViewGroup. dispatchAttachedToWindow(xxx)方法。
至于为什么会调用到ViewGroup里面,可以看下View和ViewGroup的关系,这里就不多了。
那么,来看看,ViewGroup怎么实现这个方法的。
代码路径:
frameworks/base/core/java/android/view/ViewGroup.java
代码:
01void 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    }
这里嘛,主要就是找到这个ViewGroup的所有子视图,然后挨个分发,调用它们的dispatchAttachedToWindow(xxx)方法。

看看View里面怎么实现这个dispatchAttachedToWindow(xxx)方法的吧…
1void dispatchAttachedToWindow(AttachInfo info, int visibility) {
2        mAttachInfo = info;
3        ….
4}
把从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
代码:
1public void setContentView(View view) {
2        getWindow().setContentView(view);
3        
4}
这个会调用到PhoneWindow的setContentView(xxx)里面,这里用PhoneWindow,顾名思义,就是为手机设计的Window实现类,如果以后要是让android支持其他设备的话,在这里就可以为那种设备新建一个XXXWindow..
代码路径:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java
代码:
01public 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    }
这里的话,它会构造出一个mDecorView,然后把传入的view放到 mDecorView下面。也就是mDecorView是个壳子,里面包含了我们要显示的内容。
Ok…这一步的话,我们把我们想要显示的view放到了window里面的mDecorView里面。

那么继续往下面看,看哪里呢?看Activity要resume的时候,做了什么…
代码路径:
frameworks/base/core/java/android/app/ActivityThread.java
代码:
01final 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    }
首先,取得window对应的mDecorView,然后赋值给Activity的mDecor…这样,Activity的mDecorView也就包含我们想显示的内容了..

然后,再看Activity. makeVisible()方法
代码路径:
frameworks/base/core/java/android/app/Activity.java
代码:
1void makeVisible() {
2        if (!mWindowAdded) {
3            ViewManager wm = getWindowManager();
4            wm.addView(mDecor, getWindow().getAttributes());
5            mWindowAdded = true;
6        }
7        mDecor.setVisibility(View.VISIBLE);
8    }
这里的话,调用wm.addView(xxx)方法,第一个参数就是上面我们设置的mDecor这个view啦!第二个参数getWindow().getAttributes(),看看怎么来的
代码路径:
frameworks/base/core/java/android/view/Window.java
代码;
1public final WindowManager.LayoutParams getAttributes() {
2        return mWindowAttributes;
3}
4 
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
代码:
1public LayoutParams() {
2            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
3            type = TYPE_APPLICATION;
4            format = PixelFormat.OPAQUE;
5        }
嗯,这里设置了type属性,是个应用窗口的属性啦~~~

回到开始,我们来看wm.addView(xxx)怎么实现的..
代码路径:
frameworks/base/core/java/android/view/WindowManagerImpl.java
代码:
1public void addView(View view, ViewGroup.LayoutParams params) {
2        mGlobal.addView(view, params, mDisplay, mParentWindow);
3    }
继续…
代码路径:
frameworks/base/core/java/android/view/WindowManagerGlobal.java
代码:
01public 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}
这里主要是创建ViewRootImple对象,一个添加到wms实现的View对应一个ViewRootImpl对象。
然后这里有3个数组
1mViews = new View[1];
2mRoots = new ViewRootImpl[1];
3mParams = 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
代码:
01Dialog(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对象缓存起来,这个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
代码:
1public 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    }
这里重写只针对两种服务,一个是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
代码:
01public void show() {
02        ….
03 
04        WindowManager.LayoutParams l = mWindow.getAttributes();
05        ….
06 
07        try {
08            mWindowManager.addView(mDecor, l);
09            ….
10        } finally {
11        }
12    }
首先是取得mWindow.getAttributes();在上面已经讲过,它的实际是:
代码路径:
frameworks/base/core/java/android/view/WindowManager.java
代码:
1public LayoutParams() {
2            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
3            type = TYPE_APPLICATION;
4            format = PixelFormat.OPAQUE;
5        }
这个方法的第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
代码:
1switch (type) {
2            case TYPE_TOAST:               
3            case TYPE_INPUT_METHOD:
4            case TYPE_WALLPAPER:
5            
6            break;
这三种类型对应的分别是Toas,输入法,墙纸

接下来看看,Toast是怎么实现的呢?
1.    首先来看看Toast的makeText(xxx)方法
代码路径:
frameworks/base/core/java/android/widget/Toast.java
代码:
01public 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}
这个方法构造了一个Toast,然后把要显示的文本放到这个View里面,然后把View,以及Toast的持续时间,都封装到一个result对象里面,然后返回…

2.    然后我们来看Toast.show()方法
01public 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    }
这个方法首先判断我们刚才创建的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
代码:
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    }
这里主要是新建一个ToastRecord对象record,然后把这个对象放置到一个mToastQueue队列里面..
最后,调用showNextToastLocked()方法,准备弹出Toast

继续看…
01private 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取出来,然后调用
record.callback.show();
这个callback其实就是一个TN 对象啦,就是我们从应用程序传过来滴,不过我们暂且不看它的实现,继续看下一行:
scheduleTimeoutLocked(record, false);

代码:
1private 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    }
这里,主要是根据我们toast设置的时间长短(Toast.length_show/Toast.length_long)设置一个延迟时间,如果是short的话,就延迟2s,如果是long的话,就延迟3.5s,这也可以看出,toast的持续时间是多少秒。
设置好延迟时间之后,发送一个消息MESSAGE_TIMEOUT,那我们再看看怎么处理这个消息的。
它其实辗转反侧之后,会调用到:
01private 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    }
首先会调用callback.hide()方法,也就是到了指定时间,通知客户端去取消toast,然后再show下一个toast

好吧,我们再来反过头看看这个TN.show()方法怎么实现的.

代码路径:
frameworks/base/core/java/android/widget/Toast.java
代码:
1public void handleShow() {
2            
3            if (mView != mNextView) {
4                ….
5                mWM.addView(mView, mParams);
6                ….
7            }
8        }
这里辗转反侧之后,会调用这里,也是调用mWM.addView(xxx)方法来向wms请求添加view。不过由于Toast发送到wms的参数是没有Token值。不过没关系wms不会检查Toast的token值。
这也是为什么Toast其实不会依赖弹出它的Activity的原因。

最后是NotificationManagerService通知TN去消失Toast,实现都差不多

最后总结下为什么Toast要采用NotificationManagerService来管理Toast吧
1.    因为Toast是每个应用程序都会弹出的,而且位置都差不多,那么如果不统一管理的话,就会出现覆盖现象。


写的好累…


  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以使用 Android 设备中的 adb 工具来查看当前显示的所有窗口及它们的层级关系。以下是相关命令: 1. 首先,确保你已经将 Android 设备连接到电脑,并且已经打开了开发者选项和 USB 调试模式。 2. 打开命令行界面,输入以下命令: ``` adb shell dumpsys window windows ``` 3. 执行该命令后,会输出当前显示的所有窗口及它们的层级关系,包括窗口的名称、ID、类型、位置、大小、可见性等信息。 例如: ``` Window #1xxx8 ApplicationWindowToken{1xx9f1 token=Token{5xxa3 ActivityRecord{1xx6f u0 com.example.app/.MainActivity t1}}} Window #2xxx8 com.android.internal.policy.PhoneWindow$DecorView{xx0d0e I.E...... R......D 0,0-1920,1080} Window #3xxx8 android.widget.LinearLayout{xx0d0f V.E...... ......I. 0,0-1920,1080} Window #4xxx8 android.widget.FrameLayout{xx0d10 V.E...... ......I. 0,72-1920,1080} Window #5xxx8 android.view.ViewStub{xx0d11 G.E...... ......I. 0,72-1920,1080 #xxxxx} Window #6xxx8 android.widget.LinearLayout{xx0d12 V.E...... ......I. 0,72-1920,1008} Window #7xxx8 android.widget.TextView{xx0d13 V.ED..... ......I. 0,0-1920,936 #xxxxx} Window #8xxx8 android.widget.ListView{xx0d14 VFED.V.. ......I. 0,936-1920,1008 #xxxxx} ``` 其中,每一行代表一个窗口,行首的数字为该窗口的唯一标识符,后面是该窗口的属性信息。 注意:这个命令需要在具有 root 权限的设备上运行。如果你的设备没有 root 权限,则只会输出当前应用的窗口信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值