【Android网络开发の2】XML之SAX方式 解析和生成XML文件

上一章写了如何使用DOM来解析和生成xml

接下来这章将讲解SAX方式解析和生成xml

下面我们看下DOM和SAX的优缺点分析

  
  
  
  
  1. DOM(文件对象模型)解析:解析器读入整个文档,然后构建一个驻留内存的树结构,然后代码就可以根据DOM接口来操作这个树结构了。  
  2.   
  3.   优点:整个文档读入内存,方便操作:支持修改、删除和重现排列等多种功能。  
  4.   
  5.   缺点:将整个文档读入内存中,保留了过多的不需要的节点,浪费内存和空间。  
  6.   
  7.   使用场合:一旦读入文档,还需要多次对文档进行操作,并且在硬件资源充足的情况下(内存,CPU)。  
  8.   
  9. 为了解决DOM解析存在的问题,就出现了SAX解析。其特点为:  
  10.   
  11.   优点:不用实现调入整个文档,占用资源少。尤其在嵌入式环境中,如android,极力推荐使用SAX解析。  
  12.   
  13.   缺点:不像DOM解析一样将文档长期驻留在内存中,数据不是持久的。如果事件过后没有保存数据,数据就会丢失。  
  14.   
  15.   使用场合:机器有性能限制。  

一、SAX解析XML

下面是SAX实现实体解析的步骤

  
  
  
  
  1. //下面使用XMLReader 来解析 
  2. (一)第一步:新建一个工厂类SAXParserFactory,代码如下: 
  3. SAXParserFactory factory = SAXParserFactory.newInstance(); 
  4. (二)第二步:让工厂类产生一个SAX的解析类SAXParser,代码如下: 
  5. SAXParser parser = factory.newSAXParser(); 
  6. (三)第三步:从SAXPsrser中得到一个XMLReader实例,代码如下: 
  7. XMLReader reader = parser.getXMLReader(); 
  8. (四)第四步:把自己写的handler注册到XMLReader中,一般最重要的就是ContentHandler,代码如下: 
  9. reader.setContentHandler(this); 
  10. (五)第五步:将一个xml文档或者资源变成一个java可以处理的InputStream流后,解析正式开始,代码如下: 
  11. reader.parse(new InputSource(is)); 
  12.  
  13.  
  14. //下面使用SAXParser来解析 
  15. (一)第一步:新建一个工厂类SAXParserFactory,代码如下: 
  16. SAXParserFactory factory = SAXParserFactory.newInstance(); 
  17. (二)第二步:让工厂类产生一个SAX的解析类SAXParser,代码如下: 
  18. SAXParser parser = factory.newSAXParser(); 
  19. (三)第三步:将一个xml文档或者资源变成一个java可以处理的InputStream流后,解析正式开始,代码如下: 
  20. parser.parse(is,this); 

估计大家都看到了ContentHandler ,下面具体的讲下

解析开始之前,需要向XMLReader/SAXParser 注册一个ContentHandler,也就是相当于一个事件监听器,在ContentHandler中定义了很多方法

  
  
  
  
  1. //设置一个可以定位文档内容事件发生位置的定位器对象 
  2. public void setDocumentLocator(Locator locator) 
  3.  
  4. //用于处理文档解析开始事件 
  5. public void startDocument()throws SAXException 
  6.  
  7. //处理元素开始事件,从参数中可以获得元素所在名称空间的uri,元素名称,属性类表等信息 
  8. public void startElement(String namespacesURI , String localName , String qName , Attributes atts) throws SAXException 
  9.  
  10. //处理元素结束事件,从参数中可以获得元素所在名称空间的uri,元素名称等信息 
  11. public void endElement(String namespacesURI , String localName , String qName) throws SAXException 
  12.  
  13. //处理元素的字符内容,从参数中可以获得内容 
  14. public void characters(char[] ch , int start , int length)  throws SAXException 

顺便介绍下XMLReader中的方法。

  
  
  
  
  1. //注册处理XML文档解析事件ContentHandler 
  2. public void setContentHandler(ContentHandler handler) 
  3.  
  4. //开始解析一个XML文档 
  5. public void parse(InputSorce input) throws SAXException 

下面是流程图

大概的讲的差不多了  接下来开始讲解解析的步骤

我们还是用上一章的代码

