• Android XML文件结构 和 用XmlPullParser 来解析xml文件


    1. 背景

            在工作中,不管是写app代码,还是阅读framework中的源码,涉及到解析xml的代码片段非常多,本篇文章从两个点来讲解一下,第一个点:xml文件结构  第二个点:怎么用XmlPullParser去解析。掌握这些知识后,对在阅读分析PMS解析包安装管理xml文件的源码,就会比较清晰明了。

    2. XML文件结构

    2.1 XML 定义

            XML: Extentsible Markup Language(可扩展标记语言)的缩写,它的格式与HTML文件的格式类似,xml是使用自定义标记来定义对象和每个对象中的数据,xml文件可以被认为是基于文本的数据库。

    2.2 XML文件结构和基本语法

            一个XML文档由两部分构成:第一部分是文档声明,第二部分是文档元素(节点)

            文档声明通常位于XML文档的顶端,根元素之前出现,它是一个特定的包含XML 文档设定信息的部分

            XML 文档由如下几个部分组成:

    • XML 声明:用来设置XML文档解析时所需的基本参数。
    • 处理指令:为某个特定类型的软件反馈一条特殊的指令。
    • 文档类型定义:用来设置更多高级的信息,如实体、属性及有效性相关的信息。
    • 注释:用于提醒XML文档作者或临时标注出文档中不完善的部分。

    接下来介绍几个重要的概念: 

    xml文档声明 

            XML 文档声明是为XML 解析器进行文档处理时提供相关信息的一个很小的配置信息集合。每一个XML 文档应当包含一个XML 声明,并且XML 声明必须放在文档的第一行。如下:

    "1.0" encoding="utf-8" standalone="no"?> 

            XML 声明中包括三个属性,每个属性设置的具体形式为:属性名称="属性值"。其中属性值需要是使用双引号或者单引号括起来,多个属性之间使用空格进行分隔。XML 声明中的三个属性的名称分别是:version、encoding和standalone。

            version:  此属性用来声明XML 文档所遵循的XML 标准版本。现在通常情况下该属性的值都是1.0,尽管 XML 1.1 已经称为 W3C 的推荐标准,但是大部分的 XML 解析器还是采用 XML 1.0 标准。version 是 XML 声明中必须包含的一个属性。

           encoding:   encoding 属性用来告诉 XML 解析程序当前 XML 文档使用什么样的字符编码。该属性是可选的。当 XML 声明中没有明确给出字符编码方式时,XML 解析程序将默认为 XML 文档采用的是 UTF-8 字符编码。

           standalone : standalone 属性定义了是否可以在不读取任何其他文件的情况下处理该文档。例如,XML 文档没有引用任何其他文件,则可以指定属性值为 yes。如果 XML 文档引用其他描述该文档可以包含的文件,则可以指定属性值为 no。因为 no 是 standalone 属性默认的属性值,所以较少会在 XML 声明中看到 standalone 属性。

            注意点:如果同时设置了 encoding 和 standalone 属性,standalone 属性必须位于 encoding 属性之后。

    元素

           XML元素指XML文件中出现的标签,一个标签分为起始和结束标签(不能省略),一个标签有如下几种书写形式

    ---- 包含标签主体:    some content

    ----不含标签主体:

            一个标签中可以嵌套若干个子标签,但所有标签必须合理的嵌套,不允许有交叉嵌套

    ----      错误 

            一个XML文档必须有且仅有一个根标签。其他标签都是这个根标签的子标签或孙标签

            

    属性:

            一个元素可以有多个属性,每个属性都有它自己的名称和取值,例如:

            属性值一定要用引号“单引号或双引号”引用起来

            元素中的属性是不允许重复的

            在XML技术中,标签属性所代表的信息也可以用子元素的形式来描述

        语法格式如下:

    <tagName attribute1="value1" attribute2="value2"......>tagData</tagName>

    注释:

            XML 注释的语法格式:

           注意点:XML声明前不能有注释,也就是第一行之前不能有注释,否则报错

                         注释不能嵌套

    2.3 XML实例

    demo.xml代码如下:

    1. "1.0" encoding="utf-8"?>
    2. <resources>
    3. 这里是资源文本
    4. <color value="#FF0000" name="红色">color>
    5. <color value="#00FF00" name="绿色"/>
    6. resources>

    代码解读:

             第一行为文档声明

             为根元素   

            为子元素,其内部有2个属性 value 和 name 

            

    第二个xml例子: 标签属性所代表的信息也可以用子元素的形式来描述,比如name 和age 就是用子元素形式表示的,但是不提倡这么写,这样子在解析的时候,代码就会比较难写。推荐上面的那种写法。

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <students>
    3. <person id="1111">
    4. <name>李四</name> <!--标签属性所代表的信息也可以用子元素的形式来描述-->
    5. <age>30</age>
    6. </person>
    7. <person id="2222" name="张三" age="20"></person >
    8. <person id="3333" name="王五" age="10"/>
    9. </students>

    第一行  就是XML声明,当前面版本号为1, 使用的是UTF-8编码

     students 为根元素: 文件体的最顶层元素称为根元素,根元素只能有一对,其名字可以自定义。所有其他元素均以子元素的形式存在于根元素内。子元素是可以重复出现的。

    子元素: person

    上面的xml中 子元素 person,定义了三个属性值 id   name  age  上面的三种写法都是等价的

    好了,理解上面xml各标签的定义后,接下来开始解析内容。


    3. XmlPullPaser 解析

    先看看第一个Demo:

    1. import java.io.IOException;
    2. import java.io.StringReader;
    3. import org.xmlpull.v1.XmlPullParser;
    4. import org.xmlpull.v1.XmlPullParserException;
    5. import org.xmlpull.v1.XmlPullParserFactory;
    6. public class SimpleXmlPullApp
    7. {
    8. public static void main (String args[])
    9. throws XmlPullParserException, IOException
    10. {
    11. XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
    12. factory.setNamespaceAware(true);
    13. XmlPullParser xpp = factory.newPullParser();
    14. xpp.setInput( new StringReader ( "Hello World!" ) );
    15. int eventType = xpp.getEventType();
    16. while (eventType != XmlPullParser.END_DOCUMENT) {
    17. if(eventType == XmlPullParser.START_DOCUMENT) {
    18. System.out.println("Start document");
    19. } else if(eventType == XmlPullParser.START_TAG) {
    20. System.out.println("Start tag "+xpp.getName());
    21. } else if(eventType == XmlPullParser.END_TAG) {
    22. System.out.println("End tag "+xpp.getName());
    23. } else if(eventType == XmlPullParser.TEXT) {
    24. System.out.println("Text "+xpp.getText());
    25. }
    26. eventType = xpp.next();
    27. }
    28. System.out.println("End document");
    29. }
    30. }

    终端输出如下:

    1. Start document
    2. Start tag foo
    3. Text Hello World!
    4. End tag foo
    5. End document

    代码解读:

    eventType 有五种解析类型:

    int值事件名事件定义
    0START_DOCUMENT开始读取文档
    1END_DOCUMENT结束文档
    2START_TAG读取标签
    3END_TAG结束标签
    4TEXT标签中的数据内容

    然后根据获取标签数值,做对应的逻辑处理,xmlpullparser是一行一行的从上到下解析文件的。

    XmlPullParser相关的API

    方法说明
    getEventType()

    获取当前解析的事件类型,有5种:START_DOCUMENT 

    END_DOCUMENT   

    START_TAG

    END_TAG

    TEXT

    getName()
    获取元素的标签名  在 START_TAG事件中使用
    getText()获取文本内容, 在TEXT 事件中使用
    getAttributeValue(String namespace,
                                    String name);
    类似于通过key值来获取value值, 第一个参数为default值, 第二个参数为 key   返回:一个字符串

    如果上个例子不是很明白,我们接下来继续这个Demo:

    1. public class MainActivity extends AppCompatActivity {
    2. @Override
    3. protected void onCreate(Bundle savedInstanceState) {
    4. super.onCreate(savedInstanceState);
    5. setContentView(R.layout.activity_main);
    6. try {
    7. //解析本地assets下的 xml资源文件
    8. parserAssetXml();
    9. } catch (XmlPullParserException e) {
    10. e.printStackTrace();
    11. } catch (IOException e) {
    12. e.printStackTrace();
    13. }
    14. }
    15. private void parserAssetXml()throws XmlPullParserException, IOException {
    16. //获取XmlPullParserFactory实例
    17. XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
    18. //通过XmlPullParserFactory 来获取 xmlPullParser对象
    19. XmlPullParser pullParser = factory.newPullParser();
    20. //通过此API来获取输入流,作为pullParser的参数
    21. InputStream inputStream = getAssets().open("demo.xml");
    22. //pullParser.setInput(输入流,"编码类型");
    23. pullParser.setInput(inputStream, "UTF-8");
    24. //获取解析的类型, eventType有五种类型:START_DOCUMENT END_DOCUMENT START_TAG END_TAG TEXT
    25. int eventType = pullParser.getEventType();
    26. Log.e("test", "=====第一次打印的eventType值为 :" + eventType);
    27. // 开始解析
    28. while (eventType != XmlPullParser.END_DOCUMENT) {
    29. eventType = pullParser.getEventType();
    30. Log.e("test", "=====解析过程中eventType值为 :" + eventType);
    31. switch (eventType) {
    32. case XmlPullParser.START_DOCUMENT:
    33. Log.e("test", "=====这里是文档声明====1.0\" encoding=\"UTF-8\"?>====");
    34. break;
    35. case XmlPullParser.START_TAG:
    36. String tagName = pullParser.getName();
    37. Log.e("test", "=====解析过程中tagName值为 :" + tagName);
    38. //如果 子元素的tagName为 color,就开始打印其内容
    39. if (tagName != null && tagName.equals("color")) {
    40. String colorValue = pullParser.getAttributeValue("", "value");
    41. Log.e("test", "=====解析过程中colorValue值为 :" + colorValue);
    42. String colorName = pullParser.getAttributeValue("", "name");
    43. Log.e("test", "=====解析过程中colorName值为 :" + colorName);
    44. }
    45. break;
    46. case XmlPullParser.END_TAG:
    47. Log.e("test", "====这里是元素的结束标签==="+pullParser.getName());
    48. break;
    49. case XmlPullParser.TEXT:
    50. Log.e("test", "====解析文本内容==="+pullParser.getText());
    51. break;
    52. default:
    53. break;
    54. }
    55. //继续解析下一个子元素
    56. eventType = pullParser.next();
    57. }
    58. }
    59. }

    注意Android studio工程中的assets的资源路径,不要放错位置了,否则会提示资源找不到

    打印log内容如下:

    1. 17:30:33.903 29787 29787 E test : =====第一次打印的eventType值为 :0
    2. 17:30:33.903 29787 29787 E test : =====解析过程中eventType值为 :0
    3. 17:30:33.903 29787 29787 E test : =====这里是文档声明====<?xml version="1.0" encoding="UTF-8"?>====
    4. 17:30:33.903 29787 29787 E test : =====解析过程中eventType值为 :2
    5. 17:30:33.903 29787 29787 E test : =====解析过程中tagName值为 :resources
    6. 17:30:33.903 29787 29787 E test : =====解析过程中eventType值为 :4
    7. 17:30:33.903 29787 29787 E test : ====解析文本内容===
    8. 17:30:33.903 29787 29787 E test : 这里是资源文本
    9. 17:30:33.903 29787 29787 E test :
    10. 17:30:33.903 29787 29787 E test :
    11. 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :2
    12. 17:30:33.904 29787 29787 E test : =====解析过程中tagName值为 :color
    13. 17:30:33.904 29787 29787 E test : =====解析过程中colorValue值为 :#FF0000
    14. 17:30:33.904 29787 29787 E test : =====解析过程中colorName值为 :红色
    15. 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :3
    16. 17:30:33.904 29787 29787 E test : ====这里是元素的结束标签===color
    17. 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :4
    18. 17:30:33.904 29787 29787 E test : ====解析文本内容===
    19. 17:30:33.904 29787 29787 E test :
    20. 17:30:33.904 29787 29787 E test :
    21. 17:30:33.904 29787 29787 E test :
    22. 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :2
    23. 17:30:33.904 29787 29787 E test : =====解析过程中tagName值为 :color
    24. 17:30:33.904 29787 29787 E test : =====解析过程中colorValue值为 :#00FF00
    25. 17:30:33.904 29787 29787 E test : =====解析过程中colorName值为 :绿色
    26. 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :3
    27. 17:30:33.904 29787 29787 E test : ====这里是元素的结束标签===color
    28. 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :4
    29. 17:30:33.904 29787 29787 E test : ====解析文本内容===
    30. 17:30:33.904 29787 29787 E test : =====解析过程中eventType值为 :3
    31. 17:30:33.904 29787 29787 E test : ====这里是元素的结束标签===resources

    把代码结合log一起看:

    其中 0 表示 START_DOCUMENT ,开始解析文档

    每次解析一个元素的标签时,事件类型为2  START_TAG,

    接下来都会走 打印 4   TEXT , 你从log中看出,只有第一次解析根元素resources标签时打印了“

    这里是资源文本       接下来解析子元素color时,打印都是为空  这是为什么?

     接下来打印 3  END_TAG 事件  这个元素就解析完毕

    接下来就 调用 next()方法,继续解析下一个元素,重复打印 2   3   4事件。

    好了,我们回答上面提出的问题?解释如下:

    就是不管你有没有在 元素的标签后面添加文本内容,代码解析的时候都会走4  TEXT事件。

    而且test.xml 文件你用浏览器查看的时候也是ok的,说明格式是正确的,如下:

     好了,到这里,我们就把解析流程梳理完成了。 把这些理解清楚后,我们可以看看PMS中解析AndroidManifest.xml的代码片段,后续会在framework中的文章中会介绍,这里先提前说明一下


     4. PMS源码片段

    在PackageParser.java中,有解析AndroidManifest.xml的片段代码,如下:

    1. private boolean parseSplitApplication(Package owner, Resources res, XmlResourceParser parser,
    2. int flags, int splitIndex, String[] outError)
    3. throws XmlPullParserException, IOException {
    4. TypedArray sa = res.obtainAttributes(parser,
    5. com.android.internal.R.styleable.AndroidManifestApplication);
    6. if (sa.getBoolean(
    7. com.android.internal.R.styleable.AndroidManifestApplication_hasCode, true)) {
    8. owner.splitFlags[splitIndex] |= ApplicationInfo.FLAG_HAS_CODE;
    9. }
    10. final String classLoaderName = sa.getString(
    11. com.android.internal.R.styleable.AndroidManifestApplication_classLoader);
    12. if (classLoaderName == null || ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) {
    13. owner.applicationInfo.splitClassLoaderNames[splitIndex] = classLoaderName;
    14. } else {
    15. outError[0] = "Invalid class loader name: " + classLoaderName;
    16. mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
    17. return false;
    18. }
    19. final int innerDepth = parser.getDepth();
    20. int type;
    21. while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    22. && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
    23. if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
    24. continue;
    25. }
    26. ComponentInfo parsedComponent = null;
    27. // IMPORTANT: These must only be cached for a single <application> to avoid components
    28. // getting added to the wrong package.
    29. final CachedComponentArgs cachedArgs = new CachedComponentArgs();
    30. String tagName = parser.getName();
    31. //解析清单文件中的activity信息
    32. if (tagName.equals("activity")) {
    33. Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
    34. owner.baseHardwareAccelerated);
    35. if (a == null) {
    36. mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
    37. return false;
    38. }
    39. owner.activities.add(a);
    40. parsedComponent = a.info;
    41. //解析清单文件中的广播信息
    42. } else if (tagName.equals("receiver")) {
    43. Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
    44. true, false);
    45. if (a == null) {
    46. mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
    47. return false;
    48. }
    49. owner.receivers.add(a);
    50. parsedComponent = a.info;
    51. //解析清单文件中的服务信息
    52. } else if (tagName.equals("service")) {
    53. Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
    54. if (s == null) {
    55. mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
    56. return false;
    57. }
    58. owner.services.add(s);
    59. parsedComponent = s.info;
    60. } else if (tagName.equals("provider")) {
    61. Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
    62. if (p == null) {
    63. mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
    64. return false;
    65. }
    66. owner.providers.add(p);
    67. parsedComponent = p.info;
    68. }

  • 相关阅读:
    【C++练级之路】【Lv.22】C++11——右值引用和移动语义
    SpringMVC之前端增删改查实现
    【Java----String类详解】
    Golang 数组基础
    七、函数-存储过程-触发器
    React原理
    寒假训练——第一周(二分)
    华为数通方向HCIP-DataCom H12-831题库(单选题:281-300)
    Java设计模式之解释器模式
    【物理应用】基于matlab GUI气象参数计算综合指标和IAQI【含Matlab源码 2116期】
  • 原文地址:https://blog.csdn.net/u012514113/article/details/126807779