作者簡介? 原創微信公眾號郭霖 WeChat ID: guolin_blog
本篇來自程序媛fanfan_story的投稿,透過Android6.0源碼,分析了Activity加載View的過程,希望大家喜歡。
fanfan_story的博客地址:
http://blog.csdn.net/zrf1335348191
認識Activity的布局
對于研究布局這種東西,必須要掌握一些視圖工具,在這里推薦一個sdk查看視圖的工具sdk\tools\hierarchyviewer,隨意找一個界面去查看 activity 的 view視圖:
在這個 activity 界面中我把導航欄給隱藏了,所以不存在導航欄,根據這張圖的話大致可以看到一個 activity 的布局,再結合對?setContentView 的研究,可以總結出 activity 的布局圖如下:
從這張 activity 的布局圖可以看到:一個 activity 對應一個應用窗口 mWindow,應用窗口 mWindow 包括 activity 的 頂級view是 mDecorView,mDecorView 包括 狀態欄statusbar和 導航欄navigationbar 以及要加載 activity 布局的view一一mDecorContentParent,該 view 又包括一個 標題欄titlebar 和 activity的內容布局 contentparent。在 contentParent 中就是該 activity 的 view樹。
1. mWindow:Window對象,Window是一個抽象類,是 activity 的頂層外觀和行為的代理。會往 windowmanage 中添加該類的一個實例作為 頂層view。window 提供基本的UI代理,比如背景啊,標題區域啊,按鍵處理啊等等,Window 只有一個實現類 PhoneWindow,所以 mWindow對象 實際是 PhoneWindow對象。當啟動一個activity的過程中會初始化一個屬于 Phonewindow 的 window對象。Phonewindow對象 的創建在activity 的 attach方法 中。
2. mDecor:DecorView對象,繼承自 framelayout,是 window窗口 的 頂級view,包含 window 的裝飾。類的定義位于 PhoneWindow.java 中
3. mDecorContentParent:DecorContentParent對象,實現類是 ActionbarOverlayLayout,屬于 activity 布局的 最外層view,包括標題欄和activity的內容布局
4. mContentParent:activity 的內容布局,繼承自 ViewGroup,用來加載存放 activity 的 view樹,如果沒有標題欄,那么 mContentparent 的大小會和 mDecorContentParent 相等,以此類推
5. 導航欄:statusbar,對應的 id 為 statusBarBackground,在 PhoneWindow 中會加載,當 window屬性 發生改變時會刷新導航欄。但不論是導航欄和狀態欄,從這個id也可以看出,PhoneWindow 只是加載他們的 background,即相當于只加載一個view的占位,先告訴應用窗口,系統窗口要求將狀態欄和導航欄布局在這里,你不要占用,但此時不會加載導航欄和狀態欄的view,只是繪制背景而已。
6. 狀態欄:navigationbar,對應的 id 為 navigationBarBackground,在 PhoneWindow 中會加載,當 window屬性 發生改變時會刷新狀態欄
7. 標題欄:titlebar,對于導航欄,狀態欄和標題欄的存在與否,與 window 的屬性特征有關,在加載 view 時所以會去判斷 window 的屬性特征,進而決定是否要加載這三者。
對 activity 的布局大致有個了解之后,就開始去分析 activity 啟動后加載view的流程
Activity加載View布局
對于 activity 的布局的加載大致分為兩部分,一部分是加載view,另一部分是將view綁定到應用窗口Window。其中這兩個步驟中將 view 綁定到 window 是在 啟動activity時 完成的操作,是將 mDecor 綁定到 window。然后再往 mDecor 中添加 各種view。對于 activity的啟動過程 留待以后進行分析,現在分析加載view一一始于 Activity.Java?的setContentView方法,看一下加載view的流程。
可以看到代碼流程很簡單,從 Activity.java 的 setContentView方法 進入,到 PhonewWindow.java 的 setContentView方法 進行一系列處理,接下來進入代碼進行分析
Activity.java 的 setContentView 方法
代碼路徑\Android\frameworks\base\core\java\android\app
源碼中對該方法的解釋是,從一個 layout 文件中取出 view 設置成 activity 的 content,該資源文件會被填充,并遍歷文件中的 所有view 添加到 activity。意思就是填充一個資源文件,加載view。做了兩件事兒:
一是 getWindow 獲取到 Window對象,然后去調用 Window 的 setContentView方法。
二是 initWindowDecorActionbar(),創建 actionbar對象,填充 mDecor 下的 actionbarView,并把 view 加載上去(博主猜測是在 Window 的 setContentView方法 中只是填充一個 actionbar 的占位,然后 initWindowDecorActionbar() 完成 view 的加載)
重點研究第一步:getWindow().setContentView方法。
首先一個問題,為什么我要說 getWindow.setContentView 調用的是 PhoneWindow 中的setContentView方法?
解疑:查看getWindow方法:
publicWindowgetWindow() {
returnmWindow;}
返回的是 activity 的 mWindow對象,對于 mWindow對象 的創建也是在 Activity.java 中的 attach方法 中:
同時,進入到 Window.java 中也可以看到這一點:
源碼中對于 Window類 的說明是:
Window 是一個抽象類,是最頂層的窗口的外觀和行為的代理,window 的實例應該被作為最頂層的UI添加到 WindowManage?中。Window 提供了基本的ui,比如背景,標題區域,默認的按鍵處理過程等等。Window 只有一個唯一的實現類 PhoneWindow,當需要 Window對象 時需要去初始化 PhoneWindow。
至此,對于 Activity 中的 mWindow對象 大致有了一個清晰的認識了:他是個 PhoneWindow對象,Window.java 中方法的實現在?PhoneWindow.java 中
PhoneWindow.java中的setContentView方法
代碼路徑\android\frameworks\base\core\java\com\android\internal\policy
先來總結一下代碼的流:
在新啟動一個 activity 時 mContentParent 還未綁定id,此時 mContentParent 為null。從代碼流程圖中可以看出 setContentView 做了三件事:
installDecor 實例化 DecorView對象 和 mContentParent對象
填充 layout 文件
通知 activity 布局已經改變
為什么說是通知 activity 布局已經改變呢?這是因為在 Activity.java 的 attach方法 中 mWindow對象 設置了 callback 為this,所以在 getCallback 時獲取到的 cb 為當前與該 window 對應的 activity。
PhoneWindow.java中的installDecor方法分析
實例化 DecorView對象 和 mContentParent對象
在創建一個 activity 時 mDecor 和 mContentParent 均為null
借助 generateDecor 方法實例化 mDecor,即獲取到 activity 的 最頂級的view
借助 generateLayout 方法實例化 mContentParent對象,并且根據 window 的不同 Feature 來選擇對應的布局文件(總之,generatelayout 其實就是根據當前的 window 的特征屬性 feature 來加載內容布局,并獲取到當前布局的 最外層view。也就是說
generatelayout 本質就是根據 activity 的 theme主題 來找到對應的xml布局并且獲取到id為 content 的 ViewGroup賦給 mContentParent
獲取到 mDecorParent對象,并且根據 getLocalFeature 獲取到的 Feature 來設置(這也就說明了在自定義Activity時為什么要將?getWindow.requestFeature方法 寫在 setContentView方法 之前)
對 title 進行隱藏或者是設置內容的操作
如果需要切入切出動畫,那么就獲取到各種動畫資源
接下來對 installDecor 中某些代碼做一些分析:
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
用于對焦點的傳遞設置:只有當 子view 不想獲取焦點時 mDecor 才會去獲取焦點
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable)
去開啟初始化menu菜單的線程
在這里說明一句,為什么 requestFeature 要寫在 setContentView 前面,這是因為在調用 setContentView 時會獲取到 window 的各種 feature 進行一些判斷設置。
PhoneWindow.java中的generateLayout方法研究
第一步:首先是獲取到 window 的布局style
TypedArraya=getWindowStyle();
第二步:獲取到各種屬性并進行 requestFeature 的設置
第三步:通過獲取到的 window 的布局去獲取 window 的各種屬性,并根據 window 的各種屬性去選擇不同的 layout 的文件,比如標題欄是否隱藏,window 是懸浮窗還是全屏等等問題。當然因為在3.0和4.0以及5.0對于menukey的支持不同,所以會有一個與版本相關的一個判斷。至于這個版本之間有什么不同可以參考總結說明中列出來的文件。
其實 generatelayout 就做了一件事,那就是根據 window 的各種屬性去獲取不同的xml文件。
總結
setContentView 執行流程中主要涉及到3個類 PhoneWindow.java,Activity.java 和 Window.java
Window 和 windowmanager 中的 各種feature 和 flag的style 對應的各種含義以及動畫 style在\android\android\frameworks\base\core\res\res\values\attrs.xml 文件中有注釋說明
在 menu鍵 的設置中涉及到了版本問題,包括 3.0,4.0 和 5.0 分別有對應的不同處理,參考 \android\android\frameworks\base\core\java\android\os\Build.java 可以看到注釋有說明各版本有什么不同
至于為什么說 mDecor 是 最外層view,是因為在 generateLayout 方法中 mDecor 將填充該xml文件的 view一一mContentRoot 添加了進來。
Activity在啟動加載布局的操作
創建 DecorView 的布局:setContentView 的流程基本是用來創建 DecorView 的布局
將布局添加到 window 窗口:在 Activity 的啟動過程中,會將應用窗口添加到 WindowManager 中進行統一管理,以及綁定 DecorView
對于狀態欄和導航欄,是在每次 window 屬性發生變化時會去更新,但是只是設置了一個背景色,只是占位用,沒有加載這些view
文章原創作者GuoLin 書籍推薦
郭林大神原創android 書籍:《第一行代碼 android》