Android源碼解析系列1——Activity啓動和界面加載

注:本文分析的源碼版本爲Android 27java

若有轉載,請標明出處android

(更新時間:2021-6-11)數組

1、應用的啓動

首先,咱們須要知道:緩存

ActivityThreadmain方法,是Android應用程序啓動時的入口點。markdown

public final class ActivityThread {
    // 省略部分代碼

    public static void main(String[] args) {
        // 省略部分代碼

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}
複製代碼

在main方法裏執行了如下操做:app

  • 一、首先調用了Looper.prepareMainLooper()方法,它會建立一個與當前線程(主線程)相關聯的Looper對象。
  • 二、而後建立一個ActivityThread對象,並調用其attach()方法。
  • 三、最後調用Looper.loop()方法,讓剛建立的Looper對象,從它的MessageQueue中循環讀取數據並執行。

如今,咱們開始分析ActivityThread的attach方法作了什麼?ide

public final class ActivityThread {
    final ApplicationThread mAppThread = new ApplicationThread();
    // 省略部分代碼

    private void attach(boolean system) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        if (!system) {
            // 省略部分代碼

            RuntimeInit.setApplicationObject(mAppThread.asBinder());
            final IActivityManager mgr = ActivityManager.getService();
            try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
            // 省略後續代碼
        }
    }
}
複製代碼

在attach方法中,調用ActivityManager.getService()方法,獲取到遠程的IActivityManager對象,並將一個ApplicationThread實例傳入。oop

ApplicationThread繼承至IApplicationThread.Stub,即Binder進程間通訊的本地實現類。它有不少與Activity生命週期相關的方法,大都以scheduleXXX命名:佈局

image.png

至此,咱們能夠總結一下:ui

  1. ActivityThreadmain方法,是應用程序啓動的入口。
  2. Activity生命週期是由系統底層控制,經過Binder機制,回調到ApplicationThreadscheduleXXX方法中。
  3. ActivityThreadApplicationThread針對每一個應用,都只有一個實例。

這裏有個疑問:

爲何沒有scheduleStartActivity方法?難道Activity的onStart()生命週期回調,不是由ActivityManager發送消息控制的?

帶着這個疑問,咱們開始閱讀這些scheduleXXX方法的源碼。

2、Activity生命週期

咱們從ApplicationThread中的scheduleLaunchActivity方法開始分析,由於由名字能夠猜想它應該會執行Activity建立和啓動工做。

public final class ActivityThread {
    // 省略部分代碼

    private class ApplicationThread extends IApplicationThread.Stub {
        // 省略部分代碼

        @Override
        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state, PersistableBundle persistentState, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

            updateProcessState(procState, false);

            ActivityClientRecord r = new ActivityClientRecord();

            // 給ActivityClientRecord賦值

            updatePendingConfiguration(curConfig);

            sendMessage(H.LAUNCH_ACTIVITY, r);
        }
        // 省略部分代碼
    }
}
複製代碼

在scheduleLaunchActivity方法中,首先建立了一個ActivityClientRecord對象,並對其進行賦值。

ActivityClientRecord:用於記錄與Activity相關的數據。

而後經過Handler,將線程由Binder線程切換到主線程。最終調用到ActivityThread的handleLaunchActivity方法。

public final class ActivityThread {
    // 省略部分代碼

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        // 省略部分代碼

        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            // 省略部分代碼
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

           if (!r.activity.mFinished && r.startsNotResumed) {
                // The activity manager actually wants this one to start out paused, because it
                // needs to be visible but isn't in the foreground. We accomplish this by going
                // through the normal startup (because activities expect to go through onResume()
                // the first time they run, before their window is displayed), and then pausing it.
                // However, in this case we do -not- need to do the full pause cycle (of freezing
                // and such) because the activity manager assumes it can just retain the current
                // state it has.
                performPauseActivityIfNeeded(r, reason);
                // 省略部分代碼
            }
        }
    }
}
複製代碼

這裏,咱們暫時無論performLaunchActivity方法中作了什麼,僅分析後續代碼。後續代碼中,調用了handleResumeActivity,猜想它應該會調用Activity的onResume方法。

根據Activity生命週期推測到:

performLaunchActivity方法裏,必定會依次調用Activity的onCreateonStart方法。

帶着這個思路,開始分析performLaunchActivity方法。

public final class ActivityThread {
    // 省略部分代碼

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // 省略部分代碼

        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            // 省略部分代碼
        } catch (Exception e) { /* 省略部分代碼 */ }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            // 省略部分代碼

            if (activity != null) {
                // 省略部分代碼

                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(activity);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
                // 省略部分代碼

                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                // 省略部分代碼

                if (!r.activity.mFinished) {
                    activity.performStart();
                    r.stopped = false;
                }
                if (!r.activity.mFinished) {
                    activity.mCalled = false;
                    if (r.isPersistable()) {
                        mInstrumentation.callActivityOnPostCreate(activity, r.state,
                                r.persistentState);
                    } else {
                        mInstrumentation.callActivityOnPostCreate(activity, r.state);
                    }
                    // 省略部分代碼
                }
            }
            r.paused = true;

            // 保存ActivityClientRecord
            mActivities.put(r.token, r);

        } catch  { /* 省略catch相關代碼 */ }

        return activity;
    }
