探究 LayoutInflater 源碼布局解析原理
本文轉(zhuǎn)載自微信公眾號「Android開發(fā)編程」,作者Android開發(fā)編程。轉(zhuǎn)載本文請聯(lián)系A(chǔ)ndroid開發(fā)編程公眾號。
前言
在開發(fā)中,對于 LayoutInflater 的 inflate() 方法,它的作用是把 xml 布局轉(zhuǎn)換為對應(yīng)的 View 對象,我們幾乎天天在用;
今天我們就來分析講解下;
一、什么是LayoutInflater?
LayoutInflater 的作用就是將XML布局文件實例化為相應(yīng)的 View 對象,需要通過Activity.getLayoutInflater() 或 Context.getSystemService(Class) 來獲取與當前Context已經(jīng)關(guān)聯(lián)且正確配置的標準LayoutInflater;
- @SystemService(Context.LAYOUT_INFLATER_SERVICE)
- public abstract class LayoutInflater {
- ...
- }
獲取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()
- Activity.class的源代碼:
- public class Activity extends ....... {
- .........
- @NonNull
- public LayoutInflater getLayoutInflater() {
- return getWindow().getLayoutInflater();
- }
- .........
- @NonNull
- public LayoutInflater getLayoutInflater() {
- return getWindow().getLayoutInflater();
- }
- .........
- final void attach(.....){
- ......
- mWindow = new PhoneWindow(this, window, activityConfigCallback);
- .......
- }
- .........
- }
- PhoneWindow源碼:
- public PhoneWindow(Context context) {
- super(context);
- mLayoutInflater = LayoutInflater.from(context);
- }
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)
- @SystemService(Context.LAYOUT_INFLATER_SERVICE)
- public abstract class LayoutInflater {
- .....
- /**
- * Obtains the LayoutInflater from the given 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;
- }
- .....
- }
二、源碼分析
1、LayoutInflater#inflate(...)
調(diào)用inflate()進行布局解析
- 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í)行解析,是解析的主流程
2、inflate
- public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
- 1. 結(jié)果變量
- View result = root;
- 2. 最外層的標簽
- 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);
- }
- 3、createViewFromTag
- createViewFromTag(),它負責由 <tag> 創(chuàng)建 View 對象
- View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
- boolean ignoreThemeAttr) {
- if (name.equals("view")) {
- name = attrs.getAttributeValue(null, "class");
- }
- // Apply a theme wrapper, if allowed and one is specified.
- 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();
- }
- if (name.equals(TAG_1995)) {
- // Let's party like it's 1995!
- return new BlinkLayout(context, attrs);
- }
- try {
- View view;
- if (mFactory2 != null) {
- // ① 有mFactory2,則調(diào)用mFactory2的onCreateView方法
- view = mFactory2.onCreateView(parent, name, context, attrs);
- } else if (mFactory != null) {
- // ② 有mFactory,則調(diào)用mFactory的onCreateView方法
- view = mFactory.onCreateView(name, context, attrs);
- } else {
- view = null;
- }
- if (view == null && mPrivateFactory != null) {
- // ③ 有mPrivateFactory,則調(diào)用mPrivateFactory的onCreateView方法
- view = mPrivateFactory.onCreateView(parent, name, context, attrs);
- }
- if (view == null) {
- // ④ 走到這步說明三個Factory都沒有,則開始自己創(chuàng)建View
- final Object lastContext = mConstructorArgs[0];
- mConstructorArgs[0] = context;
- try {
- if (-1 == name.indexOf('.')) {
- // ⑤ 如果View的name中不包含 '.' 則說明是系統(tǒng)控件,會在接下來的調(diào)用鏈在name前面加上 'android.view.'
- view = onCreateView(parent, name, attrs);
- } else {
- // ⑥ 如果name中包含 '.' 則直接調(diào)用createView方法,onCreateView 后續(xù)也是調(diào)用了createView
- view = createView(name, null, attrs);
- }
- } finally {
- mConstructorArgs[0] = lastContext;
- }
- }
- return view;
- } catch (InflateException e) {
- throw e;
- }
- }
- createViewFromTag 方法比較簡單,首先嘗試通過 Factory 來創(chuàng)建View;
- 如果沒有 Factory 的話則通過 createView 來創(chuàng)建View;
3、createView 方法解析
- public final View createView(String name, String prefix, AttributeSet attrs)
- throws ClassNotFoundException, InflateException {
- Constructor<? extends View> constructor = sConstructorMap.get(name);
- if (constructor != null && !verifyClassLoader(constructor)) {
- constructor = null;
- sConstructorMap.remove(name);
- }
- Class<? extends View> clazz = null;
- try {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
- if (constructor == null) {
- // Class not found in the cache, see if it's real, and try to add it
- clazz = mContext.getClassLoader().loadClass(
- prefix != null ? (prefix + name) : name).asSubclass(View.class);
- if (mFilter != null && clazz != null) {
- boolean allowed = mFilter.onLoadClass(clazz);
- if (!allowed) {
- failNotAllowed(name, prefix, attrs);
- }
- }
- // ① 反射獲取這個View的構(gòu)造器
- constructor = clazz.getConstructor(mConstructorSignature);
- constructor.setAccessible(true);
- // ② 緩存構(gòu)造器
- sConstructorMap.put(name, constructor);
- } else {
- // If we have a filter, apply it to cached constructor
- if (mFilter != null) {
- // Have we seen this name before?
- Boolean allowedState = mFilterMap.get(name);
- if (allowedState == null) {
- // New class -- remember whether it is allowed
- clazz = mContext.getClassLoader().loadClass(
- prefix != null ? (prefix + name) : name).asSubclass(View.class);
- boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
- mFilterMap.put(name, allowed);
- if (!allowed) {
- failNotAllowed(name, prefix, attrs);
- }
- } else if (allowedState.equals(Boolean.FALSE)) {
- failNotAllowed(name, prefix, attrs);
- }
- }
- }
- Object lastContext = mConstructorArgs[0];
- if (mConstructorArgs[0] == null) {
- // Fill in the context if not already within inflation.
- mConstructorArgs[0] = mContext;
- }
- Object[] args = mConstructorArgs;
- args[1] = attrs;
- // ③ 使用反射創(chuàng)建 View 對象,這樣一個 View 就被創(chuàng)建出來了
- final View view = constructor.newInstance(args);
- if (view instanceof ViewStub) {
- // Use the same context when inflating ViewStub later.
- final ViewStub viewStub = (ViewStub) view;
- viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
- }
- mConstructorArgs[0] = lastContext;
- return view;
- } catch (ClassCastException e) {
- }
- }
createView 方法也比較簡單,通過反射來創(chuàng)建的 View 對象;
4、 Factory2 接口
Factory2可以攔截實例化 View 的步驟,在 LayoutInflater 中有兩個方法可以設(shè)置:
- 方法1:
- public void setFactory2(Factory2 factory) {
- if (mFactorySet) {
- 關(guā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);
- }
- }
使用 setFactory2() 和 setPrivateFactory() 可以設(shè)置 Factory2 接口(攔截器),其中同一個 LayoutInflater 的setFactory2()不能重復(fù)設(shè)置,setPrivateFactory() 是 hide 方法;
總結(jié)
- 通過 XML 的 Pull 解析方式獲取 View 的標簽;
- 通過標簽以反射的方式來創(chuàng)建 View 對象;
- 如果是 ViewGroup 的話則會對子 View 遍歷并重復(fù)以上步驟,然后 add 到父 View 中;
- 與之相關(guān)的幾個方法:inflate ——》 rInflate ——》 createViewFromTag ——》 createView ;
- Factory2 是一個很實用的接口,需要掌握通過 setFactory2() 攔截布局解析的技巧;