开发一个简单的android rss阅读器

虽然google reader都已经关闭了,但是个人觉得rss还是有它存在的价值,尤其对程序员来说。而且,假如你想做一个阅读类的安卓软件,由于一般来说你没有现成的信息来源,将rss作为信息源也是不错的选择。

主要功能:

支持用户自定义rss类别和每个类别下的频道。通过添加的频道了解该频道下的最新新闻,内置了很多频道,如:javaeye的各版块,名人博客,英语学习,雅虎新闻,新浪新闻等。

首先要了解什么是RSS,关于这个我前面的文章里有解释,这里就不多说了,主要作用是订阅自己感兴趣的新闻,这样就不用一个网站一个网站的去找了,通过RSS阅读器就可以轻松实现实时更新阅读。一个RSS文件就是一个规范的xml数据文件,该文件一般以rss,xml或者rdf作为后缀。下面是一个RSS2.0文件结构:

以下是RSS 2.0的代码样例
  <!-- XML版本和字符集 -->
  <?xml version="1.0"?>
  <!-- RSS版本 -->
  <rss version="2.0">
  <!-- 以下为频道信息及新闻列表 -->
  <channel>
  <!-- 频道总体信息:开始 -->
  <!-- 频道标题 -->
  <title>Lift Off News</title>
  <!-- 频道链接的总地址 -->
  <link>http://liftoff.msfc.nasa.gov/</link>
  <!-- 频道描述文字 -->
  <description>Liftoff to Space Exploration.</description>
  <!-- 频道使用的语言(zh-cn表示简体中文) -->
  <language>en-us</language>
  <!-- 频道发布的时间 -->
  <pubDate>Tue, 10 Jun 2003 04:00:00 GMT</pubDate>
  <!-- 频道最后更新的时间-->
  <lastBuildDate>Tue, 10 Jun 2003 09:41:01 GMT</lastBuildDate>
  <docs>http://blogs.law.harvard.edu/tech/rss</docs>
  <!-- 频道生成器 -->
  <generator>Weblog Editor 2.0</generator>
  <managingEditor>editor@example.com</managingEditor>
  <webMaster>webmaster@example.com</webMaster>
  <ttl>5</ttl>
  <!-- 频道总体信息:结束 -->
  <!-- 每条RSS新闻信息都包含在item节点中, -->
  <item>
  <!-- 新闻标题 -->
  <title>Star City</title>
  <!-- 新闻链接地址 -->
  <link>http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp</link>
  <!-- 新闻内容简要描述 -->
  <description>How do Americans get ready to work with Russians aboard the
  International Space Station? They take a crash course in culture, language
  and protocol at Russia's Star City.</description>
  <!-- 新闻发布时间 -->
  <pubDate>Tue, 03 Jun 2003 09:39:21 GMT</pubDate>
  <!-- 新闻目录 -->
  <category>IT</category>
  <!-- 新闻作者 -->
  <author>bill</author>
  <guid>http://liftoff.msfc.nasa.gov/2003/06/03.html#item573</guid>
  </item>
  <item>
  <title>Space Exploration</title>
  <link>http://liftoff.msfc.nasa.gov/</link>
  <description>Sky watchers in Europe, Asia, and parts of Alaska and Canada
  will experience a partial eclipse of the Sun on Saturday, May 31st.</description>
  Fri, 30 May 2003 11:06:42 GMT</pubDate>
  <guid>http://liftoff.msfc.nasa.gov/2003/05/30.html#item572</guid>
  </item>
  <item>
  <title>The Engine That Does More</title>
  <link>http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp</link>
  <description>Before man travels to Mars, NASA hopes to design new engines
  that will let us fly through the Solar System more quickly. The proposed
  VASIMR engine would do that.</description>
  Tue, 27 May 2003 08:37:32 GMT</pubDate>
  <guid>http://www.zhanghangfeng.cn/rss.xml</guid>
  </item>
  <item>
  <title>Astronauts' Dirty Laundry</title>
  <link>http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp</link>
  <description>Compared to earlier spacecraft, the International Space
  Station has many luxuries, but laundry facilities are not one of them.
  Instead, astronauts have other options.</description>
  Tue, 20 May 2003 08:56:02 GMT</pubDate>
  <guid>http://liftoff.msfc.nasa.gov/2003/05/20.html#item570</guid>
  </item>
  </channel>
  </rss>

我主要是获得item下的title、link、pubDate、description的内容,rss阅读器最核心的是解析rss文件,其实就是解析xml文件。现在流行的有三种解析xml的方式,DOM、SAX、Pull。关于DOM和SAX的解析方式完全不同,DOM是基于文档,而SAX是基于事件驱动的,再移动设备上最好是选择SAX方式,网上有很多关于这方面的资料,读者可以自己去查阅,XmlPullParser和SAX原理差不多,但是更简单。这里采用XmlPullParser方式。解析Rss我主要用到三个类,XmlPullParserUtil.java,RSSFeed.java,RSSItem.java,XmlPullParserUtil是解析RSS文件用的,RSSFeed和RSSItem用来存储解析得到的数据。RSSFeed和一个完整的RSS文件相对应,RSSItem和RSS文件里的item标签相对应。

下面是RSSFeed.java的源码

/**
 * 
 * @author alex
 *备注:RSSFeed和一个完整的RSS文件相对应
 *RSS的描述信息有:
 *title:标题信息
 *link:链接信息
 *description:描述信息
 */
public class RSSFeed {
 
    private String title = null; //新闻标题
    private String pubdate = null;
    public String getPubdate() {
        return pubdate;
    }
 