複製代碼

上述代碼主要執行了如下操做:

  1. 建立Activity對象

調用InstrumentationnewActivity方法,經過反射建立Activity對象。

  1. 初始化Activity

調用Activity對象的attach方法,用於初始化Activity的一些數據,同時會爲Activity設置Window對象。 注意:Activity的Window對象,與傳入的Window對象不是同一個對象。這也意味着:每一個Activity都有各自的Window對象

public class Activity extends .... {
    // 省略部分代碼
    private Window mWindow;
    private WindowManager mWindowManager;

    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, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        // 省略部分代碼

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        // 省略部分代碼

        mWindowManager = mWindow.getWindowManager();
        // 省略部分代碼
    }
    // 省略部分代碼
}
複製代碼
  1. 調用3個生命週期方法

一、Instrumentation.callActivityOnCreate方法,該方法中會調用activity.performCreate()方法。
二、activity.performStart()方法。
三、Instrumentation.callActivityOnPostCreate方法,該方法中會調用activity.onPostCreate()方法。

查看裏面的源碼,確實依次調用了onCreateonStartonPostCreate方法,驗證了咱們以前對performLaunchActivity的猜測。

總結一下

handleLaunchActivity方法裏,會回調如下生命週期:

onCreate() -> onStart() -> onPostCreate() -> onResume()

注意:若是ActivityClientRecord.startsNotResumed = true時,生命週期流程將會變爲:

onCreate() -> onStart() -> onPostCreate() -> onResume() -> onPause()


2、界面加載

經過上節內容的介紹,咱們知道在handleLaunchActivity方法裏,會回調Activity的onCreate()生命週期方法。

而通常咱們在建立Activity時,會在onCreate()中調用setContentView來設置佈局文件。

下面,咱們開始分析,咱們自定義的佈局文件是如何被加載出來的。

2.一、設置佈局

首先分析Activity中的setContentView方法的源碼。

public class Activity extends .... {
    // 省略部分代碼

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    // 省略部分代碼
}
複製代碼

根據前面的分析可知,這裏的getWindow()方法返回的是該Activity所特有的Window對象,它在attach方法中被賦值。

而Window是一個抽象類,經過查看類上的註解可知,它只有一個名爲PhoneWindow的子類,因此咱們直接看PhoneWindow的setContentView方法。

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    // 省略部分代碼
    ViewGroup mContentParent;

    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
    // 省略部分代碼
}
複製代碼

代碼仍是比較清楚的,若是mContentParent == null,調用installDecor()方法,後續將傳入的佈局資源,加載到mContentParent中。

因此這裏能夠確定:installDecor()方法,主要做用就是爲了建立mContentParent對象。

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    // 省略部分代碼
    private DecorView mDecor;
    ViewGroup mContentParent;

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            // 省略部分代碼
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            // 省略後續設置icon、title等代碼
        }
    }
}
複製代碼

嗯,這裏確實經過generateLayout方法建立了mContentParent對象,但在建立以前,先建立了一個DecorView對象,並將其做爲參數傳入generateLayout方法裏。

DecorView,咱們只須要知道它繼承至FrameLayout便可,由於此時分析它的細節,對於咱們並沒太大幫助。

那咱們分析generateLayout作了什麼:

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    // 省略部分代碼
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

    protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        // 省略部分代碼:從主題文件中讀取內容,並設置對應的Flag

        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        //省略部分代碼:經過features,爲layoutResource設置不一樣佈局資源id

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        //省略部分代碼:爲mDecor設置background、elevation、title、titleColor等數據

        mDecor.finishChanging();

        return contentParent;
    }
}
複製代碼

主要作了如下內容:

  1. 從主題中獲取數據,並應用到當前Window中。
  2. 根據features,讓mDecor加載不一樣的佈局文件。
  3. 得到mContentParent對象(id爲com.android.internal.R.id.content)。

這裏咱們能夠得出如下信息:

  1. 不一樣的主題會讓Window加載不一樣的佈局到DecorView中。
  2. setContentView方法,實際是將自定義的佈局文件,加載到mContentParent中。

至此,咱們能夠簡單總結一下setContentView的流程:

一、首先,Activity中的Window對象會建立一個DecorView
二、而後根據不一樣的主題,讓DecorView加載不一樣的佈局資源。
三、獲取這些佈局資源中的mContentParent,它的id爲com.android.internal.R.id.content
四、最後將自定義的佈局加載到mContentParent中。

2.二、渲染布局

在上述setContentView的流程中,全部的佈局資源都已加載完畢,而佈局的加載又會涉及到addView方法。

通常來講,調用addView方法,都會間接調用到requestLayout()invalidate(true)方法,形成界面從新佈局和刷新。

而咱們也知道:

Activity在onCreate時,界面並不會被加載出來。

