Android LayoutInflater & Theme

  1. LayoutInflater創建View的流程:

    • createViewFromTag(View parent, String name(View在xml中的名稱), AttributeSet attrs):
      • 如果name就是”view”, 那麼會從attrs中找出一個名稱爲”class”的xml屬性的value作爲name.
      • 如果定義了mFactory2/mFactory/mPrivateFactory, 那麼會使用這3者的onCreateView(…)來從xml屬性中構造出一個View, 優先級從高到低排列.
      • 如果上面三個都沒有設定或者沒有構造出View, 那麼使用默認的create view的方法:
        • 如果name中沒有”.”, 那麼調用 onCreateView(parent, name, attrs),該函數其實就是在name前補充一個”android.view.”的prefix -> createView(name, “android.view.這就解釋了爲何對Android的原生View在xml佈局文件中名字不需要寫全的原因”, attrs)
        • 如果有”.”, 直接調用createView(name, null, attrs)
      • 在這個過程中, 如果出現了InflateException,直接throw, 如果出現了ClassNotFoundException/其他的Exception,都會被包裝爲一個InflateException 再throw出去.
  2. createView(String name, String prefix, AttributeSet attrs):

    • Constructor <? extends View> constructor = sConstructorMap.get(name): 先根據傳入的name在靜態全局的構造函數緩存中找到相應的構造函數.
      • 如果在緩存中沒有找到:
        • 首先調用context的getClassLoader獲得類加載器, 然後loadClass(綜合考慮prefix和name)load相應的View的Class對象, 對得到的Class對象還會調用asSubclass(View.class), 其意思是試圖將其轉型爲一個View.class的子類型, 如果該類不能成功的話,會拋出ClassCastException.
        • 如果load得到了一個Class對象clazz並且設置了mFilter, 那麼會調用mFilter.onLoadClass(clazz), 如果返回是false,則表示這個Class是不允許被load進來的. 會調用failNotAllowed(…)來拋出一個InflateException.
        • 如果通過了上面的filter, 那麼會調用clazz.getConstructor(mConstructorSignature)得到簽名形式是**mConstructorSignature的構造函數, 這裏mConstructorSignature是mConstructorSignature = new Class[] {
          Context.class, AttributeSet.class}, 即有兩個參數,第一個是context類型,第二個是AttributeSet類型**, 並將此構造函數放入到sConstructorMap這個緩存中.
      • 如果在緩存中找到:
        • 如果設置了mFIlter, 那麼還是會試圖從mFilterMap中根據name找到相應的allowedState(返回true/false), 如果在mFilterMap中沒有對應name的vvalue,那麼需要重新進行一次loadClass,並重新使用mFilters進行檢驗. 並將結果保存在mFilterMap中,同樣,如果沒有通過filter,那麼會throw一個InflateException.
    • 將attrs填充到mConstructorArgs中並將其作爲參數調用之前得到的constructor的newInstance(…)的參數來得到一個View(**構造參數形式爲{
      Context.class, AttributeSet.class}的構造參數**).
    • 對於ViewStub會做特殊的處理,會將ViewStub的Inflater設置爲當前的LayoutInflater.
    • 上面的分析說明,在layout xml被inflate的過程中,View被構造時,被調用的構造函數是簽名爲Class[] {Context.class, AttributeSet.class}的兩個參數的構造函數
  3. 以TextView(Context context, AttributeSet attrs)作爲例子:

    • 在被inflate出來時,TextView的構造函數調用的是TextView(Context context, AttributeSet attrs), 而其內部則是調用了this(context, attrs, com.android.internal.R.attr.textViewStyle(defStyle))這個3參數構造函數.
    • View(Context context, AttributeSet attrs, int defStyleAttr):
      • context: 在這裏重要的是: 通過這個context可以知道自己當前的theme
      • attrs: 從XML文件中得到的此View的屬性.
      • defStyleAttr: 在當前的theme中的一個屬性, 當前的tehme包含了一個指向style資源的引用,會被作用到當前View上.
    • Theme如何影響View的attr? 就是在context.obtainStyledAttributes(attrs, **com.android.internal.R.styleable.View(這個int[] 屬性類表是針對View的,對於其他的View,會有額外的自己的屬性表),
      defStyleAttr, 0)這個函數中:**
      • Context中的此函數調用的其實是**getTheme().(可以看到,其實最終是調用context的Theme對象)**obtainStyledAttributes(…)
      • 而Theme類的obtainStyledAttributes(AttributeSet set,
        int[] attrs, int defStyleAttr, int defStyleRes)函數的註釋則說明: 在決定某個屬性的值時, 有四個方面去考慮, 優先級由高到底:
        • 已經包含在了AttributeSet中的此attr的值(如果有的話)
        • 在AttributeSet中style資源中的此attr的值(如果有的話)
        • 由defStyleAttr和defStyleRes定義的此attr的值(如果有的話)
        • Theme中的此attr的默認值, Theme就是這樣影響的
      • 在其實現裏,可以看到其主體調用是調用AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes, parser != null ? parser.mParseState : 0, attrs(要取得的attr列表), array.mData, array.mIndices); 後者則是native層函數.
  4. 上面用到的getTheme()一般是在ContextThemeWrapper中具體實現的:

    • 如果mTheme不是null,直接返回mTheme.
    • mThemeResource = Resources.selectDefaultTheme(mThemeResource,
      getApplicationInfo() -> mPackageInfo.getApplicationInfo() -> LoadedApk的getApplicationInfo() -> LoadedApk的mApplicationInfo(構造時傳入的值) -> ActivityThread中調用getPackageManager().getApplicationInfo(packageName, PackageManager.GET_SHARED_LIBRARY_FILES, userId) -> PackageParser.generateApplicationInfo(…).targetSdkVersion);
    • 調用initializeTheme():
      • 如果mTheme還沒有被創建, 那麼會調用getResources().newTheme()返回一個Theme對象並賦給mTheme.
      • 然後嘗試獲取mBase.getTheme()(即其內部所包裝的真正的ContextImpl的getTheme()函數返回值), 如果得到了,會調用mTheme,setTo(theme).
      • 最後調用onApplyThemeResource(mTheme, mThemeResource, first) -> theme.applyStyle(resid(就是mThemeResource), true)
  5. ContextImp的getTheme()實現:

    • 也是判斷當前是否已經有創建過mTheme了.
    • mThemeResource = Resources.selectDefaultTheme(mThemeResource,
      getOuterContext().getApplicationInfo().targetSdkVersion);
    • mTheme = mResources.newTheme();
    • mTheme.applyStyle(mThemeResource, true);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章