CursorAdapterとContentProviderの関係
AndroidでCursorのデータを一覧表示する場合、CursorAdapterが使用されていました。
このとき、データの更新をViewに反映するということについてフレームワークがなにを提供しているのかがわかりづらかったので、メモとして整理しておきます。
MediaStoreのデータをListViewで表示した場合
ContentProvider経由でCursorを取得し、CursorAdapterで表示するとどうなるか確認します。
そこで今回は、MediaStoreに保存された画像すべてを、ListViewで一覧表示してみます。
(サンプルなので、UIスレッドでクエリを発行しちゃいます)
public class SampleCursorAdapterActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ListView listView = (ListView) findViewById(R.id.listView1); Cursor cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null); SimpleCursorAdapter mAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, cursor, new String[] {MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID}, new int[] {android.R.id.text2,android.R.id.text1}); listView.setAdapter(mAdapter); } }
これで、ListViewに画像一覧が表示されます。
このアクティビティが起動したまま、画像をギャラリーに追加されるとどうなるでしょうか。
(カメラアプリで写真を撮るなどして)
実際に写真を撮って、バックグラウンドのアクティビティを再度表示してみると、ListViewにはきちんとレコードが追加され、表示が更新されていることが確認できます。
notifyDatasetChangedや、requeryなどは明示的に行っていないにも関わらずです。
CursorAdapterは関連づけられたUriの変更を検知する
このような動作になるのは、CursorAdapterがCursorに関連づけられたUriの変更を検知できるからです。
CursorとCONTENT_URIの紐付けは、各URIのContentProvider内部で実装されています。
正しく実装されたContentProviderは、クエリの結果としてCursorを返却するときにCursor#setNotificationUriによってCursorにnotificationUri(ギャラリー画像ならMediaStore.Images.Media.EXTERNAL_CONTENT_URI)が設定されています。
これにより、ギャラリーへの画像追加がContentProvider経由で行われると、ContentProvider#notifyChangeでcursorに変更を通知します。onContentChangedで検知し、自身のデータを更新します。
CursorAdapterが変更を検知できるのは、正しく実装されたContentProvider経由でCursorを取得したときだけ
ContentProviderを経由せず直接SQLiteとCursorをやりとりしたり、ContentProviderを経由していてもsetNotificationUriの実装が抜けていたりすると、CursorAdapterのデータ更新機能は動作しません。
HoneyCombからはCursorLoader
サンプルコードではmanagedQueryなど細かい部分には触れていませんが、honeyCombではmanagedQueryが廃止され、CursorLoaderへの移行が促されています。これについては後日あらためて書こうと思います。