這篇文章主要是配合源碼簡單的介紹一下,程序的加載過程,Activity 中佈局的加載過程,可以大致的瞭解整個過程。不過過分的追究細節,由於裏面任何一個細節可能都夠你研究一段時間的!先了解掌握大致過程,再慢慢來!html
咱們都知道,Activity 是有生命週期的,onCreate()
、onStart()
、onResume
等等那麼這些方法是如何調用的呢?它不會無緣無故的本身調用。其實當咱們開啓 APP 的時候會建立一個叫作 ActivityThread 的類,咱們能夠認爲這個類是主類,就和 Java 程序中的啓動類同樣,ActivityThread 類有一個 main
函數,這個函數就是咱們的程序入口!java
相信看到這個,你們都會恍然大悟了,這就是咱們學習 Java 的時候的 main
方法,認爲是程序的入口。能夠看到方法裏面對一個 Looper
對象進行了初始化,Looper.prepareMainLooper()
經過這個方法就給當前的線程初始化了 Looper,這個 Looper 成了 Application 的 main looper,這也是爲何咱們在主線程中不用本身再 Looper.prepare 的緣由。能夠認爲任何在主線程的操做都會發送到這個 Looper
對應的 Handler
中去。很容易能夠找到 Looper
對應的 Handler
,其實就是 ActivityThread 的一個內部類,繼承了 Handler
android
這個就是 ActivityThread
中 Handler
的部分代碼實現,而後看到 Handler 裏面的這段代碼app
public void handlerMessage(Message msg){
.....;
switch(msg.what){
case LAUNCH_ACTIVITY:
.....;
// r 是在 msg 裏面拿到的對象 msg.obj
handlerLaunchActivity(r,null,"LAUNCH_ACTIVITY");
.....;
}
}
複製代碼
下面再來看 handlerLaunchActivity()
這個方法:ide
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 方法,這裏省略了。oop
**結論:**在 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 的內容顯示有關係學習
下面就開始執行 Activity 中的 onCreate 方法了。this
咱們都知道,每個 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() 方法裏面仍是處理了許多關鍵的事情:
上面介紹的這些,只是執行完了 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 的啓動一塊串聯起來!
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() 方法了
其實這個和 measure() 方法類似。都是在父控件中調用 layout() 方法,而後在 layout 方法中會調用 onLayout 方法。和 measure 不一樣的是 onLayout 方法是給本身的子 View 來佈局的,若是不是 ViewGroup 就不須要重寫 onLayout 方法了。measure 的寬度和高度是該控件指望的尺寸,真正在屏幕上的位置和大小是由 layout 來決定的。接下來就是遞歸調用他們的 onLayout 方法。
接下來就是開始 Drawa 了,介紹到這裏整個過程算是完成了。
參考:www.cnblogs.com/kross/p/402…
我的水平有限,若有紕漏歡迎指正!