Android 源碼分析 - Activity的結構分析

  在很早之前,樓主簡單的學習過Activity的結構,可是當時介於各類緣由,只是淺嘗輒止,並無進行深刻的學習。同時,我發現本身今年在畢業以後有點頹廢,再也不有去年那股學習勁兒。通過屢次的自省,發現本身是由於找不到學習的方向而頹廢的。bash

  通過深入反思自我以後,爲了改變如今的情況,也爲了彌補彌補當初學習Activity的遺憾,還爲了提高自身的技術水平,我決定從Activity開始入手,學習並記錄Android framework的知識。app

  本文打算介紹並分析Activity的結構設計,包含:ActivityWindowView相關知識。本文參考資料:異步

  1. Activity、View、Window的理解一篇文章就夠了
  2. 反思|Android LayoutInflater機制的設計與實現
  3. 從Activity建立到View呈現中間發生了什麼?

1. 結構介紹

  大夥兒應該都知道,Activity的結構分爲三層,分別是:ActivityWindowView,不一樣層承擔着不一樣的責任。 ide

  上面的圖簡單的描述了 Activity整個結構的構建流程。這裏我再簡單的介紹一下這幾個部分的做用。

  1. ActivityThread:每一個流程調用起始地點,至於ActivityThread的內容不是本文的重點,因此本文不會過多介紹,後續有相關的文章來介紹。
  2. Activity:至關因而一個管理者,負責建立WindowManagerWindow,同時初始化View
  3. Window:承載着View,同時代Activity處理一切View的事務。
  4. WindowManager:從字面意思來理解是Window的管理,實際上是管理Window上的View,包括addViewremove

  而這幾個角色的對應關係是:ActivityThread全局惟一,整個App進程中只一個實例。ActivityThread能夠對應多個Activity,一個Activity對應一個Window(這裏不考慮Dialog之類的),一個Window對應一個WindowManageroop

  本文打算參考上面的流程圖,分別分析Activity的attach過程、Activity的onCreateView過程和WindowManager的addView過程。佈局

2. Activity的attach

  Activity的attach過程就是一個初始化的過程,分別對Window進行初始化、WindowManager進行初始化。咱們先來看看ActivityThreadhandleLaunchActivity方法:post

public Activity handleLaunchActivity(ActivityClientRecord r,
       // ······

       final Activity a = performLaunchActivity(r, customIntent);
        // ······
   }
複製代碼

  handleLaunchActivity方法主要調用performLaunchActivity來啓動一個Activity,再來看看performLaunchActivity方法:學習

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  // ·······
  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);
   // ·······
}
複製代碼

  在這裏,咱們看到了Activityattach方法的調用,咱們來看看attach方法裏面作了哪些事情。ui

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 = new PhoneWindow(this, window, activityConfigCallback);
       // ······
       mWindow.setWindowManager(
               (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
               mToken, mComponent.flattenToString(),
               (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
       // ······
   }
複製代碼

  attach方法一共作了兩件事情:this

  1. 初始化Window,其中Window的構造方法還作了一些事,好比說,咱們比較關心的LayoutInflater的初始化。
  2. 初始化WindowManager,而且set到Window裏面去。

3. Activity的onCreate

  當初,咱們剛入門Android的時候就知道,在ActivityonCreate方法調用setContentView能夠給當前頁面設置一個佈局。當時有沒有以爲這個很是的神奇,而且對其充滿了好奇,惋惜的是當時本身對整個Android的設計還很陌生,不敢去探究。今天咱們能夠正式進入setContentView的內部了,去一探究竟🤪。

  其實ActivityonCreate過程不只作setContentView操做,其實還作了其餘的事情。從源碼中咱們也能夠看到,好比說,初始化AppCompatDelegate(這裏以AppCompatActivity爲例),設置Theme等。不過這些不是本文的重點,後面我會專門的文章來分析這些過程,本文的重點是setContentView方法。

  ActivitysetContentView沒有作啥事,實際作事的是它的Window。咱們來看看WindowsetContentView方法:

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

  setContentView方法裏面主要是作了以下幾件事:

  1. 經過installDecor方法建立DecorViewmContentParentDecorView的建立在generateDecor方法;mContentParent的建立在generateLayout方法裏面,generateLayout方法裏面有一個重要地方根據屬性的配置設置了Windowflagsfeatures。這裏flags的生效是在DecorViewupdateColorViews方法,會根據flags來計算DecorView的大小;features將那種佈局加載到DecorView,這將影響到Activity默認的佈局樣式。不過我有一點疑惑至今無解,從mContentParent的註釋瞭解到,mContentParent也有多是DecorView,可是我看了installDecor以及內部調用的generateLayout方法,都沒有找到相關可能性。
  2. ContentView加載到mContentParent上去。

  針對上面設置flag生效的解釋我還想補充一下,大夥兒應該都知道,在Android中,咱們能夠經過setFlags或者addFlags來改變一些屬性,可是有沒有想過是怎麼生效的呢?其實過程是很是的簡單,咱們就不一一的追蹤源碼了,直接來看一下調用流程:

  Window#setFlags -> PhoneWindow#dispatchWindowAttributesChanged -> DecorView#updateColorViews

  最終,會根據設置好的Flag來計算DecorView的位置和大小,例如說,咱們設置了FLAG_FULLSCREEN讓界面全屏,最終在updateColorViews方法這麼來計算DecorView的高度:

// If we didn't request fullscreen layout, but we still got it because of the // mForceWindowDrawsStatusBarBackground flag, also consume top inset. boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 && (sysUiVisibility & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 && mForceWindowDrawsStatusBarBackground && mLastTopInset != 0; 複製代碼

  上面的代碼表達的意思很是簡單,就是判斷是否消費狀態欄的高度。至於其餘Flag也是如此,有興趣的同窗能夠看一看。

4. WindowManager的addView

  咱們知道,DecrorViewViewParentViewRootImpl,而View最重要的三大流程就是由ViewRootImpl觸發的。在正式介紹這一塊的知識以前,咱們先來看一個簡單的結構圖:

  這個圖相信你們已經熟悉的不能再熟悉了,不過這裏我仍是將它貼出來。參考上面的圖,咱們能夠知道,通過上面的兩個流程,咱們將 DecorView部分建立完成,如今還須要兩件事須要作:

  1. 初始化ViewRootImpl,而且將ViewRootImplWindow綁定。
  2. 將以前建立好的DecorView添加到ViewRootImpl裏面去。

  咱們先來看看ActivityThreadhandleResumeActivity方法:

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        // ······
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        // ·······
        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);
                }
                // ·······
            }
        }
        // ······

    }
