Android Tips #32 CursorLoader で ContentProvider アクセスを非同期化する
ContentProvider アクセスには CursorLoader を使おう
Android アプリでデータベースを扱いたい場合は SQLite を使用しますが、他のアプリで使用している データベースへのアクセスには ContentProvider を使用して行います。 この ContentProvider に非同期でアクセスするための CursorLoader が Android 3.0 より導入されました (Support Package にも互換用クラスがあるので Android 1.6 から使用可能) 。今回はこの CursorLoader を使って簡単な電話帳一覧を作ってみたいと思います!今回も Support Package を使う場合の実装方法を解説します。
CursorLoader の使いかた
CursorLoader は AsyncTaskLoader のサブクラスです。なので基本的に AsyncTaskLoader と使いかたは同じになります。AsyncTaskLoader については以前弊社小室が記事を書きましたので、そちらも参考にしてください。
1. LoaderManager で CursorLoader を読み込む
まずは FragmentActivity を継承した Activity をつくり FragmentActivity#getSupportLoaderManager で LoaderManager を読み込みます。LoaderManager は 複数の Loader を管理するためのクラスです。LoaderManager#initLoader() を呼びます。第三引数に渡す LoaderManager.LoaderCallbacks は次でつくります。下図は onCreate() で実装した場合です。せっかくの非同期なので ProgressDialog を表示するようにしています。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // レイアウトを作成 mListView = new ListView(this); setContentView(mListView); // ProgressDialog を表示 mDialog = ProgressDialog.show(this, null, "Loading..."); // CursorLoader を読み込む getSupportLoaderManager().initLoader(0, null, null); }
2. LoaderManager.LoaderCallbacks を実装する
次に CursorLoader が実行されたあとの処理を書くために LoaderManager.LoaderCallbacks を実装します。 LoaderCallbacks を実装するときには Loader クラスで取り扱う 型を ジェネリクス型で指定できるので、 CursorLoader の場合は Cursor を指定します。 以下のメソッドをオーバーライドします。
onCreateLoader(int id, Bundle args) | Loader が新しくインスタンス化されたときに呼ばれる。 |
---|---|
onLoadFinished(Loader loader, D data) | Loader の処理が終わったあとに呼ばれる。 |
onLoaderReset(Loader loader) | 以前に作成した Loader がリセットされたときに呼ばれる。 |
単純に実装しただけだと以下のようになります。
private LoaderManager.LoaderCallbacks<Cursor> callbacks = new LoaderCallbacks<Cursor>() { @Override public void onLoaderReset(Loader<Cursor> arg0) { } @Override public void onLoadFinished(Loader<Cursor> arg0, Cursor arg1) { } @Override public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) { return null; } };
3. CursorLoader をインスタンス化する
次に LoaderCallbacks#onCreateLoader() で CursorLoader をインスタンス化して返すようにします。CursorLoader のコンストラクタ引数には以下のような検索条件を渡すことができます。指定の必要なない場合は null を渡します。
第一引数 | Context | getApplidationContext() で OK |
---|---|---|
第二引数 | Uri | 対象とする ContentResolver の Uri |
第三引数 | String[] | 取得するカラム |
第四引数 | String | WHERE 句 |
第五引数 | String[] | WHERE 句の値 |
第六引数 | String | ORDER BY 句 |
今回は特に指定せずに取得してみました。
@Override public Loader<Cursor> onCreateLoader(int id, Bundle bundle) { Uri uri = People.CONTENT_URI; return new CursorLoader(getApplicationContext(), uri, null, null, null, null); }
今回は Android 1.6 (APIレベル4) で動作する想定なので、電話帳アクセスの URI は People.CONTENT_URI を参照しています。Android 2.0 (APIレベル5) からは Contacts.CONTENT_URI で参照しましょう。
4. 非同期処理が終わったあとの処理を記述する
次に LoaderCallbacks#onLoadFinished() で CursorLoader の処理が終わったあとの処理を記述します。今回は CursorAdapter を使って ListView に表示してみました。
@Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { // ProgressDialog を閉じる mDialog.dismiss(); // Adapter をつくり ListView にセットする PhoneCursorAdapter adapter = new PhoneCursorAdapter(getApplicationContext(), cursor, false); mListView.setAdapter(adapter); }
PhoneCursorAdapter.java
private class PhoneCursorAdapter extends CursorAdapter { public PhoneCursorAdapter(Context context, Cursor c, boolean autoRequery) { super(context, c, autoRequery); } @Override public void bindView(View view, Context context, Cursor cursor) { TextView nameView = (TextView) view.findViewById(android.R.id.text1); int nameColumn = cursor.getColumnIndexOrThrow(People.DISPLAY_NAME); String name = cursor.getString(nameColumn); nameView.setText(name); TextView numberView = (TextView) view.findViewById(android.R.id.text2); int numberColumn = cursor.getColumnIndexOrThrow(People.NUMBER); String number = cursor.getString(numberColumn); numberView.setText(number); } @Override public View newView(Context context, Cursor cursor, ViewGroup viewGroup) { LayoutInflater inflater = getLayoutInflater(); View view = inflater.inflate(android.R.layout.simple_list_item_2, viewGroup, false); return view; } }
以上で完成です!
まとめ
SQLite からデータを取得する処理スピードは端末の性能やデータ量に依存します。例えばユーザーがギャラリーにデータを大量に保存している場合、全件取得しようとするとかなり重たい処理になってしまいます。このような重い処理をメインスレッドで実装してしまうとアプリがフリーズしてしまう原因になるので、非同期で処理するべきです。 CursorLoader を使うと非同期処理がかなり簡単に実装できるので、ぜひ使っていきましょう!