SimpleCursorAdapterを用いたDBデータのリスト表示

sqliteデータベースの内容のリスト表示は、SimpleCursorAdapterを使うとけっこう簡単に実現する。

スポンサーリンク

SimpleCursorAdapterの使い方

本家のAPIリファレンスによると、SimpleCursorAdapterのコンストラクタは以下のような引数を取る。

public SimpleCursorAdapter (Context context, 
                int layout, 
                Cursor c, 
                String[] from, 
                int[] to, 
                int flags)

layoutにリソースのレイアウト、cはSQLで抽出されるデータのカーソル、fromとtoでカーソルのフィールド名とレイアウトのidをそれぞれ同じ配列のインデックス位置に指定して対応づける。

flagsは、FLAG_AUTO_REQUERYとFLAG_REGISTER_CONTENT_OBSERVERが設定できると本家リファレンスに書いてあるが、まずは0を指定した。フラグの意味をちゃんと書いてあるところが見つからなかったが、

  • FLAG_AUTO_REQUERY: DBが更新されたときにクエリを再発行してくれる
  • FLAG_REGISTER_CONTENT_OBSERVER: 何らかの監視機構に登録してくれる

ということのようだ。特に、FLAG_AUTO_REQUERYに関しては現在「非推奨」扱いらしい。このへんはリストの更新と関わるようで詳しくは後述。

Cursorの準備

前回記事「Androidアプリでsqliteデータベースを操作する」にて作ったDBAdapterクラスに以下のCursorを返すメソッドを追加する。

    private static final String PHOTO_TABLE_NAME = "photolog";
    public static final String COL_ID = "id";
    public static final String COL_PHOTO_URI = "photo_uri";

    Cursor getURIcursor() {
    final String query = "SELECT " + COL_ID + " as _id," + COL_PHOTO_URI + " FROM " + PHOTO_TABLE_NAME + ";";
    Cursor c = null;
    try {
        c = db.rawQuery(query, null);
    } catch(SQLiteException err){
            Log.e(LOG_TAG, err.getMessage());
        }
    return c;   
    }

カラム名”id”(COL_ID)をわざわざ”_id”にリネームしているのは、ビルドが通っても実行時にエラーになってしまうため。Cursorに”_id”というカラムが無いといけないのは、SimpleCursorAdapterの仕様のようだ。本家のAPIリファレンスにもこのへんがはっきり書いてないのは困りもの。

レイアウトの準備

ListViewの中身のレイアウトをxmlで記述する。idとuriの2つのフィールドの幅をlayout_weightの比で指定しており、下記の記述ではidとuriが1:7で表示されるようになる。layout_widthで直接指定してしまうと、画面の幅が変わったときに格好悪い(回転させた時など)。layout_weightによって比で指定するので、layout_widthの絶対値は不要であり(無視される)、0dpを指定している。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/id"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

    <TextView
        android:id="@+id/uri"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="7" />

</LinearLayout>

ListView構築コード

MainActivity.javaのOnCreateメソッドの中に以下を記述する。fromの指定は本来URIみたくクラス変数の参照にした方がスマートな感じだが、上記の通りCursorに_idというカラムが必須という変な仕様のため_idが直指定でちょっと格好悪い。

    Cursor c = dba.getURIcursor();
    String[] from = {"_id", LogDBAdapter.COL_PHOTO_URI};
    int[] to = {R.id.id, R.id.uri};
    SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.list_item, c, from, to, 0);
    ListView listView = (ListView)findViewById(R.id.listView);
    listView.setAdapter(adapter);

実行結果キャプチャ

上記コードの実行結果。DBの内容がバッチリ表示されている。しかし、新しい写真を撮って戻ってきても新しいレコードがリストに追加されてくれない…。

写真追加時のリストの更新

新しい写真を撮ってもリストに反映されないのはよろしくない。そこで色々調べていたところ、こちらの記事を見つけて、なかなか奥深い問題があることを知った。要するにSimpleCursorAdapterはUIと同じスレッドで動いてしまうため、ここでDBの再クエリを行っていしまうとUIスレッドでクエリを実行することになり、UIのレスポンスが悪くなって(見かけ上の)パフォーマンスが悪くなってしまう。

SimpleCursorAdapterのコンストラクタでflags引数が更新に関わっていると上で簡単に触れた。要はUIスレッドでDB更新されることによるパフォーマンス低下ををGoogleが(ユーザーが?)嫌ったため「非推奨」という扱いになった模様。どうりでSimpleCursorAdapterでリスト更新の方法を調べても出てこないわけだ。

じゃあどうするの?の答えは「CursorLoaderを使う」ということらしい。この仕組みの場合は、DB操作は別スレッドにてバックグラウンド実行されるので、パフォーマンス低下に繋がらないと。
ただ、バックグラウンド実行には別スレッドと同期を取るためのオーバーヘッドもあるわけで、DBからほぼ固定の情報を引っ張ってきて表示するぶんには上記のSimpleCursorAdapterで十分事足りるし、SimpleCursorAdapter自体が非推奨になっているわけでもないので、状況によって使い分ければ良いんだろう。

というわけで、次回記事「CursorLoaderを使ったListView一覧表示」につづく。

コメント