Activity 的組成

Activity 系列博客

Activity 的組成

Activity 代表一個(gè)窗口粮彤,實(shí)際上這個(gè)窗口并不是 Activity,而是由 一個(gè) 繼承至Window抽象類的 PhoneWindow 對(duì)象mWindow 來表示的叹俏,mWindowActivity 的成員變量恋拍,主要作用就是負(fù)責(zé)窗口的管理蠢挡。之所以說是管理窗口的嫩与,是因?yàn)樗⒉荒軐⒔缑嬲故境鰜恚宫F(xiàn)界面是由它管理的 DecorView 對(duì)象來完成留特, 在Activity中我們可以通過getWindow().getDecorView()方法來獲取DecorView對(duì)象堂鲤,DecorView 類是 FrameLayout 的子類咙咽, 也是整個(gè) View 樹的根布局(這里也能說明Android事件的分發(fā)是從ViewGroup開始的老玛,如果需要了解更多關(guān)于Android事件分發(fā)問題,可以瀏覽《 Android中的事件分發(fā)機(jī)制》這篇博客)钧敞。DecorView 由三部分構(gòu)成: ActionBar蜡豹、標(biāo)題區(qū)(標(biāo)題區(qū)根據(jù)加載布局的不同,可能會(huì)沒有)和內(nèi)容區(qū)犁享。

Activity 構(gòu)成.png

對(duì)上圖進(jìn)行說明:

  1. 首先是最外層的 Activity余素,一般我們是繼承自 AppCompatActivity,但是 AppCompatActivity 最終也是繼承自 Activity 的炊昆,所以我們的最外框?qū)樱褪?Activity
  2. Activity相當(dāng)于一個(gè)框威根,它包含了Window(實(shí)際上是Window的子類PhoneWindow)來管理窗口凤巨,Activity其他功能這里不做說明
  3. PhoneWindow管理窗口,它包含了DecorView用來渲染和顯示內(nèi)容(包括ActionBar洛搀、TitleBar和ContentParent部分)
  4. DecorViewFrameLayout的子類敢茁,也是整個(gè)視圖的根節(jié)點(diǎn),開發(fā)者寫的布局layout.xml文件最終是被添加到DecorView的ContentParent部分
  5. 當(dāng)在討論Android事件的分發(fā)是從View還是ViewGroup開始時(shí)留美,從DecorView extends FrameLayout extends ViewGroup就能得到Android事件是從ViewGroup開始的(如果需要了解更多關(guān)于Android事件分發(fā)問題彰檬,可以瀏覽《 Android中的事件分發(fā)機(jī)制》這篇博客)

源碼解析

注意:源碼版本為 Android 10(Api 29),不同Android版本可能有一些差別

setContentView()執(zhí)行過程

    // Activity 中的 setContentView() 方法
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

getWindow() 方法返回的是 Window 類型的成員變量 mWindow 谎砾,實(shí)際是 PhoneWindow 對(duì)象逢倍。(mWindow 的創(chuàng)建過程請(qǐng)繼續(xù)往下看,在后面【Activity中PhoneWindow在什么時(shí)候創(chuàng)建的】中會(huì)說到)景图,我們現(xiàn)在繼續(xù)看 PhoneWindow 中的 setContentView() 方法:

@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor(); // 內(nèi)容根布局為null時(shí)较雕,調(diào)用 installDecor() 方法創(chuàng)建 DecorView
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        // 將傳遞的 布局文件id 通過 inflate 添加到 mContentParent 中
        mLayoutInflater.inflate(layoutResID, mContentParent); 
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

通過以上我們知道了,Activity 中調(diào)用 setContentView() 方法實(shí)際上就是調(diào)用了 PhoneWindow 中的 setContentView() 方法挚币,然后創(chuàng)建 DecorView 以及將布局內(nèi)容增加到 DecorView 中過程亮蒋。

DecorView 的創(chuàng)建和 布局內(nèi)容填充的過程

在上面方法中,在初次的時(shí)候妆毕,mContentParent 肯定為 null慎玖,所以會(huì)走 installDecor() 方法創(chuàng)建 DecorView

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        mDecor = generateDecor(-1); // 關(guān)鍵點(diǎn)1:創(chuàng)建 DecorView
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor); // 關(guān)鍵點(diǎn)2:初始化內(nèi)容區(qū)域 mContentParent
        mDecor.makeOptionalFitsSystemWindows();

        // 下面的代碼是設(shè)置標(biāo)題(如果需要并且存在TitleView)和Activity樣式、效果等笛粘,忽略
    }
}
  • 關(guān)鍵點(diǎn)1 generateDecor() 方法創(chuàng)建 DecorView

      protected DecorView generateDecor(int featureId) {
          Context context;
          if (mUseDecorContext) {
              Context applicationContext = getContext().getApplicationContext();
              if (applicationContext == null) {
                  context = getContext();
              } else {
                  context = new DecorContext(applicationContext, getContext());
                  if (mTheme != -1) {
                      context.setTheme(mTheme);
                  }
              }
          } else {
              context = getContext();
          }
          return new DecorView(context, featureId, this, getAttributes());
      }
    

