前言
- 在 Android UI 開發(fā)中,經(jīng)常需要用到 LayoutInflater 類,它的基本作用是將 xml 布局文件解析成 View / View 樹。除了基本的布局解析功能,LayoutInflater 還可以用于實(shí)現(xiàn) 動(dòng)態(tài)換膚、視圖轉(zhuǎn)換、屬性轉(zhuǎn)換 等需求。
- 在這篇文章里,我將帶你理解 LayoutInflater 的源碼。另外,文末的應(yīng)試建議也不要錯(cuò)過哦,如果能幫上忙,請(qǐng)務(wù)必點(diǎn)贊加關(guān)注,這真的對(duì)我非常重要。
相關(guān)文章
- 《Android | 一個(gè)進(jìn)程有多少個(gè) Context 對(duì)象(答對(duì)的不多)》
- 《Android | 帶你探究 LayoutInflater 布局解析原理》
- 《Android | View & Fragment & Window 的 getContext() 一定返回 Activity 嗎?》
- 《Android | 說說從 android:text 到 TextView 的過程》
目錄
1. 獲取 LayoutInflater 對(duì)象
@SystemService(Context.LAYOUT_INFLATER_SERVICE)
public abstract class LayoutInflater {
...
}
首先,你要獲得 LayoutInflater 的實(shí)例,由于 LayoutInflater 是抽象類,不能直接創(chuàng)建對(duì)象,因此這里總結(jié)一下獲取 LayoutInflater 對(duì)象的方法。具體如下:
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)
,這其實(shí)也是平時(shí)用的最多的方式。現(xiàn)在,我們看getSystemService(...)
內(nèi)的邏輯:
ContextImpl.java
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
SystemServiceRegistry.java
private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS = new ArrayMap<String, ServiceFetcher<?>>();
static {
...
1. 注冊(cè) Context.LAYOUT_INFLATER_SERVICE 與服務(wù)獲取器
關(guān)注點(diǎn):CachedServiceFetcher
關(guān)注點(diǎn):PhoneLayoutInflater
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
注意:getOuterContext(),參數(shù)使用的是 ContextImpl 的代理對(duì)象,一般是 Activity
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
...
}
2. 根據(jù) name 獲取服務(wù)對(duì)象
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
注冊(cè)服務(wù)與服務(wù)獲取器
private static <T> void registerService(String serviceName, Class<T> serviceClass, ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
3. 服務(wù)獲取器創(chuàng)建對(duì)象
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}
可以看到,ContextImpl 內(nèi)部通過 SystemServiceRegistry 來獲取服務(wù)對(duì)象,邏輯并不復(fù)雜:
1、靜態(tài)代碼塊注冊(cè)了 name - ServiceFetcher 的映射
2、根據(jù) name 獲得 ServiceFetcher
3、ServiceFetcher 創(chuàng)建對(duì)象
ServiceFetcher 的子類有三種類型,它們的getSystemService()
都是線程安全的,主要差別體現(xiàn)在 單例范圍,具體如下:
ServiceFetcher子類 | 單例范圍 | 描述 | 舉例 |
---|---|---|---|
CachedServiceFetcher | ContextImpl域 | / | LayoutInflater、LocationManager等(最多) |
StaticServiceFetcher | 進(jìn)程域 | / | InputManager、JobScheduler等 |
StaticApplicationContextServiceFetcher | 進(jìn)程域 | 使用 ApplicationContext 創(chuàng)建服務(wù) | ConnectivityManager |
對(duì)于 LayoutInflater 來說,服務(wù)獲取器是 CachedServiceFetcher 的子類,最終獲得的服務(wù)對(duì)象為 PhoneLayoutInflater。
這里有一個(gè)重點(diǎn),這句代碼非常隱蔽,要留意:
return new PhoneLayoutInflater(ctx.getOuterContext());
LayoutInflater.java
public Context getContext() {
return mContext;
}
protected LayoutInflater(Context context) {
mContext = context;
initPrecompiledViews();
}
可以看到,實(shí)例化 PhoneLayoutInflater 時(shí)使用了 getOuterContext(),也就是參數(shù)使用的是 ContextImpl 的代理對(duì)象,一般就是 Activity 了。也就是說,在 Activity / Fragment / View / Dialog 中,獲取LayoutInflater#getContext()
,返回的就是 Activity。
小結(jié):
- 1、獲取 LayoutInflater 對(duì)象只有通過
LayoutInflater.from(context)
,內(nèi)部委派給Context#getSystemService(...)
,線程安全; - 2、使用同一個(gè) Context 對(duì)象,獲得的 LayoutInflater 是單例;
- 3、LayoutInflater 的實(shí)現(xiàn)類是 PhoneLayoutInflater。
2. inflate(...) 主流程源碼分析
上一節(jié),我們分析了獲取 LayoutInflater 對(duì)象的過程,現(xiàn)在我們可以調(diào)用inflate()
進(jìn)行布局解析了。LayoutInflater#inflate(...)
有多個(gè)重載方法,最終都會(huì)調(diào)用到:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
1. 解析預(yù)編譯的布局
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
2. 構(gòu)造 XmlPull 解析器
XmlResourceParser parser = res.getLayout(resource);
try {
3. 執(zhí)行解析
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
-
tryInflatePrecompiled(...)
是解析預(yù)編譯的布局,我后文再說; - 構(gòu)造 XmlPull 解析器 XmlResourceParser
- 執(zhí)行解析,是解析的主流程
提示: 在這里,我剔除了與 XmlPull 相關(guān)的代碼,只保留了我們關(guān)心的邏輯:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
1. 結(jié)果變量
View result = root;
2. 最外層的標(biāo)簽
final String name = parser.getName();
3. <merge>
if (TAG_MERGE.equals(name)) {
3.1 異常
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> 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,設(shè)置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 未結(jié)束) {
if (TAG_INCLUDE.equals(name)) {
1) <include>
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
2) <merge>
throw new InflateException("<merge /> 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);
}
關(guān)于 <include> & <merge>
,我后文再說。對(duì)于參數(shù) root & attachToRoot
的不同情況,對(duì)應(yīng)得到的輸出不同,我總結(jié)為一張圖:
3. createViewFromTag():從 <tag> 到 View
在 第 2 節(jié) 主流程代碼中,用到了 createViewFromTag()
,它負(fù)責(zé)由 <tag> 創(chuàng)建 View 對(duì)象:
已簡(jiǎn)化
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
1. 應(yīng)用 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 實(shí)例化 View,相當(dāng)于攔截
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 實(shí)例化 View,相當(dāng)于攔截
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
4. 調(diào)用自身邏輯
if (view == null) {
if (-1 == name.indexOf('.')) {
4.1 <tag> 中沒有.
view = onCreateView(parent, name, attrs);
} else {
4.2 <tag> 中有.
view = createView(name, null, attrs);
}
}
return view;
}
-> 4.2 <tag> 中有.
構(gòu)造器方法簽名
static final Class<?>[] mConstructorSignature = new Class[] {
Context.class, AttributeSet.class};
緩存 View 構(gòu)造器的 Map
private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
new HashMap<String, Constructor<? extends View>>();
public final View createView(String name, String prefix, AttributeSet attrs) {
1) 緩存的構(gòu)造器
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
2) 新建構(gòu)造器
if (constructor == null) {
2.1) 拼接 prefix + name 得到類全限定名
clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);
2.2) 創(chuàng)建構(gòu)造器對(duì)象
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
2.3) 緩存到 Map
sConstructorMap.put(name, constructor);
}
3) 實(shí)例化 View 對(duì)象
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 <tag> 中沒有.
PhoneLayoutInflater.java
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
已簡(jiǎn)化
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);
}
- 應(yīng)用 ContextThemeWrapper 以支持
android:theme
,這是處理針對(duì)特定 View 設(shè)置主題;
- 應(yīng)用 ContextThemeWrapper 以支持
- 使用
Factory2 / Factory
實(shí)例化 View,相當(dāng)于攔截,我后文再說;
- 使用
- 使用
mPrivateFactory
實(shí)例化View,相當(dāng)于攔截,我后文再說;
- 使用
-
- 調(diào)用 LayoutInflater 自身邏輯,分為:
- 4.1 <tag> 中沒有
.
,這是處理<linearlayout>、<TextView>
等標(biāo)簽,依次嘗試拼接 3 個(gè)路徑前綴,進(jìn)入 3.2 實(shí)例化 View - 4.2 <tag> 中有
.
,真正實(shí)例化 View 的地方,主要分為 4 步:
1) 緩存的構(gòu)造器 2) 新建構(gòu)造器 3) 實(shí)例化 View 對(duì)象 4) ViewStub 特殊處理
小結(jié):
- 使用 Factory2 接口可以攔截實(shí)例化 View 對(duì)象的步驟;
- 實(shí)例化 View 的優(yōu)先順序?yàn)椋篎actory2 / Factory -> mPrivateFactory -> PhoneLayoutInflater;
- 使用反射實(shí)例化 View 對(duì)象,同時(shí)構(gòu)造器對(duì)象做了緩存;
4. Factory2 接口
現(xiàn)在我們來討論Factory2
接口,上一節(jié)提到,Factory2
可以攔截實(shí)例化 View 的步驟,在 LayoutInflater 中有兩個(gè)方法可以設(shè)置:
LayoutInflater.java
方法1:
public void setFactory2(Factory2 factory) {
if (mFactorySet) {
關(guān)注點(diǎn):禁止重復(fù)設(shè)置
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)在,我們來看源碼中哪里調(diào)用這兩個(gè)方法:
4.1 setFactory2()
在 AppCompatActivity & AppCompatDialog 中,相關(guān)源碼簡(jiǎn)化如下:
AppCompatDialog.java
@Override
protected void onCreate(Bundle savedInstanceState) {
設(shè)置 Factory2
getDelegate().installViewFactory();
super.onCreate(savedInstanceState);
getDelegate().onCreate(savedInstanceState);
}
AppCompatActivity.java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
設(shè)置 Factory2
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
夜間主題相關(guān)
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) {
關(guān)注點(diǎn):設(shè)置 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 初始化時(shí),都通過setFactory2()
設(shè)置了攔截器,設(shè)置的對(duì)象是 AppCompatDelegateImpl:
AppCompatDelegateImpl.java
已簡(jiǎn)化
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 的核心流程差不多,主要差別是前者會(huì)將<TextView>
等標(biāo)簽解析為AppCompatTextView
對(duì)象:
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 中調(diào)用,相關(guān)源碼簡(jiǎn)化如下:
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);
關(guān)注點(diǎn):設(shè)置 Factory2
mWindow.getLayoutInflater().setPrivateFactory(this);
...
}
可以看到,這里設(shè)置的 Factory2 其實(shí)就是 Activity 本身(this),這說明 Activity 也實(shí)現(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);
}
}
原來<fragment>
標(biāo)簽的處理是在這里設(shè)置的 Factory2 處理的,關(guān)于FragmentController#onCreateView(...)
內(nèi)部如何生成 Fragment 以及返回 View 的邏輯,我們?cè)谶@篇文章里討論,請(qǐng)關(guān)注:《Android | 考點(diǎn)爆滿,帶你全方位圖解 Fragment 源碼》。
小結(jié):
- 使用 setFactory2() 和 setPrivateFactory() 可以設(shè)置 Factory2 接口(攔截器),其中同一個(gè) LayoutInflater 的
setFactory2()
不能重復(fù)設(shè)置,setPrivateFactory() 是 hide 方法; - AppCompatDialog & AppCompatActivity 初始化時(shí),調(diào)用了
setFactory2()
,會(huì)將一些<tag>
轉(zhuǎn)換為AppCompat
版本; - Activity 初始化時(shí),調(diào)用了
setPrivateFactory()
,用來處理<fragment>
標(biāo)簽。
5. <include> & <merge> & <ViewStub>
這一節(jié),我們專門來討論<include> & <merge> & <ViewStub>
的用法與注意事項(xiàng):
5.1 <include> 布局重用
5.2 <merge> 降低布局層次
5.3 <viewstub> 布局懶加載
Editting...
6. 總結(jié)
- 應(yīng)試建議
- 理解 獲取 LayoutInflater 對(duì)象的方式,知曉 3 種 getSystemService() 單例域的區(qū)別,其中 Context 域是最多的,LayoutInflater 是屬于 Context 域。
- 重點(diǎn)理解 LayoutInflater 布局解析的 核心流程;
- Factory2 是一個(gè)很實(shí)用的接口,需要掌握通過 setFactory2() 攔截布局解析 的技巧。
推薦閱讀
- 密碼學(xué) | Base64是加密算法嗎?
- 算法面試題 | 回溯算法解題框架
- 算法面試題 | 鏈表問題總結(jié)
- Java | 帶你理解 ServiceLoader 的原理與設(shè)計(jì)思想
- Android | 面試必問的 Handler,你確定不看看?
- Android | 帶你理解 NativeAllocationRegistry 的原理與設(shè)計(jì)思想
- 計(jì)算機(jī)組成原理 | Unicode 和 UTF-8是什么關(guān)系?
- 計(jì)算機(jī)組成原理 | 為什么浮點(diǎn)數(shù)運(yùn)算不精確?(阿里筆試)
- 計(jì)算機(jī)網(wǎng)絡(luò) | 圖解 DNS & HTTPDNS 原理