Android XML 实例化的过程
安卓提供了XML布局方式,但是我们必须明白,XML布局最终也是通过xml的pull解析方式,得到布局名称和控件名称,以及相关的属性,然后利用反射机制创建的java对象的,所以效率上来说,java代码要比XML布局高不少,也更安全。但是写java代码又比XML写起来更麻烦,更不直观。下面我们来分析一下XML转换成java对象的过程。
我们通常通过以下方式把一个XML布局转换成java对象:
通过View的静态方法inflate:
View view = View.inflate(R.layout.resourceid, null);
获取LayoutInflater对象,调用inflate:
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.resourceid, null);
那么两种方法之间有什么区别呢?答案是没有区别。
以下是view中的inflate方法:
public static View inflate(Context context, int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
我们发现该方法实际也是获取了LayoutInflater对象,调用了inflate方法。只不过是通过LayoutInflater的静态方法from。
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的from方法,最终我们发任然现是通过Context的静态方式获取,为什么不直接通过new的方式获取LayoutInflater呢?
因为LayoutInflater的构造方法为protected的,我们是无法直接使用的。
为什么google要把这个类交由系统进行创建,而不由我们程序员来创建呢,我猜想该类用于填充一个XML的布局,需要用到一些系统的环境,那么我们来看看LayoutInflater是如何被创建的吧。
我们知道,该类是由Context的静态方法获取的:
public abstract Object getSystemService(String name);
我们发现,Context并没有给我们一个交代,因为该方法竟然是一个抽象的,只剩下查看Context的实现类了。
Context有很多子类,直接子类有ContextWapper(实际是Context的包装类,该类直接把所有方法转手又交给了Context),MockContext(该类是Context的模拟类,做法更简单,直接抛出异常)。而其他子类例如Application、Activity、Service等都没有返回LayoutInflater。
实际上,对于Context,还有系统给出了一个实现类ContextImpl,该类在\frameworks\base\core\java\android\app\ContextImpl.java,SDK的源码无法查看,需要Frameworks层的源码。
public Object getSystemService(String name){if (LAYOUT_INFLATER_SERVICE.equals(name)) { synchronized (mSync) { LayoutInflater inflater = mLayoutInflater; //是否已经赋值,如果是,直接返回引用 if (inflater != null) { return inflater; } //返回一个LayoutInflater对象,getOuterContext()指的是我们的Activity、Service或者Application引用 mLayoutInflater = inflater = PolicyManager.makeNewLayoutInflater(getOuterContext()); return inflater; } } ... //下面是对其他系统服务的获取,就不再详述
}
我们发现Context又把这个问题甩给了PolicyManager,继续去看PolicyManager吧。该类在\frameworks\base\core\java\com\android\internal\policy\PolicyManager.java
public final class PolicyManager { private static final String POLICY_IMPL_CLASS_NAME = "com.android.internal.policy.impl.Policy"; private static final IPolicy sPolicy; // 这可不是Binder机制额,这只是是一个接口,别想多啦 static { //我们可以看到该类直接在静态代码块中使用了反射创建了实例对象。try { Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME); sPolicy = (IPolicy)policyClass.newInstance(); } ... } ... public static LayoutInflater makeNewLayoutInflater(Context context) { return sPolicy.makeNewLayoutInflater(context); //继续去实现类中去查找 }
}
好吧,IPolicy又一次的把问题抛出了,我们继续去追查它的实现类Policy类。路径:/frameworks/base/policy/src/com/android/internal/policy/impl/Policy.java
public class Policy implements IPolicy{ ... public PhoneLayoutInflater makeNewLayoutInflater(Context context) { //实际上返回的是PhoneLayoutInflater类。 return new PhoneLayoutInflater(context); }
}
继续追查PhoneLayoutInflater
//PhoneLayoutInflater继承至LayoutInflater类
public class PhoneLayoutInflater extends LayoutInflater { ... public PhoneLayoutInflater(Context context) { super(context); } ...
}
终于,它调用了父类的的构造方法,返回了实例对象,所以我们拿到的实际上是一个PhoneLayoutInflater。为什么绕这么一个大圈,最终又回到了原点呢,我猜想就是为了在使用之前,确保是由PolicyManager来创建,该类是一个策略管理者,在Frameworks层,它创建了很多其他对象,类似于一个工厂,而该类是hide隐藏的,外部无法访问,这样由它来产生,各大手机厂商都可以根据修改它来实现自身的策略,并给出各自特有的PhoneLayoutInflater,而开发者只用知道外部的统一API即可调用。终于明白了LayoutInflater的来源,接下来我们可以看看inflate方法是如何把一个XML文件转变成java的view对象。
那么我们再来看看LayoutInflater的inflate方法,看看它是如何把一个XML布局转变成java对象的。
public View inflate(int resource, ViewGroup root) { return inflate(resource, root, root != null); //调用了重构方法
}
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {//可以看到,通过我们传递的资源ID,返回了一个Xml的parser对象。XmlResourceParser parser = getContext().getResources().getLayout(resource);try {//继续调用它的重构方法return inflate(parser, root, attachToRoot);} finally {parser.close();}
}
我们发现XmlResourceParser是一个继承自XmlPullParser和AttributeSet的接口。从名字可以看出,它是一个pull解析者,并能获取所有属性集合。至于它的实现,我们先放一放。我们再来看看inflate的第二个重构方法,它由三个参数,第一个是XML解析者,第二个是这个布局所要加入的根View,那么第三个参数是什么呢?从面我们得知它是根据root!=null的结果得出,也就是如果我们调用第一个方法,root存在,它即为true,但第二个方法我们即可传递root,又能人为的把该值设置为false。其实从名字我们可以看出,这个参数是决定我们XML布局是否依附到root下,也就是是否add。
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {synchronized (mConstructorArgs) {//从parser对象中获取AttributeSet的实例对象,猜测其中包含了所有的view的属性final AttributeSet attrs = Xml.asAttributeSet(parser);mConstructorArgs[0] = mContext;//拿到了contextView result = root;//把返回结果默认设置为root。try {int type;while ((type = parser.next()) != XmlPullParser.START_TAG &&type != XmlPullParser.END_DOCUMENT) {}//既不是开始标签,又不是XML树的结束点,那么就不是View对象了,不做任何处理if (type != XmlPullParser.START_TAG) {//前面已经排除了两种情况以外的所有情况,那么现在又不是开始标签,那么只能是文档结束点了。throw new InflateException(parser.getPositionDescription()+ ": No start tag found!");}final String name = parser.getName();//拿到标签名if (TAG_MERGE.equals(name)) {//如果标签名是merge,但是没有父view,就抛出异常if (root == null || !attachToRoot) {throw new InflateException("<merge /> can be used only with a valid "+ "ViewGroup root and attachToRoot=true");}rInflate(parser, root, attrs);//拿到第一个标签后,就把parser对象又交出去了。} else {// 创建rootViewView temp = createViewFromTag(name, attrs);//根据标签名创建一个view,因为是第一个标签,所以是这个XML的根布局。ViewGroup.LayoutParams params = null;if (root != null) {// 根据父view来获取一个匹配的paramsparams = root.generateLayoutParams(attrs);if (!attachToRoot) {//不需要添加到父view中,那么就给他设置一个临时的params吧//设置一个临时的paramstemp.setLayoutParams(params);}}rInflate(parser, temp, attrs);//把剩余的parser交出去处理。//如果给出了root,并且允许添加到父view中,就直接添加。if (root != null && attachToRoot) {root.addView(temp, params);}//如果根view为空,或者不允许加到父view中,就返回这个view,否则就返回rootif (root == null || !attachToRoot) {result = temp;}}} ...//异常的捕获return result;}
}
从上面的代码看,填充所有子view的过程都交给了rInflate,我们再来看看该方法的实现。
该方法会由上至下递归的初始化所有子view和子view的子view。在此方法被调用完成后 会调用此view的父view的onFinishInflate方法。表明其子view全部加载完毕。
private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs) throws XmlPullParserException, IOException { //用于记录XML的深度,如果当前的parser处于根view,则深度为0,每进入一层,深度+1,每退出一层,深度-1。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_INCLUDE.equals(name)) { if (parser.getDepth() == 0) {//当深度为0,即根标签等于include的时候,抛出异常。 throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, parent, attrs);//解析include的类容 } else if (TAG_MERGE.equals(name)) {//因为这是一个填充子view的过程,肯定不是root了,不符合merge的用法。 throw new InflateException("<merge /> must be the root element"); } else { //看这里,创建view的方法。而且这里已经重新获得了它的 final View view = createViewFromTag(name, attrs); //和上面一样,拿到标签名后就可以创建view了final ViewGroup viewGroup = (ViewGroup) parent; //拿到它的父viewfinal ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs); //调用方法本身判断它有没有子view,进行递归viewGroup.addView(view, params); //递归完毕后把自身添加进去} } parent.onFinishInflate(); //递归调用完毕后,通知它的父view说自身已经填充完毕。
}
我们再来看看createViewFromTag,根据一个名字创建一个view。
View createViewFromTag(String name, AttributeSet attrs) { if (name.equals("view")) { //如果名字是view的情况,给view重新赋值,可以针对这种情况:如 <View class="com.lipan.view"></View> name = attrs.getAttributeValue(null, "class"); } try {//mFactory是一个抽象的接口,该接口的实例对象在初始化LayoutInflate的时候,被赋值。View view = (mFactory == null) ? null : mFactory.onCreateView(name, mContext, attrs); if (view == null) { //如果返回的view等于null,不存在factory或者factory创建失败。if (-1 == name.indexOf('.')) { //这里只是为了判断xml文件中tag的属性是否加了包名,不包含包名,则创建系统View view = onCreateView(name, attrs); } else { //创建自定义的Viewview = createView(name, null, attrs); } } return view; } ...//异常捕捉
}
在上面我们还是没看到一个View的真正创建,而只是看到分别调用了onCreateView和createView。
创建系统的View用了onCreateView,创建自定义View用了createView。我们先来看看onCreateView。
protected View onCreateView(String name, AttributeSet attrs)throws ClassNotFoundException {return createView(name, "android.view.", attrs);
}
发现该方法直接调用了createView,并且把包名默认为”android.view.”,那么createView就是真正的创建一个View了。
该方法接受三个参数,view名,view类的包名,以及view的属性。
public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { Constructor constructor = sConstructorMap.get(name); //缓存中是否已经有了一个构造函数,这是一种优化手段,以view的名字为键,构造函数为值,下次再重新创建相同的View对象时,可以直接使用该函数。Class clazz = null; try { if (constructor == null) { //通过类名获得一个class对象 clazz = mContext.getClassLoader().loadClass( //拿到类加载器根据包名和类名去加载一个类。prefix != null ? (prefix + name) : name); if (mFilter != null && clazz != null) { //mFilter是一个过滤器,用来判断该clazz是否允许被创建boolean allowed = mFilter.onLoadClass(clazz); //很费解这里不把判别状态存入map集合。if (!allowed) { //不允许被创建failNotAllowed(name, prefix, attrs); //该方法内部是抛出一个异常} } //通过参数类型获得一个构造器,参数列表为context,attrs constructor = clazz.getConstructor(mConstructorSignature); 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); 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; //args[0]已经在前面初始好了,就context。这里只要初始化args[1] return (View) constructor.newInstance(args); //通过反射new出一个对象,终于创建完了。} ...//异常的捕获
}
明白了View的创建,我们再来看看XML布局是如何被解析的吧,从上面我们可知XmlResourceParser是从Resource类的getLayout方法获得的。
public XmlResourceParser getLayout(int id) throws NotFoundException { return loadXmlResourceParser(id, "layout"); //传进了"layout",意思是说去找layout下的,可以用于区分R文件中的id
}
XmlResourceParser loadXmlResourceParser(int id, String type) throws NotFoundException { synchronized (mTmpValue) { //TypedValue对象是安卓提供的用于保存一个数据的容器,它的特别之处在于不仅可以保存数据,还能同时保存数据的类型。//例如该数据时boolean型的还是String类型的,甚至是某个对象的引用类型。这些在attr.xml文件下都有定义。TypedValue value = mTmpValue; getValue(id, value, true); //该方法只用于查找id的控件,如果找到,value对其进行引用,如果没找到,抛出异常if (value.type == TypedValue.TYPE_STRING) { return loadXmlResourceParser(value.string.toString(), id, value.assetCookie, type); } throw new NotFoundException( "Resource ID #0x" + Integer.toHexString(id) + " type #0x" + Integer.toHexString(value.type) + " is not valid"); }
}
根据上面的线索,我们先来看看getValue的实现:
/*getValue方法,id表示要查找的控件的 id,outValue是一个对象,用于保存一些属性相关信息 resolveRefs为true表明,当通过属性id找到xml文件中的标签时,比如是一个<Button android:id="@+id/button"/>
它的值是一个引用,则继续解析获得这个id的值。这里看AssetManager类的实现*/
public void getValue(int id, TypedValue outValue, boolean resolveRefs) throws NotFoundException { boolean found = mAssets.getResourceValue(id, outValue, resolveRefs); if (found) { return; } throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
}
我们看到上面的方法把value交给了loadXmlResourceParser来处理,那么我们看看是如何处理的
XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws NotFoundException { if (id != 0) { try { //取缓存 synchronized (mCachedXmlBlockIds) { //首先在缓存集合中查看 final int num = mCachedXmlBlockIds.length; //可以从源码中看出它是一个4固定长度的数组,用于缓存4个idfor (int i=0; i<num; i++) { if (mCachedXmlBlockIds[i] == id) { //找到了,直接newParser返回return mCachedXmlBlocks[i].newParser(); //是一个用于缓存XmlBlock的数组,长度也是4} } //第一次加载时,会打开这个文件获取一个xml数据块对象。 // 这里先看AssetManager类的实现 XmlBlock block = mAssets.openXmlBlockAsset( assetCookie, file); //下面会把此xmlBlock对象缓存起来,保存id和block, //以后如果是同样的id,直接在缓存中取XmlBlock。 //这样就不用再在本地方法中打开文件创建解析树了。 if (block != null) { int pos = mLastCachedXmlBlockIndex+1; if (pos >= num) pos = 0; //如果角标超了,就直接替换第一个mLastCachedXmlBlockIndex = pos; XmlBlock oldBlock = mCachedXmlBlocks[pos]; if (oldBlock != null) { oldBlock.close(); } mCachedXmlBlockIds[pos] = id; mCachedXmlBlocks[pos] = block; //返回的内部类继承了XmlResourceParser,在APi中此类是隐藏的 return block.newParser(); } } } ...//异常的处理}
从上面可以看出,安卓对XML的最终解析是通过JNI调用的本地方法,由于我不懂C,能力有限,所以追索到此为止,但是大体思想我们应该也能明白,如果不考虑效率问题,其实我们也可以自己用pull解析来对XML文件解析,写出自己的布局填充器,解析自定义的XML文件,或者是jason格式的布局,甚至任意与格式的布局,因为布局填充器是你写的,你能解读它。
Android XML 实例化的过程相关推荐
- Android的证书验证过程
说明:本文分析源码基于Android_8.1 (还在看代码和修改文章的阶段,有点乱,慢慢来-) 文章目录 0x01 系统证书 0x02 证书管理类 0x03 证书验证流程 3.1 建立安全的连接 3. ...
- Android Studio 的 build 过程
我们知道,在 Android 项目的开发过程中,只需要点一下 Android Studio 的运行按钮![](https://user-gold-cdn.xitu.io/2018/6/24/16431 ...
- android绘制view的过程
1 android绘制view的过程简单描述 简单描述可以解释为:计算大小(measure),布局坐标计算(layout),绘制到屏幕(draw): 下面看看每一步的动作到底 ...
- Android应用程序签名过程和解析过程分析
在正式解释Android应用程序签名过程之前,作为铺垫,还得先讲讲最基本的一些概念. 非对称加密算法 非对称加密算法需要两个密钥:公开密钥(简称公钥)和私有密钥(简称私钥).公钥与私钥是一对,如果用公 ...
- Android 系统(239)---Android PMS的创建过程
Android PMS的创建过程 ------转自 刘望舒 刘望舒 前言 PMS的创建过程分为两个部分进行讲解,分别是SyetemServer处理部分和PMS构造方法.其中SyetemServer ...
- Android 系统 (79)---Android应用程序安装过程解析
Android应用程序安装过程解析 Android应用程序安装过程解析 1.程序安装的4大步骤 (1) 拷贝apk文件到指定目录 在Android系统中,apk安装文件是会被保存起来的,默认情况下,用 ...
- Android XML解析器– XMLPullParser
Welcome to android xml parser example using XMLPullParser. We will have a sample XML file that we wi ...
- android apk编译打包过程
Android安装包的后缀都是.apk, apk是Android Package的缩写. 解压apk文件后包含AndroidManifest.xml.assets目录.classes.dex(还可能有 ...
- Android应用程序安装过程解析(源码解析)
Android应用程序安装过程解析 1.程序安装的4大步骤 (1) 拷贝apk文件到指定目录 在Android系统中,apk安装文件是会被保存起来的,默认情况下,用户安装的apk首先会被拷贝到 /da ...
最新文章
- python读取excel送到网页_python怎么读取excel!怎么用python将excel数据写入网页中
- HDU - 2296 Ring(AC自动机+dp)
- python语句关键词用法_python中关键字as的使用方法简介
- 基于Linux操作系统的底层驱动技术
- buildroot 使用本地交叉编译器记录
- 一天已不足24小时?一年不足365天?求每年元旦为周几的公式还能用吗?(标题党石锤了)
- html怎么快捷复制粘贴,如何使用快捷键复制粘贴
- 百度API查询经纬度小页面
- 五步教你如何利用python爬虫制作一个中国慕课视频下载器
- 在C#中使用WIA获取扫描仪数据
- 2023中职网络安全竞赛Web安全应用任务解析答案
- 程序猿的自救 从零备考NSCA/CSCS 1 身体系统的构造与系统
- flask_pagedown小修改
- 五大数据分析软件对比:Python、Excel、R、SPSS、SAS
- 2022年计算机软件水平考试信息安全工程师(中级)练习题及答案
- 报错java.lang.ClassNotFoundException: net.sf.ezmorph.Morpher解决方案
- CentOS8 在线安装向导
- 什么是Prometheus?
- 正态分布-python建模
- 微信小程序页面跳转传递参数(富文本)