這裏彷彿出現了矛盾,那咱們再分析分析,看看爲何調用了addView方法後,界面卻沒有被加載出來。

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    // 省略部分代碼

    public void addView(View child, int index, LayoutParams params) {
        // 省略部分代碼
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }
    // 省略部分代碼
}
複製代碼
public class View implements ... {
    // 省略部分代碼

    public void requestLayout() {
        // 省略部分代碼
        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        // 省略部分代碼
    }

    public void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
        // 省略部分代碼
        if (skipInvalidate()) {
            return;
        }
        // 省略後續代碼
    }

    private boolean skipInvalidate() {
        return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
                (!(mParent instanceof ViewGroup) ||
                        !((ViewGroup) mParent).isViewTransitioning(this));
    }
    // 省略部分代碼
}
複製代碼

哦,原來此時DecorView沒有父容器,致使這裏只會執行添加操做,而不會去從新佈局和刷新。


那何時,界面纔會被加載出來呢?

只要學過Android生命週期的人,都知道:

當Activity處於onCreate時,是不可見的。
當Activity處於onStart時,可見,但不可交互。
當Activity處於onResume時,纔是可見可交互的。

那咱們看看ActivityperformStart方法實現:

public class Activity extends .... {
    // 省略部分代碼

    final void performStart() {
        mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
        mFragments.noteStateNotSaved();
        mCalled = false;
        mFragments.execPendingActions();
        mInstrumentation.callActivityOnStart(this);
        if (!mCalled) {
            throw new SuperNotCalledException(
                "Activity " + mComponent.toShortString() +
                " did not call through to super.onStart()");
        }
        mFragments.dispatchStart();
        mFragments.reportLoaderStart();

       // 省略部分代碼

        mActivityTransitionState.enterReady(this);
    }
複製代碼

這裏只要調用了InstrumentationcallActivityOnStart方法,而callActivityOnStart方法內部的實現,只是簡單調用了傳入的Activity對象的onStart()方法。

emmmmm......好像有點不對啊,performStart方法裏好像也沒作什麼操做啊,難不成界面渲染是在onResume()裏?

帶着疑惑,咱們一塊兒來看看handleResumeActivity方法:

public final class ActivityThread {
    // 省略部分代碼

    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        // 省略部分代碼

        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);

        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;
                // 省略部分代碼

                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);
                    } else {
                        // The activity will get a callback for this {@link LayoutParams} change
                        // earlier. However, at that time the decor will not be set (this is set
                        // in this method), so no action will be taken. This call ensures the
                        // callback occurs with the decor set.
                        a.onWindowAttributesChanged(l);
                    }
                }
                // 省略後續代碼
            }
        }
    }
}
複製代碼

其中performResumeActivity也沒作過多操做,只是調用了Activity的performResume()方法,間接調用到onResume,咱們就不過多分析了。

這裏比較核心的是,將DecorView添加到ViewManager中,這裏間接調用到WindowManagerGlobaladdView方法,它是一個單例對象,注意這裏的斷定條件,能夠看出,一個ActivityClientRecord對象,以後執行一次。

public final class WindowManagerGlobal {
    // 省略部分代碼
    private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();

    public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
        // 省略部分代碼

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // 省略部分代碼

            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) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
    // 省略部分代碼
}
複製代碼

這裏先建立ViewRootImpl對象,而後進行緩存到數組中。接下來把DecorView設置到ViewRootImpl中,進而執行到ViewRootImplrequestLayout() -> scheduleTraversals() -> doTraversal()方法,最終執行到performTraversals()方法中。

performTraversals()裏,會執行View的繪製流程,包括測量擺放繪製三部分,這個咱們後面單獨在View渲染章節來說。咱們只需明白,此時界面就已經被渲染出來了。

2.三、延伸

如今,咱們已經知道了,Activity渲染到屏幕,是在onResume()以後才完成的,那爲啥說,onStart()可見但不可交互的呢? 這裏就不賣關子了,其實官方所說的"可見",其實有必定的歧義,我認爲:

"可見"只是針對於已被渲染過的Activity,而不是正在建立的Activity。

參照下面這張圖來解釋一下:

image.png

  • 一、對於建立的Activity,只是簡單的走了生命週期,渲染是在Resumed時完成的。
  • 二、對於已被渲染過的Activity:
  1. 當它由Resumed狀態切換到Started狀態,界面被部分覆蓋,失去焦點,即沒法進行交互。
  2. 當它由Started狀態切換到Created狀態,界面被徹底覆蓋,即不可見。
  3. 當它由Created狀態切換到Started狀態,界面再次被部分覆蓋,依然獲取不到焦點,沒法交互。
  4. 當它由Started狀態切換到Resumed狀態,界面徹底顯示。

正是由於這樣,纔會形成如下這個的問題:

Activity建立過程當中,在onCreate()onStart()onResume()方法裏,都沒法獲取控件的寬高。

3、總結

本文講解了Activity啓動時,生命週期的調用和界面加載流程,總結起來,整個流程以下:

image.png

相關文章
相關標籤/搜索