天天看点

Android XML解析学习——Pull方式 .

一.基础知识

通过前面的学习我们已经知道了Android上使用SAX和DOM方式解析XML的方法,并且对两种做了简单的比较,通过比较我们知道对在往往内存比较稀缺的移动设备上运行的Android系统来说,SAX是一种比较合适的XML解析方式。

但是SAX方式的特点是需要解析完整个文档才会返回,如果在一个XML文档中我们只需要前面一部分数据,但是使用SAX方式还是会对整个文档进行解析,尽管XML文档中后面的大部分数据我们其实都不需要解析,因此这样实际上就浪费了处理资源。

就以USGS的地震数据为例,USGS网上的这个数据是定时更新的,但是一次更新往往只更新前面几条地震数据,大部分数据还是相同的,因此我们在解析时可以在上一次解析的结果之上根据<updated>元素标签中的值解析前面几条比之前updated值更新的地震数据即可。但是如果使用SAX方式的话,每次还是都会解析整个XML文档,而这却浪费了处理器资源和延长了处理的时间。

不过Android系统还提供了另一种XML解析方式可以使你更好的处理这种情况,就是Pull方式解析XML数据。

Pull解析器和SAX解析器虽有区别但也有相似性。他们的区别为:SAX解析器的工作方式是自动将事件推入注册的事件处理器进行处理,因此你不能控制事件的处理主动结束;而Pull解析器的工作方式为允许你的应用程序代码主动从解析器中获取事件,正因为是主动获取事件,因此可以在满足了需要的条件后不再获取事件,结束解析。这是他们主要的区别。

而他们的相似性在运行方式上,Pull解析器也提供了类似SAX的事件(开始文档START_DOCUMENT和结束文档END_DOCUMENT,开始元素START_TAG和结束元素END_TAG,遇到元素内容TEXT等),但需要调用next() 方法提取它们(主动提取事件)。

Android系统中和Pull方式相关的包为org.xmlpull.v1,在这个包中提供了Pull解析器的工厂类XmlPullParserFactory和Pull解析器XmlPullParser,XmlPullParserFactory实例调用newPullParser方法创建XmlPullParser解析器实例,接着XmlPullParser实例就可以调用getEventType()和next()等方法依次主动提取事件,并根据提取的事件类型进行相应的逻辑处理。

下面我们就用上面介绍的Pull方式来实现解析XML形式的USGS地震数据的Demo例子。

二.实例开发

我们要完成的效果图如下图1所示:

Android XML解析学习——Pull方式 .

图1 ListView列表显示的地震数据

和上一部分Demo例子的一样,也是解析完地震数据后用ListView列表的方式显示每条地震的震级和地名信息。

新建一个Android工程AndroidXMLDemoPull。

要添加的基本内容和上一个Demo中的一样,这里就不再赘述,这次要添加的解析器新类为PullEarthquakeHandler,内容如下所示:

