多瞭解一點Activity

weixin_34365417發表於2018-01-23

Activity和普通類的重要區別在於其有生命週期的回撥方法,本文意在通過其回撥方法的呼叫,揭開其神祕面紗

1.幾個重要的類

(1)ActivityThread

應用啟動的時候會建立一個獨立的程式,在這個程式裡面會有一個主執行緒,主執行緒首先做的事就是呼叫ActivityThread的main方法,也就是說ActivityThread的main方法是應用程式的入口,相當於java的main方法.
注意:ActivityThread不是執行緒!!!

(2)ApplicationThread

是ActivityThread的私有內部類,繼承自ApplicationThreadNative,ApplicationThreadNative繼承了Binder並實現了IApplicationThread介面,說明了ApplicationThread具有跨程式通訊的能力.

IApplicationThread是一個介面,我們看看它的介紹:

/**
 * System private API for communicating with the application.  This is given to
 * the activity manager by an application  when it starts up, for the activity
 * manager to tell the application about things it needs to do.
 * <p>
 * {@hide}
 */

翻譯:這是一個用於和應用通訊的系統私有API,當應用啟動的時候會被應用交給ActivityManager,讓ActivityManager告訴應用要做什麼.
我們看看這個介面裡面的一些方法:


2485866-db76b10477ca3252.png

這只是一部分方法,通過上面的部分方法我們能看出這個介面是AMS控制Activity,Service等生命週期的一個介面,ApplicationThread實現了該介面,也就是說AMS通過ApplicationThread實現對Activity,Service等的宣告週期的控制.在程式間通訊中這個時候AMS是Client,ApplicationThread是Server(實際中AMS持有的是ApplicationThread的代理物件ApplicationThreadProxy)

(3)H

H是ActivityThread的內部類,繼承自Handler,簡單說H就是一個處理訊息的類.
我們在上面已經知道AMS通過ApplicationThread來呼叫Activity的生命週期方法,那ApplicationThread又是怎麼呼叫的呢? ApplicationThread是通過H來傳送Message,然後再由H來處理訊息

(4)ActivityClientRecord,ActivityRecord

這兩者都是用來描述Activity的,比如Activity所屬的window,配置資訊等等,二者有不同,但大體上一樣.ActivityClientRecord是在我們應用程式端使用的,ActivityRecord是AMS使用的

(5)Instrumentation

是一個輔助類,用於建立application,開啟Activity,呼叫Activity的各個生命週期的方法.

(6)ViewRoot

ViewRoot可能比較陌生,但是其作用非常重大。所有View的繪製以及事件分發等互動都是通過它來執行或傳遞的。
ViewRoot對應ViewRootImpl類,它是連線WindowManagerService和DecorView的紐帶,View的三大流程(測量(measure),佈局(layout),繪製(draw))均通過ViewRoot來完成。
ViewRoot並不屬於View樹的一份子。從原始碼實現上來看,它既非View的子類,也非View的父類,但是,它實現了ViewParent介面,這讓它可以作為View的名義上的父檢視。RootView繼承了Handler類,可以接收事件並分發,Android的所有觸屏事件、按鍵事件、介面重新整理等事件都是通過ViewRoot進行分發的。同時持有WindowSession通過Binder與WMS通訊,同時持有IWindow作為WSM的回撥介面,用於例如touch事件的回撥。

(7)Window

Window是檢視的承載器,內部持有一個 DecorView,而這個DecorView才是 view 的根佈局。Window是一個抽象類,實際在Activity中持有的是其子類PhoneWindow。PhoneWindow中有個內部類DecorView,通過建立DecorView來載入Activity中設定的佈局R.layout.activity_main。Window 通過WindowManager將DecorView載入其中,並將DecorView交給ViewRoot,進行檢視繪製以及其他互動。

(8)DecorView

DecorView是FrameLayout的子類,它可以被認為是Android檢視樹的根節點檢視。DecorView作為頂級View,一般情況下它內部包含一個豎直方向的LinearLayout,在這個LinearLayout裡面有上下三個部分,上面是個ViewStub,延遲載入的檢視(應該是設定ActionBar,根據Theme設定),中間的是標題欄(根據Theme設定,有的佈局沒有),下面的是內容欄。具體情況和Android版本及主體有關,以其中一個佈局為例,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

下面通過程式碼把上面這些類串起來,看看Activity是怎麼顯示出我們設定的佈局來的.

2.程式碼分析

我們只關注過程,不關注細節