通過 new DecorView() 直接創(chuàng)建 DecorView 對(duì)象趁怔,看看 DecorView 的定義远舅,繼承至 FrameLayout

    public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks
  • 關(guān)鍵點(diǎn)2 generateLayout() 方法初始化內(nèi)容區(qū)域

      protected ViewGroup generateLayout(DecorView decor) {
          // 獲取和設(shè)置Activity各種屬性、樣式痕钢、效果等图柏,忽略
    
          // Inflate the window decor.
    
          int layoutResource;
          // 根據(jù)Activity不同的特征加載不同的布局(這個(gè)布局是系統(tǒng)中的,也是DecorView唯一的子孩子控件)
          int features = getLocalFeatures();
          if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
              layoutResource = R.layout.screen_swipe_dismiss;
              setCloseOnSwipeEnabled(true);
          } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
              if (mIsFloating) {
                  TypedValue res = new TypedValue();
                  getContext().getTheme().resolveAttribute(
                          R.attr.dialogTitleIconsDecorLayout, res, true);
                  layoutResource = res.resourceId;
              } else {
                  layoutResource = R.layout.screen_title_icons;
              }
              removeFeature(FEATURE_ACTION_BAR);
          } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                  && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
              layoutResource = R.layout.screen_progress;
          } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
              if (mIsFloating) {
                  TypedValue res = new TypedValue();
                  getContext().getTheme().resolveAttribute(
                          R.attr.dialogCustomTitleDecorLayout, res, true);
                  layoutResource = res.resourceId;
              } else {
                  layoutResource = R.layout.screen_custom_title;
              }
              removeFeature(FEATURE_ACTION_BAR);
          } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
              if (mIsFloating) {
                  TypedValue res = new TypedValue();
                  getContext().getTheme().resolveAttribute(
                          R.attr.dialogTitleDecorLayout, res, true);
                  layoutResource = res.resourceId;
              } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                  layoutResource = a.getResourceId(
                          R.styleable.Window_windowActionBarFullscreenDecorLayout,
                          R.layout.screen_action_bar);
              } else {
                  layoutResource = R.layout.screen_title;
              }
          } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
              layoutResource = R.layout.screen_simple_overlay_action_mode;
          } else {
              layoutResource = R.layout.screen_simple;
          }
    
          mDecor.startChanging();
          // 關(guān)鍵點(diǎn)任连,解析資源文件并將解析后的View添加到 DecorView 中
          mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    
          // 布局添加到 DecorView 中之后蚤吹,通過 findViewById() 找到用于填充內(nèi)容的 contentParent (FrameLayout 布局)
          ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
          if (contentParent == null) {
              throw new RuntimeException("Window couldn't find content container view");
          }
    
          // 設(shè)置窗體背景等
    
          return contentParent;
      }
    

以上代碼中的資源文件位于 ...\sdk\platforms\android-29\data\res\layout 目錄下(android-29表示對(duì)應(yīng)的android版本),以下是 screen_title.xml 文件中的內(nèi)容:

<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>

