Activity 原理剖析

做爲 Android 四大組件之一的 Activity 個人印象中就是用來展現界面的,在很長一段時間裏,只要說起界面、UI、View 我腦子裏第一個閃過的就是 Activity ,個人理解中一直認爲 界面、UI、View == Activity。其實呢非也!若是你曾經和我同樣,提及 Actvity 就是聊他的生命週期,各個方法背的倒背如流,但也僅限於此而已並無真正的去認識 Activity 的話,那麼請跟我一塊兒從【 Activity 的組成】、【啓動】、【顯示】來從新認識一下 Activityhtml

一 Activity 的本質

首先明確一下 Activity 的基本概念,咱們先來看下 Google 官方對 Activity 給出的定義:java

Activity 是一個應用組件,用戶可與其提供的屏幕進行交互,以執行撥打電話、拍攝照片、發送電子郵件或查看地圖等操做。 每一個 Activity 都會得到一個用於繪製其用戶界面的窗口。窗口一般會充滿屏幕,但也可小於屏幕並浮動在其餘窗口之上。android

讀完了好像也沒整明白。架構

咱們拋開以前對 Activity 的印象,從代碼層面上來看他就是一個普通的 Java 類,本質上並非一個 View 或 ViewGroup,他沒有繼承任何 View 和 ViewGroup 相關類。app

image-activity-1

從源碼中能夠看到 Activity 實現了 Window 、KeyEvent 等一系列的 Callback 以及 Listener 。看到這些大概能夠知道 Activity 的職責至關於一個控制器了,接收各類回調以及處理監聽事件。ide

基於 Google 官方的定義和 Activity 源碼,Activity 的大概職責能夠總結成一張圖:oop

image

1.1 小結

  1. Activity 自己並不具有 View 任何屬性,從代碼層面看就是普通的類。
  2. 自己不是 View ,因此 Activity 自己並不作和 UI、視圖控制相關的事,控制 UI、視圖的另有其人。
  3. 在總體架構中扮演控制器角色,負責控制生命週期和事件處理。

二 Activity 組成剖析

是時候來揭開 Activity 的廬山真面目了。一圖勝千言!先來看一張圖。佈局

image

上圖描述了 Activity 從外到內大概的一個層級結構post

2.1 DecorView

角色

頂層 View
當前 Activity 全部 View 的祖先, 即當前 Activity 視圖樹根節點。ui

做用

  1. 顯示 & 加載佈局。但其自己並不作任何 View 呈現。
  2. 分發 View 層事件,View 層事件由 ViewRoot 分發至 DecorView,再由 DecorView 分發給具體 View。

簡介

  1. DecorView 本質上是一個 FrameLayout ( DecorView 類繼承自 FrameLayout )。

  2. 其內部包含一個豎直佈局的 LinearLayout ,分爲2部分:

    id 爲 title_container 的 TitleBar 。

    id 爲 content 的 Content 。在Activity中經過 setContentView()所設置的佈局文件最終就是被添加到此處的 Content 中。

  3. Content 內部的視圖樹對應的就是 Activity 的佈局文件。

2.2 PhoneWindow

角色

視圖承載器

做用

承載視圖 View 的顯示。

簡介

PhoneWindow 爲 Window 的實現類。每一個 Activity 均會建立一個 Window 用以承載 View ,是視圖真正的控制者。

2.3 ViewRoot

角色

鏈接器

做用

  1. 鏈接 WindowManagerDecorView 的紐帶。
  2. 完成 View 繪製的三大流程: measurelayoutdraw
  3. 向 DecorView 分發用戶發起的 View 層事件,如 KeyEvent,TouchEvent。

簡介

