原文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
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; |
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
代碼:
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; |
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; |
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
代碼:
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 ) { |
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是每個應用程序都會彈出的,而且位置都差不多,那麼如果不統一管理的話,就會出現覆蓋現象。
寫的好累…