本文主要讲述android中xml的解析方式。
主要参考了《android解析xml文件的方式(其一)》《Android XML解析》
在androd手机中处理xml数据时很常见的事情,通常在不同平台传输数据的时候,我们就可能使用xml,xml是与平台无关的特性,被广泛运用于数据通信中,那么在android中如何解析xml文件数据呢?
通常有三种方式:DOM,SAX,PULL。Pull为Android内置的XML文件解析器。
为了讲解xml文件解析,我这里做了一个android项目,其内容如下:
其中相关文件定义为:
river.xml
<?xml version="1.0" encoding="utf-8"?>
<rivers>
<river name="灵渠" length="605">
<introduction>
灵渠在广西壮族自治区兴安县境内,是世界上最古老的运河之一,有着“世界古代水利建筑明珠”的美誉。灵渠古称秦凿渠、零渠、陡河、兴安运河,于公元前214年凿成通航,距今已2217年,仍然发挥着功用。
</introduction>
<imageurl>
http://imgsrc.baidu.com/baike/pic/item/389aa8fdb7b8322e08244d3c.jpg
</imageurl>
</river>
<river name="胶莱运河" length="200">
<introduction>
胶莱运河南起黄海灵山海口,北抵渤海三山岛,流经现胶南、胶州、平度、高密、昌邑和莱州等,全长200公里,流域面积达5400平方公里,南北贯穿山东半岛,沟通黄渤两海。胶莱运河自平度姚家村东的分水岭南北分流。南流由麻湾口入胶州湾,为南胶莱河,长30公里。北流由海仓口入莱州湾,为北胶莱河,长100余公里。
</introduction>
<imageurl>
http://imgsrc.baidu.com/baike/pic/item/389aa8fdb7b8322e08244d3c.jpg
</imageurl>
</river>
<river name="苏北灌溉总渠" length="168">
<introduction>
位于淮河下游江苏省北部,西起洪泽湖边的高良涧,流经洪泽,青浦、淮安,阜宁、射阳,滨海等六县(区),东至扁担港口入海的大型人工河道。全长168km。
</introduction>
<imageurl>
http://imgsrc.baidu.com/baike/pic/item/389aa8fdb7b8322e08244d3c.jpg
</imageurl>
</river>
</rivers>
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello" />
<Button
android:id="@+id/btn_dom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="dom" />
<Button
android:id="@+id/btn_sax"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="sax" />
<Button
android:id="@+id/btn_pull"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="pull" />
<ListView android:id="@+id/lv_riverlist"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
riveritem.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/tv_length"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
<TextView
android:id="@+id/tv_introduction"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
XmlTestActivity.java
public class XmlTestActivity extends Activity {
/** Called when the activity is first created. */
private Button btn_dom;
private Button btn_sax;
private Button btn_pull;
private TextView tv_type;
private RiverAdapater riverAdapter;
private List<River> rivers;
private String fileName="river.xml";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn_dom=(Button)findViewById(R.id.btn_dom);
btn_sax=(Button)findViewById(R.id.btn_sax);
btn_pull=(Button)findViewById(R.id.btn_pull);
tv_type=(TextView)findViewById(R.id.tv_type);
ListView listView =(ListView)findViewById(R.id.lv_riverlist);
riverAdapter = new RiverAdapater();
listView.setAdapter(riverAdapter);
btn_dom.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{
DomXml domXml=new DomXml();
rivers=domXml.getRiversFromXml(fileName, XmlTestActivity.this.getApplicationContext());
riverAdapter.setRiverList(rivers);
tv_type.setText("dom"+String.valueOf(rivers.size()));
}
});
btn_sax.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{
DomXml domXml=new DomXml();
rivers=domXml.getRiversFromXml(fileName, XmlTestActivity.this.getApplicationContext());
riverAdapter.setRiverList(rivers);
tv_type.setText("sax"+String.valueOf(rivers.size()));
}
});
btn_pull.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{
DomXml domXml=new DomXml();
rivers=domXml.getRiversFromXml(fileName, XmlTestActivity.this.getApplicationContext());
riverAdapter.setRiverList(rivers);
tv_type.setText("pull"+String.valueOf(rivers.size()));
}
});
}
}
River.java
public class River implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public String getIntroduction() {
return introduction;
}
public void setIntroduction(String introduction) {
this.introduction = introduction;
}
public String getImageurl() {
return imageurl;
}
public void setImageurl(String imageurl) {
this.imageurl = imageurl;
}
private int length;
private String introduction;
private String imageurl;
}
RiverAdapater.java
public class RiverAdapater extends BaseAdapter{
private List<River> riverList = Collections.emptyList();
public int getCount() {
return riverList.size();
}
public Object getItem(int position) {
return riverList.get(position);
}
public long getItemId(int position) {
return position;
}
public void setRiverList(List<River> riverList)
{
this.riverList = riverList;
notifyDataSetInvalidated();
}
public class RiverHolder {
public TextView rname;
public TextView rlength;
public TextView rintroduction;
}
public View getView(int position, View convertView, ViewGroup parent) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.riveritem, null);
RiverHolder wh = new RiverHolder();
wh.rname = (TextView) convertView.findViewById(R.id.tv_name);
wh.rlength = (TextView) convertView.findViewById(R.id.tv_length);
wh.rintroduction = (TextView) convertView.findViewById(R.id.tv_introduction);
River wb = riverList.get(position);
if(wb!=null){
wh.rname.setText(wb.getName());
wh.rlength.setText(String.valueOf(wb.getLength()));
wh.rintroduction.setText(wb.getIntroduction(), TextView.BufferType.SPANNABLE);
}
return convertView;
}
}
第一部分先来介绍SAX解析方式:
SAX即是:Simple API for XML
SAX是基于事件驱动的。当然android的事件机制是基于回调函数的,在用SAX解析xml文档时候,在读取到文档开始和结束标签时候就会回调一个事件,在读取到其他节点与内容时候也会回调一个事件。
既然涉及到事件,就有事件源,事件处理器。在SAX接口中,事件源是org.xml.sax包中的XMLReader,它通过parser()方法来解析XML文档,并产生事件。事件处理器是org.xml.sax包中ContentHander、DTDHander、ErrorHandler,以及EntityResolver这4个接口
XMLReader通过相应事件处理器注册方法setXXXX()来完成的与ContentHander、DTDHander、ErrorHandler,以及EntityResolver这4个接口的连接,详细介绍请见下表:
但是我们无需都继承这4个接口,SDK为我们提供了DefaultHandler类来处理,DefaultHandler类的一些主要事件回调方法如下:
由以上可知,我们需要XmlReader 以及DefaultHandler来配合解析xml。
处理思路是:
1:创建SAXParserFactory对象
2: 根据SAXParserFactory.newSAXParser()方法返回一个SAXParser解析器
3:根据SAXParser解析器获取事件源对象XMLReader
4:实例化一个DefaultHandler对象
5:连接事件源对象XMLReader到事件处理类DefaultHandler中
6:调用XMLReader的parse方法从输入源中获取到的xml数据
7:通过DefaultHandler返回我们需要的数据集合。
代码如下:
public class SaxXml {
public List<River> parse(String xmlPath,Context context){
List<River> rivers=null;
SAXParserFactory factory=SAXParserFactory.newInstance();
try {
SAXParser parser=factory.newSAXParser();
//获取事件源
XMLReader xmlReader=parser.getXMLReader();
//设置处理器
RiverHandler handler=new RiverHandler();
xmlReader.setContentHandler(handler);
//解析xml文档
//xmlReader.parse(new InputSource(new URL(xmlPath).openStream()));
xmlReader.parse(new InputSource(context.getAssets().open(xmlPath)));
rivers=handler.getRivers();
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return rivers;
}
public class RiverHandler extends DefaultHandler{
private boolean isRiver;
private boolean xintroduction;
private boolean ximageurl;
private River river;
private List<River> rivers = null;
public List<River> getRivers() {
return rivers;
}
/*
* 接收文档的开始的通知。
*/
@Override public void startDocument() throws SAXException {
rivers = new ArrayList<River>();
}
/**导航到开始标签触发**/
public void startElement (String uri, String localName, String qName, Attributes attributes){
String tagName=localName.length()!=0?localName:qName;
tagName=tagName.toLowerCase().trim();
//如果读取的是river标签开始,则实例化River
if(tagName.equals(Contant.RIVER)){
isRiver=true;
river=new River();
/**导航到river开始节点后**/
river.setName(attributes.getValue(Contant.NAME));
river.setLength(Integer.parseInt(attributes.getValue(Contant.LENGTH)));
}
//然后读取其他节点
if(isRiver){
if(tagName.equals(Contant.INTRODUCTION)){
xintroduction=true;
}else if(tagName.equals(Contant.IMAGEURL)){
ximageurl=true;
}
}
}
/**导航到结束标签触发**/
public void endElement (String uri, String localName, String qName){
String tagName=localName.length()!=0?localName:qName;
tagName=tagName.toLowerCase().trim();
//如果读取的是river标签结束,则把River添加进集合中
if(tagName.equals(Contant.RIVER)){
isRiver=true;
rivers.add(river);
}
//然后读取其他节点
if(isRiver){
if(tagName.equals(Contant.INTRODUCTION)){
xintroduction=false;
}else if(tagName.equals(Contant.IMAGEURL)){
ximageurl=false;
}
}
}
//这里是读取到节点内容时候回调
public void characters (char[] ch, int start, int length){
//设置属性值
if(xintroduction){
//解决null问题
river.setIntroduction(river.getIntroduction()==null?"":river.getIntroduction()+new String(ch,start,length));
}else if(ximageurl){
//解决null问题
river.setImageurl(river.getImageurl()==null?"":river.getImageurl()+new String(ch,start,length));
}
}
}
}
重点在于DefaultHandler对象中对每一个元素节点,属性,文本内容,文档内容进行处理。
前面说过DefaultHandler是基于事件处理模型的,基本处理方式是:当SAX解析器导航到文档开始标签时回调startDocument方法,导航到文档结束标签时回调endDocument方法。当SAX解析器导航到元素开始标签时回调startElement方法,导航到其文本内容时回调characters方法,导航到标签结束时回调endElement方法。
根据以上的解释,我们可以得出以下处理xml文档逻辑:
1:当导航到文档开始标签时,在回调函数startDocument中,可以不做处理,当然你可以验证下UTF-8等等。
2:当导航到rivers开始标签时,在回调方法startElement中可以实例化一个集合用来存贮list,不过我们这里不用,因为在构造函数中已经实例化了。
3:导航到river开始标签时,就说明需要实例化River对象了,当然river标签中还有name ,length属性,因此实例化River后还必须取出属性值,attributes.getValue(NAME),同时赋予river对象中,同时添加为导航到的river标签添加一个boolean为真的标识,用来说明导航到了river元素。
4:当然有river标签内还有子标签(节点),但是SAX解析器是不知道导航到什么标签的,它只懂得开始,结束而已。那么如何让它认得我们的各个标签呢?当然需要判断了,于是可以使用回调方法startElement中的参数String localName,把我们的标签字符串与这个参数比较下,就可以了。我们还必须让SAX知道,现在导航到的是某个标签,因此添加一个true属性让SAX解析器知道。因此
5:它还会导航到文本内标签,(就是<img></img>里面的内容),回调方法characters,我们一般在这个方法中取出就是<img></img>里面的内容,并保存。
6:当然它是一定会导航到结束标签</river> 或者</rivers>的,如果是</river>标签,记得把river对象添加进list中。如果是river中的子标签</introduction>,就把前面设置标记导航到这个标签的boolean标记设置为false.