探究 LayoutInflater 源码布局解析原理

开发 前端
LayoutInflater 的作用就是将XML布局文件实例化为相应的 View 对象,需要通过Activity.getLayoutInflater() 或 Context.getSystemService(Class) 来获取与当前Context已经关联且正确配置的标准LayoutInflater.

[[431614]]

本文转载自微信公众号「Android开发编程」,作者Android开发编程。转载本文请联系Android开发编程公众号。

前言

在开发中,对于 LayoutInflater 的 inflate() 方法,它的作用是把 xml 布局转换为对应的 View 对象,我们几乎天天在用;

今天我们就来分析讲解下;

一、什么是LayoutInflater?

LayoutInflater 的作用就是将XML布局文件实例化为相应的 View 对象,需要通过Activity.getLayoutInflater() 或 Context.getSystemService(Class) 来获取与当前Context已经关联且正确配置的标准LayoutInflater;

  1. @SystemService(Context.LAYOUT_INFLATER_SERVICE) 
  2. public abstract class LayoutInflater { 
  3.     ... 

获取LayoutInflater

1、 View.inflate(...)

  1. public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) { 
  2.     LayoutInflater factory = LayoutInflater.from(context); 
  3.     return factory.inflate(resource, root); 

2、Activity#getLayoutInflater()

  1. Activity.class的源代码: 
  2. public class Activity extends .......  { 
  3. ......... 
  4.  @NonNull 
  5.     public LayoutInflater getLayoutInflater() { 
  6.         return getWindow().getLayoutInflater(); 
  7.     } 
  8. ......... 
  9.  @NonNull 
  10.     public LayoutInflater getLayoutInflater() { 
  11.         return getWindow().getLayoutInflater(); 
  12.     } 
  13. ......... 
  14.    final void attach(.....){ 
  15.           ...... 
  16.            mWindow = new PhoneWindow(this, window, activityConfigCallback); 
  17.           ....... 
  18.     } 
  19. ......... 
  20. PhoneWindow源码: 
  21.    public PhoneWindow(Context context) { 
  22.         super(context); 
  23.         mLayoutInflater = LayoutInflater.from(context); 
  24.     } 

3、PhoneWindow#getLayoutInflater()

  1. private LayoutInflater mLayoutInflater; 
  2. public PhoneWindow(Context context) { 
  3.     super(context); 
  4.     mLayoutInflater = LayoutInflater.from(context); 
  5. public LayoutInflater getLayoutInflater() { 
  6.     return mLayoutInflater; 

4、LayoutInflater#from(Context)

  1. @SystemService(Context.LAYOUT_INFLATER_SERVICE) 
  2. public abstract class LayoutInflater { 
  3.  ..... 
  4.  /** 
  5.      * Obtains the LayoutInflater from the given context. 
  6.      */ 
  7.     public static LayoutInflater from(Context context) { 
  8.         LayoutInflater LayoutInflater = 
  9.                 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
  10.         if (LayoutInflater == null) { 
  11.             throw new AssertionError("LayoutInflater not found."); 
  12.         } 
  13.         return LayoutInflater; 
  14.     } 
  15. ..... 

二、源码分析

1、LayoutInflater#inflate(...)

调用inflate()进行布局解析

  1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { 
  2.     final Resources res = getContext().getResources(); 
  3.     1. 解析预编译的布局 
  4.     View view = tryInflatePrecompiled(resource, res, root, attachToRoot); 
  5.     if (view != null) { 
  6.         return view
  7.     } 
  8.     2. 构造 XmlPull 解析器  
  9.     XmlResourceParser parser = res.getLayout(resource); 
  10.     try { 
  11.     3. 执行解析 
  12.         return inflate(parser, root, attachToRoot); 
  13.     } finally { 
  14.         parser.close(); 
  15.     } 
  • tryInflatePrecompiled(...)是解析预编译的布局;
  • 构造 XmlPull 解析器 XmlResourceParser
  • 执行解析,是解析的主流程

2、inflate

  1. public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { 
  2.     1. 结果变量 
  3.     View result = root; 
  4.     2. 最外层的标签 
  5.     final String name = parser.getName(); 
  6.     3. <merge> 
  7.     if (TAG_MERGE.equals(name)) { 
  8.         3.1 异常 
  9.         if (root == null || !attachToRoot) { 
  10.             throw new InflateException("<merge /> can be used only with a valid " 
  11.                 + "ViewGroup root and attachToRoot=true"); 
  12.         } 
  13.         3.2 递归执行解析 
  14.         rInflate(parser, root, inflaterContext, attrs, false); 
  15.     } else { 
  16.         4.1 创建最外层 View 
  17.         final View temp = createViewFromTag(root, name, inflaterContext, attrs); 
  18.         ViewGroup.LayoutParams params = null
  19.         if (root != null) { 
  20.             4.2 创建匹配的 LayoutParams 
  21.             params = root.generateLayoutParams(attrs); 
  22.             if (!attachToRoot) { 
  23.                 4.3 如果 attachToRoot 为 false,设置LayoutParams 
  24.                 temp.setLayoutParams(params); 
  25.             } 
  26.         } 
  27.         5. 以 temp 为 root,递归执行解析 
  28.         rInflateChildren(parser, temp, attrs, true); 
  29.         6. attachToRoot 为 true,addView() 
  30.         if (root != null && attachToRoot) { 
  31.             root.addView(temp, params); 
  32.         } 
  33.         7. root 为空 或者 attachToRoot 为 false,返回 temp 
  34.         if (root == null || !attachToRoot) { 
  35.             result = temp
  36.         } 
  37.     } 
  38.     return result; 
  39. -> 3.2 
  40. void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) { 
  41.     while(parser 未结束) { 
  42.         if (TAG_INCLUDE.equals(name)) { 
  43.             1) <include> 
  44.             if (parser.getDepth() == 0) { 
  45.                 throw new InflateException("<include /> cannot be the root element"); 
  46.             } 
  47.             parseInclude(parser, context, parent, attrs); 
  48.         } else if (TAG_MERGE.equals(name)) { 
  49.             2) <merge> 
  50.             throw new InflateException("<merge /> must be the root element"); 
  51.         } else { 
  52.             3) 创建 View  
  53.             final View view = createViewFromTag(parent, name, context, attrs); 
  54.             final ViewGroup viewGroup = (ViewGroup) parent; 
  55.             final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); 
  56.             4) 递归 
  57.             rInflateChildren(parser, view, attrs, true); 
  58.             5) 添加到视图树 
  59.             viewGroup.addView(view, params); 
  60.         } 
  61.     } 
  62. -> 5. 递归执行解析 
  63. final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, 
  64.             boolean finishInflate) throws XmlPullParserException, IOException { 
  65.     rInflate(parser, parent, parent.getContext(), attrs, finishInflate); 
  66. 3、createViewFromTag 
  67. createViewFromTag(),它负责由 <tag> 创建 View 对象 
  68.     View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, 
  69.             boolean ignoreThemeAttr) { 
  70.         if (name.equals("view")) { 
  71.             name = attrs.getAttributeValue(null"class"); 
  72.         } 
  73.         // Apply a theme wrapper, if allowed and one is specified. 
  74.         if (!ignoreThemeAttr) { 
  75.             final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); 
  76.             final int themeResId = ta.getResourceId(0, 0); 
  77.             if (themeResId != 0) { 
  78.                 context = new ContextThemeWrapper(context, themeResId); 
  79.             } 
  80.             ta.recycle(); 
  81.         } 
  82.         if (name.equals(TAG_1995)) { 
  83.             // Let's party like it's 1995! 
  84.             return new BlinkLayout(context, attrs); 
  85.         } 
  86.         try { 
  87.             View view
  88.             if (mFactory2 != null) { 
  89.                 // ① 有mFactory2,则调用mFactory2的onCreateView方法 
  90.                 view = mFactory2.onCreateView(parent, name, context, attrs); 
  91.             } else if (mFactory != null) { 
  92.                 // ② 有mFactory,则调用mFactory的onCreateView方法 
  93.                 view = mFactory.onCreateView(name, context, attrs); 
  94.             } else { 
  95.                 view = null
  96.             } 
  97.             if (view == null && mPrivateFactory != null) { 
  98.                 // ③ 有mPrivateFactory,则调用mPrivateFactory的onCreateView方法 
  99.                 view = mPrivateFactory.onCreateView(parent, name, context, attrs); 
  100.             } 
  101.             if (view == null) { 
  102.                 // ④ 走到这步说明三个Factory都没有,则开始自己创建View 
  103.                 final Object lastContext = mConstructorArgs[0]; 
  104.                 mConstructorArgs[0] = context; 
  105.                 try { 
  106.                     if (-1 == name.indexOf('.')) { 
  107.                         // ⑤ 如果Viewname中不包含 '.' 则说明是系统控件,会在接下来的调用链在name前面加上 'android.view.' 
  108.                         view = onCreateView(parent, name, attrs); 
  109.                     } else { 
  110.                         // ⑥ 如果name中包含 '.' 则直接调用createView方法,onCreateView 后续也是调用了createView 
  111.                         view = createView(namenull, attrs); 
  112.                     } 
  113.                 } finally { 
  114.                     mConstructorArgs[0] = lastContext; 
  115.                 } 
  116.             } 
  117.             return view
  118.         } catch (InflateException e) { 
  119.             throw e; 
  120.         }  
  121.     } 
  • createViewFromTag 方法比较简单,首先尝试通过 Factory 来创建View;
  • 如果没有 Factory 的话则通过 createView 来创建View;

