Android開発に挑戦してみようとして、つまづいた事の記録。入門者、初心者の助けになれば幸い。
Xamarin と VisualStudio 2015 Community の環境で開発しているので言語はC#。
Android関係はネットを検索してもほとんどJavaのコードが出てくるので初心者的には些細な違いに苦労する。

背景


カメラにintentを送って撮影した画像を外部ストレージに保存しようとしたときに、ContentResolverにアクセスする必要があった。少し検索するとContentResolverの使い方は出てくるが、一体こいつがどんなデータをどんな形で保持しているかはあまり触れられていなかったので少し調べてみた。
(SQLとかの知識がある人にとってはあまりにも当たり前だから記事にする人がいないのかも知れない。)

ContentResolver とは

端的に言うとContentResolverは、あるファイルの位置に関して"content://・・・"という形式(以下contentスキーマ)で表現したURIを、そのファイルに関する他の情報によって検索、取得するためのテーブルを管理、利用する機能を提供するクラスであるといえる。


ContentResolver を用いて目的のファイルのURIを得る方法

目的のファイルより上の階層に存在するcontentスキーマのURIを引数としてContentResolver.Query()メソッドをコールすることで、与えたURIより下位に存在するファイルを一覧するデータテーブルへのカーソルを取得することができる。データテーブルは1行が1つのファイルを表し、各列はそのファイルの情報(ファイル名やファイルの種類、URIなど)を表す。よって、目的のファイルの名前が既知の場合はファイル名の列をサーチし、ヒットした行を取り出すことで、目的のファイルのURIやその他テーブルに含まれる情報が得られる。

ContentrResolverの返すテーブル ContentrResolverの返すテーブル


具体例を挙げて説明

わかりにくいので具体例を挙げる。

■状況を仮定
外部ストレージに画像ファイルがあり、Java.IO.File型のオブジェクトfileとしてその位置がわかっているとする。これをfile.ToString()として書き出すと
"file:///storage/sdcard0/DCIM/Camera/XXX.jpg"
という形式(以下fileスキーマ)で書かれたURIが得らる。

さて、このfileスキーマのURIという情報をもとにcontentスキーマのURIを取得したいときにContentResolverが登場する。

ContentResolverへのアクセス

Xamarin C#ではContentResolverのインスタンスがMainActivityのメンバオブジェクト(正確にはプロパティ?)になっているので、MainActivity内では単にthis.ContentResolverという形でアクセスできる。
(Android SDK Javaの場合はgetContentResolver()によってオブジェクトを取得するので、同じようにやろうとするとメソッドは見つからずに混乱する。ContentResolverに限らず、JavaではgetXXX(), setXXX()によって取得しているオブジェクトの一部が、C#ではプロパティとしてアクセスする仕様になっている。C#らしいコード記述を可能にする反面、混乱を招く仕様な気がする。)

ContentResolverから目的のURIを含むテーブルへのカーソルを得る


MainActivity内で以下のように記述する。


cursoleオブジェクトは第一引数で指定した外部ストレージのURI以下のファイルを一覧したデータテーブルを参照することになる。第二引数以降はデータテーブルの一部だけを取り出すために使用するもので、nullを指定しておくことでテーブル全体が参照できる。(もちろん実行速度的にはあまり推奨されない操作である。)

デバッガでのぞくと、第一引数の実態は
"content://media/external/images/media"
という文字列だった。

カーソルを介してテーブルの情報をのぞいてみる


cursoleオブジェクトは参照するテーブルそのものに関する情報や、cursole自身が指しているテーブル内の特定の行の情報にアクセスできる。

参照するテーブルがどのような列を持っているかを知りたい場合はGetColumnNames()メソッドを利用する。


デバッグウィンドウの表示を見ると、このテーブルは34列の情報を持つことが分かった。
列名を一部列挙すると
_id, _data, _size, _display_name, mime_type, title, date_added, date_modified, description picasa_id isprivate, latitude, longitude, datetaken, orientation, mini_thumb_magic, bucket_id, bucket_display_name, width, height, ...
だった。

結局、このテーブルが持つ各列がどのようなデータを格納しているのかを事前に知っていないとContentResolverを使えない。が、どこにその情報がまとまっているんだろう?

今回必要なのは先頭の2つ"_id"と"_data"。
以下のコードでその中身をのぞいてみる。


_idの列には数桁の整数値、_dataの列にはfileスキーマのURIから"file://"を除いたものが格納されていることが確認できる。
_idの値はContentResolver.Query()メソッドの第一引数で指定したベースURIの末尾に組み合わせることでその行の示すファイルのURIとして完成する。今回の場合は、
MediaStore.Images.Media.ExternalContentUri + "/" +cursole.GetString(0)
とすることで、
"content://media/external/images/media/(数桁の数値)"
という形の目的のURIが得られる。

状況として仮定した、
"fileスキーマのURIという情報をもとにcontentスキーマのURIを取得したい"
という目的は、このテーブルの_data列を上から順に確認して、既知のfileスキーマのURIに一致する行を取り出し、その行の_id列に入っている値をベースURIの末尾に加えることで達成できる。

まとめ


ContentResolverは、QueryされたベースURI以下に存在するファイルのURIや名前などの情報まとめたデータテーブルへのアクセスを提供する。
今回はテーブルの参照を行う例を示したが、テーブルに行を追加したり削除したりといった、テーブルの管理を行う機能も提供する。