簡単なRSSリーダーを作ってみる

第4回 簡単なRSSリーダーを作ってみる
第5回 RSSリーダーの要、パース機能を知る
第6回 詳細画面を付けて、簡易RSSリーダーの完成

という記事があって、コードをまとめてみて動かしてみようかと。
XMLの受信、パース、表示の流れはクラウドアプリでは必ず必要だと思うので。

ファイル構成

rssreader
|-- AndroidManifest.xml
|-- bin
|-- build.properties
|-- build.xml
|-- default.properties
|-- libs
|-- local.properties
|-- res
|   |-- layout
|   |   |-- main.xml
|   |   `-- item_row.xml
|   `-- values
|       `-- strings.xml
`-- src
    `-- com
        `-- sample
            `-- rssreader
                |-- RssReader.java (*RssReaderActivity.java)
                |-- RssListAdapter.java
                |-- RssParserTask.java
                |-- Item.java
                `-- ItemDetailActivity.java

AndroidManifest.xml

アクティビティは2つ。許可するパーミッションはインターネット接続のみ。
アイコンは画像がないので指定しない。
SDKのバージョンは1.6以上とする。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.sample.rssreader"
          android:versionCode="1"
          android:versionName="1.0">
  <application android:label="@string/app_name">
    <activity android:name=".RssReader"
              android:label="@string/app_name">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <activity android:name=".ItemDetailActivity" />
  </application>
  <uses-sdk android:minSdkVersion="4" />
  <uses-permission android:name="android.permission.INTERNET" />
</manifest>

res/layout/main.xml

メインアクティビティのリストビューのレイアウト。
それぞれのリストひとつひとつは、item_row.xmlで。

<?xml version="1.0" encoding="utf-8"?>
<!-- mainアクティビティ用 -->
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<!-- リストビュー -->
	<ListView
		android:id="@android:id/list"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content" />
	<!-- リストが空のときに表示 -->
	<TextView
		android:id="@android:id/empty"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content" />
</LinearLayout>

res/layout/item_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	android:id="@+id/item_container"
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:orientation="vertical"
	xmlns:android="http://schemas.android.com/apk/res/android">
	<TextView
		android:id="@+id/item_title"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:textColor="#ffffff"
		android:textSize="18sp"
		android:lines="1" />
	<TextView
		android:id="@+id/item_descr"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:lines="2"
		android:ellipsize="end" />
</LinearLayout>

src/com/sample/rssreader/RssReader.java

package com.sample.rssreader;

import android.os.Bundle;
import android.app.Activity;
import android.app.ListActivity;
import android.content.Intent;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ListView;
import java.util.ArrayList;

public class RssReader extends ListActivity {

    private static final String RSS_FEED_URL = "http://itpro.nikkeibp.co.jp/rss/ITpro.rdf";
    public static final int MENU_ITEM_RELOAD = Menu.FIRST;
    private RssListAdapter mAdapter;
    private ArrayList<Item> mItems;

    @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);
    }

    // リストの項目を選択した時の処理
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        Item item = mItems.get(position);
        Intent intent = new Intent(this, ItemDetailActivity.class);
        intent.putExtra("TITLE", item.getTitle());
        intent.putExtra("DESCRIPTION", item.getDescription());
        startActivity(intent);
    }


    // MENUボタンを押したときの処理
    @Override
        public boolean onCreateOptionsMenu(Menu menu) {
        boolean result = super.onCreateOptionsMenu(menu);
        // デフォルトではアイテムを追加した順番通りに表示する
        menu.add(0, MENU_ITEM_RELOAD, 0, "更新");
        return result;
    }


    // MENUの項目を押したときの処理
    @Override
        public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            // 更新
        case MENU_ITEM_RELOAD:
            // アダプタを初期化し、タスクを起動する
            mItems = new ArrayList();
            mAdapter = new RssListAdapter(this, mItems);
            // タスクはその都度生成する
            RssParserTask task = new RssParserTask(this, mAdapter);
            task.execute(RSS_FEED_URL);
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

}

