android View的探索(一) : setContentView()過程研究

這里開啟一輪關(guān)于View的研究肉津,主要選擇View開始研究的原因是我愿意(? ??_??)?,碴开!在深入了解View的過程中桥嗤,主要有兩個目的咨堤,其一是整理一下以前對于View的認(rèn)識且糾正一下錯思維誤區(qū),其二是更深入了解其運(yùn)行原理以備日后能夠侃侃而談騙基友顿涣。


下面進(jìn)入正題:setContentView()

首先從最宏觀的角度來說波闹,我們在學(xué)習(xí)android的過程中第一次遇到關(guān)于View方法就是setContentView(),隨著學(xué)習(xí)的加深涛碑,我們發(fā)現(xiàn)當(dāng)代碼中要實例化一個View的時候精堕,很多時候是通過LayoutInflater來進(jìn)行的,那為什么setContentView()不需要通過這種方式蒲障?

為啥不需要歹篓,就是我們這篇文章要研究的內(nèi)容,so read the fucking code揉阎。


   ---- Acvtivity.java ----

   public void setContentView(@LayoutRes int layoutResID) {
       getWindow().setContentView(layoutResID);
       initWindowDecorActionBar();
   }
?
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);
       mFragments.attachHost(null /*parent*/);
       mWindow = new PhoneWindow(this);
       mWindow.setCallback(this);
       mWindow.setOnWindowDismissedCallback(this);
       mWindow.getLayoutInflater().setPrivateFactory(this);
       if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
           mWindow.setSoftInputMode(info.softInputMode);
       }
       if (info.uiOptions != 0) {
           mWindow.setUiOptions(info.uiOptions);
       }
     ....
   }

上述代碼來源于最基礎(chǔ)的Activity類庄撮,我們從最基本的看起即可,在代碼可以看到主要做了兩個操作:

獲取window進(jìn)行設(shè)置contentView

初始化ActionBar

從上方我們可以推測毙籽,初始化contentView的過程洞斯,其實是在window中進(jìn)行的,而這個window的具體實現(xiàn)則是PhoneWindow,在Activity的attach方法中我們可以知道坑赡,因此需要進(jìn)入的PhoneWindow中過去查看setContentView():

   ---- PhoneWindow.java ----

?
// This is the view in which the window contents are placed. It is either
   // mDecor itself, or a child of mDecor where the contents go.
   private ViewGroup mContentParent;
 // This is the top-level view of the window, containing the window decor.
   private DecorView mDecor;
@Override
   public void setContentView(int layoutResID) {
       if (mContentParent == null) {
         //初始化DecorView
           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 {
         //調(diào)用inflate初始化
           mLayoutInflater.inflate(layoutResID, mContentParent);

       }
       mContentParent.requestApplyInsets();
       final Callback cb = getCallback();
       if (cb != null && !isDestroyed()) {
           cb.onContentChanged();
       }
   }

在上述代碼中FEATURE_CONTENT_TRANSITIONS是5.0增加的一個標(biāo)識來識別是否進(jìn)行實現(xiàn)Activity或者Fragment切換時的異常復(fù)雜的動畫效果烙如,我們可以省略來進(jìn)行代碼分析:

installDecor()扭仁,初始化DecorView以及mContentParent操作

調(diào)用LayoutInflater.inflate()方法初始化布局

   ---- PhoneWindow.java ----

?
     private void installDecor() {
       if (mDecor == null) {
         //此方法new了一個DecorView
           mDecor = generateDecor();
           mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
           mDecor.setIsRootNamespace(true);
           if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
               mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
           }
       }
       if (mContentParent == null) {
         //進(jìn)行mContentParent初始化的過程
           mContentParent = generateLayout(mDecor);
?
           // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
           mDecor.makeOptionalFitsSystemWindows();
           ...
   }

在DecorView和mContentParent初始化完成后,系統(tǒng)再調(diào)用inflate()方法來進(jìn)行布局的初始化的過程厅翔,我們需要知道具體是哪個LayoutInflater來完成工作的:

 // PhoneWindow.java ----

public PhoneWindow(Context context) {
       super(context);
       mLayoutInflater = LayoutInflater.from(context);
   }
 //LayoutInflater.java ----

 public static LayoutInflater from(Context context) {
       LayoutInflater LayoutInflater =
               (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

       if (LayoutInflater == null) {
           throw new AssertionError("LayoutInflater not found.");
       }
       return LayoutInflater;
   }
//ContextImpl.java

@Override
   public Object getSystemService(String name) {
       return SystemServiceRegistry.getSystemService(this, name);

   }
//SystemServiceRegistry

registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
               new CachedServiceFetcher<LayoutInflater>() {
           @Override
           public LayoutInflater createService(ContextImpl ctx) {
               return new PhoneLayoutInflater(ctx.getOuterContext());

           }});

我們看到inflater的初始化來自于from方法,這在我們擼代碼的時候其實也經(jīng)常會那么寫搀突,但是點到進(jìn)去會發(fā)現(xiàn)是個抽象類刀闷,而inflater由context.getSystemService()獲取,由于context的具體實現(xiàn)類是ContextImpl仰迁,我們再一次追溯甸昏,知道在SystemServiceRegistry中找到了具體實現(xiàn)類:PhoneLayoutInflater。
接著我們回溯到PhoneWindow的mLayoutInflater.inflate(layoutResID, mContentParent)徐许,其實到這步就已經(jīng)跟我們在布局中使用LayoutInflater的效果一樣了施蜜。
but,我們要有好奇心雌隅,點進(jìn)去看看我們跳回inflate()這個方法,跳回PhoneLayoutInflater中進(jìn)行查看翻默,發(fā)現(xiàn)里面并沒有inflate方法,那只能重新回到LayoutInflater.java中了恰起,發(fā)現(xiàn)找了半天并沒有什么卵用修械,不過了解到LayoutInlfater的具體實現(xiàn)是PhoneLayoutInflater對我們也是很有幫助的= ̄ω ̄=。

//LayoutInflater.java

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
   //獲取資源文件

       final Resources res = getContext().getResources();

       if (DEBUG) {
           Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                   + Integer.toHexString(resource) + ")");
       }
  //獲取xml解析器XmlResourceParser

       final XmlResourceParser parser = res.getLayout(resource);

       try {
          //返回解析后得到的View

           return inflate(parser, root, attachToRoot);

       } finally {
           parser.close();
       }
   }