其他不同的資源文件具體內(nèi)容可以自行查看随抠,最外層都是 LinearLayout 包裹的裁着,也就就是為什么在上方圖中,DecorView 中為什么先包含了LinearLayout 之后拱她,在 LinearLayout 中包含 ActionView二驰、TitleView、ContentParent 的原因秉沼。

  • 關(guān)鍵點(diǎn)桶雀,解析資源文件并將解析后的View添加到。查看 DecorView 中的 onResourcesLoaded() 方法:

      void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
          if (mBackdropFrameRenderer != null) {
              loadBackgroundDrawablesIfNeeded();
              mBackdropFrameRenderer.onResourcesLoaded(
                      this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                      mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                      getCurrentColor(mNavigationColorViewState));
          }
    
          mDecorCaptionView = createDecorCaptionView(inflater);
          final View root = inflater.inflate(layoutResource, null);
          if (mDecorCaptionView != null) {
              if (mDecorCaptionView.getParent() == null) {
                  addView(mDecorCaptionView,
                          new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
              }
              mDecorCaptionView.addView(root,
                      new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
          } else {
    
              // Put it below the color views.
              addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
          }
          mContentRoot = (ViewGroup) root;
          initializeElevation();
      }
    

當(dāng)完成后唬复,我們回頭繼續(xù)看 PhoneWindow 中的 setContentView() 方法中的 mLayoutInflater.inflate(layoutResID, mContentParent) 這行代碼矗积,這行代碼的作用就是將自定義的界面布局加載到 mContentParent 中。具體的實(shí)現(xiàn)過程敞咧,可以通過《Android inflate解析》《Android inflate實(shí)例解析》 進(jìn)行了解棘捣。

通過以上過程,我們就從源碼方面知道了 Activity 的組成以及 setContentView() 方法的具體執(zhí)行過程了休建。下面做一個(gè)簡(jiǎn)單的總結(jié):

  • setContentView() 方法過程總結(jié):

    Activity 中調(diào)用 setContentView() 方法乍恐, 實(shí)際上是調(diào)用的 PhoneWindow 中的 setContentView() 方法,在 PhoneWindow 中的 setContentView() 方法中會(huì)調(diào)用 installDecor() 方法去創(chuàng)建一個(gè) DecorView 對(duì)象测砂,然后根據(jù)不同情況加載進(jìn)不同的布局(最外層都是 LinearLayout 包裹的,包含的具體控件一定有表示內(nèi)容的 Content茵烈,根據(jù)情況包含 ActionView 和 TitleView)到 DecorView 中;最后通過 LayoutInflater.inflate() 方法將自定義的界面布局加載到 mContentParent 中邑彪。

  • Activity 的組成總結(jié):

    Activity 包含了一個(gè)管理窗口的繼承至 WindowPhoneWindow 對(duì)象瞧毙,而在PhoneWindow 中創(chuàng)建了一個(gè)繼承至 FrameLayout 類的 DecorView 對(duì)象,并且把包含了ActionBar寄症、TitleBar和Content的布局文件通過 addView() 方法添加到了DecorView 中宙彪,當(dāng)開發(fā)者定義了layout.xml布局并調(diào)用了 setContentView() 方法時(shí),就是將自定義的布局加載到了DecorView 中表示 content 的部分(setContentView() 部分具體過程同 【setContentView() 方法過程總結(jié)】)有巧。

Activity中PhoneWindow在什么時(shí)候創(chuàng)建的

通過查看Activity的源碼可以找到PhoneWindow的創(chuàng)建是在Activityattach()方法中(attach()方法在Activity中是onCreate()方法調(diào)用之前系統(tǒng)自動(dòng)調(diào)用的一個(gè)方法)释漆,同時(shí)也獲取到了WindowManager對(duì)象

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) {
    attachBaseContext(context);
    ...
    // 創(chuàng)建PhoneWindow對(duì)象
    mWindow = new PhoneWindow(this);
    ... // 將各種參數(shù)賦給Activity的成員變量
    // 通過PhoneWindow對(duì)象獲取mWindowManager對(duì)象
    mWindowManager = mWindow.getWindowManager();
    ...
}

Activty 系統(tǒng)回調(diào) attach() 方法的過程可以通過以下博客了解:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市篮迎,隨后出現(xiàn)的幾起案子男图,更是在濱河造成了極大的恐慌示姿,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逊笆,死亡現(xiàn)場(chǎng)離奇詭異栈戳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)难裆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門子檀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人乃戈,你說我怎么就攤上這事褂痰。” “怎么了症虑?”我有些...
    開封第一講書人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵缩歪,是天一觀的道長。 經(jīng)常有香客問我谍憔,道長匪蝙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任韵卤,我火速辦了婚禮骗污,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沈条。我一直安慰自己,他們只是感情好诅炉,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開白布蜡歹。 她就那樣靜靜地躺著,像睡著了一般涕烧。 火紅的嫁衣襯著肌膚如雪月而。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評(píng)論 1 312
  • 那天议纯,我揣著相機(jī)與錄音父款,去河邊找鬼。 笑死瞻凤,一個(gè)胖子當(dāng)著我的面吹牛憨攒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播阀参,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼肝集,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蛛壳?” 一聲冷哼從身側(cè)響起杏瞻,我...
    開封第一講書人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤所刀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后捞挥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浮创,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年砌函,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斩披。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胸嘴,死狀恐怖雏掠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情劣像,我是刑警寧澤乡话,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站耳奕,受9級(jí)特大地震影響绑青,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屋群,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一闸婴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芍躏,春花似錦邪乍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至否纬,卻和暖如春吕晌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背临燃。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來泰國打工睛驳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人膜廊。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓乏沸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親溃论。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屎蜓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容