3、createView 方法解析

  1. public final View createView(String name, String prefix, AttributeSet attrs) 
  2.             throws ClassNotFoundException, InflateException { 
  3.         Constructor<? extends View> constructor = sConstructorMap.get(name); 
  4.         if (constructor != null && !verifyClassLoader(constructor)) { 
  5.             constructor = null
  6.             sConstructorMap.remove(name); 
  7.         } 
  8.         Class<? extends View> clazz = null
  9.         try { 
  10.             Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); 
  11.             if (constructor == null) { 
  12.                 // Class not found in the cache, see if it's realand try to add it 
  13.                 clazz = mContext.getClassLoader().loadClass( 
  14.                         prefix != null ? (prefix + name) : name).asSubclass(View.class); 
  15.                 if (mFilter != null && clazz != null) { 
  16.                     boolean allowed = mFilter.onLoadClass(clazz); 
  17.                     if (!allowed) { 
  18.                         failNotAllowed(name, prefix, attrs); 
  19.                     } 
  20.                 } 
  21.                 // ① 反射获取这个View的构造器 
  22.                 constructor = clazz.getConstructor(mConstructorSignature); 
  23.                 constructor.setAccessible(true); 
  24.                 // ② 缓存构造器 
  25.                 sConstructorMap.put(name, constructor); 
  26.             } else { 
  27.                 // If we have a filter, apply it to cached constructor 
  28.                 if (mFilter != null) { 
  29.                     // Have we seen this name before? 
  30.                     Boolean allowedState = mFilterMap.get(name); 
  31.                     if (allowedState == null) { 
  32.                         // New class -- remember whether it is allowed 
  33.                         clazz = mContext.getClassLoader().loadClass( 
  34.                                 prefix != null ? (prefix + name) : name).asSubclass(View.class); 
  35.                         boolean allowed = clazz != null && mFilter.onLoadClass(clazz); 
  36.                         mFilterMap.put(name, allowed); 
  37.                         if (!allowed) { 
  38.                             failNotAllowed(name, prefix, attrs); 
  39.                         } 
  40.                     } else if (allowedState.equals(Boolean.FALSE)) { 
  41.                         failNotAllowed(name, prefix, attrs); 
  42.                     } 
  43.                 } 
  44.             } 
  45.             Object lastContext = mConstructorArgs[0]; 
  46.             if (mConstructorArgs[0] == null) { 
  47.                 // Fill in the context if not already within inflation. 
  48.                 mConstructorArgs[0] = mContext; 
  49.             } 
  50.             Object[] args = mConstructorArgs; 
  51.             args[1] = attrs; 
  52.             // ③ 使用反射创建 View 对象,这样一个 View 就被创建出来了 
  53.             final View view = constructor.newInstance(args); 
  54.             if (view instanceof ViewStub) { 
  55.                 // Use the same context when inflating ViewStub later. 
  56.                 final ViewStub viewStub = (ViewStub) view
  57.                 viewStub.setLayoutInflater(cloneInContext((Context) args[0])); 
  58.             } 
  59.             mConstructorArgs[0] = lastContext; 
  60.             return view
  61.         } catch (ClassCastException e) { 
  62.         }  
  63.     } 

