Android4.0窗口機制token分析以及activitiy, dialog, toast 窗口創建過程分析

原文http://bbs.51cto.com/thread-1072344-1.html



一  前言

在談到這個話題的時候,腦海裏面千頭萬緒,因爲它涉及到了方方面面的知識… 比如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
代碼:
1 static class W extends IWindow.Stub {
2         ….
3 }
2.    指向ActivityRecord裏面appToken的IApplicationToken對象
文件路徑:
frameworks/base/services/java/com/android/server/am/ActivityRecord.java
代碼:
1 static class Token extends IApplicationToken.Stub {
2         ….
3 }
然後它的對象定義在代碼就是:
1 final class ActivityRecord {
2         
3         final IApplicationToken.Stub appToken; // window manager token
4
5 }
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      */
5 final ArrayList<ActivityRecord> mHistory = new ArrayList<ActivityRecord>();
不過這個東西不是我們今天要講的重點,以後有機會可以分析分析。

那麼,在android窗口管理裏面究竟有哪些相關的Token呢?
如下表:                                                                                               
     
           
     
      
      
代碼路徑 類名 變量
frameworks/base/core/java/android/app/Activity.java Activity.java   IBinder mToken
frameworks/base/core/java/android/view/Window.java Window.java   IBinder mAppToken
frameworks/base/core/java/android/view/WindowManager.java WindowManager.LayoutParams   IBinder token
frameworks/base/core/java/android/view/ViewRootImpl.java ViewRootImpl   View.AttachInfo mAttacheInfo
frameworks/base/core/java/android/view/View.java View View.AttachInfo mAttacheInfo
frameworks/base/core/java/android/view/View.java   View.attachInfo IBinder mWindowToken

IBinder mPanelParentWindowToken

IWindow mWindow



下面,對上面這些token一一進行講解

1.    Activity的mToken
它的值其實就是ActivityRecord裏面的mAppToken值
Ok…還是看代碼吧,講再多,也不如來點代碼實在,從ams啓動Activity開始
代碼路徑:
frameworks/base/services/java/com/android/server/am/ActivityStack.java
代碼:
01 final boolean realStartActivityLocked(ActivityRecord r,
02             ProcessRecord app, boolean andResume, boolean checkConfig)
03             throws RemoteException {
04         ….
05         app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
06                     System.identityHashCode(r), r.info,
07                     new Configuration(mService.mConfiguration),
08                     r.compat, r.icicle, results, newIntents, !andResume,
09                     mService.isNextTransitionForward(), profileFile, profileFd,
10                     profileAutoStop);
11  
12         ….
13 }
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文件裏面
1 case LAUNCH_ACTIVITY: {
2                     ….
3                     ActivityClientRecord r = (ActivityClientRecord)msg.obj;
4  
5                     r.packageInfo = getPackageInfoNoCheck(
6                             r.activityInfo.applicationInfo, r.compatInfo);
7                     handleLaunchActivity(r, null);
8                     
9                 } break;
很明顯,它是把剛纔新建的ActivityClientRecord對象從Message裏面取出來,然後給它的一些屬性繼續賦值,再調用handleLaunchActivity(r, null)方法。
Ok….繼續看…
1 private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
2         ….
3         Activity a = performLaunchActivity(r, customIntent);
4         ….
5 }
繼續調用performLaunchActivity(r, customIntent); 這個方法返回了一個Activity對象,這個就是我們要啓動的Activity了。看看裏面做了什麼…
01 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
02         ….
03  
04         Activity activity = null;
05         try {
06             java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
07             activity = mInstrumentation.newActivity(
08                     cl, component.getClassName(), r.intent);
09             ….
10         } catch (Exception e) {
11             ….
12         }
13         …..
14  
15         if (activity != null) {
16                 Context appContext = createBaseContextForActivity(r, activity);
17                 CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
18                 Configuration config = new Configuration(mCompatConfiguration);
19                 …..
20                 activity.attach(appContext, this, getInstrumentation(), r.token,
21                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
22                         r.embeddedID, r.lastNonConfigurationInstances, config);
23         }
24  
25         ….
26     }
這段代碼,主要首先用反射機制,把我們配置好的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);
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
代碼:
01 final void attach(Context context, ActivityThread aThread,
02             Instrumentation instr, IBinder token, int ident,
03             Application application, Intent intent, ActivityInfo info,
04             CharSequence title, Activity parent, String id,
05             NonConfigurationInstances lastNonConfigurationInstances,
06             Configuration config) {
07         ….
08         mToken = token;
09         ….
10         mWindow.setWindowManager(
11                 (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
12                 mToken, mComponent.flattenToString(),
13                 (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
14         ….
15 }
這其實就是剛纔上面講的代碼啦,看看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) {
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
代碼:
01 Dialog(Context context, int theme, boolean createContextThemeWrapper) {
02         …..
03  
04         mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
05         Window w = PolicyManager.makeNewWindow(mContext);
06         mWindow = w;
07         w.setCallback(this);
08         w.setWindowManager(mWindowManager, null, null);
09         ….
10     }
從上面的代碼可以看到,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) {
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
代碼:
01 void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {      
02         CharSequence curTitle = wp.getTitle();
03         if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
04             wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
05             if (wp.token == null) {
06                 View decor = peekDecorView();
07                 if (decor != null) {
08                     wp.token = decor.getWindowToken();
09                 }
10             }
11             …..
12             }
13         } else {         
14             if (wp.token == null) {
15                 wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
16             }
17             …..
18         }
19         ….
20 }
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) {
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)方法
1 private 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
代碼:
01 void dispatchAttachedToWindow(AttachInfo info, int visibility) {
02         ….
03         final int count = mChildrenCount;
04         final View[] children = mChildren;
05         for (int i = 0; i < count; i++) {
06             final View child = children[i];
07             child.dispatchAttachedToWindow(info,
08                     visibility | (child.mViewFlags&VISIBILITY_MASK));
09         }
10     }
這裏嘛,主要就是找到這個ViewGroup的所有子視圖,然後挨個分發,調用它們的dispatchAttachedToWindow(xxx)方法。

