Android | 帶你探究 LayoutInflater 布局解析原理
作者:彭旭銳
鏈接:
https://juejin.im/post/6886052422260228103
本文由作者授權發(fā)布。
前言
在 Android UI 開發(fā)中,經常需要用到 LayoutInflater 類,它的基本作用是將 xml 布局文件解析成 View / View 樹。除了基本的布局解析功能,LayoutInflater 還可以用于實現(xiàn) 動態(tài)換膚、視圖轉換、屬性轉換 等需求。 在這篇文章里,我將帶你理解 LayoutInflater 的源碼。另外,文末的應試建議也不要錯過哦,如果能幫上忙,請務必點贊加關注,這真的對我非常重要。
目錄

1. 獲取 LayoutInflater 對象
@SystemService(Context.LAYOUT_INFLATER_SERVICE)
public?abstract?class?LayoutInflater?{
????...
}
首先,你要獲得 LayoutInflater ?的實例,由于 LayoutInflater 是抽象類,不能直接創(chuàng)建對象,因此這里總結一下獲取 LayoutInflater 對象的方法。具體如下:
1. View.inflate(...)
public?static?View?inflate(Context?context,?@LayoutRes?int?resource,?ViewGroup?root)?{
????LayoutInflater?factory?=?LayoutInflater.from(context);
????return?factory.inflate(resource,?root);
}
2. Activity#getLayoutInflater()
public?LayoutInflater?getLayoutInflater()?{
????return?getWindow().getLayoutInflater();
}
3. PhoneWindow#getLayoutInflater()
private?LayoutInflater?mLayoutInflater;
public?PhoneWindow(Context?context)?{
????super(context);
????mLayoutInflater?=?LayoutInflater.from(context);
}
public?LayoutInflater?getLayoutInflater()?{
????return?mLayoutInflater;
}
4. LayoutInflater#from(Context)
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;
}
可以看到,前面 3 種方法最后走到LayoutInflater#from(Context),這其實也是平時用的最多的方式。現(xiàn)在,我們看getSystemService(...)內的邏輯:
ContextImpl.java
@Override
public?Object?getSystemService(String?name)?{
????return?SystemServiceRegistry.getSystemService(this,?name);
}
SystemServiceRegistry.java
private?static?final?Map>?SYSTEM_SERVICE_FETCHERS?=?new?ArrayMap>();
static?{
????...
????1.?注冊?Context.LAYOUT_INFLATER_SERVICE?與服務獲取器
????關注點:CachedServiceFetcher
????關注點:PhoneLayoutInflater
????registerService(Context.LAYOUT_INFLATER_SERVICE,?LayoutInflater.class,?new?CachedServiceFetcher()?{
????????@Override
????????public?LayoutInflater?createService(ContextImpl?ctx)?{
????????????注意:getOuterContext(),參數(shù)使用的是 ContextImpl 的代理對象,一般是 Activity
????????????return?new?PhoneLayoutInflater(ctx.getOuterContext());
????????}});
????...
}
2.?根據?name?獲取服務對象
public?static?Object?getSystemService(ContextImpl?ctx,?String?name)?{
????ServiceFetcher>?fetcher?=?SYSTEM_SERVICE_FETCHERS.get(name);
????return?fetcher?!=?null???fetcher.getService(ctx)?:?null;
}
注冊服務與服務獲取器
private?static??void?registerService(String?serviceName,?Class?serviceClass,?ServiceFetcher?serviceFetcher)?{
????SYSTEM_SERVICE_FETCHERS.put(serviceName,?serviceFetcher);
}
3.?服務獲取器創(chuàng)建對象
static?abstract?interface?ServiceFetcher?{
????T?getService(ContextImpl?ctx);
}
可以看到,ContextImpl 內部通過 SystemServiceRegistry 來獲取服務對象,邏輯并不復雜:
1、靜態(tài)代碼塊注冊了 name - ServiceFetcher 的映射 2、根據 name 獲得 ServiceFetcher 3、ServiceFetcher 創(chuàng)建對象
ServiceFetcher 的子類有三種類型,它們的getSystemService()都是線程安全的,主要差別體現(xiàn)在 單例范圍,具體如下:
| ServiceFetcher子類 | 單例范圍 | 描述 | 舉例 |
|---|---|---|---|
| CachedServiceFetcher | ContextImpl域 | / | LayoutInflater、LocationManager等(最多) |
| StaticServiceFetcher | 進程域 | / | InputManager、JobScheduler等 |
| StaticApplicationContextServiceFetcher | 進程域 | 使用 ApplicationContext 創(chuàng)建服務 | ConnectivityManager |
對于 LayoutInflater 來說,服務獲取器是 CachedServiceFetcher 的子類,最終獲得的服務對象為 PhoneLayoutInflater。