[java] view plain copy print ?

  1. public class PullEarthquakeHandler {  
  2.     //xml解析用到的Tag   
  3.     private String kEntryElementName = "entry";  
  4.     private String kLinkElementName = "link";  
  5.     private String kTitleElementName = "title";  
  6.     private String kUpdatedElementName = "updated";  
  7.     private String kGeoRSSPointElementName = "georss:point";  
  8.     private String kGeoRSSElevElementName = "georss:elev";  
  9.     //用于保存xml解析获取的结果   
  10.     private ArrayList<EarthquakeEntry> earthquakeEntryList = null;  
  11.     private EarthquakeEntry earthquakeEntry = null;   
  12.     private Boolean startEntryElementFlag = false;  
  13.     //解析xml数据   
  14.     public ArrayList<EarthquakeEntry> parse(InputStream inStream)  
  15.     {  
  16.         try {  
  17.                         //创建XmlPullParser,有两种方式   
  18.             //方式一:使用工厂类XmlPullParserFactory   
  19.             XmlPullParserFactory pullFactory = XmlPullParserFactory.newInstance();  
  20.             XmlPullParser xmlPullParser = pullFactory.newPullParser();  
  21. //          //方式二:使用Android提供的实用工具类android.util.Xml   
  22. //          XmlPullParser xmlPullParser = Xml.newPullParser();   
  23.             xmlPullParser.setInput(inStream, "UTF-8");  
  24.             int eventType = xmlPullParser.getEventType();  
  25.             boolean isDone = false;   
  26.             //具体解析xml   
  27.             while ((eventType != XmlPullParser.END_DOCUMENT)&&(isDone != true)) {  
  28.                 String localName = null;  
  29.                 switch (eventType) {  
  30.                     case XmlPullParser.START_DOCUMENT:  
  31.                     {  
  32.                         earthquakeEntryList = new ArrayList<EarthquakeEntry>();  
  33.                     }  
  34.                         break;  
  35.                     case XmlPullParser.START_TAG:  
  36.                     {  
  37.                         localName = xmlPullParser.getName();  
  38.                         if(localName.equalsIgnoreCase(kEntryElementName))  
  39.                         {  
  40.                             earthquakeEntry = new EarthquakeEntry();  
  41.                             startEntryElementFlag = true;  
  42.                         }  
  43.                         else if(startEntryElementFlag == true)  
  44.                         {  
  45.                             String currentData = null;  
  46.                             if(localName.equalsIgnoreCase(kTitleElementName))  
  47.                             {  
  48.                                 currentData = xmlPullParser.nextText();  
  49. //                              Log.v("Pull", currentData);   
  50.                                 //提取强度信息   
  51.                                 String magnitudeString = currentData.split(" ")[1];  
  52.                                 int end =  magnitudeString.length()-1;  
  53.                                 magnitudeString = magnitudeString.substring(0, end);  
  54.                                 double magnitude = Double.parseDouble(magnitudeString);  
  55.                                 earthquakeEntry.setMagnitude(magnitude);  
  56.                                 //提取位置信息   
  57.                                 String place = currentData.split(",")[1].trim();  
  58.                                 earthquakeEntry.setPlace(place);                                  
  59.                             }  
  60.                             else if (localName.equalsIgnoreCase(kUpdatedElementName)) {  
  61.                                 currentData = xmlPullParser.nextText();  
  62. //                              Log.v("Pull", currentData);   
  63.                                 //构造更新时间   
  64.                                 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");  
  65.                                 Date qdate = new GregorianCalendar(0,0,0).getTime();  
  66.                                 try {  
  67.                                   qdate = sdf.parse(currentData);  
  68.                                 } catch (ParseException e) {  
  69.                                   e.printStackTrace();  
  70.                                 }  
  71.                                 earthquakeEntry.setDate(qdate);  
  72.                             }  
  73.                             else if (localName.equalsIgnoreCase(kGeoRSSPointElementName)) {  
  74.                                 currentData = xmlPullParser.nextText();  
  75. //                              Log.v("Pull", currentData);   
  76.                                 //提取经纬度信息   
  77.                                 String[] latLongitude = currentData.split(" ");  
  78.                                 Location location = new Location("dummyGPS");  
  79.                                 location.setLatitude(Double.parseDouble(latLongitude[0]));  
  80.                                 location.setLongitude(Double.parseDouble(latLongitude[1]));  
  81.                                 earthquakeEntry.setLocation(location);  
  82.                             }  
  83.                             else if (localName.equalsIgnoreCase(kGeoRSSElevElementName)) {  
  84.                                 currentData = xmlPullParser.nextText();  
  85. //                              Log.v("Pull", currentData);   
  86.                                 //提取海拔高度信息   
  87.                                 double evel;  
  88.                                 //因为USGS数据有可能会输错,比如为"--10000",多了一个"-"号   
  89.                                 try {  
  90.                                     evel = Double.parseDouble(currentData);  
  91.                                 } catch (Exception e) {  
  92.                                     // TODO: handle exception   
  93.                                     e.printStackTrace();  
  94.                                     evel = 0;  
  95.                                 }  
  96.                                 Log.v("Pull_Elev", String.valueOf(evel));  
  97.                                 earthquakeEntry.setElev(evel);  
  98.                             }  
  99.                             else if(localName.equalsIgnoreCase(kLinkElementName))  
  100.                             {  
  101.                                 String webLink = xmlPullParser.getAttributeValue("", "href");  
  102.                                 earthquakeEntry.setLink(webLink);  
  103. //                              Log.v("Pull", webLink);   
  104.                             }  
  105.                         }                     
  106.                     }  
  107.                         break;  
  108.                     case XmlPullParser.END_TAG:  
  109.                     {  
  110.                         localName = xmlPullParser.getName();  
  111.                         if((localName.equalsIgnoreCase(kEntryElementName))&&(startEntryElementFlag==true))  
  112.                         {  
  113.                             earthquakeEntryList.add(earthquakeEntry);  
  114.                             startEntryElementFlag = false;  
  115.                         }  
  116.                     }  
  117.                         break;  
  118.                     default:  
  119.                         break;  
  120.                     }  
  121.                 eventType = xmlPullParser.next();  
  122.             }  
  123.         } catch (Exception e) {  
  124.             // TODO Auto-generated catch block   
  125.             e.printStackTrace();  
  126.         }  
  127.         Log.v("Pull", "End");  
  128.         return earthquakeEntryList;  
  129.     }  
  130. }  