(1)從ActivityThread的main方法開始
//ActivityThread.java
public static void main(String[] args) {
          ......
        //就是為主執行緒建立一個looper,用於處理訊息
        Looper.prepareMainLooper();
        //這裡記住一點,在建立ActivityThread的時候也建立了ApplicationThread
        ActivityThread thread = new ActivityThread();
        //這裡是重點,主要是與ActivityManagerService互動
        thread.attach(false);

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

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        //開始輪詢,處理訊息
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

在這個方法裡面涉及到了Handler,具體Handler的原理可以參考:
https://www.jianshu.com/p/d4415033349d
我們這裡著重關注thread.attach(false)方法

private void attach(boolean system) {
       ...
            //AMS執行在一個單獨的程式中,與我們的app程式不一樣,這裡我們通過ActivityManagerNative
            //獲得其遠端代理物件
            final IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
            //mAppThread就是ApplicationThread
            //通過上面對ApplicationThread的介紹,我們知道ApplicationThread和AMS關係密切,這裡就是二者的互動
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        ...
}
(2)第一步中,該關聯的都關聯了.下面該啟動一個Activity了,啟動Activity是由AMS呼叫的,具體反映就是AMS遠端呼叫ApplicationThread的scheduleLaunchActivity方法.然後該方法通過H 的sendMessage方法傳送了一個訊息,我們看看H是怎麼處理這個訊息的.
            case LAUNCH_ACTIVITY: {
                 
                    //這個obj是遠端的AMS傳送過來的,告訴app要啟動哪個Activity
                    //AMS怎麼知道要啟動哪個Activity呢,這是在啟動該Activity傳入的intent引數中獲得的
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                } break;
(3)接著看handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");方法
//ActivityThread.java
 private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
      
        //先看這個方法做了什麼,看完這個方法的解析再往下看
        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
          //performLaunchActivity看完之後就看這裡
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

            if (!r.activity.mFinished && r.startsNotResumed) {
                
                performPauseActivityIfNeeded(r, reason);

                if (r.isPreHoneycomb()) {
                    r.state = oldState;
                }
            }
        } else {
            // If there was an error, for any reason, tell the activity manager to stop us.
            try {
                ActivityManagerNative.getDefault()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }

performLaunchActivity()方法的主要邏輯(這裡只貼出部分主要程式碼):
①通過反射建立Activity

            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            //從這裡我們看到Activity的建立用的是Instrumentation輔助類,這裡用到了反射,我們不去細究
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);

②建立application物件

            //建立application物件,每個應用都有一個application,原來是在這裡建立的.
            //具體的建立者也是Instrumentation
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

③建立window物件

 //建立Window物件,對Activity內部成員變數進行賦值,把Activity和Window進行關聯
                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);

④執行onCreate()方法

            //從中我們看出來是用到了Instrumentation來呼叫Activity的onCreate方法
             if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }

⑤執行onStart()方法

              if (!r.activity.mFinished) {
                    //這個方法點進去,發現是Instrumentation最終呼叫了Activity的onStart()方法
                    activity.performStart();
                    r.stopped = false;
                }

我們著重看下第④個方法
這個方法我們點進去看發現就是回撥了我們建立Activity時的onCreate()方法.我們在這個方法裡面通過setContentView()來設定頁面佈局,下面我們看看這個佈局是怎麼顯示出來的

//Activity.java
public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

原來是呼叫window的setContentView(),通過本文開頭的解釋,我們知道這個Window就是PhoneWindow.

//PhoneWindow.java
 @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) {
            //初始化DecorView,這個view前面已經介紹
            //在這個方法裡面除了建立decorview,也建立了mContentParent
            //mContentParent是我們設定的佈局的父佈局
            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 {
            //沒有轉場動畫就直接把我們設定的layout新增到mContentParent
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

注意:執行完PhoneWindow.setContentView()之後,這時view還沒有顯示,只是新增到DecorView.
上面五個主要方法執行完之後,我們再回到handleLaunchActivity()方法,我們接著看handleResumeActivity()方法

(4)handleResumeActivity()

從這個方法的名字可以看出來,這是要呼叫onResume方法了,也就是到了要顯示我們設定的layout的地方了
我們只看這個方法最核心的部分:
顯示view

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

            //這個時候,Activity.onResume()已經呼叫了,但是現在介面還是不可見的
            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對使用者不可見
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;

                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;

                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //被新增進WindowManager了,但是這個時候,還是不可見的
                    wm.addView(decor, l);
                }

                if (!r.activity.mFinished && willBeVisible
                    && r.activity.mDecor != null && !r.hideForNow) {
                     //在這裡,執行了重要的操作,使得DecorView可見
                     if (r.activity.mVisibleFromClient) {
                            r.activity.makeVisible();
                        }
                    }
            }

看看Activity的makeVisible()方法

//Activity.java
void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

到此DecorView便可見,顯示在螢幕中。但是在這其中,wm.addView(mDecor, getWindow().getAttributes());起到了重要的作用,因為其內部建立了一個ViewRootImpl物件,負責繪製顯示各個子View。
具體來看addView()方法,因為WindowManager是個介面,具體是交給WindowManagerImpl來實現的。由於這裡使用了橋接模式,WindowManagerImpl又交給WindowManagerGlobal 的addView()方法去實現.

//WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
             ......
                 synchronized (mLock) {

                 ViewRootImpl root;
                  //例項化一個ViewRootImpl物件
                 root = new ViewRootImpl(view.getContext(), display);
                 view.setLayoutParams(wparams);

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

             try {
                //將DecorView交給ViewRootImpl 
                root.setView(view, wparams, panelParentView);
            }catch (RuntimeException e) {
            }

            }
 }

看到其中例項化了ViewRootImpl物件,然後呼叫其setView()方法。其中setView()方法經過一些列折騰,最終呼叫了performTraversals()方法,然後依照下圖流程層層呼叫,完成繪製,最終介面才顯示出來。


2485866-2992d118301b702d.png
View繪製流程

至此,結束,長舒一口氣~

相關文章