2012/12/02

シンプルなAndroid RSSリーダーを作ってみました

もう12月ですね。ということはアドベントカレンダーの季節になりました♪

子供の時、アドベントカレンダー(チョコレートが入っているほう)でずいぶん楽しんでましたので、大人になった自分もこの季節を楽しめるようにPerl Advent Calendar(http://www.perladvent.org)のRSSリーダーを作成してみました。

RSSのXMLの構成はこんな構成

<entry>
  <title>タイトル</title>
  <id>URL</id>
  <summary>要約</summary>
  <updated>更新時間</updated>
</entry>

なので、以下のようなPOJOを作成しました。


public class Entry implements Serializable {   //後でIntentのBundleに入れるようにSerializableを実装
 
 private String title;
 private String link;
 private String summary;
 private String updated;
 
 public void setTitle(String title) {
  this.title = title;
 }
 
 public String getTitle() {
  return this.title;
 }
 
 //...長いので、linkとsummaryのゲッターとセッターを略します
 
 public void setUpdated(String updated) {
  String dateOnly = updated.substring(0, 10); 
  this.updated = dateOnly;
 }
 
 public String getUpdated() {
  return this.updated;
 }
}


SAXパーサーを使うことにしました。ハンドラークラスを使ってstartElementメソッドで適当なタグ名を見つけたらフラグを立って、中身のテキストを全部引っ張ってきて、最後にendElementメソッドでフラグを消すという訳ですが、なぜかSAXの仕様でcharactersのメソッドが何回も呼ばれることもあるらしくて、たまにテキストが2・3回読み込まれてて、困りました・・・



長い分を読み込むためにcharactersを複数回呼ぶことが必要と思うので、あまりいい解決方法ではないかと思いますけど、文字を追加してくれるbuilder.appendを呼ぶ直前にbuilder.setLength(0)でリセットすることでとりあえず解決できました・・・


builder.setLength(0);
builder.append(ch, start, length);



奇麗に表示できました。



ハンドラークラスはこんな感じになっています:


public class RSSHandler extends DefaultHandler {
 private ArrayList entries;
 private Entry currentEntry;
 private StringBuilder builder;
 
 boolean inTitle;
 boolean inLink;
 boolean inSummary;
 boolean inUpdated;
 boolean inEntry;
 
 public ArrayList getEntries() {
  return this.entries;
 }
 
 @Override
 public void startDocument() throws SAXException {
  super.startDocument();
  entries = new ArrayList();
  builder = new StringBuilder();
 }

 @Override
 public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
  super.startElement(uri, localName, name, attributes);
  if (localName.equalsIgnoreCase(ENTRY)) {
   this.currentEntry = new Entry();
   inEntry = true;
  }
  if (localName.equalsIgnoreCase(TITLE)) {
   inTitle = true;
  }
  if (localName.equalsIgnoreCase(LINK)) {
   inLink = true;
  }
  if (localName.equalsIgnoreCase(SUMMARY)) {
   inSummary = true;
  }
  if (localName.equalsIgnoreCase(UPDATED)) {
   inUpdated = true;
  }
 }
 
 @Override
 public void characters(char[] ch, int start, int length) throws SAXException {
  super.characters(ch, start, length);
  
  if (inEntry) {
   if (inTitle) {
    builder.setLength(0);
    builder.append(ch, start, length);
   }
   if (inLink) {
    builder.setLength(0);
    builder.append(ch, start, length);
   }
   if (inSummary) {
    builder.append(ch, start, length);
   }
   if (inUpdated) {
    builder.setLength(0);
    builder.append(ch, start, length);
   }
  }
 }
 
 @Override
 public void endElement(String uri, String localName, String name) throws SAXException {
  super.endElement(uri, localName, name);
  if (this.currentEntry != null) {
   if (localName.equalsIgnoreCase(TITLE)) {
    currentEntry.setTitle(builder.toString());
    inTitle = false;
   } else if (localName.equalsIgnoreCase(LINK)) {
    currentEntry.setLink(builder.toString());
    inLink = false;
   } else if (localName.equalsIgnoreCase(SUMMARY)) {
    currentEntry.setSummary(builder.toString());
    inSummary = false;
   } else if (localName.equalsIgnoreCase(UPDATED)) {
    currentEntry.setUpdated(builder.toString());
    inUpdated = false;
   } 
    
   if (localName.equalsIgnoreCase(ENTRY)) {
    entries.add(currentEntry);
    inEntry = false;
   }
  }
 }
}


そしてAsyncTaskでSAXを呼ぶことにして、出来上がったArrayListを返すことにしました。


@Override
 protected ArrayList doInBackground(Void... params) {

  try {
   URL url = new URL(xmlLocation);
   SAXParserFactory spf = SAXParserFactory.newInstance();
   SAXParser sp = spf.newSAXParser();
  
   XMLReader xr = sp.getXMLReader();
   RSSHandler handler = new RSSHandler();
   xr.setContentHandler(handler);     //ハンドラークラスを設定
   
   xr.parse(new InputSource(url.openStream()));
   
   entries = handler.getEntries();
  } catch (MalformedURLException e) {
   Log.e("LoadFeedData", "MalformedUrlException: ", e);
  } catch (Exception e) {
   Log.e("LoadFeedData", "Parsing exception", e);
  }
  return entries;
 }


後は、ArrayAdapterに渡して、そこで項目ごとにListViewにデーターを入れるだけです。

ちなみに、詳細はViewはこんな感じに表示してみました。コードはHighlighted Syntaxで読んだほうが楽なので、結局ブラウザーで読みたくて「ブラウザーで表示」みたいなボタンも用意しておきました(汗)



以前にJSONデータを読み込んでListViewで表示するようなアプリは作ったことはあったんですが、XMLは初めてですので、勉強になりました。SAX Parserの仕様をもっと深く勉強する必要があると思いますけど、一応、このRSSリーダーでアドベントカレンダーを読んでおきます。

2012/11/25

ブログを開始しました

こんにちは!laoujiと言います。26歳。
エンジニアにクラスチェンジしてからまだ1年経ってないので、どっちかというと初心者向けコンテンツになりそうですが、Techブログやらせていただきます。
プログラマーはみんなブログやっているイメージがあるので、自分もそろそろやってみようかと思いました。実際何を書けばいいかよくわかりませんが、まずブログソフトを配置すれば、何とかなるかなと思いました(汗)。
ではよろしくお願いします!