LayoutInflater 源码分析
来源:互联网 发布:android ble数据交互 编辑:程序博客网 时间:2024/06/06 01:02
0. 前言
LayoutInflater(布局填充器)
在安卓开发中,可以说是扮演着相当重要的角色,它让我们的 ListView
、 RecyclerView
等很容易变得多姿多彩,也正是它如此容易的操作,让它不由地多出了一份神秘…这篇博文将基于 Android 6.0
对 LayoutInflater
的源码进行一定分析。
1. 获取实例
protected LayoutInflater(Context context) { mContext = context; }
保护权限的构造方法使得获取 LayoutInflater
并不能直接使用 new
关键字,需要使用静态方法 from(context)
来从 SystemService
取得:
/** * 从给定的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;}
getSystemService(...)
的最终实现在 ContextImpl
调用的 SystemServiceRegistry
类中:
public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); return fetcher != null ? fetcher.getService(ctx) : null;}
也就是从 SYSTEM_SERVICE_FETCHERS
这个常量 HashMap
中获取,那么这些系统服务是什么时候 put
进去的呢?答案就在类开头的静态代码块:
// 静态代码块 第一次访问此类(SystemServiceRegistry)时执行static { // 注册辅助功能服务 registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class, new CachedServiceFetcher<AccessibilityManager>() { @Override public AccessibilityManager createService(ContextImpl ctx) { return AccessibilityManager.getInstance(ctx); }}); ... // 注册布局填充器服务 registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, new CachedServiceFetcher<LayoutInflater>() { @Override public LayoutInflater createService(ContextImpl ctx) { return new PhoneLayoutInflater(ctx.getOuterContext()); } }); ...}// 注册的时候放入SYSTEM_SERVICE_FETCHERSprivate static <T> void registerService(String serviceName, Class<T> serviceClass, ServiceFetcher<T> serviceFetcher) { SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName); SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);}
既然放进去了,那么如何取出呢?
return fetcher != null ? fetcher.getService(ctx) : null;
取出服务是通过 Fetcher
取出的, Fetcher
又是什么呢?
// ServiceFetcher是一个抽象接口static abstract interface ServiceFetcher<T> { T getService(ContextImpl ctx);}// CachedServiceFetcher 抽象类为其实现static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> { ... @Override public final T getService(ContextImpl ctx) { // 取得缓存区 final Object[] cache = ctx.mServiceCache; synchronized (cache) { // 从缓存区中获取 Object service = cache[mCacheIndex]; if (service == null) { // 如果缓存区中没有就调用createService(context) service = createService(ctx); // 并且放入缓存区 cache[mCacheIndex] = service; } return (T)service; } } // 创建实例抽象方法 public abstract T createService(ContextImpl ctx);
好了,这下终于找到 LayoutInflater
的实例化入口了,就在 CachedServiceFetcher<T>
接口的实现中,也就是 registerService(...)
的第三个参数:
new CachedServiceFetcher<LayoutInflater>() { @Override public LayoutInflater createService(ContextImpl ctx) { // 创建实例 return new PhoneLayoutInflater(ctx.getOuterContext()); }}
这里 new
出了 PhoneLayoutInflater
实例,原来 PhoneLayoutInflater
才是我们使用到 LayoutInflater
:
// 继承自LayoutInflaterpublic class PhoneLayoutInflater extends LayoutInflater { ...}
2. 填充视图
inflate(...)
有多个方法重载,此处以参数较为全面的,也是常用的一个进行分析:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot){ // 获取Resources final Resources res = getContext().getResources(); if (DEBUG) { Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" (" + Integer.toHexString(resource) + ")"); } // 传入一个Layout 返回该xml的解析器 final XmlResourceParser parser = res.getLayout(resource); try { // 接着调用含有XmlPullParser参数的重载方法 return inflate(parser, root, attachToRoot); } finally { parser.close(); }}
接下来就来到那个含有XmlPullParser参数的重载方法:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { // 首先启用同步锁确保线程安全 synchronized (mConstructorArgs) { // 开始事务 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; // 参数root将作为返回初值 防止填充错误返回null View result = root; try { // 查找根节点 int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // 跳出循环这意味到达根节点 } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); if (DEBUG) { System.out.println("**************************"); System.out.println("Creating root view: " + name); System.out.println("**************************"); } // 如开头果是<merge />标签 if (TAG_MERGE.equals(name)) { // 那么必须要展示在root布局中 if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } // 遍历子view rInflate(parser, root, inflaterContext, attrs, false); } else { // 填充根ViewGroup final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // 取得当前父容器的LayoutParams params = root.generateLayoutParams(attrs); if (!attachToRoot) { // 如果attachToRoot为false 则应用父容器的LayoutParams temp.setLayoutParams(params); } } if (DEBUG) { System.out.println("-----> start inflating children"); } // 遍历子view rInflateChildren(parser, temp, attrs, true); if (DEBUG) { System.out.println("-----> done inflating children"); } // 如果attachToRoot为false 则调用父容器的addView方法 if (root != null && attachToRoot) { root.addView(temp, params); } // 其它情况直接返回填充好的view if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { InflateException ex = new InflateException(e.getMessage()); ex.initCause(e); throw ex; } catch (Exception e) { InflateException ex = new InflateException( parser.getPositionDescription() + ": " + e.getMessage()); ex.initCause(e); throw ex; } finally { // 保存最后一次的Context 以便它用 mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; } // 事务结束 Trace.traceEnd(Trace.TRACE_TAG_VIEW); return result; }}
思路就是解析 xml
,对参数进行合理的应用,然后遍历子 view
,从而带领出两条路: 一个是填充根 view group
,一个是遍历子 view
,分别是以下两个后面将进行分析的方法:
// 根布局createViewFromTag(root, name, inflaterContext, attrs);// 子布局rInflate(parser, root, inflaterContext, attrs, false);
还有一个重点是,这段代码让我们更加理解了 inflate(...)
后面两个参数 ViewGroup root
和 boolean attachToRoot
的联系:
root
指的是需要将填充好的view所放在的父容器attachToRoot
指的是是否链接到root父容器,如果为true,则调用父容器的addView()方法,否则应用父容器的LayoutParams
另外这里补充几个标签的知识:
<merge/>
、<include/>
和<View Stub/>
标签的使用与区别
<merge/>
将视图组合,减少UI层次,但只能用于根节点,比如include一个layout,这个layout中就可以用merge作为根节点,防止include后造成多层嵌套<include/>
包含一个layout布局,减少代码重复<View Stub/>
用于需要时才加载的布局,比如错误信息的展示
2.1 填充根布局
填充根布局调用 createViewFromTag(...)
来获取 View
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) { // 调用下面的重载方法 return createViewFromTag(parent, name, context, attrs, false);}
其实调用的是含包访问权限的其重载方法:
// 参数parent 需要显示在之上的父容器// 参数name 解析xml中跟节点名字// 参数attrs 主题属性// 参数ignoreThemeAttr 是否忽略主题样式View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { // 如果标签是view if (name.equals("view")) { // 则获取null命名空间中class属性的值 name = attrs.getAttributeValue(null, "class"); } // 如果不忽略主题样式 if (!ignoreThemeAttr) { final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); if (themeResId != 0) { // 使用带有主题样式的ContextThemeWrapper来替换原有的context context = new ContextThemeWrapper(context, themeResId); } ta.recycle(); } // 如果标签是blink if (name.equals(TAG_1995)) { // Let's party like it's 1995! // 1955是什么典故么? // 返回 内部继承自FrameLayout的BlinkLayout return new BlinkLayout(context, attrs); } try { // 使用非空的Factory调用onCreateView(...)方法 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; } if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); } if (view == null) { // 如果Factory都无法正常工作 final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; // 则直接调用内部onCreateView(...)方法 try { // 判断是否包含'.' // contains()方法内部也是通过调用indexOf() if (-1 == name.indexOf('.')) { // 包含表明指定了完整类名 // 比如 android.support.v7.widget.CardView view = onCreateView(parent, name, attrs); } else { // 否则传入null使用默认的前缀 view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; } catch (InflateException e) { throw e; } catch (ClassNotFoundException e) { final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + name); ie.initCause(e); throw ie; } catch (Exception e) { final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + name); ie.initCause(e); throw ie; }}
经过一番折腾,原来真正的创建 View
在 createView(name, null, attrs)
方法中:
public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { // 依旧从缓存区中获取构造器 Constructor<? extends View> constructor = sConstructorMap.get(name); Class<? extends View> clazz = null; try { // 开始事务 Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); if (constructor == null) { // 如果构造器为获取到 就使用反射获取 并强制转换为View的class clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); // 允许客户端对其进行过滤 比如RemoteViews if (mFilter != null && clazz != null) { boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, attrs); } } // 获取构造器并放入缓存 constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(name, constructor); } else { // 过滤 if (mFilter != null) { Boolean allowedState = mFilterMap.get(name); if (allowedState == null) { 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[] args = mConstructorArgs; args[1] = attrs; // 使用构造器创建对象 final View view = constructor.newInstance(args); if (view instanceof ViewStub) { // 如果是ViewStub 就为其设置LayoutInflater 以便后续inflate final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } return view; } catch (NoSuchMethodException e) { ...}
至此,根布局的填充算是完成了。
2.2 遍历子布局
接下来便是遍历子布局:
// 深度优先遍历void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { // 获取深度 final int depth = parser.getDepth(); int type; // 只要到达结束标签 就一直循环 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } else { // 如果是ViewGroup 调用填充父容器用到的方法 final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); // 递归 深度优先 rInflateChildren(parser, view, attrs, true); // 并且添加到viewGroup中 viewGroup.addView(view, params); } } if (finishInflate) { parent.onFinishInflate(); }}
遍历子布局就是一个不断递归的过程,递归完毕parent就被充满了内容,这时返回到 inflate()
方法中,各种绚丽花哨的效果就被填充完毕,就可以随时进行展示了。
总结
LayoutInflater
是一个神奇有力的工具,将它用好,相信你的App一定会更加绚丽多姿,通过此文,希望你对 LayoutInflater
有更近一步的了解,对安卓源码的精妙设计也更感兴趣!
- LayoutInflater源码分析
- LayoutInflater源码分析
- LayoutInflater 源码分析
- 源码分析之LayoutInflater
- 源码分析之LayoutInflater
- LayoutInflater 源码分析
- LayoutInflater源码分析
- LayoutInflater源码分析
- LayoutInflater源码深度分析笔记
- SetContentView与LayoutInflater源码分析
- LayoutInflater源码分析之Inflate
- LayoutInflater源码分析与应用
- LayoutInflater源码分析与应用
- Android View 源码 分析 之 LayoutInFlater
- LayoutInflater效率分析及源码跟踪
- LayoutInflater.inflater()方法及参数源码分析
- Android LayoutInflater 源码分析及个人总结
- Android面试题-LayoutInflater源码分析
- springmvc 多数据源 SSM java redis
- 尼姆博奕(二)
- NoSuchMethodError: org.apache.commons.collections.CollectionUtils.isNotEmpty(Ljava/util/Collection;)
- 进程间通信---共享内存
- Android三种实现定时器的方法
- LayoutInflater 源码分析
- 书店系统(2)--用户模块一
- 了解使用Android ConstraintLayout
- spring component-scan
- ScrollView自动滚动(内部控件内容发生变化时)的问题
- HDU 2647 Reward
- hdoj 1034Candy Sharing Game
- 51Nod-1035-最长的循环节
- 第三个引导页添加button按钮(滑动viewpager)