1. 程式人生 > >Android Activity的UI繪製流程之setContentView方法詳解

Android Activity的UI繪製流程之setContentView方法詳解

概述

對於Android開發人員來說,想必對setContentView方法不會陌生,每當我們建立一個Activity時,都會重寫該Activity的onCreate方法,在該方法中我們必須要呼叫setContentView方法來顯示我們指定的佈局或者View。那麼setContentView方法又是如何將我們指定的佈局或者View放入到指定的Activity中顯示出來的呢?

今天這篇部落格主要就是講解Activity的UI繪製流程,而setContentView就是Activity的UI繪製起始過程。

@Override
protected void onCreate(Bundle savedInstanceState) {
   super
.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }

這裡先貼一張圖,便於大家更好的理解這個流程
這裡寫圖片描述

PhoneWindow物件

當我們建立一個Activity時,該類都是預設繼承自Activity方法,所以首先我們需要進入到Activity方法中,然後找到該類的setContentView方法,該方法還有幾個過載的方法,這裡我們主要討論的是定義資源id引數layoutResID的方法,程式碼如下:

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

通過這個方法我們可以看出,在該方法中呼叫了 getWindow().setContentView(layoutResID),那麼這個getWindow又是什麼呢?

mWindow = new PhoneWindow(this, window);

public Window getWindow() {
    return mWindow;
}

setContentView方法

通過原始碼原始碼可知,getWindow方法返回的時一個Window類物件,而Window是系統定義一個抽象類,在Activity中我們例項化的是Window的一個實現類PhoneWindow。所以我們需要進入到PhoneWindow類中,檢視setContentView方法具體實現。

但是通過Eclipse或者Android studio是不能直接檢視到PhoneWindow的原始碼的,要想檢視該類的原始碼需要去sdk的原始碼中查詢,具體路徑為: /sdk/sources/android-21/com/android/internal/policy/impl/PhoneWindow.java。

進入該類之後找到setContentView方法,

@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);
    }
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

installDecor方法

通過原始碼可知,當第一次初始化的時候會執行installDecor方法,該方法原始碼如下:

private void installDecor() {
     if (mDecor == null) {
         mDecor = generateDecor();
             mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
         mDecor.setIsRootNamespace(true);
         if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
             mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
         }
     }
     if (mContentParent == null) {
         mContentParent = generateLayout(mDecor);
     }
}
protected DecorView generateDecor() {
    return new DecorView(getContext(), -1);
}

在installDecor方法中首先會判斷mDecor物件是否為空,如果為空則會呼叫generateDecor方法,generateDecor方法很簡單就是生成一個DecorView物件。

在mDecor物件在執行完成之後又會判斷mContentParent物件是否為空,如果為空則會通過generateLayout方法生成mContentParent,大家注意這個時候mDecor是作為引數傳入到generateLayout方法中,不用質疑在該方法中會對mDecor做一些操作,那麼或許有人會疑問這個DecorView又是什麼呢?

DecorView

DecroView,其實就是PhoneWindow物件的一個內部類,該類繼承自FrameLayout,對於DecorView,這裡先不做過多的詳細介紹,大家只需要知道DecroView其實就是作為Activity的頂級佈局顯示出來的就可以了。

private final class DecorView extends FrameLayout   implements RootViewSurfaceTaker {
    ....
}

generateLayout方法

generateLayout方法如下:

protected ViewGroup generateLayout(DecorView decor) {
  //1,獲取<Application android:theme=""/>, <Activity/>節點指定的themes或者程式碼requestWindowFeature()中指定的Features, 並設定
  TypedArray a = getWindowStyle();
  //...
  
  //2,獲取視窗Features, 設定相應的修飾佈局檔案,這些xml檔案位於frameworks/base/core/res/res/layout下
  int layoutResource;
  int features = getLocalFeatures();
  if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
    if (mIsFloating) {
      TypedValue res = new TypedValue();
      getContext().getTheme().resolveAttribute(com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);
      layoutResource = res.resourceId;
    } else {
      layoutResource = com.android.internal.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 = com.android.internal.R.layout.screen_progress;
  //...
  
  mDecor.startChanging();
  //3, 將上面選定的佈局檔案inflate為View樹,新增到decorView中
  View in = mLayoutInflater.inflate(layoutResource, null);
  decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
  //將視窗修飾佈局檔案中id="@android:id/content"的View賦值給mContentParent, 後續自定義的view/layout都將是其子View
  ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
  if (contentParent == null) {
    throw new RuntimeException("Window couldn't find content container view");
  }
  //...
}

在該方法中,首先會通過getWindowStyle方法獲取window的樣式閒逛屬性並對Window進行一系列的初始化,這裡大家可以看看程式碼,方法開始時這裡通過一系列的判斷呼叫requestFeature方法,對於requestFeature方法想必大家也不陌生,在開發中我們會經常在activity中呼叫該方法來設定FEATURE_NO_TITLE等屬性。

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   getWindow().requestFeature(Window.FEATURE_NO_TITLE);
   setContentView(R.layout.activity_main);
}

