Android中Activity啟動過程探究

來源:互聯網
上載者:User

Android中Activity啟動過程探究
android.app.ActivityThread.performLaunchActivity() 以下是部分源碼,我做了一些省略。 複製代碼 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 " + component18                 + ": " + 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賦給layoutResource11 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 things18     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 window11             // manager, to make sure we do the relayout before receiving12             // 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上看原始碼,感覺還是有點收穫,學習到了一些以前從未瞭解的東西,最大的感觸就是,只要源碼給的全,慢慢看還是可以琢磨出來的。另外,關於這些內容與實際應用之間的聯絡,還有待進一步的探究和經驗的積累。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.