看看View裏面怎麼實現這個dispatchAttachedToWindow(xxx)方法的吧…
1 void 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
代碼:
1 public 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
代碼:
01 public void setContentView(View view, ViewGroup.LayoutParams params) {
02         if (mContentParent == null) {
03             installDecor();
04         } else {
05             mContentParent.removeAllViews();
06         }
07         mContentParent.addView(view, params);
08         final Callback cb = getCallback();
09         if (cb != null && !isDestroyed()) {
10             cb.onContentChanged();
11         }
12     }
這裏的話,它會構造出一個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) {
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
代碼:
1 void makeVisible() {
2         if (!mWindowAdded) {
3             ViewManager wm = getWindowManager();
4             wm.addView(mDecor, getWindow().getAttributes());
5             mWindowAdded = true;
6         }
7         mDecor.setVisibility(View.VISIBLE);
8     }
這裏的話,調用wm.addView(xxx)方法,第一個參數就是上面我們設置的mDecor這個view啦!第二個參數getWindow().getAttributes(),看看怎麼來的
代碼路徑:
frameworks/base/core/java/android/view/Window.java
代碼;
1 public final WindowManager.LayoutParams getAttributes() {
2         return mWindowAttributes;
3 }
4  
5 // The current window attributes.
6     private final WindowManager.LayoutParams mWindowAttributes =
7         new WindowManager.LayoutParams();
然後看看WindowManager.LayoutParams()怎麼實現的..
代碼路徑:
/frameworks/base/core/java/android/view/WindowManager.java
代碼:
1 public LayoutParams() {
2             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
3             type = TYPE_APPLICATION;
4             format = PixelFormat.OPAQUE;
5         }
嗯,這裏設置了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);
3     }
繼續…
代碼路徑:
frameworks/base/core/java/android/view/WindowManagerGlobal.java
代碼:
01 public void addView(View view, ViewGroup.LayoutParams params,
02             Display display, Window parentWindow) {
03             ….
04             root = new ViewRootImpl(view.getContext(), display);
05  
06             view.setLayoutParams(wparams);
07  
08             if (mViews == null) {
09                 index = 1;
10                 mViews = new View[1];
11                 mRoots = new ViewRootImpl[1];
12                 mParams = new WindowManager.LayoutParams[1];
13             } else {
14                 index = mViews.length + 1;
15                 Object[] old = mViews;
16                 mViews = new View[index];
17                 System.arraycopy(old, 0, mViews, 0, index-1);
18                 old = mRoots;
19                 mRoots = new ViewRootImpl[index];
20                 System.arraycopy(old, 0, mRoots, 0, index-1);
21                 old = mParams;
22                 mParams = new WindowManager.LayoutParams[index];
23                 System.arraycopy(old, 0, mParams, 0, index-1);
24             }
25             index--;
26  
27             mViews[index] = view;
28             mRoots[index] = root;
29             mParams[index] = wparams;
30         }
31  
32         // do this last because it fires off messages to start doing things
33         try {
34             root.setView(view, wparams, panelParentView);
35         }
36         
37 }
這裏主要是創建ViewRootImple對象,一個添加到wms實現的View對應一個ViewRootImpl對象。
然後這裏有3個數組
1 mViews = new View[1];
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) {
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
代碼:
1 public Object getSystemService(String name) {
2         if (WINDOW_SERVICE.equals(name)) {
3             return mWindowManager;
4         } else if (SEARCH_SERVICE.equals(name)) {
5             ensureSearchManager();
6             return mSearchManager;
7         }
8         return super.getSystemService(name);
9     }
這裏重寫只針對兩種服務,一個是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
代碼:
01 public void show() {
02         ….
03  
04         WindowManager.LayoutParams l = mWindow.getAttributes();
05         ….
06  
07         try {
08             mWindowManager.addView(mDecor, l);
09             ….
10         } finally {
11         }
12     }
首先是取得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;
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
代碼:
1 switch (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
代碼:
01 public static Toast makeText(Context context, CharSequence text, int duration) {
02         Toast result = new Toast(context);
03  
04         LayoutInflater inflate = (LayoutInflater)
05                 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
06         View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
07         TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
08         tv.setText(text);
09         
10         result.mNextView = v;
11         result.mDuration = duration;
12  
13         return result;
14 }
這個方法構造了一個Toast,然後把要顯示的文本放到這個View裏面,然後把View,以及Toast的持續時間,都封裝到一個result對象裏面,然後返回…

2.    然後我們來看Toast.show()方法
01 public void show() {
02         if (mNextView == null) {
03             throw new RuntimeException("setView must have been called");
04         }
05  
06         INotificationManager service = getService();
07         String pkg = mContext.getPackageName();
08         TN tn = mTN;
09         tn.mNextView = mNextView;
10  
11         try {
12             service.enqueueToast(pkg, tn, mDuration);
13         } catch (RemoteException e) {
14             // Empty
15         }
16     }
這個方法首先判斷我們剛纔創建的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

繼續看…
01 private void showNextToastLocked() {
02         ToastRecord record = mToastQueue.get(0);
03         while (record != null) {
04             if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
05             try {
06                 record.callback.show();
07                 scheduleTimeoutLocked(record, false);
08                 return;
09             } catch (RemoteException e) {
10                 ….
11             }
12         }
13     }
首先呢,把record取出來,然後調用
record.callback.show();
這個callback其實就是一個TN 對象啦,就是我們從應用程序傳過來滴,不過我們暫且不看它的實現,繼續看下一行:
scheduleTimeoutLocked(record, false);

代碼:
1 private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
2     {
3         Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
4         long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
5         mHandler.removeCallbacksAndMessages(r);
6         mHandler.sendMessageDelayed(m, delay);
7     }
這裏,主要是根據我們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);
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
代碼:
1 public 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是每個應用程序都會彈出的,而且位置都差不多,那麼如果不統一管理的話,就會出現覆蓋現象。


寫的好累…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章