而且該方法的呼叫必須是在setContentView方法之前,或許在一開始大家並不明白為什麼要在setContentView方法之前呼叫requestFeature方法,但是現在應該就明白了。因為在setContentView方法中會間接的初始化Window的屬性,這裡會呼叫requestFeature等方法,如果在開發中我們在setContentView方法之後呼叫requestFeature方法改變一些屬性值,那麼此時window的初始化已經完成,在呼叫requestFeature方法就沒有作用了。

接下來,在generateLayout方法中,mLayoutInflater會根據layoutResource建立一個View物件,這個View物件in會被放入到decor中,也就是之前建立的DecroView中。

而layoutResource的獲取則和之前requestFeature方法初始化有關,這裡我們分析下預設情況下layoutResource的值,也就是 layoutResource = R.layout.screen_simple;,這裡我們可以看看screen_simple的佈局,該佈局的檔案位置在sdk/platforms/android-21/data/res/layout/screen_simple.xml。當然,如果大家有興趣也可以看看其他情況下如actionbar下的佈局。


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <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:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

在該佈局中的FrameLayout的id為content,想必大家就不陌生了,我們通過setContentView方法指定的佈局最終就是給這個FrameLayout新增一個子佈局。

最後,對DecorView新增完view物件之後,會通過該一下程式碼獲取一個ViewGroup物件,這個物件就是generateLayout方法按最終返回的ViewGroup物件。

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

最終我們在回到setContentView方法中,在該方法中我們會填充指定給activity的佈局,並且將mContentParent作為view的跟佈局,而這個mContentParent就是通過generateLayout方法建立的。

@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);
    }
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

總結

對於setContentView方法的分析就到此為止,這裡我們總結一下:

  1. 當我們建立一個Activity時,會有一個PhoneWindow的物件被建立,PhoneWindow是抽象類Window的具體實現

  2. 在PhoneWindow中有一個內部類DecorView,DecroView是繼承自FrameLayout,該類是所有應用視窗的根View

  3. 在DecorView中會新增一個具體的View,該View會根據不同的theme和feature而不同,但是有一個共同點就是在該View中會有一個id為”@android:id/content”的FrameLayout存在,該類將作為activity中顯示指定佈局的父佈局存在,也就是activity顯示的佈局江北新增到這個FrameLayout中

最後貼上一張圖,通過hierarchyviewer工具獲取的activity的view的結構,也可以一一印證上面的結論。
這裡寫圖片描述

相關推薦

Android Activity的UI繪製流程setContentView方法

概述 對於Android開發人員來說,想必對setContentView方法不會陌生,每當我們建立一個Activity時,都會重寫該Activity的onCreate方法,在該方法中我們必須要呼叫setContentView方法來顯示我們指定的佈局或者View

Android系統原始碼分析--View繪製流程-setContentView

上一篇分析了四大元件之ContentProvider,這也是四大元件最後一個。因此,從這篇開始我們分析新的篇章--View繪製流程,View繪製流程在Android開發中佔有非常重要的位置,只要有檢視的顯示,都離不開View的繪製,所以瞭解View繪製原理對於應用開發以及系統的學習至關重要。由於View

Android 多線程IntentService 完全

required xmlns 抽象 bitmap 圖片 on() 使用 ecif ati 關聯文章: Android 多線程之HandlerThread 完全詳解 Android 多線程之IntentService 完全詳解 android多線程-AsyncTask之

react-redux connect 方法

Redux 是「React 全家桶」中極為重要的一員,它試圖為 React 應用提供「可預測化的狀態管理」機制。Redux 本身足夠簡單,除了 React,它還能夠支援其他介面框架。所以如果要將 Redux 和 React 結合起來使用,就還需要一些額外的工具,其中最重要的莫過於 react-redux 了。

Android 多執行緒HandlerThread 完全

  之前對執行緒也寫過幾篇文章,不過倒是沒有針對android,因為java與android線上程方面大部分還是相同,不過本篇我們要介紹的是android的專屬類HandlerThread,因為HandlerThread在設定思想上還是挺值得我們學習的,那麼我們下面來

jQuery$.ajax()方法