這里有一個重點,這句代碼非常隱蔽,要留意:
?return?new?PhoneLayoutInflater(ctx.getOuterContext());
LayoutInflater.java
public?Context?getContext()?{
????return?mContext;?
}
protected?LayoutInflater(Context?context)?{
????mContext?=?context;
????initPrecompiledViews();
}
可以看到,實例化 PhoneLayoutInflater 時使用了 getOuterContext(),也就是參數(shù)使用的是 ContextImpl 的代理對象,一般就是 Activity 了。也就是說,在 Activity / Fragment / View / Dialog 中,獲取LayoutInflater#getContext(),返回的就是 Activity。
小結:
1、獲取 LayoutInflater 對象只有通過 LayoutInflater.from(context),內部委派給Context#getSystemService(...),線程安全;2、使用同一個 Context 對象,獲得的 LayoutInflater 是單例; 3、LayoutInflater 的實現(xiàn)類是 PhoneLayoutInflater。
2. inflate(...) 主流程源碼分析
上一節(jié),我們分析了獲取 LayoutInflater 對象的過程,現(xiàn)在我們可以調用inflate()進行布局解析了。LayoutInflater#inflate(...)有多個重載方法,最終都會調用到:
public?View?inflate(@LayoutRes?int?resource,?@Nullable?ViewGroup?root,?boolean?attachToRoot)?{
????final?Resources?res?=?getContext().getResources();
????1.?解析預編譯的布局
????View?view?=?tryInflatePrecompiled(resource,?res,?root,?attachToRoot);
????if?(view?!=?null)?{
????????return?view;
????}
????2.?構造?XmlPull?解析器?
????XmlResourceParser?parser?=?res.getLayout(resource);
????try?{
????3.?執(zhí)行解析
????????return?inflate(parser,?root,?attachToRoot);
????}?finally?{
????????parser.close();
????}
}
tryInflatePrecompiled(...)是解析預編譯的布局,我后文再說;構造 XmlPull 解析器 XmlResourceParser 執(zhí)行解析,是解析的主流程
提示: 在這里,我剔除了與 XmlPull 相關的代碼,只保留了我們關心的邏輯:
public?View?inflate(XmlPullParser?parser,?@Nullable?ViewGroup?root,?boolean?attachToRoot)?{
????1.?結果變量
????View?result?=?root;
????2.?最外層的標簽
????final?String?name?=?parser.getName();
????3.?
????if?(TAG_MERGE.equals(name))?{
????????3.1?異常
????????if?(root?==?null?||?!attachToRoot)?{
????????????throw?new?InflateException(" ?can?be?used?only?with?a?valid?"
????????????????+?"ViewGroup?root?and?attachToRoot=true");
????????}
????????3.2?遞歸執(zhí)行解析
????????rInflate(parser,?root,?inflaterContext,?attrs,?false);
????}?else?{
????????4.1?創(chuàng)建最外層?View
????????final?View?temp?=?createViewFromTag(root,?name,?inflaterContext,?attrs);
????????
????????ViewGroup.LayoutParams?params?=?null;
????????if?(root?!=?null)?{
????????????4.2?創(chuàng)建匹配的?LayoutParams
????????????params?=?root.generateLayoutParams(attrs);
????????????if?(!attachToRoot)?{
????????????????4.3?如果?attachToRoot?為?false,設置LayoutParams
????????????????temp.setLayoutParams(params);
????????????}
????????}
????????5.?以?temp?為?root,遞歸執(zhí)行解析
????????rInflateChildren(parser,?temp,?attrs,?true);
????????
????????6.?attachToRoot?為?true,addView()
????????if?(root?!=?null?&&?attachToRoot)?{
????????????root.addView(temp,?params);
????????}
????????7.?root?為空?或者?attachToRoot?為?false,返回?temp
????????if?(root?==?null?||?!attachToRoot)?{
????????????result?=?temp;
????????}
????}
????return?result;
}
->?3.2
void?rInflate(XmlPullParser?parser,?View?parent,?Context?context,?AttributeSet?attrs,?boolean?finishInflate)?{
????while(parser?未結束)?{
????????if?(TAG_INCLUDE.equals(name))?{
????????????1)?
????????????if?(parser.getDepth()?==?0)?{
????????????????throw?new?InflateException(" ?cannot?be?the?root?element");
????????????}
????????????parseInclude(parser,?context,?parent,?attrs);
????????}?else?if?(TAG_MERGE.equals(name))?{
????????????2)?
????????????throw?new?InflateException(" ?must?be?the?root?element");
????????}?else?{
????????????3)?創(chuàng)建?View?
????????????final?View?view?=?createViewFromTag(parent,?name,?context,?attrs);
????????????final?ViewGroup?viewGroup?=?(ViewGroup)?parent;
????????????final?ViewGroup.LayoutParams?params?=?viewGroup.generateLayoutParams(attrs);
????????????4)?遞歸
????????????rInflateChildren(parser,?view,?attrs,?true);
????????????5)?添加到視圖樹
????????????viewGroup.addView(view,?params);
????????}
????}
}
->?5.?遞歸執(zhí)行解析
final?void?rInflateChildren(XmlPullParser?parser,?View?parent,?AttributeSet?attrs,
????????????boolean?finishInflate)?throws?XmlPullParserException,?IOException?{
????rInflate(parser,?parent,?parent.getContext(),?attrs,?finishInflate);
}
關于 ,我后文再說。對于參數(shù) root & attachToRoot的不同情況,對應得到的輸出不同,我總結為一張圖:

