[转载]Android Rss阅读器 - 爱生活,爱编程 - 博客园

[转载]Android Rss阅读器 – 爱生活,爱编程 – 博客园.

前言

        前几天去北京面试,题目是让我解析一下腾讯的Rss。之前虽然知道xml,但是自己从来没有去学习怎么解析,在网上查一些例子,但是就是没有解析出来。现 在看看还蛮好笑的,因为我那时候是使用sax解析xml的,可不巧的是让我解析的xml的encoding是“GBK”,但是sax默认使用utf-8。 所以没有解析出来也不足为奇了。关于如何解决这个问题下面会说。

 

Rss概念

      要在Android平台中开发RSS客户端,首先需要了解什么是RSS,RSS怎样工作,及怎样解析XML等知识。

      首先,我们必须要了解什么是RSS:

1. RSS指Really Simple Syndication(真正简易联合)

2. RSS使您有能力聚合(syndicate)网站的内容

3. RSS定义了非常简单的方法来共享和查看标题和内容

4. RSS文件可被自动更新

5. RSS允许为不同的网站进行视图的个性化

6. RSS使用XML编写

RSS被设计用来展示选定的数据。如果没有 RSS,用户就不得不每日都来您的网站检查新的内容。对许多用户来说这样太费时了。通过RSS feed(RSS 通常被称为 News feed 或 RSS feed),用户们可以使用 RSS 聚合器来更快地检查您的网站更新(RSS 聚合器是用来聚集并分类 RSS feed 的网站或软件)。由于RSS 数据很小巧并可快速加载,它可轻易地被类似移动电话或 PDA 的服务使用。拥有相似内容的网站环(Web-rings)可以轻易地在它们的网站共享内容,使这些网站更出色更有价值。

谁应当使用RSS?

那些极少更新内容的网管们不需要RSS!

RSS对那些频繁更新内容的网站是很有帮助的,比如:

新闻站点          列出新闻的标题、日期以及描述

企业                 列出新闻和新产品

日程表             列出即将来临的安排和重要日期

站点更新         列出更新过的页面或新的页面

RSS如何工作

使用RSS,您在名为聚合器的公司注册您的内容。

步骤之一是,创建一个RSS 文档,然后使用.xml 后缀来保存它。然后把此文件上传到您的网站。接下来,通过一个RSS 聚合器来注册。每天,聚合器都会到被注册的网站搜索RSS文档,校验其链接,并显示有关 feed 的信息,这样客户就能够链接到使他们产生兴趣的文档。

       一个RSS实例文档

RSS文档使用一种简单的自我描述的语法: 让我们看一个简单的RSS文档:

<?xml version="1.0" encoding="ISO-8859-1" ?>  
<rss version="2.0">  

<channel>  
  <title>W3School Home Page</title>  
  <link>http://www.w3school.com.cn</link>  
  <description>Free web building tutorials</description>  
  <item>  
    <title>RSS Tutorial</title>  
    <link>http://www.w3school.com.cn/rss</link>  
    <description>New RSS tutorial on W3School</description>  
  </item>  
  <item>  
    <title>XML Tutorial</title>  
    <link>http://www.w3school.com.cn/xml</link>  
    <description>New XML tutorial on W3School</description>  
  </item>  
</channel>  

</rss>

文档中的第一行:XML声明 – 定义了文档中使用的XML 版本和字符编码。此例子遵守1.0规范,并使用ISO-8859-1 (Latin-1/West European) 字符集。

下一行是标识此文档是一个RSS文档的RSS 声明(此例是 RSS version 2.0)。

下一行含有 <channel> 元素。此元素用于描述RSS feed。

<channel> 元素有三个必需的子元素:

<title> – 定义频道的标题。(比如 w3school 首页)

<link> – 定义到达频道的超链接。(比如:www.w3school.com.cn)

<description> – 描述此频道(比如免费的网站建设教程)

每个<channel>元素可拥有一个或多个 <item> 元素。

每个<item>元素可定义 RSS feed 中的一篇文章或 “story”。

<item> 元素拥有三个必需的子元素:

<title> – 定义项目的标题。(比如 RSS 教程)

