安卓提供了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 实例化的过程相关推荐

  1. Android的证书验证过程

    说明:本文分析源码基于Android_8.1 (还在看代码和修改文章的阶段,有点乱,慢慢来-) 文章目录 0x01 系统证书 0x02 证书管理类 0x03 证书验证流程 3.1 建立安全的连接 3. ...

  2. Android Studio 的 build 过程

    我们知道,在 Android 项目的开发过程中,只需要点一下 Android Studio 的运行按钮![](https://user-gold-cdn.xitu.io/2018/6/24/16431 ...

  3. android绘制view的过程

    1 android绘制view的过程简单描述  简单描述可以解释为:计算大小(measure),布局坐标计算(layout),绘制到屏幕(draw):             下面看看每一步的动作到底 ...

  4. Android应用程序签名过程和解析过程分析

    在正式解释Android应用程序签名过程之前,作为铺垫,还得先讲讲最基本的一些概念. 非对称加密算法 非对称加密算法需要两个密钥:公开密钥(简称公钥)和私有密钥(简称私钥).公钥与私钥是一对,如果用公 ...

  5. Android 系统(239)---Android PMS的创建过程

    Android PMS的创建过程 ------转自   刘望舒 刘望舒 前言 PMS的创建过程分为两个部分进行讲解,分别是SyetemServer处理部分和PMS构造方法.其中SyetemServer ...

  6. Android 系统 (79)---Android应用程序安装过程解析

    Android应用程序安装过程解析 Android应用程序安装过程解析 1.程序安装的4大步骤 (1) 拷贝apk文件到指定目录 在Android系统中,apk安装文件是会被保存起来的,默认情况下,用 ...

  7. Android XML解析器– XMLPullParser

    Welcome to android xml parser example using XMLPullParser. We will have a sample XML file that we wi ...

  8. android apk编译打包过程

    Android安装包的后缀都是.apk, apk是Android Package的缩写. 解压apk文件后包含AndroidManifest.xml.assets目录.classes.dex(还可能有 ...

  9. Android应用程序安装过程解析(源码解析)

    Android应用程序安装过程解析 1.程序安装的4大步骤 (1) 拷贝apk文件到指定目录 在Android系统中,apk安装文件是会被保存起来的,默认情况下,用户安装的apk首先会被拷贝到 /da ...

最新文章

  1. python读取excel送到网页_python怎么读取excel!怎么用python将excel数据写入网页中
  2. HDU - 2296 Ring(AC自动机+dp)
  3. python语句关键词用法_python中关键字as的使用方法简介
  4. 基于Linux操作系统的底层驱动技术
  5. buildroot 使用本地交叉编译器记录
  6. 一天已不足24小时?一年不足365天?求每年元旦为周几的公式还能用吗?(标题党石锤了)
  7. html怎么快捷复制粘贴,如何使用快捷键复制粘贴
  8. 百度API查询经纬度小页面
  9. 五步教你如何利用python爬虫制作一个中国慕课视频下载器
  10. 在C#中使用WIA获取扫描仪数据
  11. 2023中职网络安全竞赛Web安全应用任务解析答案
  12. 程序猿的自救 从零备考NSCA/CSCS 1 身体系统的构造与系统
  13. flask_pagedown小修改
  14. 五大数据分析软件对比:Python、Excel、R、SPSS、SAS
  15. 2022年计算机软件水平考试信息安全工程师(中级)练习题及答案
  16. 报错java.lang.ClassNotFoundException: net.sf.ezmorph.Morpher解决方案
  17. CentOS8 在线安装向导
  18. 什么是Prometheus?
  19. 正态分布-python建模
  20. 微信小程序页面跳转传递参数(富文本)

热门文章

  1. MapInfo数据到ARCGIS数据Shapefile的转换
  2. AVL树---平衡的二叉查找树
  3. Scala 基础(4)—— 类和对象
  4. [LeetCode] 142. Linked List Cycle II
  5. Oracle 中对表空间使用情况进行查询
  6. 机器学习如何改变大数据管理
  7. bootstrap轮播图怎么居中
  8. mysql 四 表操作
  9. 加载网络图片的框架总结
  10. jquery获取服务器控件的值