前回から簡単なRSSリーダーアプリを題材に、Androidアプリ開発の実際をお伝えしている。今回は主に、アプリの肝と言えるRSSのパース機能について、実際のプログラムに沿って解説していこう。
RSSと言っても、中身はシンプルなXMLで記述されている。ここで解説する内容は、今後RSSリーダーだけでなく、さまざまなWebサービスのAPIを利用したアプリを作る際に応用できるだろう。
XMLパーサーは用途に合わせて選ぶ
Javaプラットフォーム上でXMLを扱う際のAPIとしては、現在のところ大きく分けて次の三つが利用されている。
(1)DOM (Document Object Model) --- ツリー・ベースのAPI
(2)SAX (Simple API for XML) --- イベント・ベースのAPI(Push型)
(3)StAX (Streaming API for XML) --- イベント・ベースのAPI(Pull型)
従来、JavaでXMLを扱う場合にはDOMかSAXのどちらかが使われてきた。StAXは、SAXをベースにしつつ、その欠点を補うXMLパーサーとして、Java SE 6から導入されたAPIである。AndroidにはDOMとSAXが標準APIとして搭載されており、StAXについては、ほぼ等価なライブラリである「XmlPullParser(プル・パーサー)」が用意されている。
どのXMLパーサーを使うかはケース・バイ・ケースで判断することになる。この場では三つのAPIの違いについて詳しく説明することはしないが、DOMはSAXやStAXと比べてメモリーを多く消費するので、特にモバイルアプリケーションの場合、パフォーマンスの面で適したAPIとは言えない。したがって、実際にはSAXかXmlPullParserのどちらかを選ぶことになるだろう。本稿ではXmlPullParserを採用する。
さて、前回(第4回)はメインのアクティビティとなる「RssReaderActivity.java」と、個々の記事データを格納する「Item.java」、そしてListViewを生成するために使われる「RssListAdapter.java」という三つのクラスを定義した。
今回は新たにもう一つのクラスを定義する。「AsyncTask」というクラスを継承した「RssParserTask.java」だ。実装作業に入る前に、まずはこのAsyncTaskについて解説しておこう。
スレッド処理を簡素化するAsyncTask
AndroidのUI(ユーザーインタフェース)はシングル・スレッド・モデルを採用しており、Javaにおける通常のスレッド処理とはやや考え方が異なる。画面描画は全てメインスレッド(UIスレッド)が担うため、冗長な処理をメインスレッドで行おうとすると、その間ユーザーからの操作を一切受け付けなくなり、見た目上アプリケーションがハングアップしたような状態になってしまう。
AsyncTaskとは、Androidにおけるこのようなスレッド処理の問題点を解決すべく、Android SDK 1.5から採用されたAPIである。
処理はなるべく複数のスレッドに分散させたいが、前述のようにAndroidの画面描画は全てメインスレッドで行われ、別スレッドでは処理できない。そこで、Handlerというクラスを使ってメインスレッドで実行させる。だがそのための手続きはやや煩雑で、ソースコードの可読性を下げてしまう。
そこでAsyncTaskを使う。HandlerはAsyncTaskのクラス内部で隠蔽されるため、煩雑になりがちなAndroidでのスレッド処理を比較的簡単に、また適切に扱うことができる。
では、実際のプログラムを見てみよう。以下は、AsyncTaskクラスを継承してRssParserTask.javaという独自のタスクを定義した例だ。AsyncTaskを使うためには、必ずAsyncTaskを継承してサブクラスを定義しなければならない(リスト1)。
// RssParserTask.java
public class RssParserTask extends AsyncTask<String, Integer, RssListAdapter> {
private RssReaderActivity mActivity;
private RssListAdapter mAdapter;
private ProgressDialog mProgressDialog;
// コンストラクタ
public RssParserTask(RssReaderActivity activity, RssListAdapter adapter) {
mActivity = activity;
mAdapter = adapter;
}
// タスクを実行した直後にコールされる
@Override
protected void onPreExecute() {
// プログレスバーを表示する
mProgressDialog = new ProgressDialog(mActivity);
mProgressDialog.setMessage("Now Loading...");
mProgressDialog.show();
}
// バックグラウンドにおける処理を担う。タスク実行時に渡された値を引数とする
@Override
protected RssListAdapter doInBackground(String... params) {
RssListAdapter result = null;
try {
// HTTP経由でアクセスし、InputStreamを取得する
URL url = new URL(params[0]);
InputStream is = url.openConnection().getInputStream();
result = parseXml(is);
} catch (Exception e) {
e.printStackTrace();
}
// ここで返した値は、onPostExecuteメソッドの引数として渡される
return result;
}
// メインスレッド上で実行される
@Override
protected void onPostExecute(RssListAdapter result) {
mProgressDialog.dismiss();
mActivity.setListAdapter(result);
}
// XMLをパースする
public RssListAdapter parseXml(InputStream is) throws IOException, XmlPullParserException {
(後述)
}
}
タスクを実行した直後、まずonPreExecuteメソッドがコールされる。これはメインスレッド上で処理されるので、この例のようにプログレスバーを表示させたい場合などは、このメソッドをオーバーライドする必要がある。
バックグラウンドでの処理は「doInBackground」メソッドが担う。今回開発するRSSリーダーの場合は、HTTPを経由してITproのRSS(http://itpro.nikkeibp.co.jp/rss/ITpro.rdf)にネットワークアクセスした後に、後述する「parseXml」メソッドの引数に「InputStream」を渡してコールしている。
そして、このメソッドが返した値は、そのまま「onPostExecute」メソッドの引数として渡される。メインスレッド上で実行したい処理は、このonPostExecuteメソッドで実装する。
リスト1のRssParserTaskをメインのアクティビティ上で実行するには、RssReaderActivity.javaにほんの数行のコードを追加すればよい。RssReaderActivity.javaを以下のように修正しよう(リスト2)。
// RssReaderActivity.java
public class RssReaderActivity extends ListActivity {
public static final String RSS_FEED_URL = "http://itpro.nikkeibp.co.jp/rss/ITpro.rdf";
private ArrayList<Item> mItems;
private RssListAdapter mAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Itemオブジェクトを保持するためのリストを生成し、アダプタに追加する
mItems = new ArrayList<Item>();
mAdapter = new RssListAdapter(this, mItems);
// タスクを起動する
RssParserTask task = new RssParserTask(this, mAdapter);
task.execute(RSS_FEED_URL);
}
}
ここで、executeメソッドの引数に指定した定数「RSS_FEED_URL」は、RssParserTaskクラス内のdoInBackgroundメソッドの可変長引数として渡される。ここで一つ注意すべき点は、生成したタスクは一度しか実行できないということだ。つまり、タスクを実行したいときはその都度インスタンスを生成しなければならない。
次に、XmlPullParserを使ったparseXmlメソッドの中身を見ていこう。