public class PullEarthquakeHandler { //xml解析用到的Tag private String kEntryElementName = "entry"; private String kLinkElementName = "link"; private String kTitleElementName = "title"; private String kUpdatedElementName = "updated"; private String kGeoRSSPointElementName = "georss:point"; private String kGeoRSSElevElementName = "georss:elev"; //用于保存xml解析获取的结果 private ArrayList<EarthquakeEntry> earthquakeEntryList = null; private EarthquakeEntry earthquakeEntry = null; private Boolean startEntryElementFlag = false; //解析xml数据 public ArrayList<EarthquakeEntry> parse(InputStream inStream) { try { //创建XmlPullParser,有两种方式 //方式一:使用工厂类XmlPullParserFactory XmlPullParserFactory pullFactory = XmlPullParserFactory.newInstance(); XmlPullParser xmlPullParser = pullFactory.newPullParser();// //方式二:使用Android提供的实用工具类android.util.Xml// XmlPullParser xmlPullParser = Xml.newPullParser(); xmlPullParser.setInput(inStream, "UTF-8"); int eventType = xmlPullParser.getEventType(); boolean isDone = false; //具体解析xml while ((eventType != XmlPullParser.END_DOCUMENT)&&(isDone != true)) { String localName = null; switch (eventType) { case XmlPullParser.START_DOCUMENT: { earthquakeEntryList = new ArrayList<EarthquakeEntry>(); } break; case XmlPullParser.START_TAG: { localName = xmlPullParser.getName(); if(localName.equalsIgnoreCase(kEntryElementName)) { earthquakeEntry = new EarthquakeEntry(); startEntryElementFlag = true; } else if(startEntryElementFlag == true) { String currentData = null; if(localName.equalsIgnoreCase(kTitleElementName)) { currentData = xmlPullParser.nextText();// Log.v("Pull", currentData); //提取强度信息 String magnitudeString = currentData.split(" ")[1]; int end = magnitudeString.length()-1; magnitudeString = magnitudeString.substring(0, end); double magnitude = Double.parseDouble(magnitudeString); earthquakeEntry.setMagnitude(magnitude); //提取位置信息 String place = currentData.split(",")[1].trim(); earthquakeEntry.setPlace(place); } else if (localName.equalsIgnoreCase(kUpdatedElementName)) { currentData = xmlPullParser.nextText();// Log.v("Pull", currentData); //构造更新时间 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); Date qdate = new GregorianCalendar(0,0,0).getTime(); try { qdate = sdf.parse(currentData); } catch (ParseException e) { e.printStackTrace(); } earthquakeEntry.setDate(qdate); } else if (localName.equalsIgnoreCase(kGeoRSSPointElementName)) { currentData = xmlPullParser.nextText();// Log.v("Pull", currentData); //提取经纬度信息 String[] latLongitude = currentData.split(" "); Location location = new Location("dummyGPS"); location.setLatitude(Double.parseDouble(latLongitude[0])); location.setLongitude(Double.parseDouble(latLongitude[1])); earthquakeEntry.setLocation(location); } else if (localName.equalsIgnoreCase(kGeoRSSElevElementName)) { currentData = xmlPullParser.nextText();// Log.v("Pull", currentData); //提取海拔高度信息 double evel; //因为USGS数据有可能会输错,比如为"--10000",多了一个"-"号 try { evel = Double.parseDouble(currentData); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); evel = 0; } Log.v("Pull_Elev", String.valueOf(evel)); earthquakeEntry.setElev(evel); } else if(localName.equalsIgnoreCase(kLinkElementName)) { String webLink = xmlPullParser.getAttributeValue("", "href"); earthquakeEntry.setLink(webLink);// Log.v("Pull", webLink); } } } break; case XmlPullParser.END_TAG: { localName = xmlPullParser.getName(); if((localName.equalsIgnoreCase(kEntryElementName))&&(startEntryElementFlag==true)) { earthquakeEntryList.add(earthquakeEntry); startEntryElementFlag = false; } } break; default: break; } eventType = xmlPullParser.next(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } Log.v("Pull", "End"); return earthquakeEntryList; }} 

程序首先也是定义解析用到的变量,在定义的用于解析xml数据的方法中

public ArrayList<EarthquakeEntry> parse(InputStream inStream)

定义了一个局部变量

boolean isDone = false; 

用于标志在有满足条件时停止读取XML文档,退出解析过程。

主体部分首先创建XmlPullParser,

[java] view plain copy print ?

