Android中Activity啟動過程探究

Kross發表於2014-10-15

首先追溯到Activity的啟動,隨便啟動一個自己寫的demo專案,使用DDMS進行debug標記,然後在Debug中把主執行緒暫停,可以看到呼叫棧。如下圖所示:

於是我們先看android.app.ActivityThreadmain()方法。

android.app.ActivityThread.main()

main()方法中對一個Looper物件進行初始化,形成一個訊息迴圈,那麼任何主執行緒的操作都會傳送到這個Looper對應的Handler中去。通過原始碼,輾轉反側找到Handler的定義,它是ActivityThread中定義的一個內部類名為H繼承自Handler

觀察它的handleMessage()方法,發現了其中有一個what值為LAUNCH_ACTIVITY的switch分支,其中呼叫了handleLaunchActivity()方法。

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

android.app.ActivityThread.handleLaunchActivity()

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

先來看performLaunchActivity()做了什麼。

android.app.ActivityThread.performLaunchActivity()

以下是部分原始碼,我做了一些省略。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    ...
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        if (r.state != null) {
            r.state.setClassLoader(cl);
        }
    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException(
                "Unable to instantiate activity " + component
                + ": " + e.toString(), e);
        }
    }

    try {
        ...
        if (activity != null) {
            ...

            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config);
            ...
            activity.mCalled = false;
            mInstrumentation.callActivityOnCreate(activity, r.state);
            ...
        }
        ...
    }
    ...
}

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

7, 8行:通過Activity的類名構建一個Activity物件。

27行:呼叫了Activity.attach()方法。

32行:通過Instrumentation物件執行Activity的onCreate()方法,Activity的生命週期方法都是由Instrumentation物件來呼叫的。

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

android.app.Activity.attach()

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config) {
    attachBaseContext(context);

    mFragments.attachActivity(this, mContainer, null);

    mWindow = PolicyManager.makeNewWindow(this);

    ... //將各種引數賦給Activity的成員變數

    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    if (mParent != null) {
        mWindow.setContainer(mParent.getWindow());
    }
    mWindowManager = mWindow.getWindowManager();
    mCurrentConfig = config;
}

首先給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()的程式碼。

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    if (mContentParent == null) {
        installDecor();
    } else {
        mContentParent.removeAllViews();
    }
    mContentParent.addView(view, params);
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

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

來看官方文件的描述:

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

private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor();
        ...
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
        ...
    }
}

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

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

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

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

protected ViewGroup generateLayout(DecorView decor) {

    //...以上省去,大致上是與樣式,主題,版本相關的對檢視的設定。

    //以下開始填充decor

    // Inflate the window decor.
    int layoutResource;    //這個是用來inflate的id

    ...    //這裡省去,內容是根據Window的各種特性(feature)選擇一個合適的檢視id賦給layoutResource

    mDecor.startChanging();

    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);    //注意這個地方
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    ... //省去,內容是設定背景,設定ActionBar的一些屬性。

    mDecor.finishChanging();

    return contentParent;
}

可以看出,這個方法(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()

貼上省略後的程式碼:

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
        boolean reallyResume) {
    ...

    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();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            }
            ...
        }
    ...
    }
}

注意紅色加粗的部分:

首先是performResumeActivity()方法,這個方法內就是通過Instrumentation呼叫Activity的onResume()方法。

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

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

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ...
    ViewRootImpl root;
    View panelParentView = null;

    ...
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }

    // do this last because it fires off messages to start doing things
    try {
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        ...
        throw e;
    }
}

從上面的程式碼可以看出,addView方法中,new了一個ViewRootImpl物件,然後呼叫ViewRootImpl.setView()方法。

android.view.ViewRootImpl.setView()

/**
 * We have one child
 */
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
            ...

            // Schedule the first layout -before- adding to the window
            // manager, to make sure we do the relayout before receiving
            // any other events from the system.
            requestLayout();

            ...

            view.assignParent(this);
            ...
        }
    }
}

省略後的程式碼如上所示,首先將傳進來的引數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上看原始碼,感覺還是有點收穫,學習到了一些以前從未了解的東西,最大的感觸就是,只要原始碼給的全,慢慢看還是可以琢磨出來的。另外,關於這些內容與實際應用之間的聯絡,還有待進一步的探究和經驗的積累。

相關文章