Android視窗管理框架原始碼解析整理
原文地址ofollow,noindex">Android open source project analysis 感謝原作者guoxiaoxing 及相關技術大佬的無私付出.
此篇文章在各位大佬的原始碼分析文章的基礎上對自己的理解進行整理,各位可結合原文分析使用,如有不實之處歡迎指正.
Android視窗管理框架
-
measure流程
- ViewGroup在onMeasure()中會呼叫所有子View的measure讓它們進行自我測量並在onMeasure中會計算出自己的尺寸然後儲存
- ViewGroup根據子View計算出的期望尺寸來計算出它們的實際尺寸和位置然後儲存。同時,根據子View的尺寸和位置來計算出自己的尺寸然後儲存.
- viewGroup onMeasure() -> view measure() -> view onMeasure()
- 日常開發中我們接觸最多的不是MeasureSpec而是LayoutParams,在View測量的時候,LayoutParams會和父View的MeasureSpec相結合被換算成View的MeasureSpec,進而決定View的大小。
- View的MeasureSpec計算原始碼如下所示,該方法用來獲取子View的MeasureSpec,由引數我們就可以知道子View的MeasureSpec由父容器的spec,父容器中已佔用的的空間大小padding,以及子View自身大小childDimension共同來決定的。
public abstract class ViewGroup extends View implements ViewParent, ViewManager { public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); } }
- View事件分發
ViewGroup dispatchTouchEvent --> ViewGroup onInterceptTouchEvent --> onInterceptTouchEvent is True --> |True| block --> |False| View dispatchTouchEvent --> View onTouchEvent --> View performClick
window 抽象類,定義了視窗型別,視窗引數以及視窗模式。在定義的視窗回撥中進行事件分發,Activity實現了Window.Callback介面,將Activity關聯給Window,Window就可以將一些事件交由Activity處理。
public interface Callback { //鍵盤事件分發 public boolean dispatchKeyEvent(KeyEvent event); //觸控事件分發 public boolean dispatchTouchEvent(MotionEvent event); //軌跡球事件分發 public boolean dispatchTrackballEvent(MotionEvent event); //可見性事件分發 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event); //建立Panel View public View onCreatePanelView(int featureId); //建立menu public boolean onCreatePanelMenu(int featureId, Menu menu); //畫板準備好時回撥 public boolean onPreparePanel(int featureId, View view, Menu menu); //menu開啟時回撥 public boolean onMenuOpened(int featureId, Menu menu); //menu item被選擇時回撥 public boolean onMenuItemSelected(int featureId, MenuItem item); //Window Attributes發生變化時回撥 public void onWindowAttributesChanged(WindowManager.LayoutParams attrs); //Content View發生變化時回撥 public void onContentChanged(); //視窗焦點發生變化時回撥 public void onWindowFocusChanged(boolean hasFocus); //Window被新增到WIndowManager時回撥 public void onAttachedToWindow(); //Window被從WIndowManager中移除時回撥 public void onDetachedFromWindow(); */ //畫板關閉時回撥 public void onPanelClosed(int featureId, Menu menu); //使用者開始執行搜尋操作時回撥 public boolean onSearchRequested(); }
-
視窗的唯一實現類為PhoneWindow,PhoneWindow裡包含了以下內容:
- private DecorView mDecor:DecorView是Activity中的頂級View,它本質上是一個FrameLayout,一般說來它內部包含標題欄和內容欄(com.android.internal.R.id.content)
- ViewGroup mContentParent:視窗內容檢視,它是mDecor本身或者是它的子View。
- private ImageView mLeftIconView:左上角圖示
- private ImageView mRightIconView:右上角圖示
- private ProgressBar mCircularProgressBar:圓形loading條
- private ProgressBar mHorizontalProgressBar:水平loading條
-
其他的一些和轉場動畫相關的Transition與listener
在PhoneWindow的setContentView方法中判斷是否存在DecorView,沒有的化就建立DecorView並將建立好的DecorView賦值給mContentParent具體實現如下:
public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { //1. 如果沒有DecorView則建立它,並將建立好的DecorView賦值給mContentParent installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { //2. 將Activity傳入的佈局檔案生成View並新增到mContentParent中 mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { //3. 回撥Window.Callback裡的onContentChanged()方法,這個Callback也被Activity //所持有,因此它實際回撥的是Activity裡的onContentChanged()方法,通知Activity //檢視已經發生改變。 cb.onContentChanged(); } mContentParentExplicitlySet = true; } }
wms內容較為複雜建議參考原文分析理解
-
獲取XmlResourceParser
- Activity 呼叫LayoutInflater的inflate
- inflate中呼叫Resources的getLayout(resource)去獲取對應的XmlResourceParser。
- getLayout(resource)又去呼叫了Resources的loadXmlResourceParser()方法來完成XmlResourceParser的載入
-
解析View樹
- 獲取樹的深度,執行深度優先遍歷.
- 逐個進行元素解析。
- 解析新增ad:focusable="true"的元素,並獲取View焦點。
- 解析View的tag。
- 解析include標籤,注意include標籤不能作為根元素,而merge必須作為根元素。
- 根據元素名進行解析,生成View。
- 遞迴呼叫解析該View裡的所有子View,也是深度優先遍歷,rInflateChildren內部呼叫的也是rInflate()方法,只是傳入了新的parent View。
- 將解析出來的View新增到它的父View中。
- 回撥根容器的onFinishInflate()方法,這個方法我們應該很熟悉。
-
解析view標籤
- 解析View標籤createViewFromTag。
- 如果標籤與主題相關,則需要將context與themeResId包裹成ContextThemeWrapper。
- BlinkLayout是一種會閃爍的佈局,被包裹的內容會一直閃爍,像QQ訊息那樣。
- 使用者可以設定LayoutInflater的Factory來進行View的解析,但是預設情況下這些Factory都是為空的。
- 預設情況下沒有Factory,而是通過onCreateView()方法對內建View進行解析,createView()方法進行自定義View的解析。這裡有個小技巧,因為我們在使用自定義View的時候是需要在xml指定全路徑的,例如:com.guoxiaoxing.CustomView,那麼這裡就有個.了,可以利用這一點判定是內建View還是自定義View。
[圖片上傳失敗...(image-4f89d3-1536563272182)]
- Adapter將資料DataSet翻譯成RecyclerView可以理解的ViewHolder,Recycler負責對這些ViewHolder進行管理,LayoutManager從Recycler獲取這些ViewHolder,然後在RecyclerView裡對它們進行佈局,在佈局的過程中還可以通過ItemDecoration、ItemAnimator為這些ViewHolder新增分隔條、轉場動畫等東西,讓整個RecyclerView更加具有互動性。
Android元件管理框架
init程序 –> Zygote程序 –> SystemServer程序 –>各種應用程序
- [x]Android元件管理框架:Android檢視容器Activity
- 點選桌面應用圖示,Launcher程序將啟動Activity(MainActivity)的請求以Binder的方式傳送給了AMS。
- AMS接收到啟動請求後,交付ActivityStarter處理Intent和Flag等資訊,然後再交給ActivityStackSupervisior/ActivityStack處理Activity進棧相關流程。同時以Socket方式請求Zygote程序fork新程序。
- Zygote接收到新程序建立請求後fork出新程序。
- 在新程序裡建立ActivityThread物件,新建立的程序就是應用的主執行緒,在主執行緒裡開啟Looper訊息迴圈,開始處理建立Activity。
- ActivityThread利用ClassLoader去載入Activity、建立Activity例項,並回調Activity的onCreate()方法。這樣便完成了Activity的啟動。
-
什麼情況下需要設定FLAG_ACTIVITY_NEW_TASK標誌位
- 呼叫者不是Activity Context
- 呼叫者Activity呼叫single Instance
- 目標Activity設定的有single Instance或者single task
- 呼叫者處於finish狀態
-
啟動模式一共有四種
- standard:多例項模式,每次啟動都會有建立一個例項,預設會進入啟動它的那個Activity所屬的任務棧的棧頂,讀者以前可能使用過Application Context去啟動Activity,這是情況下會報錯,就是因為 Application Context沒有所謂的任務棧,解決的方式就是給它新增一個FLAG_ACTIVITY_NEW_TASK的標誌位,建立一個新的任務棧。
- singleTop:棧頂複用模式,如果新啟動的Activity已經位於任務棧頂,則不會建立新的例項,而是回撥原來Activity例項的onNewIntent()方法,如果新啟動的Activity沒有位於任務棧頂,則會建立 新的Activity例項。
- singleTask:棧內複用模式,如果新啟動的Activity已經位於任務棧內,則不會建立新的例項,而是回撥原來Activity例項的onNewIntent()方法並且清除它之上的Activity(這裡需要注意一下:任務棧裡 的Activity是永遠不會重排序的,所以它會清楚上方所有的Activity來讓自己回到棧頂),如果新啟動的Activity 沒有位於任務棧,則新建一個Activity例項。
- singleInstance:單例項模式,和singleTask相似,但是singleTask可以在多個棧裡擁有多個例項,而singleInstance在多個棧裡只能有唯一例項,這個一般用在特殊場景裡,例如電話介面。