2014/11/26

Android: SQLiteOpenHelperコンストラクタの引数CursorFactoryについて

Overview

SQLiteOpenHelperのコンストラクタを見てみると第三引数にCursorFactoryを受け取るようになっている.

SQLiteOpenHelper (Context, String, CursorFactory, int)

SQLiteデータベースを使用したサンプルコードではnullが指定されるケースが多く, あまり活用事例を見ない.

db = new MyDatabase(context, DB_NAME, null /*CursorFactory*/, DB_VERSION);

本稿ではCursorFactoryの利用する方法について記載する.

SQLiteDatabaseにクエリを発行するとCursorが返却される.
Android標準では, このCursor生成処理を拡張できるCursorFactoryクラスを用意している.
CursorFactoryを使ってCursorクラスを得るまでの一般的な流れは下記.

CursorFactoryを実装することで次のことが実現できる.

  • Cursor生成のタイミングをフックする
  • クエリ結果として独自Cursorを返す

ただし, 2点目の独自Cursorを返すについては注意事項がある(後述).

API Reference.

CursorFactoryと周辺のクラスを次に記す.

SQLiteOpenHelper - API Reference - Android Developers
SQLiteDatabaseの生成とバージョンを管理するクラス.

SQLiteDatabase - API Reference - Android Developers
SQLiteデータベースの生成・破棄・問合せといったSQLの実行や管理を行うクラス.

SQLiteDatabase.CursorFactory - API Reference - Android Developers
SQLiteDatabaseへのクエリ結果を格納したCursorのファクトリクラス. ここで生成されるCursorインスタンスがクエリの戻り値(Cursor)として使われる.

SQLiteCursor - API Reference - Android Developers
SQLiteへのクエリ結果を格納したCursorの具象クラス. 内部のSQLiteCursorを直接操作する場合は別途スレッドセーフを自前で担保する必要がある.

SQLiteQueryBuilder - API Reference - Android Developers
SQLiteDatabaseへのクエリ構築を手助けするビルダクラス.

Set CursorFactory

Android標準でCursorの生成はSQLiteDirectCursorDriver.queryで行われる.

public Cursor query(CursorFactory factory, String[] selectionArgs) {
    ...
        if (factory == null) {
            cursor = new SQLiteCursor(this, mEditTable, query);
        } else {
            cursor = factory.newCursor(mDatabase, this, mEditTable, query);
        }
    ...
}

CursorFactoryが指定されていればCursorFactory.newCursorメソッドを経由してCursorを生成する.
ここで独自のCursorFactoryが指定されていればCursorの生成をフックし, 独自Cursorを返すことができる.

implements

CursorFactoryはSQLiteDatabaseの取得時に指定する.
SQLiteDatabaseにCursorFactoryを設定するメソッドはいくつかある.

しかし, 一般的にSQLiteDatabaseのメソッドを直接操作することはせず, SQLiteOpenHelperやSQLiteQueryBuilderを経由する.
そのため, SQLiteOpenHelperやSQLiteQueryBuilderにもCursorFactoryを設定するためのAPIが用意されている.

public class MyDBHelper extends SQLiteOpenHelper{
    public MyDBHelper(Context context){
        super(context, DB_NAME, CURSOR_FACTORY, DB_VERSION);
    }
    ...
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
                    String[] selectionArgs, String sortOrder) {
    SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
    builder.setCursorFactory(CURSOR_FACTORY);
    ...
}

なお, CursorFactoryが指定されない場合はCursorのサブクラスであるSQLiteCursorが生成される.

次に, 独自のCursorFactoryを用意する.
CursorFactoryにはカーソルを生成するnewCursorメソッドが用意されており, ここでCursorを生成する.

public class MyCursorFactory implements CursorFactory {
    @Override
    public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
            String editTable, SQLiteQuery query) {
        return new SQLiteCursor(masterQuery, editTable, query);
    }
}

独自CursorFactoryを設定するためにSQLiteOpenHelperのコンストラクタ引数にこれを指定する.
他にもクエリ発行時にCursorFactoryを指定することもできる(後述).

public class MyContentProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        db = new MySQLiteOpenHelper(context, DB_NAME, 
                new MyCursorFactory(), DB_VERSION);
        ...
    }
}

Query API with CursorFactory

SQLiteDatabaseにクエリを問い合わせるために次のメソッドが用意されている.

queryWithFactory/rawQueryWithFactoryメソッドはCursorFactoryを直接指定してクエリを実行する.
一方, query/rawQueryメソッドは引数cursorFactoryにnullを指定して最終的にrawQueryWithFactoryを呼ぶ.

rawQueryWithFactoryメソッドは引数cursorFactoryにnullが指定されるとSQLiteDatabaseに設定されているCursorFactoryを使用する. この挙動にあたる部分のソースコードは下記.

// rawQuery系メソッドでは引数にnullを指定すると設定した独自CursorFactoryが使用される.
public Cursor rawQueryWithFactory(
        CursorFactory cursorFactory, String sql, String[] selectionArgs,
        String editTable, CancellationSignal cancellationSignal) {
    acquireReference();
    try {
        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(
                this, sql, editTable, cancellationSignal);
        return driver.query(
                cursorFactory != null ? cursorFactory : mCursorFactory,
                selectionArgs);
    } finally {
        releaseReference();
    }
}

MEMO
SQLiteDatabase.queryメソッドは簡易な検索に向いている.
複雑なクエリを組み立てるにはデータバインドにも対応しているSQLiteDatabase.rawQueryメソッドが便利.

SQLiteDatabaseにクエリを発行するにはいくつか方法がある. 下記はその一例.

  1. SQLiteDatabaseインスタンスに直接クエリを投げる
  2. SQLiteQueryBuilder経由でクエリを投げる

SQLiteDatabase

前述の通りqueryWithFactory/rawQueryWithFactoryメソッドの引数にCursorFactoryを指定できる.
nullを指定した場合はSQLiteDatabaseに設定したCursorFactoryが使用される.

SQLiteQueryBuilder

結果的にSQLiteDatabaseのrawQueryWithFactoryが実行される.
SQLiteQueryBuilderにはCursorFactoryを指定するメソッドが用意されている.

Attention

SQLiteDatabaseを使ったアプリケーションではContentProviderも使われることが多い.
CursorFactoryとContentProviderを併用する場合に注意すべき点がある.

ContentProviderサイドで生成されたCursorは直接クライアントサイドには届かない.
SQLiteDatabaseで生成されるCursorはWrapやコピーを経てクライアントサイドに届く.
つまり, CursorFactoryで独自Cursorを返却しても, それを独自Cursorとして使用(Cast)できるのはそれを生成したContentProviderの処理内に限定される.
クライアントサイドで独自Cursorにキャストし直すことはできない.

Usage

CursorFactoryは使いどころの難しいAPIではあるが, たとえばCursor生成をフックして次のようにクエリをロギングする仕組みは楽に作ることができる.

public class MyCursorFactory implements CursorFactory {
    @Override
    public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
            String editTable, SQLiteQuery query) {
        if (debug) {
            Log.d(TAG, query.toString());
        }
        return new SQLiteCursor(masterQuery, editTable, query);
    }
}

以上.