src/com/sample/rssreader/RssListAdapter.java

package com.sample.rssreader;
import android.view.View;
import android.view.ViewGroup;
import android.content.Context;
import android.widget.TextView;
import android.widget.ArrayAdapter;
import android.view.LayoutInflater;
import java.util.List;

public class RssListAdapter extends ArrayAdapter<Item> {
    private LayoutInflater mInflater;
    private TextView mTitle;
    private TextView mDescr;

    public RssListAdapter(Context context, List<Item> objects) {
        super(context, 0, objects);
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    // 1行ごとのビューを生成する
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = convertView;

        if (convertView == null) {
            view = mInflater.inflate(R.layout.item_row, null);
        }

        // 現在参照しているリストの位置からItemを取得する
        Item item = this.getItem(position);
        if (item != null) {
            // Itemから必要なデータを取り出し、それぞれTextViewにセットする
            String title = item.getTitle().toString();
            mTitle = (TextView) view.findViewById(R.id.item_title);
            mTitle.setText(title);
            String descr = item.getDescription().toString();
            mDescr = (TextView) view.findViewById(R.id.item_descr);
            mDescr.setText(descr);
        }
        return view;
    }
}

src/com/sample/rssreader/RssParserTask.java

package com.sample.rssreader;
import android.os.AsyncTask;
import android.app.ProgressDialog;
import android.util.Xml;
import java.io.InputStream;
import java.io.IOException;
import java.net.URL;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

public class RssParserTask extends AsyncTask<String, Integer, RssListAdapter> {
    private RssReader mActivity;
    private RssListAdapter mAdapter;
    private ProgressDialog mProgressDialog;

    // コンストラクタ
    public RssParserTask(RssReader 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 {
        XmlPullParser parser = Xml.newPullParser();
        try {
            parser.setInput(is, null);
            int eventType = parser.getEventType();
            Item currentItem = null;
            while (eventType != XmlPullParser.END_DOCUMENT) {
                String tag = null;
                switch (eventType) {
                case XmlPullParser.START_TAG:
                    tag = parser.getName();
                    if (tag.equals("item")) {
                        currentItem = new Item();
                    } else if (currentItem != null) {
                        if (tag.equals("title")) {
                            currentItem.setTitle(parser.nextText());
                        } else if (tag.equals("description")) {
                            currentItem.setDescription(parser.nextText());
                        }
                    }
                    break;
                case XmlPullParser.END_TAG:
                    tag = parser.getName();
                    if (tag.equals("item")) {
                        mAdapter.add(currentItem);
                    }
                    break;
                }
                eventType = parser.next();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mAdapter;
    }
}

src/com/sample/rssreader/Item.java

package com.sample.rssreader;

public class Item {

    // 記事のタイトル
    // 記事の本文
    private CharSequence mTitle;
    private CharSequence mDescription;

    public Item() {
        mTitle = "";
        mDescription = "";
    }

    public CharSequence getDescription() {
        return mDescription;
    }

    public void setDescription(CharSequence description) {
        mDescription = description;
    }

    public CharSequence getTitle() {
        return mTitle;
    }

    public void setTitle(CharSequence title) {
        mTitle = title;
    }
}

src/com/sample/rssreader/ItemDetailActivity.java

package com.sample.rssreader;
import android.os.Bundle;
import android.app.Activity;
import android.widget.TextView;
import android.widget.ArrayAdapter;
import android.content.Intent;

public class ItemDetailActivity extends Activity {
    private TextView mTitle;
    private TextView mDescr;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.item_detail);

        Intent intent = getIntent();

        String title = intent.getStringExtra("TITLE");
        mTitle = (TextView) findViewById(R.id.item_detail_title);
        mTitle.setText(title);
        String descr = intent.getStringExtra("DESCRIPTION");
        mDescr = (TextView) findViewById(R.id.item_detail_descr);
        mDescr.setText(descr);
    }
}