Android中Activity啓動過程探究

首先追溯到Activity的啓動,隨便啓動一個本身寫的demo項目,使用DDMS進行debug標記,而後在Debug中把主線程暫停,能夠看到調用棧。以下圖所示:java

因而咱們先看android.app.ActivityThreadmain()方法。android

android.app.ActivityThread.main()程序員

main()方法中對一個Looper對象進行初始化,造成一個消息循環,那麼任何主線程的操做都會發送到這個Looper對應的Handler中去。經過源碼,展轉反側找到Handler的定義,它是ActivityThread中定義的一個內部類名爲H繼承自Handler數組

觀察它的handleMessage()方法,發現了其中有一個what值爲LAUNCH_ACTIVITY的switch分支,其中調用了handleLaunchActivity()方法。app

接下來看android.app.ActivityThread.handleLaunchActivity()方法。ide

 

android.app.ActivityThread.handleLaunchActivity()oop

如上圖所示,該方法中執行了兩個比較關鍵的步驟,一個是performLaunchActivity(),另外一個是handleResumeActivity()佈局

先來看performLaunchActivity()作了什麼。學習

 

android.app.ActivityThread.performLaunchActivity()this

如下是部分源碼,我作了一些省略。

 1 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
 2     
 3     ...
 4     Activity activity = null;
 5     try {
 6         java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
 7         activity = mInstrumentation.newActivity(  8  cl, component.getClassName(), r.intent);  9         StrictMode.incrementExpectedActivityCount(activity.getClass());
10         r.intent.setExtrasClassLoader(cl);
11         if (r.state != null) {
12             r.state.setClassLoader(cl);
13         }
14     } catch (Exception e) {
15         if (!mInstrumentation.onException(activity, e)) {
16             throw new RuntimeException(
17                 "Unable to instantiate activity " + component
18                 + ": " + e.toString(), e);
19         }
20     }
21 
22     try {
23         ...
24         if (activity != null) {
25             ...
26             
27             activity.attach(appContext, this, getInstrumentation(), r.token,
28                     r.ident, app, r.intent, r.activityInfo, title, r.parent,
29                     r.embeddedID, r.lastNonConfigurationInstances, config);
30             ...
31             activity.mCalled = false;
32  mInstrumentation.callActivityOnCreate(activity, r.state); 33             ...
34         }
35         ...
36     }
37     ...
38 }

重點關注紅色加粗的部分:

7, 8行:經過Activity的類名構建一個Activity對象。

27行:調用了Activity.attach()方法。

32行:經過Instrumentation對象執行Activity的onCreate()方法,Activity的生命週期方法都是由Instrumentation對象來調用的。

其中attach()方法裏面也作了很重要的事情。

 

