Activity 啟動過程的簡單分析

  • 2019 年 12 月 26 日
  • 筆記

這篇文章主要是配合源碼簡單的介紹一下,程式的載入過程,Activity 中布局的載入過程,能夠大體的了解整個過程。不過過度的追究細節,因為裡面任何一個細節可能都夠你研究一段時間的!先了解掌握大體過程,再慢慢來!

開始啟動

我們都知道,Activity 是有生命周期的,onCreate()onStart()onResume 等等那麼這些方法是如何調用的呢?它不會平白無故的自己調用。其實當我們開啟 APP 的時候會創建一個叫做 ActivityThread 的類,我們可以認為這個類是主類,就和 Java 程式中的啟動類一樣,ActivityThread 類有一個 main 函數,這個函數就是我們的程式入口!

相信看到這個,大家都會恍然大悟了,這就是我們學習 Java 的時候的 main 方法,認為是程式的入口。可以看到方法裡面對一個 Looper 對象進行了初始化,Looper.prepareMainLooper() 通過這個方法就給當前的執行緒初始化了 Looper,這個 Looper 成了 Application 的 main looper,這也是為什麼我們在主執行緒中不用自己再 Looper.prepare 的原因。可以認為任何在主執行緒的操作都會發送到這個 Looper 對應的 Handler 中去。很容易可以找到 Looper 對應的 Handler,其實就是 ActivityThread 的一個內部類,繼承了 Handler

這個就是 ActivityThreadHandler 的部分程式碼實現,然後看到 Handler 裡面的這段程式碼