複製代碼

  handleResumeActivity方法裏面主要作了兩件事:

  1. 調用performResumeActivity進而回調ActivityonResume方法。
  2. 調用WindowManageraddView方法,將DecorView添加到ViewRootImpl中去。

  調用的addView方法最終是進入WindowManager的實現類WindowManagerImpl中去,而在WindowManagerImpladdView方法中調用了WindowManagerGlobaladdView方法。   從名字中,咱們就能夠看出來,WindowManagerGlobal是屬於全局的。咱們再來看看WindowManagerGlobaladdView方法:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // ······
        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;
            }
        }
    }
複製代碼

  addView方法主要是作了兩件事:

  1. 初始化ViewRootImpl,而且將該Window相關信息保存起來,包括:ViewRootImplDecorViewLayoutParams
  2. 調用ViewRootImplsetView方法,將DecorView添加到ViewRooImpl,而且觸發View的三大流程。

  咱們都知道,每一個Window都對應着一個DecorView,而從這裏咱們能夠發現,每一個DecorView都對應着一個ViewRootImpl,從而得知,若是是一個Dialog或者其餘新Window的界面,一定有一個新的ViewRootImpl來觸發View的三大流程,而不是由宿主WindowViwRootImpl觸發的。

(1). 爲何在Activity的onResume方法調用Handler的post不能獲取View的寬高呢?

  在回答這個問題以前,咱們應該須要注意的是,這裏是Handler的post方法,而不是View的post方法。ps:View的post方法能拿到View的寬高。

  咱們知道ActivityonResume方法的執行是在ViewRootImpl觸發測量過程以前,同時ViewRootImpl是經過以下的方式來觸發測量過程的:

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
複製代碼

  咱們發現這裏經過Handler post了一個異步消息來進行測量。但是,儘管post的是異步消息,在onResume方法post的消息也先於它執行,由於它在其後post的。因此,在Activity的onResume方法調用Handler的post不能獲取View的寬高。

5. 總結

  到此位置,對Activity的結構分析也差很少,在這裏,咱們作一個簡單的總結。

  1. Activity的結構分爲三層,分別是:ActivityWindowView
  2. 結構的建立過程分爲三步:1. 建立Activity,而且建立與其相關的WindowManagerWindow,對應着是Activityattach方法的調用;2. 初始化DecorViewContentView,對應着的是ActivityonCreate方法;3. 建立ViewRootImpl,而且將DecorView添加到ViewRootImpl中,同時觸發View樹的三大流程。
  3. generateLayout方法裏面,會根據設置不一樣的flags來計算DecorView的大小和位置,計算邏輯在updateColorViews方法裏面;仍是根據設置不一樣的features方法來選擇默認加載到DecorView中,好比說設置了NO_ACTION_BARfeatures,就會加載不帶ActionBar的佈局。
相關文章
相關標籤/搜索