```
最終還是比較簡單的检盼,我們在inflate中看到肯污,其實獲得XmlResourceParser解析器,然后將布局文件添加到了根布局當(dāng)中吨枉。

等等最后還有一個方法:
```java
//PhoneWindow.java
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {  
  cb.onContentChanged();
}
```
這個callback是干哈用的蹦渣,點進(jìn)去:
```java
 public void setCallback(Callback callback) {

       mCallback = callback;
   }
?
   /**
     * Return the current Callback interface for this window.
     */
   public final Callback getCallback() {
       return mCallback;
   }
```
我們只能找到setCallBack()這個方法的具體位置,我們在上面的activity找到了window=new PhoneWindow()這個實現(xiàn):
```java
//activity
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);
       mFragments.attachHost(null /*parent*/);
       mWindow = new PhoneWindow(this);
       mWindow.setCallback(this);
       mWindow.setOnWindowDismissedCallback(this);
       mWindow.getLayoutInflater().setPrivateFactory(this);
       if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
           mWindow.setSoftInputMode(info.softInputMode);
       }
       if (info.uiOptions != 0) {
           mWindow.setUiOptions(info.uiOptions);
       }
     ....
   }
```
 mWindow.setCallback(this)這句話是不是夠了貌亭!嗯哼柬唯!那么 cb.onContentChanged()這個方法其實就是調(diào)用了在activity中的onContentChanged方法,這是通知ContentView發(fā)生改變的方法圃庭,我們可以重寫進(jìn)行一些你喜歡的操作权逗。

----

####總結(jié)
畫張圖吧,這樣比較好理解:

![zzzz.png](http://upload-images.jianshu.io/upload_images/1838912-699d1a9f08cc5789.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

-----
  如果您發(fā)現(xiàn)哪里寫的不對冤议,麻煩您指出一下斟薇,我會給您一個么么噠,希望一起進(jìn)步恕酸!
                                        祝好~
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末堪滨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蕊温,更是在濱河造成了極大的恐慌袱箱,老刑警劉巖遏乔,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異发笔,居然都是意外死亡盟萨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門了讨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捻激,“玉大人,你說我怎么就攤上這事前计“罚” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵男杈,是天一觀的道長丈屹。 經(jīng)常有香客問我,道長伶棒,這世上最難降的妖魔是什么旺垒? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮肤无,結(jié)果婚禮上袖牙,老公的妹妹穿的比我還像新娘。我一直安慰自己舅锄,他們只是感情好鞭达,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著皇忿,像睡著了一般畴蹭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鳍烁,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天叨襟,我揣著相機(jī)與錄音,去河邊找鬼幔荒。 笑死糊闽,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的爹梁。 我是一名探鬼主播右犹,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼姚垃!你這毒婦竟也來了念链?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掂墓,沒想到半個月后谦纱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡君编,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年跨嘉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吃嘿。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡祠乃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出唠椭,到底是詐尸還是另有隱情,我是刑警寧澤忍饰,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布贪嫂,位于F島的核電站,受9級特大地震影響艾蓝,放射性物質(zhì)發(fā)生泄漏力崇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一赢织、第九天 我趴在偏房一處隱蔽的房頂上張望亮靴。 院中可真熱鬧,春花似錦于置、人聲如沸茧吊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搓侄。三九已至,卻和暖如春话速,著一層夾襖步出監(jiān)牢的瞬間讶踪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工泊交, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留乳讥,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓廓俭,卻偏偏與公主長得像云石,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子研乒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

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