<link> – 定义到达项目的超链接。(比如:http://www.w3school.com.cn/rss)

<description> – 描述此项目(比如 w3school 的RSS 教程)

最后,后面的两行关闭 <channel> 和 <rss> 元素。

 

       上面的只是是一个例子,下面来看一下实际应用的Rss:

<?xml version="1.0" encoding="gb2312"?>

<?xml-stylesheet type="text/xsl" href="/css/rss_xml_style.css"?>

<rss version="2.0">
  <channel>
    <title>新闻国内</title>
    <image>
      <title>新闻国内</title>
      <link>http://news.qq.com</link>
      <url>http://mat1.qq.com/news/rss/logo_news.gif</url>
    </image>
    <description>新闻国内</description>
    <link>http://news.qq.com/china_index.shtml</link>
    <copyright>Copyright 1998 - 2005 TENCENT Inc. All Rights Reserved</copyright>
    <language>zh-cn</language>
    <generator>www.qq.com</generator>
    <item>
      <title>浙江温州泰顺广播电视台原台长受贿获刑十年</title>
      <link>http://news.qq.com/a/20130608/012227.htm</link>
      <author>www.qq.com</author>
      <category/>
      <pubDate>2013-06-08 12:05:21</pubDate>
      <comments/>
      <description>  中新网温州6月8日电(记者赵小燕通讯员晨翔)利用担任广播电视台台长的职务便利,在产品采购、干部任用、人事调动、人事招录等方面为他人谋取利益,先后收受许某某等人所送共计284200元。近日,温州泰顺县人民法院以受贿罪判处被告人张某某有期徒刑十年,并处没收财产30000元;没收被告人张某某受贿违法所得284200元,上</description>
    </item>
    <item>
      <title>河南新蔡回应开发商违规 对外界质疑只字不提</title>
      <link>http://news.qq.com/a/20130608/012148.htm</link>
      <author>www.qq.com</author>
      <category/>
      <pubDate>2013-06-08 11:59:42</pubDate>
      <comments/>
      <description>中新网驻马店6月8日电(记者侯伟胜)本网对河南省新蔡县“城市1号工程”伟利国际广场在征地、施工、销售等环节涉嫌违规一事进行报道后,引起广泛关注。6月7日下午,新蔡县国土局以电子邮件的形式对报道内容进行了说明。但是对于群众最为关注的几点质疑,却只字未提。伟利国际广场是驻马店市、新蔡县重点招商引资项目,新蔡县</description>
    </item>

       后面省略了很多item,大体看一下结构就可以。
        RSS中的注释

       在RSS 中书写注释的语法与HTML 的语法类似:

       <!– This is an RSS comment –>

       RSS使用XML来编写

       因为RSS也是XML,请记住:

       1. 所有的元素必许拥有关闭标签

       2. 元素对大小写敏感

       3. 元素必需被正确地嵌套

       4. 属性值必须带引号

       以上内容来自http://www.w3school.com.cn/rss/index.asp,对RSS进行了简单讲解,如果想看更详细的内容可以 去w3school看看。我们后面要讲的Android RSS客户端就是以此为基础完成的,所以掌握好RSS基础很重要。

 

Rss解析 

          了解了以上内容,我们就通过一个例子来学习RSS,如何对XML文件进行解析。首先,展示一下完成之后项目的运行结果:

                                               

                                                

       下面是程序的具体实现

一:界面布局,具体代码如下:

activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ListView 
        android:id="@+id/list" 
        android:cacheColorHint="#00000000"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</RelativeLayout>

activity_show_description.xml  :

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:autoLink="web"
        android:text="" />

    <Button
        android:ida="@+id/back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:text="@string/back" />

</LinearLayout>

二:编写实体类RssFeed及RssItem

 

RssFeed.java:

package com.example.rssnews.domain;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class RssFeed {

	private String title; // 标题
	private String pubdate; // 发布日期

	private int itemCount; // 用于计算列表的数目
	private List<RssItem> rssItems; // 用于描述列表item

	public RssFeed() {
		rssItems = new ArrayList<RssItem>();
	}

	// 添加RssItem条目,返回列表长度
	public int addItem(RssItem rssItem) {
		rssItems.add(rssItem);
		itemCount++;
		return itemCount;
	}

	// 根据下标获取RssItem
	public RssItem getItem(int position) {
		return rssItems.get(position);
	}

	public List<HashMap<String, Object>> getAllItems() {
		List<HashMap<String, Object>> data = new ArrayList<HashMap<String, Object>>();
		for (int i = 0; i < rssItems.size(); i++) {
			HashMap<String, Object> item = new HashMap<String, Object>();
			item.put(RssItem.TITLE, rssItems.get(i).getTitle());
			item.put(RssItem.PUBDATE, rssItems.get(i).getPubdate());
			data.add(item);
		}
		return data;

	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getPubdate() {
		return pubdate;
	}

	public void setPubdate(String pubdate) {
		this.pubdate = pubdate;
	}

	public int getItemCount() {
		return itemCount;
	}

	public void setItemCount(int itemCount) {
		this.itemCount = itemCount;
	}

}

RssItem.java:

 

package com.example.rssnews.domain;

public class RssItem {

	private String title;
	private String link;
	private String author;
	private String category;
	private String pubdate;
	private String comments;
	private String description;

	public static final String TITLE = "title";
	public static final String PUBDATE = "pubdate";

	public RssItem() {

	}

	public String getTitle() {
		if (title.length() > 20) {
			return title.substring(0, 19) + "...";
		}
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public String getLink() {
		return link;
	}

	public void setLink(String link) {
		this.link = link;
	}

	public String getCategory() {
		return category;
	}

	public void setCategory(String category) {
		this.category = category;
	}

	public String getPubdate() {
		return pubdate;
	}

	public void setPubdate(String pubdate) {
		this.pubdate = pubdate;
	}

	public String getComments() {
		return comments;
	}

	public void setComments(String comments) {
		this.comments = comments;
	}

	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	@Override
	public String toString() {
		return "RssItem [title=" + title + ", description=" + description
				+ ", link=" + link + ", category=" + category + ", pubdate="
				+ pubdate + "]";
	}

}

三:编写解析XML文件时的处理类,用自定义的RssHandler继承自DefaultHandler,然后重载DefaultHandler的五个方法。具体代码如下:

 

RssHandler.java:

package com.example.rssnews.util;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.example.rssnews.domain.RssFeed;
import com.example.rssnews.domain.RssItem;
import android.util.Log;

public class RssHandler extends DefaultHandler {

	RssFeed rssFeed;
	RssItem rssItem;

	String lastElementName = "";// 标记变量,用于标记在解析过程中我们关心的几个标签,若不是我们关心的标签记做0

	final int RSS_TITLE = 1;// 若是 title 标签,记做1,注意有两个title,但我们都保存在item的成员变量中
	final int RSS_LINK = 2;// 若是 link 标签,记做2
	final int RSS_AUTHOR = 3;
	final int RSS_CATEGORY = 4;// 若是category标签,记做 4
	final int RSS_PUBDATE = 5; // 若是pubdate标签,记做5,注意有两个pubdate,但我们都保存在item的pubdate成员变量中
	final int RSS_COMMENTS = 6;
	final int RSS_DESCRIPTION = 7;// 若是 description 标签,记做3

	int currentFlag = 0;

	public RssHandler() {

	}

	@Override
	public void startDocument() throws SAXException {
		super.startDocument();
		rssFeed = new RssFeed();
		rssItem = new RssItem();

	}

	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException {
		super.characters(ch, start, length);
		// 获取字符串
		String text = new String(ch, start, length);
		Log.i("i", "要获取的内容:" + text);

		switch (currentFlag) {
		case RSS_TITLE:
			rssItem.setTitle(text);
			currentFlag = 0;// 设置完后,重置为开始状态
			break;
		case RSS_PUBDATE:
			rssItem.setPubdate(text);
			currentFlag = 0;// 设置完后,重置为开始状态
			break;
		case RSS_CATEGORY:
			rssItem.setCategory(text);
			currentFlag = 0;// 设置完后,重置为开始状态
			break;
		case RSS_LINK:
			rssItem.setLink(text);
			currentFlag = 0;// 设置完后,重置为开始状态
			break;
		case RSS_AUTHOR:
			rssItem.setAuthor(text);
			currentFlag = 0;// 设置完后,重置为开始状态
			break;
		case RSS_DESCRIPTION:
			rssItem.setDescription(text);
			currentFlag = 0;// 设置完后,重置为开始状态
			break;
		case RSS_COMMENTS:
			rssItem.setComments(text);
			currentFlag = 0;// 设置完后,重置为开始状态
			break;
		default:
			break;
		}
	}

	@Override
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		super.startElement(uri, localName, qName, attributes);
		if ("chanel".equals(localName)) {
			// 这个标签内没有我们关心的内容,所以不作处理,currentFlag=0
			currentFlag = 0;
			return;
		}
		if ("item".equals(localName)) {
			rssItem = new RssItem();
			return;
		}
		if ("title".equals(localName)) {
			currentFlag = RSS_TITLE;
			return;
		}
		if ("description".equals(localName)) {
			currentFlag = RSS_DESCRIPTION;
			return;
		}
		if ("link".equals(localName)) {
			currentFlag = RSS_LINK;
			return;
		}
		if ("pubDate".equals(localName)) {
			currentFlag = RSS_PUBDATE;
			return;
		}
		if ("category".equals(localName)) {
			currentFlag = RSS_CATEGORY;
			return;
		}

		if ("author".equals(localName)) {
			currentFlag = RSS_AUTHOR;
			return;
		}

		if ("comments".equals(localName)) {
			currentFlag = RSS_COMMENTS;
			return;
		}
	}

	@Override
	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		super.endElement(uri, localName, qName);
		// 如果解析一个item节点结束,就将rssItem添加到rssFeed中。
		if ("item".equals(localName)) {

			rssFeed.addItem(rssItem);
			return;
		}
	}

	@Override
	public void endDocument() throws SAXException {
		super.endDocument();
	}

	public RssFeed getRssFeed() {
		return rssFeed;
	}

}

四: 从 SAXParser解析器中获得解析xml文件的xmlReader ,使用三中自定义的xml解析器作为xmlReader的处理器,然后使用url打开流,并将流作为 xmlReader解析的输入源并解析。这里需要注意Rss的编码格式,做一个处理。具体代码如下:

package com.example.rssnews.util;

import info.monitorenter.cpdetector.io.ASCIIDetector;
import info.monitorenter.cpdetector.io.CodepageDetectorProxy;
import info.monitorenter.cpdetector.io.JChardetFacade;
import info.monitorenter.cpdetector.io.ParsingDetector;
import info.monitorenter.cpdetector.io.UnicodeDetector;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import com.example.rssnews.domain.RssFeed;

public class RssFeed_SAXParser {

	public RssFeed getFeed(String urlStr) throws ParserConfigurationException,
			SAXException, IOException {
		URL url = new URL(urlStr);
		SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); // 构建SAX解析工厂
		SAXParser saxParser = saxParserFactory.newSAXParser(); // 解析工厂生产解析器
		XMLReader xmlReader = saxParser.getXMLReader(); // 通过saxParser构建xmlReader阅读器

		RssHandler rssHandler = new RssHandler();
		xmlReader.setContentHandler(rssHandler);
		// 使用url打开流,并将流作为 xmlReader解析的输入源并解析

		CodepageDetectorProxy detector = CodepageDetectorProxy.getInstance();
		// 向代理对象添加探测器
		detector.add(JChardetFacade.getInstance());
		// 得到编码字符集对象
		Charset charset = detector.detectCodepage(url);
		// 得到编码名称
		String encodingName = charset.name();
		System.out.println(encodingName);

		InputSource inputSource = null;
		InputStream stream = null;

		// 如果是GB2312编码
		if ("GBK".equals(encodingName)) {
			stream = url.openStream();
			// 通过InputStreamReader设定编码方式
			InputStreamReader streamReader = new InputStreamReader(stream,
					encodingName);
			inputSource = new InputSource(streamReader);
			xmlReader.parse(inputSource);
			return rssHandler.getRssFeed();
		} else {
			// 是utf-8编码
			inputSource = new InputSource(url.openStream());
			inputSource.setEncoding("UTF-8");
			xmlReader.parse(inputSource);
			return rssHandler.getRssFeed();
		}
	}

	/**
	 * 获得远程URL文件的编码格式
	 */
	public static String getReomoteURLFileEncode(URL url) {

		CodepageDetectorProxy detector = CodepageDetectorProxy.getInstance();
		detector.add(new ParsingDetector(false));
		detector.add(JChardetFacade.getInstance());
		detector.add(ASCIIDetector.getInstance());
		detector.add(UnicodeDetector.getInstance());
		java.nio.charset.Charset charset = null;
		try {
			System.out.println(url);
			charset = detector.detectCodepage(url);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		if (charset != null) {
			return charset.name();
		} else {
			return "utf-8";
		}
	}
}

五:编写MainActivity,将解析的XML文件以列表的形式显示出来,具体代码如下:

 

MainActivity.java:

package com.example.rssnews;

import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import com.example.rssnews.domain.RssFeed;
import com.example.rssnews.domain.RssItem;
import com.example.rssnews.util.RssFeed_SAXParser;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.SimpleAdapter;

public class MainActivity extends Activity implements OnItemClickListener {

	// 从网络获取RSS地址
	public final String RSS_URL = "http://news.qq.com/newsgn/rss_newsgn.xml";

	public final String tag = "RSSReader";
	private RssFeed feed = null;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		try {
			feed = new RssFeed_SAXParser().getFeed(RSS_URL);
			System.out.println(feed.getAllItems());
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		showListView();
	}

	/*
	 * 把RSS内容绑定到ui界面进行显示
	 */
	private void showListView() {

		ListView itemList = (ListView) this.findViewById(R.id.list);
		if (feed == null) {
			setTitle("访问的RSS无效");
			return;
		}
		SimpleAdapter simpleAdapter = new SimpleAdapter(this,
				feed.getAllItems(), android.R.layout.simple_list_item_2,
				new String[] { RssItem.TITLE, RssItem.PUBDATE }, new int[] {
						android.R.id.text1, android.R.id.text2 });
		itemList.setAdapter(simpleAdapter);
		itemList.setOnItemClickListener(this);
		itemList.setSelection(0);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {

		Intent intent = new Intent();
		intent.setClass(this, ShowDescriptionActivity.class);
		Bundle bundle = new Bundle();
		bundle.putString("title", feed.getItem(position).getTitle());
		bundle.putString("description",feed.getItem(position).getDescription());
		bundle.putString("link", feed.getItem(position).getLink());
		bundle.putString("pubdate", feed.getItem(position).getPubdate());
		// 用android.intent.extra.INTENT的名字来传递参数
		intent.putExtra("android.intent.extra.rssItem", bundle);
		startActivityForResult(intent, 0);
	}

}

编写ShowDescriptionActivity,用于显示每条新闻的详情,具体代码如下:

 

ShowDescriptionActivity.java:

package com.example.rssnews;

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class ShowDescriptionActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_show_description);
        String content =null;

        Intent intent = getIntent();
        if(intent!=null){
        	Bundle bundle = intent.getBundleExtra("android.intent.extra.rssItem");
        	if(bundle==null){
        		content = "不好意思程序出错啦";
        	}else{
        		content = bundle.getString("title") + "nn"
						+ bundle.getString("pubdate") + "nn"
						+ bundle.getString("description").replace('n', ' ')
						+ "nn详细信息请访问以下网址:n" + bundle.getString("link");
        	}
        }else{
        	content = "不好意思程序出错啦";
        }

        TextView contentText = (TextView) this.findViewById(R.id.content);
        contentText.setText(content);

        Button backButton = (Button) this.findViewById(R.id.back);
        backButton.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				finish();
			}
		});
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}

 

最 后,不要忘了在AndroidManifest.xml文件中添加访问网络的权限:<uses-permission android:name=”android.permission.INTERNET”/>这样,自此一个简易的RSS阅读器就完成了,还有很多 需要完善的地方,感兴趣的朋友可以在这个基础上进一步完善~相应的项目已上传至csdn资源中、欢迎下载

推荐阅读:

  1. http://www.ourunix.org/android/post/57.html
  2. http://architecture.riaos.com/?p=3003376
  3. http://www.ibm.com/developerworks/cn/education/xml/x-androidrss/section5.html
  4. http://www.jizhuomi.com/android/example/146.html
赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