Android Xml转换为View过程详解
作者:载渔之洲
Android布局文件Xml
通过setContentView(@LayoutResint layoutResID)或者LayoutInflater.from(context).inflate(int ResID)转换为Java对象,开发工具Android Studio 提供的预览功能,开发过程中界面和业务可以并行开发,提高了开发效率。以下分析过程是基于 Android API 25 Platform 源码,并以setContentView()方法为入口。
Xml 转成 Java 对象方式
1、Activity中setContentView(@LayoutResint layoutResID)方法;
该方法都会被每个继承 android.app.Activity 的子类重载;
2、LayoutInflater.from(Context context).inflate(@LayoutResint resource, ...)。
一般使用 Activity
- 1). android.support.v7.app.AppCompatActivity
- 2). android.support.v4.app.FragmentActivity
- 3). android.app.Activity
- 4). 其他 Activity
从Activity中setContentView()方法开始
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
跟踪getWindow()源码
public Window getWindow() { return mWindow; }
mWindow在Activity.java中attach()方法里初始化
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window) { ... mWindow = new PhoneWindow(this, window); ... }
所以Window.java的实现类是PhoneWindow.java类,@hide
代表 PhoneWindow
的源码在 sdk 里面是隐藏的,查看 PhoneWindow.setContentView(layoutResID)
如下:
@Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }
从上面代码可以发现如果没有转场动画时,执行的是
mLayoutInflater.inflate(layoutResID, mContentParent);
在PhoneWindow构造函数里发现mLayoutInflater对象赋值代码如下:
public PhoneWindow(Context context) { super(context); mLayoutInflater = LayoutInflater.from(context); }
所以可以得出一个结论 Activity.setContentView(resId) 最终还是使用LayoutInflater.from(context).inflate(resId, ……)。
在看下其他activity android.support.v7.app.AppCompatActivity
和android.support.v4.app.FragmentActivity
发现 android.support.v4.app.FragmentActivity
没有重载 android.app.Activity.setContentView(resId)
但是 android.support.v7.app.AppCompatActivity
重载了
@Override public void setContentView(@LayoutRes int layoutResID) { getDelegate().setContentView(layoutResID); }
getDelegate()源代码最终会调用到 android.support.v7.app.AppCompatDelegateImplV9.setContentView(resId)
@Override public void setContentView(int resId) { ensureSubDecor(); ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); contentParent.removeAllViews(); LayoutInflater.from(mContext).inflate(resId, contentParent); mOriginalWindowCallback.onContentChanged(); }
因此xml 转成 Java 对象是通过LayoutInflater
的inflate()
方法来完成的
LayoutInflater 对象获取方式
关键字abstract
,LayoutInflater
是一个抽象类,不能实例化,LayoutInflater
对象获取的方式有:
1). 在 Activity 中通过 getLayoutInflater() 获取
2). LayoutInflater里静态方法from(context) 获取
3). context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) 获取
如 Activity 的 getLayoutInflater()
/** * Convenience for calling * {@link android.view.Window#getLayoutInflater}. */ @NonNull public LayoutInflater getLayoutInflater() { return getWindow().getLayoutInflater(); }
可以看出 Activity 通过 getLayoutInflater() 获取的是 PhoneWindow 的 mLayoutInflater。
LayoutInflater.from(context)
/** * 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; }
所以LayoutInflater对象都是通过服务获取 LayoutInflater 实例对象
跟踪下源码context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);Context
的实现类是ContextImpl.java
,如:
@Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); }
跟踪 SystemServiceRegistry.java
/** * Gets a system service from a given context. */ public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); return fetcher != null ? fetcher.getService(ctx) : null; } /** * Statically registers a system service with the context. * This method must be called during static initialization only. */ private static <T> void registerService(String serviceName, Class<T> serviceClass, ServiceFetcher<T> serviceFetcher) { SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName); SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher); }
在 SystemServiceRegistry 类,这里只注册各种系统服务的处,通过 Context.LAYOUT_INFLATER_SERVICE找到注册代码地方,如下:
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, new CachedServiceFetcher<LayoutInflater>() { @Override public LayoutInflater createService(ContextImpl ctx) { return new PhoneLayoutInflater(ctx.getOuterContext()); }});
通过以上代码发现 LayoutInflater
的实现类是 PhoneLayoutInflater
LayoutInflater 读取 Xml 文件并创建 View 对象,继续跟踪LayoutInflater.inflate()方法
1).View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
2).View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
重点看第二个方法,代码如下:
/** * Inflate a new view hierarchy from the specified xml resource. Throws * {@link InflateException} if there is an error. * * @param resource ID for an XML layout resource to load (e.g., * <code>R.layout.main_page</code>) * @param root Optional view to be the parent of the generated hierarchy (if * <em>attachToRoot</em> is true), or else simply an object that * provides a set of LayoutParams values for root of the returned * hierarchy (if <em>attachToRoot</em> is false.) * @param attachToRoot Whether the inflated hierarchy should be attached to * the root parameter? If false, root is only used to create the * correct subclass of LayoutParams for the root view in the XML. * @return The root View of the inflated hierarchy. If root was supplied and * attachToRoot is true, this is root; otherwise it is the root of * the inflated XML file. */ public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); if (DEBUG) { Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" (" + Integer.toHexString(resource) + ")"); } final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }
根据以上代码逻辑,首先通过 resource
对象把 resId 指向的 xml 文件转换为XmlResourceParser
,然后执行inflate(parser, root, attachToRoot)
方法,核心代码如下:
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; View result = root; try { // Look for the root node. ... final String name = parser.getName(); //分析1 if (TAG_MERGE.equals(name)) { if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } //分析2 rInflate(parser, root, inflaterContext, attrs, false); } else { //分析3 // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } //分析4 // Inflate all children under temp against its context. rInflateChildren(parser, temp, attrs, true); // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { result = temp; } } //异常处理部分 return result; } }
分析
分析1:
如果 Xml 根标签是 TAG_MERGE(即merge)
,则 root 不能为空, attachToRoot 为 true,在执行rInflate(parser, root, inflaterContext, attrs, false)
分析2 rInflate()方法
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 { 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.addView(view, params); } } if (finishInflate) { parent.onFinishInflate(); } }
rInflate(parser, root, inflaterContext, attrs, false) 总结如下
1). while遍历该节点的子节点
2). 子节点有 "requestFocus"、"tag"、""、"include"
3). 子节点不能是 "merge"
4). 子节点的其他情况,则是各种 View 的标签
5). View 标签和 "include" 标签会创建 View 对象
6). 遍历结束以后执行 parent.onFinishInflate()
如果子节点是 include,则执行 parseInclude() ,parseInclude() 的源码和 inflate(parser, root, attachToRoot) 类似,都是读取xml对应的文件,转换成 XmlResourceParser 然后遍历里的标签。
createViewFromTag(parent, name, context, attrs)
负责创建 View 对象
分析3、4
1). root 不为 null,才会读取 xml 跟布局的 params 属性;
2). attachToRoot 为 True ,返回的是 root 对象。否则返回的是 xml 创建的根标签指定的 View
3). 调用了 createViewFromTag(root, name, inflaterContext, attrs) 方法创建 View
4). rInflateChildren()->rInflate();和 分析2一样的
综上所述,LayoutInflater.createViewFromTag()
创建 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) { 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) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; //异常处理 ... }
mFactory2
、mFactory
、mPrivateFactory
三个对象,似乎都是可以创建 View , 对于android.app.Activity,这三个对象为 null 或者空实现,创建 View 对象直接看如下代码:
final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); } else { //自定义 view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; }
注:如果 name
属性里面含有.
表示这是一个自定义 View,系统自带 View 我们可以省略类的路径,而自定义 View 则不能省略
自定义View创建,核心代码如下:
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); } } constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); 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[] args = mConstructorArgs; args[1] = attrs; 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])); } return view; //异常处理 ...... }
以上代码可以看出constructor.newInstance(args)
,通过反射创建 View 对象
对于 Android 内置的各种 View 在 LayoutInflater 的实现类PhoneLayoutInflater
中重载了onCreateView()
方法
private static final String[] sClassPrefixList = { "android.widget.", "android.webkit.", "android.app." }; @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { for (String prefix : sClassPrefixList) { try { View view = createView(name, prefix, attrs); if (view != null) { return view; } } catch (ClassNotFoundException e) { // In this case we want to let the base class take a crack // at it. } } return super.onCreateView(name, attrs); }
LayoutInflater 中的代码如下:
protected View onCreateView(View parent, String name, AttributeSet attrs) throws ClassNotFoundException { return onCreateView(name, attrs); } protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { return createView(name, "android.view.", attrs); }
对于系统内置的 View,会依次在 View 的标签前面加上android.widget.
,android.webkit.
,android.app.
,android.view.
然后通过反射的方法创建 View。
以上就是Android Xml转换为View过程详解的详细内容,更多关于Android Xml转换View的资料请关注脚本之家其它相关文章!