首先 我们创建一个Person类  用来存储用户的信息

  
  
  
  
  1. package com.example.demo; 
  2.  
  3. import java.io.Serializable; 
  4.  
  5. public class Person implements Serializable { 
  6.  
  7.     /** 
  8.      *  
  9.      */ 
  10.     private static final long serialVersionUID = 1L; 
  11.     private String _id; 
  12.     private String _name; 
  13.     private String _age; 
  14.  
  15.     public String get_id() { 
  16.         return _id; 
  17.     } 
  18.  
  19.     public void set_id(String _id) { 
  20.         this._id = _id; 
  21.     } 
  22.  
  23.     public String get_name() { 
  24.         return _name; 
  25.     } 
  26.  
  27.     public void set_name(String _name) { 
  28.         this._name = _name; 
  29.     } 
  30.  
  31.     public String get_age() { 
  32.         return _age; 
  33.     } 
  34.  
  35.     public void set_age(String _age) { 
  36.         this._age = _age; 
  37.     } 

接下来  我们要实现一个ContentHandler 用来解析XML

实现一个ContentHandler 一般需要下面几个步骤

  
  
  
  
  1. 1、声明一个类,继承DefaultHandler。DefaultHandler是一个基类,这个类里面简单实现了一个ContentHandler。我们只需要重写里面的方法即可。 
  2. 2、重写 startDocument() 和 endDocument(),一般将正式解析之前的初始化放到startDocument()里面,收尾的工作放到endDocument()里面。 
  3. 3、重写startElement(),XML解析器遇到XML里面的tag时就会调用这个函数。经常在这个函数内是通过对localName的值进行判断而操作一些数据。 
  4. 4、重写characters()方法,这是一个回调方法。解析器执行完startElement()后,解析节点的内容后就会执行这个方法,并且参数ch[]就是节点的内容。
  5. 5、重写endElement()方法,这个方法与startElement()相对应,解析完一个tag节点后,执行这个方法,解析一个tag后,调用这个处理还原和清除相关信息 

首先   新建一个类 继承DefaultHandler 并重写以下几个方法

  
  
  
  
  1. public class SAX_parserXML extends DefaultHandler { 
  2.  
  3.     /** 
  4.      * 当开始解析xml文件的声明的时候就会触发这个事件, 可以做一些初始化的工作 
  5.      * */ 
  6.     @Override 
  7.     public void startDocument() throws SAXException { 
  8.         // TODO Auto-generated method stub 
  9.         super.startDocument(); 
  10.          
  11.     } 
  12.  
  13.     /** 
  14.      * 当开始解析元素的开始标签的时候,就会触发这个事件 
  15.      * */ 
  16.     @Override 
  17.     public void startElement(String uri, String localName, String qName, 
  18.             Attributes attributes) throws SAXException { 
  19.         // TODO Auto-generated method stub 
  20.         super.startElement(uri, localName, qName, attributes); 
  21.     } 
  22.  
  23.     /** 
  24.      * 当读到文本元素的时候要触发这个事件. 
  25.      * */ 
  26.     @Override 
  27.     public void characters(char[] ch, int start, int length) 
  28.             throws SAXException { 
  29.         // TODO Auto-generated method stub 
  30.         super.characters(ch, start, length); 
  31.     } 
  32.  
  33.     /** 
  34.      * 当读到结束标签的时候 就会触发这个事件 
  35.      * */ 
  36.     @Override 
  37.     public void endElement(String uri, String localName, String qName) 
  38.             throws SAXException { 
  39.         // TODO Auto-generated method stub 
  40.         super.endElement(uri, localName, qName); 
  41.     } 
  42.  

首先  我们创建一个list  用来保存解析出来的person数据

  
  
  
  
  1. List<Person> persons; 

但是?在哪里初始化呢?我们可以在startDocument()里面初始化,因为当开始解析xml文件的声明的时候就会触发这个事件所以放在这里比较合适

  
  
  
  
  1. /** 
  2.  * 当开始解析xml文件的声明的时候就会触发这个事件, 可以做一些初始化的工作 
  3.  * */ 
  4. @Override 
  5. public void startDocument() throws SAXException { 
  6.     // TODO Auto-generated method stub 
  7.     super.startDocument(); 
  8.     // 初始化list 
  9.     persons = new ArrayList<Person>(); 

接下来  就要开始解析了  

  
  
  
  
  1. /** 
  2.      * 当开始解析元素的开始标签的时候,就会触发这个事件 
  3.      * */ 
  4.     @Override 
  5.     public void startElement(String uri, String localName, String qName, 
  6.             Attributes attributes) throws SAXException { 
  7.         // TODO Auto-generated method stub 
  8.         super.startElement(uri, localName, qName, attributes); 
  9.  
  10.         // 如果读到是person标签 开始存储 
  11.         if (localName.equals("person")) { 
  12.             person = new Person(); 
  13.             person.set_id(attributes.getValue("id")); 
  14.         } 
  15.         curNode = localName; 
  16.     } 

 上面的代码中  localName表示当前解析到的元素名

  
  
  
  
  1. //步骤 
  2. //1.判断是否是person元素 
  3. //2.创建新的Person对象 
  4. //3.获取id 添加到Person对象中 

curNode 用来保存当前的元素名 在characters中会使用到

  
  
  
  
  1. /** 
  2.      * 当读到文本元素的时候要触发这个事件. 
  3.      * */ 
  4.     @Override 
  5.     public void characters(char[] ch, int start, int length) 
  6.             throws SAXException { 
  7.         // TODO Auto-generated method stub 
  8.         super.characters(ch, start, length); 
  9.  
  10.         if (person != null) { 
  11.             //取出目前元素对应的值 
  12.             String txt = new String(ch, start, length); 
  13.             //判断元素是否是name 
  14.             if (curNode.equals("name")) { 
  15.                 //将取出的值添加到person对象 
  16.                 person.set_name(txt); 
  17.             } else if (curNode.equals("age")) { 
  18.                 person.set_age(txt); 
  19.             } 
  20.         } 
  21.     } 

接下来是介绍标签结束的时候需要做的事情

  
  
  
  
  1. /** 
  2.  * 当读到结束标签的时候 就会触发这个事件 
  3.  * */ 
  4. @Override 
  5. public void endElement(String uri, String localName, String qName) 
  6.         throws SAXException { 
  7.     // TODO Auto-generated method stub 
  8.     super.endElement(uri, localName, qName); 
  9.  
  10.     // 如果是 并且person不为空,添加到list 
  11.     if (localName.equals("person") && person != null) { 
  12.         persons.add(person); 
  13.         person = null
  14.     } 
  15.  
  16.     curNode = ""

解析的事情结束了  大概流程就是

  
  
  
  
  1. 1.一个元素开始时    会调用startElement方法 
  2. 2.接下来会调用到characters方法,可以用来获取元素的值 
  3. 3.一个元素结束时    会调用到endElement方法 

解析结束之后  我们需要写一个方法  用来获取解析后保存的list

  
  
  
  
  1. public List<Person> ReadXML(InputStream is) { 
  2.  
  3.         SAXParserFactory factory = SAXParserFactory.newInstance(); 
  4.         try { 
  5.             SAXParser parser = factory.newSAXParser(); 
  6.  
  7.             // 第一种方法 
  8.             // parser.parse(is, this); 
  9.  
  10.             // 第二种方法 
  11.             XMLReader reader = parser.getXMLReader(); 
  12.             reader.setContentHandler(this); 
  13.             reader.parse(new InputSource(is)); 
  14.  
  15.         } catch (Exception e) { 
  16.             // TODO: handle exception 
  17.             e.printStackTrace(); 
  18.         } 
  19.  
  20.         return persons; 
  21.     } 

上面的代码就不解释了   只要将inputStream对象传入  就可以解析出内容

看完了代码,我来给出完整的代码

  
  
  
  
  1. package com.example.demo.Utils; 
  2.  
  3. import java.io.InputStream; 
  4. import java.util.ArrayList; 
  5. import java.util.List; 
  6.  
  7. import javax.xml.parsers.SAXParser; 
  8. import javax.xml.parsers.SAXParserFactory; 
  9.  
  10. import org.xml.sax.Attributes; 
  11. import org.xml.sax.InputSource; 
  12. import org.xml.sax.SAXException; 
  13. import org.xml.sax.XMLReader; 
  14. import org.xml.sax.helpers.DefaultHandler; 
  15.  
  16. import com.example.demo.Person; 
  17.  
  18. public class SAX_parserXML extends DefaultHandler { 
  19.  
  20.     List<Person> persons; 
  21.     Person person; 
  22.     // 当前节点 
  23.     String curNode; 
  24.  
  25.     public List<Person> ReadXML(InputStream is) { 
  26.  
  27.         SAXParserFactory factory = SAXParserFactory.newInstance(); 
  28.         try { 
  29.             SAXParser parser = factory.newSAXParser(); 
  30.  
  31.             // 第一种方法 
  32.             // parser.parse(is, this); 
  33.  
  34.             // 第二种方法 
  35.             XMLReader reader = parser.getXMLReader(); 
  36.             reader.setContentHandler(this); 
  37.             reader.parse(new InputSource(is)); 
  38.  
  39.         } catch (Exception e) { 
  40.             // TODO: handle exception 
  41.             e.printStackTrace(); 
  42.         } 
  43.  
  44.         return persons; 
  45.     } 
  46.  
  47.     /** 
  48.      * 当开始解析xml文件的声明的时候就会触发这个事件, 可以做一些初始化的工作 
  49.      * */ 
  50.     @Override 
  51.     public void startDocument() throws SAXException { 
  52.         // TODO Auto-generated method stub 
  53.         super.startDocument(); 
  54.         // 初始化list 
  55.         persons = new ArrayList<Person>(); 
  56.     } 
  57.  
  58.     /** 
  59.      * 当开始解析元素的开始标签的时候,就会触发这个事件 
  60.      * */ 
  61.     @Override 
  62.     public void startElement(String uri, String localName, String qName, 
  63.             Attributes attributes) throws SAXException { 
  64.         // TODO Auto-generated method stub 
  65.         super.startElement(uri, localName, qName, attributes); 
  66.  
  67.         // 如果读到是person标签 开始存储 
  68.         if (localName.equals("person")) { 
  69.             person = new Person(); 
  70.             person.set_id(attributes.getValue("id")); 
  71.         } 
  72.         curNode = localName; 
  73.     } 
  74.  
  75.     /** 
  76.      * 当读到文本元素的时候要触发这个事件. 
  77.      * */ 
  78.     @Override 
  79.     public void characters(char[] ch, int start, int length) 
  80.             throws SAXException { 
  81.         // TODO Auto-generated method stub 
  82.         super.characters(ch, start, length); 
  83.  
  84.         if (person != null) { 
  85.             // 取出目前元素对应的值 
  86.             String txt = new String(ch, start, length); 
  87.             // 判断元素是否是name 
  88.             if (curNode.equals("name")) { 
  89.                 // 将取出的值添加到person对象 
  90.                 person.set_name(txt); 
  91.             } else if (curNode.equals("age")) { 
  92.                 person.set_age(txt); 
  93.             } 
  94.         } 
  95.     } 
  96.  
  97.     /** 
  98.      * 当读到结束标签的时候 就会触发这个事件 
  99.      * */ 
  100.     @Override 
  101.     public void endElement(String uri, String localName, String qName) 
  102.             throws SAXException { 
  103.         // TODO Auto-generated method stub 
  104.         super.endElement(uri, localName, qName); 
  105.  
  106.         // 如果是person结尾 并且person不为空,添加到list 
  107.         if (localName.equals("person") && person != null) { 
  108.             persons.add(person); 
  109.             person = null
  110.         } 
  111.  
  112.         curNode = ""
  113.     } 
  114.  

 写个方法调用下这个类

  
  
  
  
  1. List<Person> persons = new SAX_parserXML().ReadXML(is); 
  2.             StringBuffer buffer = new StringBuffer(); 
  3.             for (int i = 0; i < persons.size(); i++) { 
  4.                 Person person =persons.get(i); 
  5.                 buffer.append("id:" + person.get_id() + "   "); 
  6.                 buffer.append("name:" + person.get_name() + "   "); 
  7.                 buffer.append("age:" + person.get_age() + "\n"); 
  8.             } 
  9.             Toast.makeText(activity, buffer, Toast.LENGTH_LONG).show(); 

 如果你看到下面的界面  说明解析成功了~

 解析的问题  就讲到这里  如果有缺少的  有问题的   可以留言  会在博客中补充

二、SAX生成XML

Sax方式创建XML,应用了标准xml构造器 javax.xml.transform.sax.TransformerHandler 事件来创建 XML 文档

首先,SAXTransformerFactory.newInstance() 创建一个工厂实例 factory

接着,factory.newTransformerHandler() 获取 TransformerHandler 的 handler 对象

然后,通过 handler 事件创建handler.getTransformer()、 handler.setResult(result),以及 startDocument()、startElement、characters、endElement、endDocument()等

写代码之前   我们来讲下几个会用到的类

SAXTransformerFactory

此类扩展了 TransformerFactory 以提供特定于 SAX 的工厂方法。它提供两种类型的 ContentHandler,一种用于创建 Transformer,另一种用于创建 Templates 对象。

如果应用程序希望设置转换期间所使用的 XMLReader 的 ErrorHandler 或 EntityResolver,那么它应使用 URIResolver 来返回提供了(通过 getXMLReader)对 XMLReader 引用的 SAXSource。

newTemplatesHandler()
获取能够将 SAX ContentHandler 事件处理为 Templates 对象的 TemplatesHandler 对象。
newTransformerHandler()
获取能够将 SAX ContentHandler 事件处理为 Result 的 TransformerHandler 对象。
newTransformerHandler(Source src)
基于参数所指定的转换指令,获取能够将 SAX ContentHandler 事件处理为 Result 的 TransformerHandler 对象。
newTransformerHandler(Templates templates) 基于 Templates 参数,获取能够将 SAX ContentHandler 事件处理为 Result 的 TransformerHandler 对象。
newXMLFilter(Source src) 
创建使用给定 Source 作为转换指令的 XMLFilter
newXMLFilter(Templates templates)  
 基于 Templates 参数,创建 XMLFilter

 

TransformerHandler

侦听 SAX ContentHandler 解析事件,并将它们转换为 Result 的 TransformerHandler

getTransformer() 获取与此处理程序关联的 Transformer,用于设置参数和输出属性。
setResult(Result result)  设置与用于转换的此 TransformerHandler 关联的 Result

 

 首先  我们来创建一个工程实例

  
  
  
  
  1. SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory 
  2.                     .newInstance(); 

接下来 获取handler对象

  
  
  
  
  1. TransformerHandler handler = factory.newTransformerHandler(); 

 通过handler.getTransformer()获取Transformer对象 ,然后设置xml的属性

  
  
  
  
  1. // 获取与此处理程序关联的 Transformer,用于设置参数和输出属性。 
  2. Transformer info = handler.getTransformer();   
  3. // 是否自动添加额外的空白   
  4. info.setOutputProperty(OutputKeys.INDENT, "yes");   
  5. // 设置字符编码   
  6. info.setOutputProperty(OutputKeys.ENCODING, "utf-8");   
  7.    
  8. info.setOutputProperty(OutputKeys.VERSION, "1.0");   

创建一个StreamResult对象用来保存创建的xml 

  
  
  
  
  1. StringWriter stringWriter = new StringWriter();  
  2. // 创建一个StreamResult对象用来保存创建的xml  
  3. StreamResult result = new StreamResult(stringWriter);  
  4. handler.setResult(result);  

下面开始处理生成xml,先创建测试数据

  
  
  
  
  1. private List<Person> getTestValues() { 
  2.         List<Person> persons = new ArrayList<Person>(); 
  3.         Person person = new Person(); 
  4.         person.set_id("23"); 
  5.         person.set_name("李磊"); 
  6.         person.set_age("30"); 
  7.         persons.add(person); 
  8.  
  9.         person = new Person(); 
  10.         person.set_id("20"); 
  11.         person.set_name("韩梅梅"); 
  12.         person.set_age("25"); 
  13.         persons.add(person); 
  14.  
  15.         return persons; 
  16.     } 

来看下最后生成的xml是什么样子的?

  
  
  
  
  1. <?xml version="1.0" encoding="UTF-8"?> 
  2. <persons> 
  3.     <person id="23"> 
  4.         <name>李磊</name> 
  5.         <age>30</age> 
  6.     </person> 
  7.     <person id="20"> 
  8.         <name>韩梅梅</name> 
  9.         <age>25</age> 
  10.     </person> 
  11. </persons> 

调用startDocument()表示开始 ,调用endDocument()表示结束,所以 写代码的时候直接把两个都写上,以防万一最后忘记

  
  
  
  
  1. // 开始xml    
  2. handler.startDocument();    
  3. /*代码写在start和end之间*/  
  4. // 结束xml    
  5. handler.endDocument();   

接下来开始写xml 第一步 创建根节点persons,同样的,接下来的代码卸载start和end之间

  
  
  
  
  1. AttributesImpl impl = new AttributesImpl(); 
  2. impl.clear(); 
  3. handler.startElement("""""persons", impl);  
  4. //创建要对应结束  所以写完start  马上补充end  
  5. handler.endElement("""""persons");  

 创建完根节点之后  开始创建person元素 person中含有一个id属性

  
  
  
  
  1. impl.clear(); 
  2. impl.addAttribute("""""id""", person.get_id()); 
  3. handler.startElement("""""person", impl); 
  4. /*在这里创建name和age元素*/ 
  5. handler.endElement("""""person"); 

下面创建name和age元素

  
  
  
  
  1. impl.clear(); 
  2. handler.startElement("""""name", impl); 
  3. String txt = person.get_name(); 
  4. handler.characters(txt.toCharArray(), 0, txt.length()); 
  5. handler.endElement("""""name"); 
  6.  
  7. impl.clear(); 
  8. handler.startElement("""""age", impl); 
  9. txt = person.get_age(); 
  10. handler.characters(txt.toCharArray(), 0, txt.length()); 
  11. handler.endElement("""""age"); 

看下完整代码

  
  
  
  
  1. public String createXML() { 
  2.     // TODO Auto-generated method stub 
  3.  
  4.     StringWriter stringWriter = new StringWriter(); 
  5.     // 创建测试数据 
  6.     List<Person> persons = getTestValues(); 
  7.  
  8.     try { 
  9.         // 创建工厂 
  10.         SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory 
  11.                 .newInstance(); 
  12.  
  13.         TransformerHandler handler = factory.newTransformerHandler(); 
  14.  
  15.         Transformer info = handler.getTransformer(); 
  16.         // 是否自动添加额外的空白 
  17.         info.setOutputProperty(OutputKeys.INDENT, "yes"); 
  18.         // 设置字符编码 
  19.         info.setOutputProperty(OutputKeys.ENCODING, "utf-8"); 
  20.  
  21.         info.setOutputProperty(OutputKeys.VERSION, "1.0"); 
  22.  
  23.         // 保存创建的xml 
  24.         StreamResult result = new StreamResult(stringWriter); 
  25.         handler.setResult(result); 
  26.  
  27.         // 开始xml 
  28.         handler.startDocument(); 
  29.  
  30.         AttributesImpl impl = new AttributesImpl(); 
  31.         impl.clear(); 
  32.         handler.startElement("""""persons", impl); 
  33.  
  34.         for (int i = 0; i < persons.size(); i++) { 
  35.             Person person = persons.get(i); 
  36.  
  37.             impl.clear(); 
  38.             impl.addAttribute("""""id""", person.get_id()); 
  39.             handler.startElement("""""person", impl); 
  40.  
  41.             impl.clear(); 
  42.             handler.startElement("""""name", impl); 
  43.             String txt = person.get_name(); 
  44.             handler.characters(txt.toCharArray(), 0, txt.length()); 
  45.             handler.endElement("""""name"); 
  46.  
  47.             impl.clear(); 
  48.             handler.startElement("""""age", impl); 
  49.             txt = person.get_age(); 
  50.             handler.characters(txt.toCharArray(), 0, txt.length()); 
  51.             handler.endElement("""""age"); 
  52.  
  53.             handler.endElement("""""person"); 
  54.  
  55.         } 
  56.  
  57.         handler.endElement("""""persons"); 
  58.  
  59.         // 结束xml 
  60.         handler.endDocument(); 
  61.  
  62.         return stringWriter.toString(); 
  63.     } catch (Exception e) { 
  64.         // TODO: handle exception 
  65.         e.printStackTrace(); 
  66.     } 
  67.  
  68.     return null

调用方法  查看返回的String是不是xml格式的  如果是的  表示调用成功了

我要讲解了  也就结束了

个人语言水平有限  不懂的  可以留言  我修改

下面一章会讲解pull解析和生成xml

 

你可能感兴趣的:(android,网络开发)