  1.             //创建XmlPullParser,有两种方式   
  2.             //方式一:使用工厂类XmlPullParserFactory   
  3.             XmlPullParserFactory pullFactory = XmlPullParserFactory.newInstance();  
  4.             XmlPullParser xmlPullParser = pullFactory.newPullParser();  
  5. //          //方式二:使用Android提供的实用工具类android.util.Xml   
  6. //          XmlPullParser xmlPullParser = Xml.newPullParser();  

//创建XmlPullParser,有两种方式 //方式一:使用工厂类XmlPullParserFactory XmlPullParserFactory pullFactory = XmlPullParserFactory.newInstance(); XmlPullParser xmlPullParser = pullFactory.newPullParser();// //方式二:使用Android提供的实用工具类android.util.Xml// XmlPullParser xmlPullParser = Xml.newPullParser(); 

创建XmlPullParser有两种方式,一种是使用我们介绍的org.xmlpull.v1包中的工厂类XmlPullParserFactory。除了这种方式外,还可以使用Android SDK提供的实用工具包android.util中的类Xml的newPullParser()方法直接创建。

接着为pull解析器设置要解析的xml文档数据,并使用主动的方式获取解析器中的事件,

[java] view plain copy print ?

  1. xmlPullParser.setInput(inStream, "UTF-8");  
  2. int eventType = xmlPullParser.getEventType();  

xmlPullParser.setInput(inStream, "UTF-8"); int eventType = xmlPullParser.getEventType();

事件将作为数值代码被发送,因此可以使用一个简单 case-switch或者if-else对事件的类型(START_TAG, END_TAG, TEXT或者其他等)进行判断,并根据对应的事件类型实现相应的处理逻辑。解析并未像 SAX 解析那样监听元素的结束,而是在开始处(START_TAG)完成了大部分处理,因为当某个元素开始时,可以调用 解析器实例的nextText() 从 XML 文档中提取所有字符数据,

currentData = xmlPullParser.nextText();

当一个事件处理完成后,可以调用next()方法主动从解析器中获取下一个事件,

eventType = xmlPullParser.next();

整个过程可以放在一个while循环中,直到碰到XML文档的结束事件,

while ((eventType != XmlPullParser.END_DOCUMENT)&&(isDone != true))

或者是设置的条件满足主动停止解析过程,可以看到在上面的while条件中除了碰到文档结束的条件外,

(eventType != XmlPullParser.END_DOCUMENT)

还有设置的标志是否满足的条件,

(isDone != true)

设置一个标记(布尔变量 isDone)来确定何时到达感兴趣内容的结束部分,允许我们提早停止读取 XML 文档,因为我们知道代码将不会关心文档的其余部分。这有时非常实用,特别是当我们只需要访问一小部分 XML 文档时。通过尽快停止解析,我们可以极大地减少解析时间。这种优化对于Android系统运行的速度较慢的移动设备尤为重要。因此Pull解析器可以提供一些性能优势以及易用性。

最后添加AndroidXMLDemoPull.java文件中的内容,内容和前一个Demo工程AndroidXMLDemoDom中的AndroidXMLDemoDom.java基本一样,

[java] view plain copy print ?