    public void setPubdate(String pubdate) {
        this.pubdate = pubdate;
    }
 
    private int itemCount; //新闻数
    private List<RSSItem> itemList; //存放的是所有新闻,一个item标签代表一条新闻
     
     
    public RSSFeed() {
        itemList = new ArrayList<RSSItem>();
    }
     
    /**
     * 负责把一个RSSItem加入到RSSFeed里面
     * @param item
     * @return
     */
    public int addItem(RSSItem item) {
        itemList.add(item);
        itemCount ++;
        return itemCount;
    }
     
    public RSSItem getItem(int location) {
        return itemList.get(location);
    }
     
    /**
     * 返回所有的新闻列表
     * @return
     */
    public List<RSSItem> getAllItems() {
        return itemList;
    }
     
    /**
     * List<Map<String,Object>>存放着要在ListView显示的数据
     * @return
     */
    public List<Map<String,Object>> getAllItemsForListView() {
        List<Map<String,Object>> data = new ArrayList<Map<String,Object>>();
        int size = itemList.size();
        for(int i=0 ;i<size; i++) {
            HashMap<String,Object> item = new HashMap<String,Object>();
            item.put(RSSItem.TITLE, itemList.get(i).getTitle()); 
            item.put(RSSItem.PUBDATE, itemList.get(i).getPubDate());
            data.add(item);
        }
        return data;
    }
 
    public String getTitle() {
        return title;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
     
    public int getItemCount() {
        return itemCount;
    }
}

下面是RSSItem.java的源码

/**
 * 
 * @author alex
 *备注:RSSItem和RSS文件里的item标签相对应
 *item包含的信息:
 *title:标题信息
 *link:链接信息
 *description:描述信息
 *pubDate:发布日期
 */
public class RSSItem {
 
    public static final String TITLE = "title";      //定义两个常量用于显示在listview上的
    public static final String PUBDATE = "pubDate"; 
     
    private String title = null;   //新闻标题
    private String link = null;    //新闻链接
    private String pubDate = null;  //新闻发布时间
    private String description = null;  //新闻描述
    private String category = null;     //新闻类别
     
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getLink() {
        return link;
    }
    public void setLink(String link) {
        this.link = link;
    }
     
    public String getPubDate() {
        return DateUtil.getSimpleDateFormat(pubDate); //对返回的日期进行格式化,DateUtil是自定义的一个对日期进行格式的类
    }
    public void setPubDate(String pubDate) {
        this.pubDate = pubDate;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public String getCategory() {
        return category;
    }
    public void setCategory(String category) {
        this.category = category;
    }
 
     
}

下面是XmlPullParser.java的源码

/**
 * 
 * @author alex
 *功能:解析读取的RSS文件,并存放到RSSFeed和RSSItem中
 */
public class XmlPullParserUtil {
 
    public static RSSFeed parseXml(URL url) {
        RSSFeed rssFeed = new RSSFeed();
        RSSItem item = null;
         
        try {
            HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection(); //通过参数url创建一个http远程连接
            urlConnection.connect();  //开启连接
            InputStream inputStream = urlConnection.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
             
            String str = "";
            StringBuffer buffer = new StringBuffer();
            while((str = reader.readLine()) != null) {
                buffer.append(str); //把从网络上读取的数据存进StringBuffer里
            }
 
             
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            factory.setNamespaceAware(true); //设置为true,则factory创建的XmlPullParser提供对xml 命名空间的支持
            XmlPullParser parser = factory.newPullParser();
             
            parser.setInput(new StringReader(buffer.toString())); //解析读取的xml文件流
             
            parser.nextTag(); //此时解析的标签为rss,parser.nextTag():调用next()方法,并返回该事件是START_TAG 还是 END_TAG
            parser.nextTag(); //此时解析的标签为channel
     
            while(parser.nextTag() == XmlPullParser.START_TAG){
                String name1=parser.getName(); //获得该标签的名字
                if(name1.equals("item")){
                    item = new RSSItem();
                     
                    while(parser.nextTag()==XmlPullParser.START_TAG){
             
                        String name2 = parser.getName();
                     
                        if(name2.equals("title")){
                            item.setTitle(parser.nextText());
                        }else if(name2.equals("link")){
                            item.setLink(parser.nextText());
                        }else if(name2.equals("description")){
                            item.setDescription(parser.nextText());
                        }
                        else if(name2.equals("pubDate")) {
                                item.setPubDate(parser.nextText());
                            }
                        else{
                            skipUnknownTag(parser);
                        }
                    }
                    rssFeed.addItem(item);
                 
                }else{
                    skipUnknownTag(parser);
                }
            }
     
     
    }catch(Exception e) {
         
    }
        return rssFeed;
    }
     
    /**
     * 标签结束时进行的处理
     * @param parser
     * @throws Exception
     */
    private static void skipUnknownTag(XmlPullParser parser) throws Exception {
    // 事件为START_DOCUMENT时,parser.next()=0
        while (parser.next() > 0) {
            if (parser.getEventType() == XmlPullParser.END_TAG)
                break;
        }
    }
 
}

以上是该rss阅读器的核心代码,关于android UI的代码由于篇幅就不贴上来了。

通过该项目掌握的知识点:1)RSS的文件结构和含义

2)解析xml文件的几种方式

3)android应用的结构和MVC模式

4)activity的生命周期,activity之间通过intent通信,handler消息机制,自定义ListView,LinearLayout、RelativeLayout、FrameLayout布局界面,WebView显示网页。android数据存储(SQLite3)的相关操作,菜单和对话框、Toast的使用,android的测试驱动,uses-permission权限配置等。

附件是源码和截图