android.app.Activity.attach()

 1 final void attach(Context context, ActivityThread aThread,
 2             Instrumentation instr, IBinder token, int ident,
 3             Application application, Intent intent, ActivityInfo info,
 4             CharSequence title, Activity parent, String id,
 5             NonConfigurationInstances lastNonConfigurationInstances,
 6             Configuration config) {
 7     attachBaseContext(context);
 8 
 9     mFragments.attachActivity(this, mContainer, null);
10     
11     mWindow = PolicyManager.makeNewWindow(this); 12     
13     ... //將各類參數賦給Activity的成員變量
14 
15  mWindow.setWindowManager(
16             (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
17             mToken, mComponent.flattenToString(),
18             (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
19     if (mParent != null) {
20         mWindow.setContainer(mParent.getWindow());
21     }
22     mWindowManager = mWindow.getWindowManager(); 23     mCurrentConfig = config;
24 }

首先給Activity.mWindow成員變量賦值,而後給mWindow變量設置WindowManager,而後給Activity.mWindowManager賦值。

mWindow是一個Window類型的變量,但實際上它是一個PhoneWindow對象,與Activity的內容顯示相關。

 

上面的attach()方法調用完成後,就天然而然的調用了Activity的onCreate()方法了。

按通常的狀況,Activity中的onCreate()方法調用了setContentView()方法,而setContentView()方法並非由Activity實現的,如下是android.app.Activity中的一段代碼:

1 public void setContentView(View view, ViewGroup.LayoutParams params) {
2  getWindow().setContentView(view, params);
3     initActionBar();
4 }

而getWindow()返回的是一個android.app.Window對象,這個對象就是剛剛在attach()中賦值的mWindow成員變量。

android.app.Window是一個抽象類,其中setContentView()方法並無具體的實現,而這個方法的真正實現是com.android.internal.policy.impl.PhoneWindow類。使用類圖表示:

 

如下是PhoneWindow中的setContentView()的代碼。

 1 @Override
 2 public void setContentView(View view, ViewGroup.LayoutParams params) {
 3     if (mContentParent == null) {
 4  installDecor();
 5     } else {
 6  mContentParent.removeAllViews();
 7     }
 8  mContentParent.addView(view, params);
 9     final Callback cb = getCallback();
10     if (cb != null && !isDestroyed()) {
11         cb.onContentChanged();
12     }
13 }

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

來看官方文檔的描述:

再回到PhoneWindow.setContentView(View, ViewGroup.LayoutParams)中的installDecor()方法作了什麼。

 1 private void installDecor() {
 2     if (mDecor == null) {
 3         mDecor = generateDecor();  4         ...
 5     }
 6     if (mContentParent == null) {
 7         mContentParent = generateLayout(mDecor);  8         ...
 9     }
10 }

mDecor是經過一個generateDecor()方法來獲取的。而generateDecor()就是new了一個DecorView而已。

1 protected DecorView generateDecor() {
2     return new DecorView(getContext(), -1);
3 }

DecorView是PhoneWindow類中定義的一個內部類,它繼承了FrameLayout,用來做爲整個PhoneWindow的根視圖。

再來看generateLayout()作了什麼事情。

 1 protected ViewGroup generateLayout(DecorView decor) {
 2 
 3     //...以上省去,大體上是與樣式,主題,版本相關的對視圖的設置。
 4     
 5     //如下開始填充decor
 6     
 7     // Inflate the window decor.
 8     int layoutResource;    //這個是用來inflate的id
 9     
10     ...    //這裏省去,內容是根據Window的各類特性(feature)選擇一個合適的視圖id賦給layoutResource
11 
12     mDecor.startChanging();
13 
14     View in = mLayoutInflater.inflate(layoutResource, null);
15     decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
16 
17     ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);    //注意這個地方
18     if (contentParent == null) {
19         throw new RuntimeException("Window couldn't find content container view");
20     }
21     
22     ... //省去,內容是設置背景,設置ActionBar的一些屬性。
23 
24     mDecor.finishChanging();
25 
26     return contentParent;
27 }

能夠看出,這個方法(generateLayout())作了以下幾件比較關鍵的事情:

1.根據各類FLAG值設置了LayoutParam參數,以上代碼省略了這部分。

2.根據各類FEATURE值,選擇了一個合適的相似於R.layout.something這樣的佈局id賦值給layoutResource。

3.將layoutResource填充爲一個View對象,加入到DecorView中。

4.【不能肯定,猜想】layoutResource所指示的佈局文件中有一個id爲ID_ANDROID_CONTENT的ViewGroup對象,上述代碼中第17行,源碼「很是自信」的findViewById獲取到這個ViewGroup。

5.最後將contentParent返回給PhoneWindow的成員變量mContentParent。

 

這樣,咱們就知道了成員變量mDecor、mContentParent的來歷。在學習的過程當中,我還同時瞭解到,mDecor管理着一個ActionBar。

綜合以上的探究,加上本身的一些思考和猜想。對PhoneWindow作一下小小的總結:

1.一個Activity對應着一個PhoneWindow對象,是一對一的關係,若是從Activity A啓動到Activity B,那麼Activity B會建立一個本身的PhoneWindow對象。

2.PhoneWindow管理着整個屏幕的內容,不包括屏幕最頂部的系統狀態條。因此,PhoneWindow或者Window是與應用的一個頁面所相關聯。

3.PhoneWindow同時管理着ActionBar和下面的內容主題,setContentView()方法是用來設置內容主體的,而setTitle()等其餘方法就是操做ActionBar的,Window中定義的requestFeature()等方法,有不少與ActionBar屬性相關的設置。另外這些方法都是公有方法,顯然是爲了給客戶端程序員調用的,也進一步佐證了這些操做的意義與做用。

4.PhoneWindow本身並非一個視圖(View),它的成員變量mDecor纔是整個界面的視圖,mDecor是在generateLayout()的時候被填充出來的,而actionBar和contentParent兩個視圖都是經過findViewById()直接從mDecor中獲取出來的。

 

以上講了這麼多,其實只是把installDector()方法給執行完了。接下來是mContentParent.removeAllViews()。這個好理解,若是setContentView()被調用兩次,第二次確定要把裏面的內容都給清空移除了。移除後就是添加,mContentParent.addView(view, params)

 

這個方法是ViewGroup中的一個方法,貼上源代碼:

首先調用的是requestLayout(),這裏的這個方法呢實際上沒有起到做用的。

ViewGourp.requestLayout()其實是調用的View.requestLayout()方法,而View.requestLayout()方法中,除去作了一些View自己的操做外,實際上調用的是View中的mParent成員變量的requestLayout()方法,而mParent這個成員變量是一個ViewParent類型的對象,ViewParent是一個接口,View中的mParent對象是經過View.assignParent(ViewParent)方法來賦值的,而assignParent()方法是由ViewRootImpl.setView()方法調用的……暫時就不考慮它了,要明確的一點,就是requestLayout()並無起到具體的做用。

接下來觀察addViewInner()方法,這個方法其實就是將child加入到本身的一個View數組中保存起來,而後在把這個child的parent標記爲本身。

到此爲止,setContentView()方法基本就執行完畢了,這個時候界面尚未顯示出任何東西來,而僅僅是將mDecor->mContentParent->(customer layout)一個這樣的樹狀結構給搭建好了而已。

 

假設setContentView()方法是onCreate()方法中惟一一個方法調用的話,那麼onCreate()方法也執行完了,調用棧繼續回退,就回到了android.app.ActivityThread.handleLaunchActivity()中,以上的因此就是剛剛執行完了android.app.ActivityThread.performLaunchActivity()。

接下來執行第二個關鍵性的方法handleResumeActivity()。

 

android.app.ActivityThread.handleResumeActivity()

貼上省略後的代碼:

 1 final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
 2         boolean reallyResume) {
 3     ...
 4 
 5     ActivityClientRecord r = performResumeActivity(token, clearHide);
 6 
 7     if (r != null) {
 8         final Activity a = r.activity;
 9         ...
10         if (r.window == null && !a.mFinished && willBeVisible) {
11             r.window = r.activity.getWindow();
12             View decor = r.window.getDecorView();
13             decor.setVisibility(View.INVISIBLE);
14             ViewManager wm = a.getWindowManager();
15             WindowManager.LayoutParams l = r.window.getAttributes();
16             a.mDecor = decor;
17             l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
18             l.softInputMode |= forwardBit;
19             if (a.mVisibleFromClient) {
20                 a.mWindowAdded = true;
21  wm.addView(decor, l);
22             }
23             ...
24         }
25     ...
26     }
27 }

注意紅色加粗的部分:

首先是performResumeActivity()方法,這個方法內就是經過Instrumentation調用Activity的onResume()方法。

下面的wm.addView()方法很是關鍵,wm是上面a.getWindowManager()獲取到的,a是Activity,getWindowManager()就是返回它的mWindowManger對象,而這個對象是WindowManagerImpl,它的內部方法大部分是代理的WindowManagerGlobal,這個在上面的內容中已經提到了。

然而,這個WindowManger的addView()是幹了什麼呢?

 1 public void addView(View view, ViewGroup.LayoutParams params,
 2         Display display, Window parentWindow) {
 3     ...
 4  ViewRootImpl root;  5     View panelParentView = null;
 6 
 7     ...
 8         root = new ViewRootImpl(view.getContext(), display);  9 
10         view.setLayoutParams(wparams);
11 
12         mViews.add(view);
13         mRoots.add(root);
14         mParams.add(wparams);
15     }
16 
17     // do this last because it fires off messages to start doing things
18     try {
19  root.setView(view, wparams, panelParentView); 20     } catch (RuntimeException e) {
21         ...
22         throw e;
23     }
24 }

