目录Android布局文件Xml Xml 转成 Java 对象方式一般使用 Activity跟踪getWindow()源码LayoutInflater 对象获取方式分析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, ……)。
在看下其他activityandroid.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 registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,new CachedServiceFetcher通过以上代码发现LayoutInflater的实现类是PhoneLayoutInflaterLayoutInflater 读取 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.,* R.layout.main_page)* @param root Optional view to be the parent of the generated hierarchy (if* attachToRoot is true), or else simply an object that* provides a set of LayoutParams values for root of the returned* hierarchy (if attachToRoot 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("
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("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的资料请关注脚本之家其它相关文章!
您可能感兴趣的文章:Android在layout xml中使用ViewStub完成动态加载问题Android自定义View中attrs.xml的实例详解Android实现在xml文件中引用自定义View的方法分析Android解析XML(PULL)展示到ListView详解Android XML中引用自定义内部类view的四个why
