团队最近打算出一款rss订阅功能的应用,本文针对通过网络http的方式获取xml的rss来做一个技术准备。
在rss reader方面,目前有一些比较成熟的应用,比如腾讯的腾讯订阅,蘑菇新闻等。个人比较喜欢简洁的蘑菇新闻。团队也针对这个做了反编译。

针对rss的应用,会涵盖http、xml解析、多线程、异步等操作。而在xml解析方面,我选择了托管在github上面的android-rss项目作为基库。参考文章[1].

android-rss库使用org.xml.sax.helpers.DefaultHandler进行解析xml,通过网络流的方式来作为解析数据的传入。然后将解析后的数据存储在RSSFeed的items中,通过函数getItems即可得到所处理后的数据。

使用步骤如下:
1 从https://github.com/ahorn/android-rss将项目代码下载到本地,可以是zip的格式。
2 解压代码到某个目录
3 使用eclipse 创建一个项目,本文取项目名称为WPReader(WordPress Reader).
4 点击src目录,创建一个新的package,取名org.mcsoxford.rss.必须是这个名字。
5 在这个包上面点击右键,弹出import
6 选择FileSystem
7 然后选择我们刚才的zip解压目录
8 将所有的文件导入即可。下面我们就可以使用这个所有的代码了。
9 创建一个item.xml用于显示每个单独的Title.
10 main.xml里面放置一个ListView即可。

主代码如下:

package com.jouhu.reader;

import java.util.ArrayList;
import java.util.HashMap;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import org.mcsoxford.rss.*;

public class WPReaderActivity extends Activity {
/** Called when the activity is first created. */
public String tag = "WPReaderActivity";
    @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
//创建一个RSSReader
        RSSReader reader = new RSSReader();
        String url = "http://feeds.bbci.co.uk/news/world/rss.xml";
//创建一个ArrayList用于存储rss数据
        ArrayList<HashMap<String, Object>> listItem = new ArrayList<HashMap<String, Object>>();
try {
//这是最核心的方法,reader.load会解析url上面的xml文件
            RSSFeed feed = reader.load(url);
            EditText et = (EditText)findViewById(R.id.editText1);
            et.setText(feed.getDescription());
            Integer it = feed.getItems().size();
//将所有的解析到的数据加入到listItem中
            for(int i = 0 ; i < it ; i ++)
            {
                RSSItem item = feed.getItems().get(i);
                HashMap<String, Object> map = new HashMap<String, Object>();
                map.put("itemtitle", item.getTitle());
//map.put("itemcontent", item.getDescription());
                map.put("itemcontent", "");
                listItem.add(map);
            }
        } catch (RSSReaderException e) {
// TODO Auto-generated catch block
            e.printStackTrace();
        }
        ListView lv = (ListView)findViewById(R.id.listView1);
//构造一个Adapter
        SimpleAdapter listItemAdapter =
new SimpleAdapter(this, listItem,R.layout.item, new String[]{"itemtitle","itemcontent"}, new int[]{R.id.title,R.id.content});
        lv.setAdapter(listItemAdapter);
    }
}

效果图:

注意第八项中,这是一个通用方法,对于一些开源的库,我们经常可以这样使用。将其所有的源代码加入到项目中,然后直接调用其库的代码。

在本项目中,最重要的一个地方就是RSSHandler类的构造。在参考文章[2]/[3]/[4]分别提到的这个类的构造方法。
在继承的时候几个最重要的方法startDocument、endDocument、startElement、endElement、characters是一般需要重载实现的。这里可以对我们需要解析的xml文件进行针对性的处理。参加下面从参考文献[3]中的demo代码。

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;
public class MyDefaultHandler extends DefaultHandler {
private StringBuffer buf;
public void startDocument() throws SAXException {
        buf=new StringBuffer();
        System.out.println("*******开始解析文档*******");
    }
public void endDocument() throws SAXException {
        System.out.println("*******解析文档结束*******");
    }
public void startPrefixMapping( String prefix, String uri ) {
System.out.println("\n前缀映射: " + prefix +" 开始!"+ "  它的URI是:"+uri);
    }
public void endPrefixMapping( String prefix ) {
       System.out.println("\n前缀映射: "+prefix+" 结束!");
    }
public void startElement( String namespaceURI, String localName,
                                  String fullName, Attributes attributes )
throws SAXException {
        System.out.println("\n元素: " + "["+fullName+"]" +" 开始解析!");
// 打印出属性信息
        for ( int i = 0; i < attributes.getLength(); i++ ) {
            System.out.println("\t属性名称:" + attributes.getLocalName(i)
                + " 属性值:" + attributes.getValue(i));
        }
    }
public void endElement( String namespaceURI, String localName,
                                                      String fullName )
throws SAXException {
//打印出非空的元素内容并将StringBuffer清空
       String nullStr="";
if (!buf.toString().trim().equals(nullStr)){
          System.out.println("\t内容是: " + buf.toString().trim());
       }
       buf.setLength(0);
//打印元素解析结束信息
        System.out.println("元素: "+"["+fullName+"]"+" 解析结束!");
    }
public void characters( char[] chars, int start, int length )
throws SAXException {
//将元素内容累加到StringBuffer中
       buf.append(chars,start,length);
    }
public void warning( SAXParseException exception ) {
        System.out.println("*******WARNING******");
        System.out.println("\t行:\t" + exception.getLineNumber());
        System.out.println("\t列:\t" + exception.getColumnNumber());
        System.out.println("\t错误信息:\t" + exception.getMessage());
        System.out.println("********************");
    }
public void error( SAXParseException exception ) throws SAXException{
        System.out.println("******* ERROR ******");
        System.out.println("\t行:\t" + exception.getLineNumber());
        System.out.println("\t列:\t" + exception.getColumnNumber());
        System.out.println("\t错误信息:\t" + exception.getMessage());
        System.out.println("********************");
    }
public void fatalError( SAXParseException exception ) throws SAXException {
        System.out.println("******** FATAL ERROR ********");
        System.out.println("\t行:\t" + exception.getLineNumber());
        System.out.println("\t列:\t" + exception.getColumnNumber());
        System.out.println("\t错误信息:\t" + exception.getMessage());
        System.out.println("*****************************");
    }
}

参考文章:
1 https://github.com/ahorn/android-rss
2 http://www.ibm.com/developerworks/opensource/library/x-android/index.html
3 http://www.ibm.com/developerworks/cn/xml/x-saxhandle/index.html?ca=drs-
4 http://qiaoakai.iteye.com/blog/583941