從上面的代碼能夠看出,addView方法中,new了一個ViewRootImpl對象,而後調用ViewRootImpl.setView()方法。

 

android.view.ViewRootImpl.setView()

 1 /**
 2  * We have one child
 3  */
 4 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
 5     synchronized (this) {
 6         if (mView == null) {
 7             mView = view;
 8             ...
 9 
10             // Schedule the first layout -before- adding to the window
11             // manager, to make sure we do the relayout before receiving
12             // any other events from the system.
13             requestLayout();
14             
15             ...
16 
17             view.assignParent(this);
18             ...
19         }
20     }
21 }

省略後的代碼如上所示,首先將傳進來的參數view賦值給mView,這裏有一點要明確ViewRootImpl其實並非一個View的子類……所以我認爲,mView將是這個對象所認識的root節點,也是整個Activity的root的節點。

接下來調用了requestLayout()方法,這個方法是有效的!

最後將view的父親註冊爲本身。終於終於,mDecor知道了本身父親是誰,或者說,整個Activity設置了一個根節點,在此以前,咱們setContentView()將本身的layout佈局add到PhoneWindow.mContentParent的時候,mDecor都不知道本身的parent是哪一個,如今整個view的樹形結構中有了根節點,也就是ViewRootImpl,那麼requestLayout()就有效了,就能夠進行後面的measure,layout,draw三步操做了。

 

android.view.ViewRootImpl.requestLayout()

該方法首先檢查了是否在主線程,而後就執行了scheduleTraversals()方法。看這個方法的名字,就知道是執行一次遍歷,遍歷的對象就是根節點開始的view tree。

 

android.view.ViewRootImpl.scheduleTraversals()

注意標記出來的Runnable對象,這個Runnable的run()方法中,調用了doTraversal()方法。而doTraversal()方法又調用了performTraversals()方法,這個方法很是長,依次調用了performMeasure(),performLayout(),performDraw()三個方法,終於開始了控件層的測量,佈局,繪製三個步驟。對於這些的探究就留到下一篇博客中好了,這篇已經寫的夠長了。

 

小結:

花了兩天時間在grepcode上看源代碼,感受仍是有點收穫,學習到了一些之前從未了解的東西,最大的感觸就是,只要源碼給的全,慢慢看仍是能夠琢磨出來的。另外,關於這些內容與實際應用之間的聯繫,還有待進一步的探究和經驗的積累。

相關文章
相關標籤/搜索