  1. public class AndroidXMLDemoPull extends Activity {  
  2.     //定义显示的List相关变量   
  3.     ListView list;  
  4.     ArrayAdapter<EarthquakeEntry> adapter;  
  5.     ArrayList<EarthquakeEntry> earthquakeEntryList;  
  6.     @Override  
  7.     public void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.main);  
  10.         //获取地震数据流   
  11.         InputStream earthquakeStream = readEarthquakeDataFromFile();  
  12.         //Pull方式进行xml解析   
  13.         PullEarthquakeHandler pullHandler = new PullEarthquakeHandler();  
  14.         earthquakeEntryList = pullHandler.parse(earthquakeStream);  
  15.         //用ListView进行显示   
  16.         list = (ListView)this.findViewById(R.id.list);  
  17.         adapter = new ArrayAdapter<EarthquakeEntry>(this, android.R.layout.simple_list_item_1, earthquakeEntryList);  
  18.         list.setAdapter(adapter);  
  19.     }  
  20.     private InputStream readEarthquakeDataFromFile()  
  21.     {  
  22.         //从本地获取地震数据   
  23.         InputStream inStream = null;  
  24.         try {  
  25.             inStream = this.getAssets().open("USGS_Earthquake_1M2_5.xml");  
  26.         } catch (IOException e) {  
  27.             // TODO Auto-generated catch block   
  28.             e.printStackTrace();  
  29.         }  
  30.         return inStream;  
  31.     }  
  32.     private InputStream readEarthquakeDataFromInternet()  
  33.     {  
  34.         //从网络上获取实时地震数据   
  35.         URL infoUrl = null;  
  36.         InputStream inStream = null;  
  37.         try {  
  38.             infoUrl = new URL("http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml");  
  39.             URLConnection connection = infoUrl.openConnection();  
  40.             HttpURLConnection httpConnection = (HttpURLConnection)connection;  
  41.             int responseCode = httpConnection.getResponseCode();  
  42.             if(responseCode == HttpURLConnection.HTTP_OK)  
  43.             {  
  44.                 inStream = httpConnection.getInputStream();  
  45.             }  
  46.         } catch (MalformedURLException e) {  
  47.             // TODO Auto-generated catch block   
  48.             e.printStackTrace();  
  49.         } catch (IOException e) {  
  50.             // TODO Auto-generated catch block   
  51.             e.printStackTrace();  
  52.         }  
  53.         return inStream;  
  54.     }  
  55. }  

public class AndroidXMLDemoPull extends Activity { /** Called when the activity is first created. */ //定义显示的List相关变量 ListView list; ArrayAdapter<EarthquakeEntry> adapter; ArrayList<EarthquakeEntry> earthquakeEntryList; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //获取地震数据流 InputStream earthquakeStream = readEarthquakeDataFromFile(); //Pull方式进行xml解析 PullEarthquakeHandler pullHandler = new PullEarthquakeHandler(); earthquakeEntryList = pullHandler.parse(earthquakeStream); //用ListView进行显示 list = (ListView)this.findViewById(R.id.list); adapter = new ArrayAdapter<EarthquakeEntry>(this, android.R.layout.simple_list_item_1, earthquakeEntryList); list.setAdapter(adapter); } private InputStream readEarthquakeDataFromFile() { //从本地获取地震数据 InputStream inStream = null; try { inStream = this.getAssets().open("USGS_Earthquake_1M2_5.xml"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return inStream; } private InputStream readEarthquakeDataFromInternet() { //从网络上获取实时地震数据 URL infoUrl = null; InputStream inStream = null; try { infoUrl = new URL("http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml"); URLConnection connection = infoUrl.openConnection(); HttpURLConnection httpConnection = (HttpURLConnection)connection; int responseCode = httpConnection.getResponseCode(); if(responseCode == HttpURLConnection.HTTP_OK) { inStream = httpConnection.getInputStream(); } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return inStream; }}

只是把进行XML解析的部分换成了如下方式:

[java] view plain copy print ?

  1. //Pull方式进行xml解析   
  2. PullEarthquakeHandler pullHandler = new PullEarthquakeHandler();  
  3. earthquakeEntryList = pullHandler.parse(earthquakeStream);  

//Pull方式进行xml解析 PullEarthquakeHandler pullHandler = new PullEarthquakeHandler(); earthquakeEntryList = pullHandler.parse(earthquakeStream);

完成了,可以保存运行看下效果。

三.总结

在这部分中我们学习了Android平台上除了SAX和DOM方式外的第三种解析XML的方式,即使用Pull解析器的方式,并且介绍了Pull方式和SAX方式比较的特点,就是可以在程序中使用代码主动从解析器中提取事件且可以提前停止XML文档的解析。并用这个方式完成了一个解析USGS地震数据的Demo例子。

这样我们就学习了Android平台上的三种解析XML的方式:SAX、DOM和Pull,这三种方式除了我们已经学习过的各自的特点外,他们的性能比较如何?那个解析的速度最快?这部分内容我们以后接着学习。