對應實現類爲 ViewRootImpl ,實現了 ViewParent 接口。做爲 WindowManagerGlobal 大部份內部實現的實際實現者,需負責與 WindowManagerService 交互通訊,以調整窗口的位置大小,以及對來自 WindowManagerService 的事件(如窗口尺寸改變等)做出相應的處理。

  1. WindowManagerGlobal 方法實際實現如:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ......
    ViewRootImpl root;
    ......
    root = new ViewRootImpl(view.getContext(), display);
     try {
    //最終實現,調用 ViewRootImpl setView 方法。
        root.setView(view, wparams, panelParentView);
      } catch (RuntimeException e) {
        ......
      }
  }
複製代碼
  1. 與 WindowManagerService 交互通訊:
public ViewRootImpl(Context context, Display display) {
    ......
    mWindowSession = WindowManagerGlobal.getWindowSession();
    ......
  }
  //與 WindowManagerService 創建遠程鏈接
  public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                InputMethodManager imm = InputMethodManager.getInstance();
                IWindowManager windowManager = getWindowManagerService();
                sWindowSession = windowManager.openSession(
                        new IWindowSessionCallback.Stub() {
                            @Override
                            public void onAnimatorScaleChanged(float scale) {
                                ValueAnimator.setDurationScale(scale);
                            }
                        },
                        imm.getClient(), imm.getInputContext());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return sWindowSession;
    }
}
複製代碼

三 Activity 顯示

在簡單分析了 Activity 各個組成元件以後,接下來就是看這些元件是如何完成完整的 Activity 組裝並將 View 呈現到用戶面前。

一圖勝千言:

image

下面跟隨上圖的流程走一遍源碼(爲避免篇幅過長只展現重點代碼)。

3.1 Activity 啓動

這部分主要作的是一些初始化設置的工做,爲 Actvity 最終呈如今用戶面前作準備。

1.初始化 Looper 建立消息循環

作爲 Android 應用程序的入口類,先來看下 ActivityTread 作了什麼工做。

源碼路徑:ActivityTread 類 main 方法。

public static void main(String[] args) {
 
    .......

    Looper.prepareMainLooper();

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

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

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

    .......

    Looper.loop();

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

方法解析:

ActivityTread 的 main 方法初始化了一個 Looper消息循環用以接收主線程的操做消息。

關於Looper消息循環詳細介紹能夠查看 Android Handler淺析一文,這裏就再也不展開。

2.接收 LAUNCH_ACTIVITY 消息

Application 啓動後,主線程會收到 LAUNCH_ACTIVITY 消息,該消息由 ActivityTread 內部類 H (繼承自 Handler) 接收。

源碼路徑:ActivityTread/H 類 handleMessage 方法。

public void handleMessage(Message msg) {
    switch (msg.what) {
        case LAUNCH_ACTIVITY: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
            final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

            r.packageInfo = getPackageInfoNoCheck(
                    r.activityInfo.applicationInfo, r.compatInfo);
            //重點看這個方法
            handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        } break;
}
複製代碼

接收消息後會調用 handleLaunchActivity() 方法,下面重點都在 handleLaunchActivity() 方法裏面了。

源碼路徑:ActivityTread 類 handleLaunchActivity 方法。

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);
        } 
        ......
    }
複製代碼

方法解析:

次方法內部重點看 performLaunchActivity()handleResumeActivity() 方法的調用。

先來看 performLaunchActivity() 方法,此方法就開始了咱們上圖的第 3~9步的流程,直到流程走完,方法調用棧會回退至 handleLaunchActivity() 中繼續往下執行,調用 handleResumeActivity()

3. 建立 Activity 對象

源碼路徑:ActivityTread 類 performLaunchActivity 方法。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        
        .......
        
        Activity activity = null;
        try {
            //經過 Activity 類名構建 Actvity 對象
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            
           ......
            
        } catch (Exception e) {
            .......
        }

        try {
            
            ......
            
            if (activity != null) {
                
                ......
                //爲當前 Activity 初始一個 Window 並設置 WindowManager
                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);

               ......
                
                //經過 Instrumentation 調用 Activity 的 onCreate 方法
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                
               ......
                
        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
           ......
        }

        return activity;
    }