public void handlerMessage(Message msg){     .....;    switch(msg.what){ case LAUNCH_ACTIVITY:             .....;            // r 是在 msg 裡面拿到的對象 msg.obj             handlerLaunchActivity(r,null,"LAUNCH_ACTIVITY");             .....;     }  }

下面再來看 handlerLaunchActivity() 這個方法:

private void handleLaunchActivity(ActivityClientRecord r,Intent customIntent,String reason){     ·····;     Activity a = performLaunchActivity(r,customIntent);     ......;        handlerResumeActivity(...);    }

裡面這兩個很重要的方法我已經列出來了,下面我們來看看 performLaunchActivity(r,customIntent) 方法:

// 這裡面程式碼同樣很多,我們只挑選重要的private Activity performLaunchActivity(ActivityClentRecord r,Intent customIntent){     .......;    // 這裡 new 出了 activity     activity = mInstrumentation.newActivity(cl,component.getClassName(),r.intent);     ......;    // 調用了 Activity 的 attach 方法(很重要)     activity.attach(appContext,this,getI......);     ......;    // 調用了 Activity 的 onCreate 方法     mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);    // 調用了 onStart 方法     ........;  }

這段程式碼中分別調用了 Activity 的 attach 方法和 onCreate 方法,同時下面也調用了 onStart 方法,這裡省略了。

結論:在 performLaunchActivity 方法中,首先通過 mInstrumention 產生了 Activity 對象,然後調用了 Activity 的 attach 方法,然後通過 Instrumentation 對象調用了 activity 的生命周期中的方法。說了這麼多,無非是大體了解了這個啟動過程。知道 Activity 在執行生命周期前是先調用 attach 方法的。其中 attach 方法內的一些程式碼是很關鍵的,和整個 Activity 的啟動有很重要的關係,下面來看一下 attach 方法的源碼:

// 這個方法存在於 Activity 類中        final void attach(Context context,ActivityThread aThread,Instrumentation instr,IBinder token,int ident,Application application,Intent intent,ActivityInf info,CharSequence title,Activity parent,String id,.........){       .........;    // Window 對象賦值     mWindow = new PhoneWindow(this,window,activityConfigCallback);     mWindow.set 許多監聽回調 (WindowContrallerCallBack、Callback)等等;     將各種傳遞過來的各種參數賦值給 Activity 中的成員;     mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken,mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED)!=0);     mWindowManager = mWindow.getWindowManager();    }

attach 這個方法的主要內容:首先給 Activity 中的 mWindow 成員變數賦值,具體的實現對象是 PhoneWindow,然後給 Activity 中的其他許多關鍵的成員賦值,給 mWindow 變數設置 WindowManager,然後給 Activity.mWindowManager 賦值。mWindow 是一個 Window 類型的變數,實際上一個 PhoneWindow 對象,和 Activity 的內容顯示有關係

onCreate

下面就開始執行 Activity 中的 onCreate 方法了。

我們都知道,每一個 Activity 對應一個布局文件,在 Activity 的 onCreate() 方法中都需要 setContentView(int res) 通過這個方法我們就可以將布局與 Activity 關聯起來了。那麼布局是怎麼載入的呢。這個時候就要看 setContentView() 的源碼了:

public void setContentView(@LayoutRes int layoutResID){     getWindow().setContentView(layoutResID);     initActionBar();  }

getWindow() 返回的是 Window 對象,這個對象剛剛在 attach 方法中我們接觸過,其實它的真正實現是 PhoneWindow,下面來看 PhoneWindow 中的 setContentView 方法:

// 放置窗口內容的視圖,它既可以是 mDecor 本身(沒有 Title 的情況下),也可以是 mDecor 的子項, 這種情況就是 mDecor 中有 Title 的情況ViewGroup mContentParent;// 這是窗口的頂層視圖,包含窗口裝飾private DecorView mDecor;public void setContentView(int layoutResID){    if(mContentParent == null){     installDecor();     }else if(!hasFeature(FEATURE_CONTENT_TRANSITIONS)){         mContentParent.removeAllView();     }    // 將 布局資源 layoutResID 填充到 mContentParent 中     mLayoutInflater.inflate(layoutResID,mContentParent);     ........;  }

PhoneWindow 類中有兩個和視圖相關的成員變數,一個是 DecorView mDecor,另一個是 ViewGroup mConentParent

下面再來看看 PhoneWindow 中 setContentView 中的 inStallDecor() 方法:

private void installDecor(){     ......;     if(mDecor == null){        // 生成 mDecor;         mDecor = generateDecor(-1);         ......;     }else{         mDecor.setWindow(this);     }     if(mContnetParent == null){         mContentParent = generateLayout(mDecor);         .......;         ....很多程式碼;        // 可以認為 DecorContentParent 是操作 Title 的         final DecorContentParent decorContentParent = (DecorContentParent)mDecor.findViewById(R.id.decor_content_parent);        if(decorContentParent != null){             mDecorContentParent = decorContentParent;             .......;             對 Title 的一系列操作;         }        // 也有獲取 TitleView 的程式碼       }    }

mDecor 是通過 generateDecor() 方法來獲取的。

protected DecorView generateDecor(int featureId){     ......;      return new DecorView(context,featureId,this,getAttributes());    }

DecorView 繼承了 FrameLayout 是整個 PhoneWindow 的根視圖

再來看看 generateLayout(DecorView decor)方法:

protected ViewGroup generateLayout(DecorView decor){      // 獲取當前主題中的一些屬性     TypedArray a = getWindowStyle();      // getWindowStyle() 的內容:mContext.obtainStyledAttributes(com.android.internal.R.styleable.Window);     // 其實就是獲得定義的屬性組中的一些資訊     ......;      // 省略的程式碼大概就是利用 a 來獲取主題中一些默認的樣式,把這些樣式設置到 DecorView 中     // 大概內容,類似於     mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloading,false);    if(mIsFloating){         setLayout(WRAP_CONTENT,WRAP_CONTENT);     }      // 有沒有 windowNoTitle 等等這種屬性     // 這裡的設置大小,可以理解為是設置 DecorView 大小,在這裡已經確定了 DecorView 的大小了     // 根據SDK 版本來設置視圖樣式程式碼       // 填充窗口的資源     int layoutResource;     .....根據不同的條件,給 layoutResource 賦予不同的值;       mDecor.startChanging();      // 資源填充到 mDecor 中     mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);     // 這裡的 findViewById 就是在 mDecor 中尋找     ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);     ....修改 mDecor 背景顏色,設置 Title 的一些屬性;      return contentParent;    }

可以看到 generateLayout() 方法裡面還是處理了許多關鍵的事情:

  • 根據各種屬性值設置了 LayoutParam 參數
  • 根據 FEATURE 的值,選擇了合適的布局資源 id 賦值給了 layoutResource。
  • 根據 Theme 中設置的不同的風格,SDK 不同的風格來對 DecorView 進行設置,Decor 裝飾的意思,所以這個名字也很符合。
  • 把 layoutResource 填充到了 DecorView 中
  • 在 layoutResource 中有一個 id 為 ID_ANDROID_CONTENT 的 ViewGroup 即我們的 contentParent
  • DecorView 包含了 Title 和 contentParent

installDecor() 小結:

  1. 一個 Activity 對應了一個 Window,這個 Window 的實現對象是 PhoneWindow,一對一的關係
  2. PhoneWindow 管理了整個 Activity 頁面內容,不包括系統狀態欄 ,PhoneWindow 是和應用的某個頁面關聯的。
  3. PhoneWindow 包含 ActionBar 和 內容。setContentViwe 方法是用來設置內容的,setTitle 是用來操作 Title 的。Window 中定義的 requestFeature() 等方法,很多與 ActionBar 屬性相關的設置。這些方法都是公共方法,是為了方便我們調用的。
  4. PhoneWindow 本身不是一個視圖,它的成員變數 mDecor 是整個頁面的視圖。mDecor 是在 generateLayout() 的時候填充的。Title 和 contentParent 都是通過 findViewById() 從 mDecor 中獲取的。

上面介紹的這些,只是執行完了 installDecor() 方法,這個時候,PhoneWindow 有了 DecorView,DecorView 有了自己的樣式,有了 Title 和 ContentParent。下一步就是向 ContentParent 中添加自己的布局了。

public void setContentView(int layoutResID){     .....;      // 上面已經分析完了     installDecor();      // 向 ContentParent 中添加我們自己的布局資源     mLayoutInflater.inflate(layoutResID,mContentParent);     .......;  }

到此 setContentView 算是分析完了,onCreate 也就執行完了。那麼我們可以認為上面的 ActivityThread 中的 performLaunchActivity() 執行完了,接下來就開始執行 handleResumeActivity() 方法了。

final void handleResumeActivity(IBinder token,boolean clearHide,boolean isForward,boolean reallyResume,int seq,String reason){     ......;      // 可以把 ActivityClientRecord 類認為是記錄 Activity 內部關鍵參數的類     ActivityClientRecord r = performResumeActivity(token,clearHide);      if(r!=null){          final Activity a = r.activity;         .....;          if(r.window == null && !a.mFinished && willBeVisible){             r.window = r.activity.getWindow();             View decor = r.window.getDecorView();             ....;             ViewManager wm = a.getWindowManager();             WindowManager.LayoutParams l = r.window.getAttributes();             .....;          if(a.mVisibleFromClient){                 a.mWindowAdded = true;                 wm.addView(decor,l)             }         }     }     。。。。。;    }

performResumeActivity()方法,這個方法內部調用了 Activity 的 performResume() 方法(這個方法在 API 中沒有出現,需要在完整源碼中查看)

看到上面 mInstrumentation.callActivityOnResume(this) 就是調用了 Activity 的 onResume 方法。

然後再來看看 hanleResumeActivity 方法裡面的 addView 方法,wm 是上面 a.getWindowManger() 獲取到的,a 是 Activity,getWindowManager() 返回了 mWindowManager 對象,而這個對象是 WindowMangerImpl,它內部方法大部分是在 WindowManagerGlobal 內部實現

private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<>();public void addView(View view,ViewGroup.LayoutParams params,Display display,Window parentWindow){     .......;     ViewRootImpl root;     View panelParentView = null;     ......;      final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;       root = new ViewRootImpl(view.getContext(),display);     view.setLayoutParams(wparams);       mViews.add(view);     mRoots.add(root);     mParams.add(wparams);      try{         root.setView(view,wparams,panelParentView);     }catch (RuntimeException e){         .....;     }  }

上面程式碼中,在 addView 方法中,new 了一個 ViewRootImpl 對象,然後調用 ViewRootImpl.setView() 方法。

/**  * We have one child  */public void setView(View view,WindowManager.LayoutParams attrs,View panelParentView){    synchronized(this){        if(mView == null){             mView = view;               mAttachInfo.mDisplayState = mDisplay.getState();             mDisplayManager.registerDisplayListener(mDisplayListener,mHandler);             ....;             requestLayout();             .....;             view.assignParent(this);             .....;         }     }    }

首先將傳進來的 view 賦值給 mView,然後調用了 requestLayout() 方法,ViewRootImpl 不是 View 的子類,可以認為 ViewRootImpl 是這個 Activity 所對應的整個布局的根,它來執行具體的繪製開始,view.assignParent(this),就是給自己分配了 Parent,Parent 就是 ViewRootImpl 對象。

public void requestLayout(){     .....;       scheduleTraversals();  }

最終調用了 performTraversals() 方法,performTraversals 方法內部程式碼很多

這裡只寫一下重要的部分

// 不傳遞參數,默認是 MATCH_PARENT        final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();// window 可以使用的寬度int mWidth;// window 可以使用的高度int mHeight;private void performTraversals(){    int desiredWindowWidth;    int desireWindowHeight;     WindowManager.LayoutParams lp = mWindowAttributes;     // 獲取應該給根 View 多大     // mWidth 就是我們窗口的大小,lp.width 始終是 MATCH_PARENT     int childWidthMeasureSpec = getRootMeasureSpec(mWidth,lp.width);    int childHeightMeasureSpec = getRootMeasureSpec(mHeight,lp.height);     // 告訴 根 View 多大     performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);    // perforMeasure 內部實現很簡單       performLayout(lp,mWidth,mHeight);       performDraw();      }

調用 getRootMeasureSpec(mWidth,lp.width) 得到 childWidthMeasureSpec :

通過這個方法就獲取了子 View 應該是多大,還有呈現的模式。然後把得到的數值,通過 performMeasure() 方法設置 view 大小。performMeasure 方法:

private void performMeasure(int childWidthMeasureSpec,int childHeightMeasureSpec){    .....;      // 這裡的 mView 就是 PhoneWindow 中的 DecorView    mView.measure(childWidthMeasureSpec,childHeightMeasureSpec);  }

通過這一步:mView.measure(childWidthMeasureSpec,childHeightMeasureSpec) 就是給 mView 確定大小值了,也就是根 View。

這樣整個 Activity 的布局對應的根 View—DecorView 的大小就確定了。具體來看看 mView.measure():

// 測量一個 View 應該是多大的,參數是由它的父 View 提供的,widthMeasureSpec// 包含了大小和約束條件public final void measure(int widthMeasureSpec,int heightMeasureSpec){     .......;     onMeasure(widthMeasureSpec,heightMeasureSpec);     .......;  }

該方法又調用了onMeasure 方法:

// 僅僅是 View 裡面的 onMeasure 方法,不同的 View 子類有不同的實現內容protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){     setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));  }// 官網文檔:測量 View 的大小,這個方法通過 measure 方法來調用,View 的子類應該重寫此方法根據子 View 的特點。在重寫這個方法的時候,你必須調用 setMeasuredDimension(int,int) 來儲存測量出來的寬度和高度。

下面就是調用 setMeasuredDimension() 了。其中最關鍵的一步就是對 View 的兩個成員變數進行了賦值(在 setMeasuredDimensionRaw()) 方法中實現的

// 這個方法必須在 onMeasure() 方法中被調用用來儲存測量出的寬度和高度。protected final void setMeasuredDimension(int measuredWidth,int measuredHeight){     ......;    // 為了方便,這裡直接把 setMeasuredDimensionRaw() 方法內容寫到下面了     //給 View 的成員變數賦值     mMeasuredWidth = measureWidth;     mMeasuredHeight = measuredHeight;       mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;    }

好了到此 View 中的 measure 方法也介紹完畢了。是不是覺得 onMeasure 方法裡面很簡單啊!這你就錯了,不要忘了我們分析的 onMeasure 方法是分析的類 View 中的。裡面注釋明確寫明了 View 的子類必須根據自身需求來重寫 onMeasure 方法 。我們上面分析的只是一個過程。不要忘了我們的根 View 是 DecorView,而 DecorView 的父類又是 FrameLayout。可以到這兩個具體的對象中看看他們的 onMeasure。感興趣的可以看一下源程式碼,其實分析到這裡就可以得出結論了(這個結論僅僅是 measure 這一部分的結論) 後面再從 Activity 的啟動一塊串聯起來!

Measure 結論

Activity 有一個祖先 View 即 DecorView,通過前面的分析 DecorView 的大小是由 ViewRootImpl 中給賦值的,我們可以認為 ViewRootImpl 是所有 View 的根,我們知道我們布局是呈現樹的結構。我這裡有一個比喻:ViewRootImpl 是這顆樹的根,是埋在地下面的,DecorView 是樹的主幹是在地上面的,ViewGroup 是枝幹,單個的 View (類似於 TextView、Button 這種)是樹葉。DecorView 這個主幹上長出許多枝幹,這裡我們的這棵樹有兩個重要的枝幹(或者一個),這個需要根據樹的種類樣式來決定。這兩個重要的枝幹就是:Title 和 mContentParent,或者只有 mContentParent。然後這個兩個枝幹又會生長出許多的枝幹,枝幹上面生長樹葉。

measure() 方法是由其父 View 來調用執行。從根上追溯,DecorView 的大小是由樹根(ViewRootImpl)來賦值的,然後枝幹又是由 DecorView 來調用的 measure 的。一層層的調用。

舉個簡單栗子:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"               android:orientation="vertical"               android:id="@+id/ll_parent"               android:layout_width="match_parent"               android:layout_height="match_parent">     <RelativeLayout         android:id="@+id/rl_parent_1"         android:layout_width="match_parent"         android:layout_height="200dp">         <TextView             android:id="@+id/tv_child_1"             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:text="@string/view"/>         <TextView             android:layout_below="@id/tv_child_2"             android:layout_width="match_parent"             android:layout_height="100dp"             android:text="@string/activity_cycle"             />     </RelativeLayout>     <LinearLayout         android:id="@+id/ll_parent_1"         android:layout_width="match_parent"         android:layout_height="match_parent">         <EditText             android:id="@+id/et_child_1"             android:inputType="text"             android:layout_width="match_parent"             android:layout_height="match_parent"             android:hint="@string/progress_bar"/>     </LinearLayout></LinearLayout>

我們這裡有這樣一棵樹,上面的布局是我們這顆樹的 mContentParent 。首先 ViewRootImpl 會給 DecorView 大小賦值(具體大小是由 generateLayout 的時候確定的)。然後 DecorView 會調用 mContentParent 的 measure ,傳入 DecorView 允許它的大小。當然具體的大小是由 measure 傳入的參數和 mContentParent 共同決定的(具體細節下面介紹)然後 mContentParent 再調用 ll_parent.measure() 給它傳入 mContentParent 所允許它的大小。這個時候就會激活 ll_parent 的 onMeasure 方法,在 ll_parent 的 onMeasure 方法裡面肯定會調用 rl_parent_1.measure 方法,然後激活 rl_parent_1 的 onMeasure 方法,在 onMeasure 方法裡面調用 tv_child_1.measure ,tv_child_1 沒有孩子了,直接設置自己的大小。然後再一層層的向父布局返回去。大體就是這樣的一個過程。

當然 measure(int widthMeasureSpec,int heightMeasureSpec) 這裡的參數包含了,子元素的大小的屬性和允許子元素的大小。具體可以看 MeasureSpec 類,很簡單。

到此每個布局就知道自己的大小了。然後開始執行 ViewRootImpl.performLayout() 方法了

Layout()

其實這個和 measure() 方法相似。都是在父控制項中調用 layout() 方法,然後在 layout 方法中會調用 onLayout 方法。和 measure 不同的是 onLayout 方法是給自己的子 View 來布局的,如果不是 ViewGroup 就不需要重寫 onLayout 方法了。measure 的寬度和高度是該控制項期望的尺寸,真正在螢幕上的位置和大小是由 layout 來決定的。接下來就是遞歸調用他們的 onLayout 方法。

接下來就是開始 Drawa 了,介紹到這裡整個過程算是完成了。