郑昀@ultrapower
|
产品名称
|
产品版本
|
Keyword: RssReader RssFeed Channel j2me midp midlet kxml xmlpull RMS RssOwl java |
RSS无线阅读器 |
0.7.1729 |
[J2ME][开源]RSSOwlMidlet
(RSS无线阅读器)
设计说明
我的RssReader资源:
1:《[J2ME]RSSOwlMidlet(RSS无线阅读器)开源说明》
2:《[J2ME]RSSOwlMidlet(RSS无线阅读器)设计说明》
3:下载源代码:http://files.cnblogs.com/zhengyun_ustc/RSSOwlMidlet-src.rar
4:下载midlet:jad: RSSOwlMidlet.jad jar: RSSOwlMidlet.jar (jar包可能有点偏大,您可以进行混淆来缩小)
摘要:
本文档给出RSS无线阅读器J2ME版本的设计思路和类说明。
第1章 简单描述RSS无线阅读器J2ME版本的背景、功能和特别之处。
第2章 描述调用RSS无线阅读器J2ME版本的设计思路。
第3章 给出了 RSS无线阅读器J2ME版本的设计过程中需要特别指出的问题。
第4章 给出了 各种下载资源。
1背景、功能和特别之处
摘要:本章 简单描述RSS无线阅读器J2ME版本的背景、功能和特别之处。
我们提供的RSS无线阅读器J2ME版本[开源]是一个可以下载到手机(例如Nokia7610)或者其他无线手持设备(例如PocketPC)的应用程序,用来阅读存在于Internet中广泛的RSS新闻源,比如
豆瓣最新评论 http://www.douban.com/feed/review/latest;
博客堂 http://blog.joycode.com/MainFeed.aspx;
博客园 http://www.cnblogs.com/rss.aspx;
CSDN技术文档 http://www.csdn.net/Rss/RssFeed.aspx?rssid=3;
ChinaUnix.net-业界新闻与评论 http://bbs.chinaunix.net/rss/46.xml;
keso's blog http://feeds.feedburner.com/PlayinWithIt
等等,这些新闻源我们将预置在本应用程序中。
下面介绍RSS无线阅读器的功能列表:
l RSS Feed列表
n 添加Rss Feed
n 编辑Rss Feed
n 删除Rss Feed
l 读取Rss Feed新闻
n 查看新闻摘要
l 重新读取Rss Feed新闻
l 关于我
1.1. RSS概念
这里简单介绍一下RSS,它并不是一个新概念,从大约2002年就已经通行天下了,最通常的是Blog的RSS输出,当然许多其他类型的站点也都提供了本站内容的RSS输出格式,比如:
论坛帖子,比如“ChinaUnix.net”;
新闻站点,比如“百度新闻”或者“Google新闻”;
门户,比如“新浪体育”,等等。
RSS其实就是特定格式的XML。所以在J2ME解决方案中,就是通过HttpConnection获取RSS XML文档并解析,将新闻条目显示在界面上,说起来很简单的。
下面,我们说说这个应用程序的特别之处。
1.2. 特别之处
这个应用程序的思路来自于两个开源的应用程序:
How to create a J2ME MIDP RSS Reader application with NetBeans 4.0 作者 Tommi Laukkanen
J2ME手机电话本 作者 garrey
RSS读取以及用xmlpull解析这部分思路,采用了Tommi 的思路;
MVC的类分解,以及对RMS记录存储的封装模式,采用了 garrey 的思路。
对于获取RMS中存储的所有的Rss Feed列表,采用了文章《探索J2ME:对记录进行排序》的代码,放弃了garrey的RecordComparator比较器的实现。
另外,在调试过程中,也加入了我的一些思考,以及兼容各种RSS的办法。
总结一下特别之处。
特别是比原来Tommi 的版本增加了几个特性:
l 最大的改动就是,原来Tommi把视图/控制器都放在RssReaderMIDlet.java了,而我们将视图分拆出来为RssFeedAdd.java、RssFeedEdit.java、RssFeedList.java等等,控制器则为GUIController.java;
l 可以添加/编辑/删除Rss Feed书签;
l 原来Tommi将Feed列表以及URL存储在RMS的一个条目,我放弃了这种做法,而是像garrey处理电话记录一样;
l 当请求远端服务器时,加入了动画等候画面,提示用户正在获取新闻列表;
l 由于采用了xmlpull方式解析XML,所以可以做到一边后台扫描XML文档,一边前台将解析到的新闻Title显示在用户界面上,不影响用户阅读新闻列表;
l 兼容博客堂/博客园这种.Text类型Blog的RSS,因为它们限制请求方的“User-Agent”;
l 采用KxmlParser的自动检测RSS XML文档的编码格式,所以不用用户专门设置编码格式。
2 设计思路
摘要:本章描述RSS无线阅读器J2ME版本的设计思路。
2.1. RSS XML读取及解析
关键三个包:
l javax.microedition.io. HttpConnection;
l org.kxml2.io. KXmlParser;
l org.xmlpull.v1.*。
HttpConnection:
下面来看看如何用HttpConnection类来请求远端的RSS服务器并获取RSS XML数据。
第一步是使用Connector类打开一个到服务器的连接,我们将把这个连接强制转换为需要的HttpConnection类型。
代码
|
HttpConnection hc = null; hc = (HttpConnection) Connector.open( url ); hc.setRequestMethod(HttpConnection.GET); |
接下来,我们得到HttpConnection上的一个InputStream,允许我们一个字符一个字符的读取服务器的响应数据。
代码
|
parseRssFeedXml( hc.openInputStream() );
public void parseRssFeedXml(InputStream is); |
org.kxml2.io. KxmlParser:
parseRssFeedXml函数就是负责用KXmlParser来解析这响应数据。
kxml2官方站点:http://kxml.sourceforge.net/。
下载kxml2代码:http://sourceforge.net/project/showfiles.php?group_id=9157&package_id=58653。
Enhydra的KXML是一个被设计用于J2ME设备的只占很小存储空间的XML语法分析程序,虽然它也可以被用于其它需要小型XML语法分析程序的环境,比如Applet。KXML支持以下特性:
1、支持XML名称空间;
2、用"松散"模式分析HTML或其它SGML格式;
3、占用很少的存储空间(21 kbps;
4、基于Pull的分析;
5、支持XML写操作;
6、可选的DOM支持;
7、可选的WAP支持。
KXML支持DOM语法分析和操作,但是不支持push语法分析。取而代之,它使用一种稍微不同的称为“Pull”的分析方法。与push语法分析相反,Pull语法分析让程序员从语法分析程序中“拉”出下一个事件。
Xmlpull官方站点:http://www.xmlpull.org/
优点:不必等整个文档解析完成,部分求值结果早就可以开始反馈给用户。
下面我们看看KXML如何做一个Pull语法分析程序。
首先我们需要创建了一个XmlParser,并把它传到一个InputStream中。
代码
|
public void parseRssFeedXml(InputStream is) throws IOException, XmlPullParserException { KXmlParser parser = new KXmlParser(); parser.setInput( is, null); // 设置null让KXmlParser自动检测该使用哪种编码 |
其次,我们需要跳过RSS根节点的“rss”之类的东西。
代码
|
parser.nextTag(); parser.require(parser.START_TAG, null, null); |
下面,我们来寻找RSS中的第一个“item”节点,它代表这个RSS文档中确实包含了新闻条目。这是通过下面的循环做到的。parser.next()的含义是“Get next parsing event”,这样就可以遍历文档,如果找不到item节点,就抛出一个异常。
代码
|
while(!"item".equals(parser.getName()) ){ /** Check if document doesn't include any item tags */ if( parser.next() == parser.END_DOCUMENT ) throw new IOException("No items in RSS feed!"); } |
确认有item节点后,我们来寻找RSS中代表每一个新闻的“item”节点下的三个节点“title”“link”“description”,这是通过下面的循环做到的。parser.next()的含义是“Call next() event if it is START_TAG or END_TAG otherwise throw an exception”,这样就可以遍历文档找全所有的新闻的主题、链接和摘要了。
代码
|
/** Parse <item> tags */ do { parser.require(parser.START_TAG, null, null);
/** Initialize properties */ title = ""; description = ""; link = "";
/** One <item> tag handling*/ while (parser.nextTag() != parser.END_TAG) { parser.require(parser.START_TAG, null, null); String name = parser.getName(); String text = parser.nextText();
/** Save item property values */ if (name.equals("title")) title = text; else if (name.equals("description")) description = text; else if (name.equals("link")) link = text;
parser.require(parser.END_TAG, null, name); }
/** Create new RSS item and add it do RSS document's item * collection */ RssItem rssItem = new RssItem(title, link, description); m_rssFeed.getItems().addElement( rssItem );
parser.nextTag();
} while("item".equals(parser.getName())); |
2.2. MVC设计模式
划分为四个模块:
l midlet:
n RssReaderMIDlet.java:
u 这当然是MIDlet必须的入口;它负责初始化控制器GUIController
l Model,模型:
n RssFeed.java
u Rss Feed所对应的类实体,基本上有这么几个属性:
l 在RMS中存储的id序号;
l Feed名称,如“博客堂”或“博客园”;
l Feed的URL,如http://blog.csdn.net/zhengyun_ustc/Rss.aspx;
n RssFeedParser.java
u 获取RSS XML并解析的类
n RssItem.java
u 对应于RSS Feed返回的每一个新闻条目的类实体,包含主题、摘要以及链接;
l View,视图:
n About.java
u “关于我”的Alert界面
n RssFeedAdd.java
u “添加Rss Feed书签”的Form界面
n RssFeedEdit.java
u “编辑Rss Feed书签”的Form界面
n RssFeedList.java
u “Rss Feed列表”的Form界面
n RssFeedOpen.java
u “读取Rss Feed新闻”的Form界面
n RssFeedView.java
u “查看Rss Feed书签”的Form界面
n RssItemView.java
u “查看新闻摘要”的Form界面
n WaitFlash.java
u “动画等待画面”的Canvas界面
l controller:
n GUIController.java
u MVC中的控制器部分,负责界面事件的处理,以及决定该显示哪一个Form
控制器的事件处理部分:
下面来看看如何处理界面事件。
代码
|
public void handleEvent( int eventID,Object[] args){ switch (eventID) { case EventID.EVENT_EXIT: { System.gc(); // 通知进行垃圾收集 Thread.yield(); // 本线程暂停一下,使得GC可以马上获得机会运行 rssMidlet.exit(false); break; } case EventID.EVENT_VIEW_DETAIL: { setCurrent(openForm); openForm.setRss((RssFeed)args[0]); break; } …… |
每一个Form的如何转发事件:
每一个Form上也有事件响应,并可以自行处理事件,然后加入参数,进一步转发事件到控制器上。这个概念来自于garrey的手机电话本源代码。
代码
|
/* * 内部监听器,监听器监听所有Command事件,并把事件响应推出来让控制器处理 */ private class RssFeedAddListener implements CommandListener{ public void commandAction(Command command, Displayable disp){ if(command == BACK_COMMAND){ controller.handleEvent(GUIController.EventID.EVENT_NEW_BACK, null); } else if(command == SAVE_COMMAND){ String title = titleField.getString(); String url = urlField.getString(); if((title == null || title.equals("")) || (url == null || url.equals("") || url.equals("http://"))){ return; }
Object[] args = {title, url}; controller.handleEvent(GUIController.EventID.EVENT_NEW_SAVE, args); }//end else } }//end inner class |
2.3. 读取新闻的后台线程问题
我们在RssFeedOpen这个“读取Rss Feed新闻”的Form界面中,做了一点特殊处理,这样才能够后台线程专门读取RSS XML,而前台界面只需要负责跟进显示解析出来的RSS新闻条目即可了。
所以我们的类这么声明:
代码
|
public class RssFeedOpen extends Form implements Runnable |
我们为了实现,特地实现了一个run函数,它不断地循环查看标志m_getPage,如果是true,则说明后台线程应该去获取RSS XML了。否则,就睡眠一段时间。
代码
|
public void run(){ /* Use networking if necessary */ long lngStart; long lngTimeTaken; while(true) { try { // 我们是依靠m_getPage来判断是否去得到并解析Rss XML的. // 如果当前m_getPage是false,那么本线程就只能先睡眠一段时间了. if( m_getPage ) { try { /** Get RSS feed */
} m_getPage = false; } lngStart = System.currentTimeMillis(); lngTimeTaken = System.currentTimeMillis() - lngStart; if(lngTimeTaken < 100) m_netThread.sleep(75 - lngTimeTaken); } catch (InterruptedException e) { break; } } } |
3 设计过程中需要特别指出的问题
摘要:本章给出了 RSS无线阅读器J2ME版本的设计过程中需要特别指出的问题。
3.2. HttpConnection请求博客堂/博客园问题
在请求Rss Feed时,遇到一个奇怪的问题。别的RSS源都没有问题,唯独博客堂/博客园始终返回这样的错误信息:
Bad Request (Invalid Header Name)
看来是HttpConnection请求时的Header设置问题。
最终经过反复试验,发现不能设置“User-Agent”字段,否则对方.Text应用不接受。
所以我把下面的代码注释:
hc.setRequestProperty("User-Agent",
"Profile/MIDP-1.0 Configuration/CLDC-1.0");
3.3. 自动识别RSS XML编码问题
Tommi的代码中,是直接用:
parser.setInput( is,"utf-8"); // 设置读取用UTF-8编码
来解析XML的。这样会有问题。
后来看到KXML2的KXMLParser.java中setInput函数其实是可以自动处理编码问题的:
代码
|
public void setInput(InputStream is, String _enc){ …. if (enc == null) { // read four bytes
int chk = 0;
while (srcCount < 4) { int i = is.read(); if (i == -1) break; chk = (chk << 8) | i; srcBuf[srcCount++] = (char) i; }
if (srcCount == 4) { switch (chk) { case 0x00000FEFF : enc = "UTF-32BE"; srcCount = 0; break;
case 0x0FFFE0000 : enc = "UTF-32LE"; srcCount = 0; break;
case 0x03c : enc = "UTF-32BE"; srcBuf[0] = '<'; srcCount = 1; break;
case 0x03c000000 : enc = "UTF-32LE"; srcBuf[0] = '<'; srcCount = 1; break;
case 0x0003c003f : enc = "UTF-16BE"; srcBuf[0] = '<'; srcBuf[1] = '?'; srcCount = 2; break;
case 0x03c003f00 : enc = "UTF-16LE"; srcBuf[0] = '<'; srcBuf[1] = '?'; srcCount = 2; break; |
4 资源
我的RssReader资源:
1:《[J2ME]RSSOwlMidlet(RSS无线阅读器)开源说明》
2:《[J2ME]RSSOwlMidlet(RSS无线阅读器)设计说明》
3:下载源代码:http://files.cnblogs.com/zhengyun_ustc/RSSOwlMidlet-src.rar
4:下载midlet:jad: RSSOwlMidlet.jad jar: RSSOwlMidlet.jar (jar包可能有点偏大,您可以进行混淆来缩小)
网络资源:
1:How to create a J2ME MIDP RSS Reader application with NetBeans 4.0 作者 Tommi Laukkanen
4:kxml2官方站点:http://kxml.sourceforge.net/
5:Xmlpull官方站点:http://www.xmlpull.org/
6:实例教您KXML:J2ME中XML语法分析的利器
编写者 |
日期 |
关键词 |
郑昀@ultrapower |
2005-10-07 |
RssReader RssFeed Channel j2me midp midlet kxml xmlpull RMS RssOwl java |