最近一直在研究View
的繪製相關的機制,發現需要補充一下Android View Architecture的相關知識,所以就特地研究了一下這方面的程式碼,寫成本篇文章
為了節約你的時間,本篇文章內容大致如下:
Activity
,DecorView
,PhoneWindow
和ViewRoot
的作用和相關關係
Android View Architecture
先來幾張圖,大致展現一下Android 檢視架構的大概。
感謝網友提醒,泛化和實現這兩種關係的箭頭畫反啦。以後要仔細學一遍UML了,平時經常畫,如果有錯誤可真是鬧笑話啊。
Activity和Window
總所周知,Activity並不負責檢視控制,它只是控制生命週期和處理事件,真正控制檢視的是Window
。一個Activity包含了一個Window,Window才是真正代表一個視窗,也就是說Activity可以沒有Window,那就相當於是Service了。在ActivityThread
中也有控制Service
的相關函式或許正好印證了這一點。
Activity
和Window
的第一次邂逅是在ActivityThread
呼叫Activity
的attach()
函式時。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//[window]:通過PolicyManager建立window,實現callback函式,所以,當window接收到 //外界狀態改變時,會呼叫activity的方法, final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor) { .... mWindow = PolicyManager.makeNewWindow(this); //當window接收系統傳送給它的IO輸入事件時,例如鍵盤和觸控式螢幕事件,就可以轉發給相應的Activity mWindow.setCallback(this); ..... //設定本地視窗管理器 mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); ..... } |
在attach()
中,新建一個Window
例項作為自己的成員變數,它的型別為PhoneWindow
,這是抽象類Window
的一個子類。然後設定mWindow
的WindowManager
。
Window,Activity和DecorView
DecorView
是FrameLayout
的子類,它可以被認為是Android檢視樹的根節點檢視。DecorView
作為頂級View,一般情況下它內部包含一個豎直方向的LinearLayout
,在這個LinearLayout裡面有上下兩個部分(具體情況和Android版本及主體有關),上面的是標題欄,下面的是內容欄。在Activity中通過setContentView所設定的佈局檔案其實就是被加到內容欄之中的,而內容欄的id是content,在程式碼中可以通過ViewGroup content = (ViewGroup)findViewById(R.android.id.content)來得到content對應的layout。
Window
中有幾個檢視相關的比較重要的成員變數如下所示:
mDecor
:DecorView
的例項,標示Window
內部的頂級檢視mContentParent
:setContetView
所設定的佈局檔案就加到這個檢視中mContentRoot
:是DecorView
的唯一子檢視,內部包含mContentParent
,標題欄和狀態列。
Activity中不僅持有一個Window
例項,還有一個型別為View
的mDecor
例項。這個例項和Window
中的mDecor
例項有什麼關係呢?它又是什麼時候被建立的呢?
二者其實指向同一個物件,這個物件是在Activity
呼叫setContentView
時建立的。我們都知道Activity
的setContentView
實際上是呼叫了Window
的setContentView
方法。
1 2 3 4 5 6 7 8 9 10 11 12 |
@Override public void setContentView(int layoutResID) { if (mContentParent == null) { //[window]如何沒有DecorView,那麼就新建一個 installDecor(); //[window] } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } .... //[window]第二步,將layout新增到mContentParent mLayoutInflater.inflate(layoutResID, mContentParent); ..... } |
程式碼很清楚的顯示了佈局檔案的檢視是新增到mContentParent
中,而且Window
通過installDecor
來新建DecorView
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
//[window]建立一個decorView private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); //直接new出一個DecorView返回 .... } if (mContentParent == null) { //[window] 這一步也是很重要的. mContentParent = generateLayout(mDecor); //mContentParent是setContentVIew的關鍵啊 ..... } .... } protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. ....... //[window] 根據不同的style生成不同的decorview啊 View in = mLayoutInflater.inflate(layoutResource, null); // 加入到deco中,所以應該是其第一個child decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mContentRoot = (ViewGroup) in; //給DecorView的第一個child是mContentView // 這是獲得所謂的content ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); } ..... return contentParent; } |
從上述的程式碼中,我們可以清楚的看到mDecor
和mContentParent
和mContentRoot
的關係。
那麼,Activity
中的mDecor
是何時被賦值的?我們如何確定它和Widnow
中的mDecor
指向同一個物件呢?我們可以檢視ActivityThread
的handleResumeActivity
函式,它負責處理Activity
的resume
階段。在這個函式中,Android直接將Window
中的DecorView
例項賦值給Activity
。
1 2 3 4 5 6 7 |
final Activity a = r.activity; r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; |
Window,DecorView 和 ViewRoot
ViewRoot
對應ViewRootImpl
類,它是連線WindowManagerService
和DecorView
的紐帶,View的三大流程(測量(measure),佈局(layout),繪製(draw))均通過ViewRoot來完成。ViewRoot
並不屬於View樹的一份子。從原始碼實現上來看,它既非View的子類,也非View的父類,但是,它實現了ViewParent
介面,這讓它可以作為View
的名義上的父檢視。RootView
繼承了Handler
類,可以接收事件並分發,Android的所有觸屏事件、按鍵事件、介面重新整理等事件都是通過ViewRoot進行分發的。ViewRoot可以被理解為“View樹的管理者”——它有一個mView成員變數,它指向的物件和上文中Window
和Activity
的mDecor
指向的物件是同一個物件。
我們來先看一下ViewRoot
的建立過程。由於ViewRoot
作為WindowMangerService
和DecorView
的紐帶,只有在WindowManager
將持有DecorView
的Window
新增進視窗管理器才建立。我們可以檢視WindowMangerGlobal
中的addView
函式。對WindowManager
不太熟悉的同學可以參考《Window和WindowManager解析》
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { // 建立ViewRootImpl,然後將下述物件新增到列表中 .... root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); .... try { // 新增啦!!!!!!!!這是通過ViewRootImpl的setView來完成,這個View就是DecorView例項 root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { .... } .... } |
那麼,Window
是什麼時候被新增到WindowManager
中的呢?我們回到ActivityThread
的handleResumeActivity
函式。我們都知道Activity的resume階段就是要顯示到螢幕上的階段,在Activity也就是DecorView
將要顯示到螢幕時,系統才會呼叫addView
方法。
我們在handleResumeActivity
函式中找到了下面一段程式碼,它呼叫了Activity
的makeVisible()
函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// ActivityThread r.activity.makeVisible(); //Activity //[windows] DecorView正式新增並顯示 void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); } |
我們通過原始碼發現,正式在makeVisible
函式中,系統進行了Window
的新增。
引用
http://wiki.jikexueyuan.com/project/deep-android-v1/surface.html
http://blog.csdn.net/guxiao1201/article/details/41744107
http://forlan.iteye.com/blog/2269381