3. createViewFromTag():從到 View
在 第 2 節(jié) 主流程代碼中,用到了 createViewFromTag(),它負責由
已簡化
View?createViewFromTag(View?parent,?String?name,?Context?context,?AttributeSet?attrs,?boolean?ignoreThemeAttr)?{
????1.?應用?ContextThemeWrapper?以支持?android:theme
????if?(!ignoreThemeAttr)?{
????????final?TypedArray?ta?=?context.obtainStyledAttributes(attrs,?ATTRS_THEME);
????????final?int?themeResId?=?ta.getResourceId(0,?0);
????????if?(themeResId?!=?0)?{
????????????context?=?new?ContextThemeWrapper(context,?themeResId);
????????}
????????ta.recycle();
????}
????2.?先使用?Factory2?/?Factory?實例化?View,相當于攔截
????View?view;
????if?(mFactory2?!=?null)?{
????????view?=?mFactory2.onCreateView(parent,?name,?context,?attrs);
????}?else?if?(mFactory?!=?null)?{
????????view?=?mFactory.onCreateView(name,?context,?attrs);
????}?else?{
????????view?=?null;
????}
????3.?使用?mPrivateFactory?實例化?View,相當于攔截
????if?(view?==?null?&&?mPrivateFactory?!=?null)?{
????????view?=?mPrivateFactory.onCreateView(parent,?name,?context,?attrs);
????}
????4.?調用自身邏輯
????if?(view?==?null)?{
????????if?(-1?==?name.indexOf('.'))?{
????????????4.1??中沒有.
????????????view?=?onCreateView(parent,?name,?attrs);
????????}?else?{
????????????4.2??中有.
????????????view?=?createView(name,?null,?attrs);
????????}
????}
????return?view;?????
}
->?4.2??中有.
構造器方法簽名
static?final?Class>[]?mConstructorSignature?=?new?Class[]?{
????????????Context.class,?AttributeSet.class};
緩存?View?構造器的?Map
private?static?final?HashMap>?sConstructorMap?=
????????????new?HashMap>();
public?final?View?createView(String?name,?String?prefix,?AttributeSet?attrs)?{
????1)?緩存的構造器
????Constructor?extends?View>?constructor?=?sConstructorMap.get(name);
????if?(constructor?!=?null?&&?!verifyClassLoader(constructor))?{
????????constructor?=?null;
????????sConstructorMap.remove(name);
????}
????Class?extends?View>?clazz?=?null;
????2)?新建構造器
????if?(constructor?==?null)?{
????????2.1)?拼接?prefix?+?name?得到類全限定名
????????clazz?=?mContext.getClassLoader().loadClass(prefix?!=?null???(prefix?+?name)?:?name).asSubclass(View.class);
????????2.2)?創(chuàng)建構造器對象
????????constructor?=?clazz.getConstructor(mConstructorSignature);
????????constructor.setAccessible(true);
????????2.3)?緩存到?Map
????????sConstructorMap.put(name,?constructor);
????}
????
????3)?實例化?View?對象
????final?View?view?=?constructor.newInstance(args);
????4)?ViewStub?特殊處理
????if?(view?instanceof?ViewStub)?{
????????//?Use?the?same?context?when?inflating?ViewStub?later.
????????final?ViewStub?viewStub?=?(ViewStub)?view;
????????viewStub.setLayoutInflater(cloneInContext((Context)?args[0]));
????}
????return?view;
}
----------------------------------------------------
->?4.1??中沒有.
PhoneLayoutInflater.java
private?static?final?String[]?sClassPrefixList?=?{
????"android.widget.",
????"android.webkit.",
????"android.app."
};
已簡化
protected?View?onCreateView(String?name,?AttributeSet?attrs)?{
????for?(String?prefix?:?sClassPrefixList)?{
????????View?view?=?createView(name,?prefix,?attrs);
????????????if?(view?!=?null)?{
????????????????return?view;
????????????}
????}
????return?super.onCreateView(name,?attrs);
}
應用 ContextThemeWrapper 以支持 android:theme,這是處理針對特定 View 設置主題;使用 Factory2 / Factory實例化 View,相當于攔截,我后文再說;使用 mPrivateFactory實例化View,相當于攔截,我后文再說;1)?緩存的構造器
2)?新建構造器
3)?實例化?View?對象
4)?ViewStub?特殊處理4.1 中沒有 .,這是處理等標簽,依次嘗試拼接 3 個路徑前綴,進入 3.2 實例化 View、 4.2 中有 .,真正實例化 View 的地方,主要分為 4 步:調用 LayoutInflater 自身邏輯,分為:
小結:
使用 Factory2 接口可以攔截實例化 View 對象的步驟; 實例化 View 的優(yōu)先順序為:Factory2 / Factory -> mPrivateFactory -> PhoneLayoutInflater; 使用反射實例化 View 對象,同時構造器對象做了緩存;

