现在的位置: 首页 > 综合 > 正文

Android XML解析学习——Pull方式

2013年04月10日 ⁄ 综合 ⁄ 共 13791字 ⁄ 字号 评论关闭

一. 基础知识

通过前面的学习我们已经知道了 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 所示:

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

新建一个 Android 工程 AndroidXMLDemoPull 。

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

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;  
    }  
}  

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 ,

//创建XmlPullParser,有两种方式   
            //方式一:使用工厂类XmlPullParserFactory   
            XmlPullParserFactory pullFactory = XmlPullParserFactory.newInstance();  
            XmlPullParser xmlPullParser = pullFactory.newPullParser();  
//          //方式二:使用Android提供的实用工具类android.util.Xml   
//          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 文档数据,并使用主动的方式获取解析器中的事件 ,

xmlPullParser.setInput(inStream,  "UTF-8" );  
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 基本一样,

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;  
    }  
}  

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 解析的部分换成了如下方式:

/Pull方式进行xml解析   
PullEarthquakeHandler pullHandler = new  PullEarthquakeHandler();  
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 ,这三种方式除了我们已经学习过的各自的特点外,他们的性能比较如何?那个解析的速度最快?这部分内容我们以后接着学习。

抱歉!评论已关闭.