複製代碼

方法解析:

performLaunchActivity 方法作了三件重要的事:

  1. 經過當前 Activity 的類名爲當前的 Activity 建立一個對象。
  2. 調用 activity.attach 方法爲當前 Activity 初始化一個 Window 對象。
  3. 經過 Instrumentation 調用 Activity 的 onCreate 方法。

5.建立 PhoneWindow對象

performLaunchActivity 方法中調用 activity.attach 方法爲當前 Activity 初始化一個 Window 對象,咱們來看下 activity.attach 方法的具體實現。

源碼路徑: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, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) {
        
        ......
        //爲 mWindow 變量賦值,實例化一個 PhoneWindow 
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        
        ......
        //爲 mWindow 設置 WindowManager 
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        ......
        //爲 mWindowManager 變量賦值
        mWindowManager = mWindow.getWindowManager();
        
        .......
    }
複製代碼

attach 方法中爲 Activity 的 mWindow 變量實例化了一個 PhoneWindow 對象,PhoneWindow 是 Window 的一個具體實現,這在上文中已經說過,就再也不贅述了。

6. 執行 Activity onCreate()

performLaunchActivity 方法中經過 Instrumentation 調用 Activity 的 onCreate 方法,該方法的具體實如今 Activity 中。 通常來講咱們都會重寫 Activity 的 onCreate 方法並在裏面調用 setContentView。下面來看下咱們熟悉的 setContentView 裏面作了些什麼。

7. 調用 setContentView()

源碼路徑:Activity 類 setContentView 方法。