createView 方法也比较简单,通过反射来创建的 View 对象;

4、 Factory2 接口

Factory2可以拦截实例化 View 的步骤,在 LayoutInflater 中有两个方法可以设置:

  1. 方法1: 
  2. public void setFactory2(Factory2 factory) { 
  3.     if (mFactorySet) { 
  4.         关注点:禁止重复设置 
  5.         throw new IllegalStateException("A factory has already been set on this LayoutInflater"); 
  6.     } 
  7.     if (factory == null) { 
  8.         throw new NullPointerException("Given factory can not be null"); 
  9.     } 
  10.     mFactorySet = true
  11.     if (mFactory == null) { 
  12.         mFactory = mFactory2 = factory; 
  13.     } else { 
  14.         mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2); 
  15.     } 
  1. 方法2 @hide 
  2. public void setPrivateFactory(Factory2 factory) { 
  3.     if (mPrivateFactory == null) { 
  4.         mPrivateFactory = factory; 
  5.     } else { 
  6.         mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory); 
  7.     } 

使用 setFactory2() 和 setPrivateFactory() 可以设置 Factory2 接口(拦截器),其中同一个 LayoutInflater 的setFactory2()不能重复设置,setPrivateFactory() 是 hide 方法;

总结

  • 通过 XML 的 Pull 解析方式获取 View 的标签;
  • 通过标签以反射的方式来创建 View 对象;
  • 如果是 ViewGroup 的话则会对子 View 遍历并重复以上步骤,然后 add 到父 View 中;
  • 与之相关的几个方法:inflate ——》 rInflate ——》 createViewFromTag ——》 createView ;
  • Factory2 是一个很实用的接口,需要掌握通过 setFactory2() 拦截布局解析的技巧;

 

责任编辑:武晓燕 来源: Android开发编程
相关推荐

2024-05-07 08:28:06

XML代码Java

2024-01-18 08:31:22

go实现gorm框架

2017-05-18 15:02:36

AndroidGC原理JVM内存回收

2022-12-09 08:10:12

kubectl容器源码

2021-09-09 06:55:43

AndroidViewDragHel原理

2024-09-04 11:42:17

Vue3.5源码API

2020-10-10 08:20:27

Spring Boot运行原理代码

2010-08-24 09:05:20

CSS+DIV

2010-09-16 14:42:44

JVM

2014-04-02 17:10:00

虚拟应用工作原理

2019-01-10 08:24:06

2023-08-31 08:12:23

应用场景业务异常HTTP

2021-08-27 07:47:07

Nacos灰度源码

2013-06-08 10:11:31

Java线程池架构

2015-10-10 09:39:42

Java线程池源码解析

2010-09-15 14:00:06

position属性DIV

2021-05-26 11:30:24

Java线程池代码

2012-05-03 08:27:20

Linux进程

2010-09-14 08:53:06

DIVTable

2010-09-15 15:59:11

CSS hack
点赞
收藏

51CTO技术栈公众号