ajax不管是前端,還是後臺都是要學習的知識點,也是必用知識點。ajax是非同步更新,只需要進行少量的資料互動便可到達頁面的區域性刷下。非常棒。 jquery中的ajax方法引數總是記不住,這裡記錄一下。 1.url:  要求為String型別的引數,(預設為當前

PHP非同步請求fsockopen()方法

正常情況下,PHP執行的都是同步請求,程式碼自上而下依次執行,但有些場景如傳送郵件、執行耗時任務等操作時就不適用於同步請求,只能使用非同步處理請求。 場景要求: 客戶端呼叫伺服器a.php介面,需要執行一個長達10s-20s不等的耗資源操作,假如客戶端響應請求時間為5秒(請求響應超時時間),5s以上無回覆

android事件匯流排EventBus3.0使用方法

2.EventBus的四種ThreadMode(執行緒模型) EventBus3.0有以下四種ThreadMode: POSTING(預設):如果使用事件處理函式指定了執行緒模型為POSTING,那麼該事件在哪個執行緒釋出出來的,事件處理函式就會在這個執行緒中執行,也就是說釋出事件和接收事件在同一個執行緒。

Mapreduec流程Shuffle過程

作為整個Mapreduce中最為神祕,複雜的部分,恰恰是平時業務中最經常接觸的地方。僅僅依靠map和reduce階段的業務程式碼編輯,是不能滿足平時的業務需要的。真正的業務處理中,經常會涉及到自定義partition,sort,groupcomparator等情況。而只有瞭

ApplicationContextgetBean方法

我們知道可以通過ApplicationContext的getBean方法來獲取Spring容器中已初始化的bean。getBean一共有以下四種方法原型:l getBean(String name)l getBean(Class<T> type)l getBean

Android中的Handler的post方法

我們都知道Handler中的post方法,並且也是經常使用它 handler.post(new Runnable(){ @Overridepublic void run() {//do something }}); 用它可以更新一個元件的內容,我們也知道Hanlder

Android開發四大元件Service(篇)

Android開發之四大元件——Service 一、Service 簡介 Service是android系統中的四大元件之一(Activity、Service、BroadcastReceiver、ContentProvider),它跟Activity的級別差不多,區別是Se

Android程式設計學習筆記 ListActivity原始碼

前言 最近在研究PreferenceActivity發現是繼承自ListActivity的,開啟看了下ListActivity的原始碼,發現也不長,就詳細閱讀認識一下。 正文 ListActi

關於FileChannel的獲取方式open方法

mismatch 詳解 -m jdk1.8 eclipse all match try sin FileChannel.open(Path path, OpenOption... options); 例子使用JDK1.8 FileChannel open方法源碼:

一文秒懂!Python字串格式化format方法

format是字串內嵌的一個方法,用於格式化字串。以大括號`{}`來標明被替換的字串,一定程度上與`%`目的一致。但在某些方面更加的方便 ## 1、基本用法 **1、按照{}的順序依次匹配括號中的值** ```python s = "{} is a {}".format('Tom', 'Boy')

Android View 的繪製流程 Layout 和 Draw 過程 (二)

View 的繪製系列文章: Android View 繪製流程之 DecorView 與 ViewRootImpl Android View 的繪製流程之 Measure 過程詳解 (一) Android View 的繪製流程之 Layout 和 Draw 過程詳解 (二) 在上一篇 

Android應用層View繪製流程DecorView與ViewRootImpl

概述 一直對Android中View的整個繪製流程不是很瞭解,View是怎麼新增到Activity當中去的?當View中的內容發生改變的時候是怎樣執行介面的重新整理的?因此,今天準備從原始碼的角度來對View的整個繪製流程來進行分析,原始碼基於API25。由於

android-進階(3)-自定義view(2)-Android中View繪製流程以及相關方法的分析

最近正在學自定義view,這篇文章主要講view的繪製流程和一些相關的方法,淺顯易懂,寫的非常好,忍不住就轉載了。             前言: 本文是我讀《Android核心剖析》第13章----View工作原理總結而成的,在此膜拜下作者 。

[js高手路]原型對象(prototype)與原型鏈相關屬性與方法

隱式 之前 username tar uname create pro getproto .get 一,instanceof: instanceof檢測左側的__proto__原型鏈上,是否存在右側的prototype原型. 我在之前的兩篇文章 [js高手之路]構造函數的基

Go語言方法

go語言 方法方法是與對象實例綁定的特殊函數。用於維護和展示對象自身的狀態。對象是內斂的。普通函數則專註與算法流程,通過接受參數來完成特定的邏輯運算,並返回最終結果,方法是有關聯狀態的,函數通常是沒有的。方法和函數定義語法區別在於前者實例接受參數,編譯器以此確定方法所屬的類型。在一些語言中盡管沒有定義,但是函