public void setContentView(@LayoutRes int layoutResID) {
    //調用 PhoneWindow 的 setContentView
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
複製代碼

Activity 的 setContentView 實際的實現來自 PhoneWindow,能夠看下 getWindow() 方法返回的其實就是步驟5attach 方法賦值的 mWindow 對象。

源碼路徑:Activity 類 getWindow 方法。

public Window getWindow() {
    return mWindow;
 }
複製代碼

既然這樣咱們不妨跟進 PhoneWindow 中一探究竟:

源碼路徑:PhoneWindow 類 setContentView 方法。

@Override
public void setContentView(int layoutResID) {
    // 如mContentParent爲空,建立一個DecroView
    if (mContentParent == null) {
        //初始化 DecroView
        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 {
        //爲 mContentParent 添加布局文件
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Window.Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        //內容改變回調
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}
複製代碼

方法解析:

  1. mContentParent 這個變量就是咱們上文在介紹 DecroView 時提到的 FrameLayout 對應的佈局部分。
  2. installDecor() 方法爲 PhoneWindow 初始化一個 DecroView 對象。
  3. 一番騷操做以後將當前 Activity 的佈局添加至 mContentParent 中。

8. 初始化 DecorView

PhoneWindow 的 setContentView 方法會爲當前的 Window 初始化一個 DecorView。

源碼路徑:PhoneWindow 類 installDecor 方法。

private void installDecor() {
    if (mDecor == null) {
        // 建立 DecorView 對象
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        // 爲 DecorView 的 content 設置佈局格式
        mContentParent = generateLayout(mDecor);

        ......
    }
}
複製代碼

方法解析:

1.調用 generateDecor 方法建立 DecorView 對象,generateDecor 實現很簡單直接。

源碼路徑:PhoneWindow 類 generateDecor 方法。

protected DecorView generateDecor(int featureId) {
    ......
    return new DecorView(context, featureId, this, getAttributes());
}
複製代碼
  1. 調用 generateLayout 方法爲 DecorView 的 content 設置佈局格式。

源碼路徑:PhoneWindow 類 generateLayout 方法。

protected ViewGroup generateLayout(DecorView decor) {
    //獲取窗口樣式信息
    TypedArray a = getWindowStyle();

    ......

    // Inflate the window decor.
    //根據主題樣式,加載窗口布局
    int layoutResource;
    int features = getLocalFeatures();

    ......

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

    // 獲取 Decorview 對應的 content 的 FrameLayout
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    ......
    
    mDecor.finishChanging();

    return contentParent;
}
複製代碼

9. 添加布局到 DecorVIew Content,設置佈局格式

這個步驟的源碼步驟7中已經貼出來了,在 setContentView 方法中:

//爲 mContentParent 添加布局文件
mLayoutInflater.inflate(layoutResID, mContentParent);
複製代碼

到這裏 handleLaunchActivity 方法中的 performLaunchActivity 方法這條線就差很少走完了,如今回到 handleLaunchActivity 方法中繼續往下執行,下面的關鍵方法是 handleResumeActivity

3.2 Activity 顯示

先看 handleResumeActivity 方法的重點代碼。

源碼路徑:ActivityThread 類 handleResumeActivity 方法。

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ......

    // TODO Push resumeArgs into the activity for consideration
    // 調用Activity的onResume()
    r = performResumeActivity(token, clearHide, reason);

    if (r != null) {
        final Activity a = r.activity;

        ......

        if (r.window == null && !a.mFinished && willBeVisible) {
         .......
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //添加 DecorView 到 Window
                    wm.addView(decor, l);
                } 
                ......
        }
        ......
    }
}
複製代碼

10. 執行 Activity onResume()

handleResumeActivity 方法中經過調用 performResumeActivity 方法來執行 Activity onResume() 方法。具體實現有興趣的能夠本身跟進源碼進去看下,這裏就再也不貼源碼了,文章篇幅已經長的頭皮發麻了。

11. 添加 DecorView 到 WindowManager

handleResumeActivity 方法中經過調用 WindowManager 的 addView 方法將以前流程中已賦值的 DecorView 對象添加至當前 Window。

12. 初始化建立 ViewRootImpl

初始化 ViewRootImpl 工做就是在 WindowManager 的 addView 方法中進行的。而 WindowManager 是一個接口,對應的實現類是 WindowManagerImpl,那就去 WindowManagerImpl 中找 addView 的具體實現咯。

源碼路徑:WindowManagerImpl 類 addView 方法。

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
複製代碼

方法解析:

該方法內部直接調用 WindowManagerGlobaladdView 方法,WindowManager 的 addView 方法由 WindowManagerGlobal 代理了。

源碼路徑:WindowManagerGlobal 類 addView 方法。

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

    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
       ......
        //建立ViewRootImpl對象
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

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

        try {
            //WindowManager將DecorView實例對象交給ViewRootImpl 繪製View
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
           ......
            throw e;
        }
    }
}
複製代碼

13. DecorView 對象傳給 ViewRootImpl

WindowManagerGlobal 類 addView 方法在建立 ViewRootImpl 對象後調用其 setView 方法將將 DecorView 實例對象交給 ViewRootImpl 用以繪製 View 。

14. 開始 View 繪製流程,呈現佈局

全部一切準備就緒以後咱們終於開始對咱們 Activity 的佈局進行繪製了。最終將調用 ViewRootImpl 的 performTraversals方法(此方法賊長)開始 View 繪製的三大流程,measure、layout、draw

源碼路徑:ViewRootImpl 類 performTraversals 方法。

private void performTraversals() {
    ...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    //執行測量流程
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    //執行佈局流程
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...
    //執行繪製流程
    performDraw();
}
複製代碼

OK到此 Activity 的部件組裝以及顯示流程就梳理了一個大概,文章篇幅有點長,可是內容不算多,貼了不少代碼,有興趣的能夠跟着個人流程本身跟進源碼看下加深印象。

參考文獻:

Android:手把手帶你清晰梳理自定義View的工做全流程!

Android View的繪製流程

Android中Activity啓動過程探究

相關文章
相關標籤/搜索