一 前言
在谈到这个话题的时候,脑海里面千头万绪,因为它涉及到了方方面面的知识… 比如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 mHistory = new ArrayList();
|
不过这个东西不是我们今天要讲的重点,以后有机会可以分析分析。
那么,在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 pendingResults,
|
06
|
List 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是每个应用程序都会弹出的,而且位置都差不多,那么如果不统一管理的话,就会出现覆盖现象。
写的好累…