4. Factory2 接口
現(xiàn)在我們來討論Factory2接口,上一節(jié)提到,Factory2可以攔截實例化 View 的步驟,在 LayoutInflater 中有兩個方法可以設置:LayoutInflater.java
方法1:
public?void?setFactory2(Factory2?factory)?{
????if?(mFactorySet)?{
????????關注點:禁止重復設置
????????throw?new?IllegalStateException("A?factory?has?already?been?set?on?this?LayoutInflater");
????}
????if?(factory?==?null)?{
????????throw?new?NullPointerException("Given?factory?can?not?be?null");
????}
????mFactorySet?=?true;
????if?(mFactory?==?null)?{
????????mFactory?=?mFactory2?=?factory;
????}?else?{
????????mFactory?=?mFactory2?=?new?FactoryMerger(factory,?factory,?mFactory,?mFactory2);
????}
}
方法2?@hide
public?void?setPrivateFactory(Factory2?factory)?{
????if?(mPrivateFactory?==?null)?{
????????mPrivateFactory?=?factory;
????}?else?{
????????mPrivateFactory?=?new?FactoryMerger(factory,?factory,?mPrivateFactory,?mPrivateFactory);
????}
}
現(xiàn)在,我們來看源碼中哪里調用這兩個方法:
4.1 setFactory2()
在 AppCompatActivity & AppCompatDialog 中,相關源碼簡化如下:
AppCompatDialog.java
@Override
protected?void?onCreate(Bundle?savedInstanceState)?{
????設置?Factory2
????getDelegate().installViewFactory();
????super.onCreate(savedInstanceState);
????getDelegate().onCreate(savedInstanceState);
}
AppCompatActivity.java
@Override
protected?void?onCreate(@Nullable?Bundle?savedInstanceState)?{
????final?AppCompatDelegate?delegate?=?getDelegate();
????設置?Factory2
????delegate.installViewFactory();
????delegate.onCreate(savedInstanceState);
????夜間主題相關
????if?(delegate.applyDayNight()?&&?mThemeId?!=?0)?{
????????if?(Build.VERSION.SDK_INT?>=?23)?{
????????????onApplyThemeResource(getTheme(),?mThemeId,?false);
????????}?else?{
????????????setTheme(mThemeId);
????????}
????}
????super.onCreate(savedInstanceState);
}
AppCompatDelegateImpl.java
public?void?installViewFactory()?{
????LayoutInflater?layoutInflater?=?LayoutInflater.from(mContext);
????if?(layoutInflater.getFactory()?==?null)?{
????????關注點:設置 Factory2 = this(AppCompatDelegateImpl)
????????LayoutInflaterCompat.setFactory2(layoutInflater,?this);
????}?else?{
????????if?(!(layoutInflater.getFactory2()?instanceof?AppCompatDelegateImpl))?{
????????????Log.i(TAG,?"The?Activity's?LayoutInflater?already?has?a?Factory?installed"
????????????????????????+?"?so?we?can?not?install?AppCompat's");
????????}
????}
}
LayoutInflaterCompat.java
public?static?void?setFactory2(@NonNull?LayoutInflater?inflater,?@NonNull?LayoutInflater.Factory2?factory)?{
????inflater.setFactory2(factory);
????if?(Build.VERSION.SDK_INT?21)?{
????????final?LayoutInflater.Factory?f?=?inflater.getFactory();
????????if?(f?instanceof?LayoutInflater.Factory2)?{
????????????forceSetFactory2(inflater,?(LayoutInflater.Factory2)?f);
????????}?else?{
????????????forceSetFactory2(inflater,?factory);
????????}
????}
}
可以看到,在 AppCompatDialog & AppCompatActivity 初始化時,都通過setFactory2()設置了攔截器,設置的對象是 AppCompatDelegateImpl:
AppCompatDelegateImpl.java
已簡化
class?AppCompatDelegateImpl?extends?AppCompatDelegate
????????implements?MenuBuilder.Callback,?LayoutInflater.Factory2?{
????@Override
????public?final?View?onCreateView(View?parent,?String?name,?Context?context,?AttributeSet?attrs)?{
????????return?createView(parent,?name,?context,?attrs);
????}
????@Override
????public?View?createView(View?parent,?final?String?name,?@NonNull?Context?context,
????????????@NonNull?AttributeSet?attrs)?{
????????if?(mAppCompatViewInflater?==?null)?{
????????????mAppCompatViewInflater?=?new?AppCompatViewInflater();
????????}
????}
????委托給?AppCompatViewInflater?處理
????return?mAppCompatViewInflater.createView(...)
}
AppCompatViewInflater ?與 LayoutInflater 的核心流程差不多,主要差別是前者會將等標簽解析為AppCompatTextView對象:
AppCompatViewInflater.java
final?View?createView(...)?{
????...
????switch?(name)?{
????????case?"TextView":
????????????view?=?createTextView(context,?attrs);
????????????break;
????????...
????????default:
????????????view?=?createView(context,?name,?attrs);
????}
????return?view;
}
@NonNull
protected?AppCompatTextView?createTextView(Context?context,?AttributeSet?attrs)?{
????return?new?AppCompatTextView(context,?attrs);
}
4.2 setPrivateFactory()
setPrivateFactory()是 hide 方法,在 Activity 中調用,相關源碼簡化如下:
Activity.java
final?FragmentController?mFragments?=?FragmentController.createController(new?HostCallbacks());
final?void?attach(Context?context,?ActivityThread?aThread,...)?{
????attachBaseContext(context);
????mFragments.attachHost(null?/*parent*/);
????mWindow?=?new?PhoneWindow(this,?window,?activityConfigCallback);
????mWindow.setWindowControllerCallback(this);
????mWindow.setCallback(this);
????mWindow.setOnWindowDismissedCallback(this);
????關注點:設置 Factory2
????mWindow.getLayoutInflater().setPrivateFactory(this);
????...
}
可以看到,這里設置的 Factory2 其實就是 Activity 本身(this),這說明 Activity 也實現(xiàn)了 Factory2 :
public?class?Activity?extends?ContextThemeWrapper?implements?LayoutInflater.Factory2,...{
????public?View?onCreateView(View?parent,?String?name,?Context?context,?AttributeSet?attrs)?{
????????if?(!"fragment".equals(name))?{
????????????return?onCreateView(name,?context,?attrs);
????????}
????????return?mFragments.onCreateView(parent,?name,?context,?attrs);
????}
}
原來標簽的處理是在這里設置的 Factory2 處理的,關于FragmentController#onCreateView(...)內部如何生成 Fragment 以及返回 View 的邏輯,我們在這篇文章里討論,請關注:《Android | 考點爆滿,帶你全方位圖解 Fragment 源碼》。
小結:
使用 setFactory2() 和 setPrivateFactory() 可以設置 Factory2 接口(攔截器),其中同一個 LayoutInflater 的 setFactory2()不能重復設置,setPrivateFactory() 是 hide 方法;AppCompatDialog & AppCompatActivity 初始化時,調用了 setFactory2(),會將一些轉換為AppCompat版本;Activity 初始化時,調用了 setPrivateFactory(),用來處理標簽。

5. & &
這一節(jié),我們專門來討論的用法與注意事項:
5.1 布局重用
5.2 降低布局層次
5.3 布局懶加載
Editting...
6. 總結
應試建議
理解 獲取 LayoutInflater 對象的方式,知曉 3 種 getSystemService() 單例域的區(qū)別,其中 Context 域是最多的,LayoutInflater 是屬于 Context 域。 重點理解 LayoutInflater 布局解析的 核心流程; Factory2 是一個很實用的接口,需要掌握通過 setFactory2() 攔截布局解析 的技巧。

