minimize

事業拡大のため、新しい仲間を募集しています。
→詳しくはこちら

Content Provider はデータの格納・検索を担当し、それを全てのアプリケーションから簡単にアクセスできるようにします。
これが、アプリケーション同士でデータを共有する唯一の方法となります。
つまり、アプリケーションが共有してデータを置けるようなストレージは存在しないということです。

Android では、一般的なイメージやオーディオ、ビデオファイル、個人情報に関する
いくつかの標準 Content Provider を用意しています。それらは android.provider パッケージにあります。

あなたが自身のデータを公開したい場合、二つの選択肢が用意されています。
一つは、Content Provider を新しく作ることです。
もう一つは、既存の Content Provider に自身のデータを載せることです。

Content Provider の基本

全ての Content Provider は、データを問い合わせて結果を返すための
汎用的なインターフェイスを実装しています。
他にも、データを追加したり、交換したり、消したりすることできます。

多くの場合、クライアントは ContentResolver オブジェクトを通じて操作をします。
Context.getContentResolver() を呼び出すことによって、このオブジェクトを取得できます。

問合せを開始するとき、Android システムは問合せ先の Content Provider を特定し
それが動作していることを確認します。

それぞれの種類の ContentProvider は、一つのインスタンスとして存在します。
クライアントは ContentResolver を経由して、このインスタンスにアクセスします。
ContentResolver がアプリケーション・プロセス・スレッド間の通信を解決してくれるので
クライアントはそれらを意識せずにプログラミングをすることができます。

データモデル

Content Provider は、データをシンプルなテーブルデータとして公開します。
つまり、1行が一つのレコードとなり、それぞれの列のデータには特定の型が割り当てられます。

_ID NUMBER NUMBER_KEY LABEL NAME TYPE
13 (425) 555 6677 425 555 6677 Kirkland office Bully Pulpit TYPE_WORK
44 (212) 555-1234 212 555 1234 NY apartment Alan Vain TYPE_HOME

各レコードは、_ID という自身を識別するフィールドを持っています。
このフィールドは自身を識別すると同時に、他のテーブルとJOINする際にも利用されます。

問合せは、Cursor オブジェクトを返します。
これはJDBCで言えば ResultSet のようなものです。
ここから各行の値を取ってくることができるし、列の型を知ることもできます(ResulSetMetaDataのように)。

URI

それぞれの Content Provider は、public URI を公開しています。
これが各データセットのユニークな識別子となります。
複数のデータセット(つまり複数のテーブルのデータ)は、それぞれが異なる URI によって公開されます。
URI は全て content:// から始まります。
この Scheme は、このデータが Content Provider によって提供されることを意味します。

新しく Content Provider を作る場合は、これらの URI を定数として定義するのが良いでしょう。
こうすれば、使う側も簡単にそのURIを扱えますし、将来URIが変わったときにも対応が容易になります。

Android が提供する Content Provider は全て、CONTENT_URI という定数を持っています。
例えば、個人の電話番号と顔写真を表すために次の定数が用意されています。

android.provider.Contacts.Phones.CONTENT_URI 
android.provider.Contacts.Photos.CONTENT_URI

同様に、最近コールした電話番号とカレンダーを表すためには

android.provider.CallLog.Calls.CONTENT_URI 
android.provider.Calendar.CONTENT_URI

といった定数が用意されています。

ContentResolver が持つメソッドは、最初の引数として URI を取ります。
これによって、そのメソッド呼び出しがどの Provider を対象としているか、
さらにその Provider のどのテーブルを対象としているかを特定することができます。

Content Provider への問合せ

問い合わせるとき、以下の3つが必要です。

もし特定のレコードだけを取得したい場合、これに加えて ID も必要になります。

Query の作成

ContentResolver.query() または Activity.managedQuery() を使って、問合せをすることができます。
両者のメソッドは同じ引数を取り、同じ結果(Cursorオブジェクト)を返します。

後者は、この Activity が Cursor のライフサイクルを管理することを意味します。
管理された Cursor は、Activity が停止したときには自身をアンロードし
Activity が再開したときには再問合せをするようになります。
これは長期間 Cursor のインスタンスを使う場面では有効な手段となるでしょう。

また、前者によって作成された Cursor も Activity.startManagingCursor() を呼び出すことによって
Activity の管理下に置くことができます。

query() メソッドの最初の引数は URI です。
一般的には、何らかのクラスで定義された CONTENT_URI 定数を渡すことになります。

URI に ID を付与することができます。

Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);

のように、withAppendedId() メソッドを使います。

query() の2番目以降の引数は、取得対象を絞り込むために使います。

query が返す値

query() は、0個もしくはそれ以上のデータベースレコード(行)を返します。
これは Cursor オブジェクトとして返されます。
このオブジェクトに対しては、データを読むことしかできません。
更新したりしたい場合は、ContentResolver の別のメソッドを使う必要があります。

値の取得方法

これは実際にコードを見てもらった方が早いでしょう。

private void getColumnData(Cursor cur){ 
    if (cur.moveToFirst()) {
        int nameColumn = cur.getColumnIndex(People.NAME); 
        int phoneColumn = cur.getColumnIndex(People.NUMBER);
        do {
            name = cur.getString(nameColumn);
            phoneNumber = cur.getString(phoneColumn);
        } while (cur.moveToNext());
    }
}

こんな感じになります。
JDBC とはちょっと違いますね。
最初に moveToFirst() で先頭行に移動し、ここで各カラムの列インデックスを取得しています。
そして2段目のループで、行ごとに値を取得しています。
JDBC のようにカラム名を指定して getString(name) のようにはできないようです。
ループが2段になるのが若干イケてない感じもしますが、まぁ良しとしましょう。

データの更新

ContentResolver を使います。
これも実際のコードを見てもらった方が早いと思うので、どんどん行きましょう。

行の追加

ContentValues values = new ContentValues();
values.put(People.NAME, "Abraham Lincoln");
values.put(People.STARRED, 1);
Uri uri = getContentResolver().insert(People.CONTENT_URI, values);

最初に ContentValues インスタンスを作成して、
そこに値をセット(put)していきます。
最後に getContentResolver().insert() で、行を追加します。

ちなみに、ID は put する必要はありません。
このカラムは Auto Increment なので、insert 時に自動採番 されます。

既存行に新しい値を追加

ここではちょっと複雑な例として、既存行に新しい値(子要素)を追加する方法を紹介します。
People 行の子要素として、Phones と ContactMethods を追加する例です。

Uri phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);
ContentValues values = new ContentValues();
values.put(People.Phones.TYPE, People.Phones.TYPE_MOBILE);
values.put(People.Phones.NUMBER, "1233214567");
getContentResolver().insert(phoneUri, values);
Uri emailUri = Uri.withAppendedPath(uri, People.ContactMethods.CONTENT_DIRECTORY);
values.clear();
values.put(People.ContactMethods.KIND, Contacts.KIND_EMAIL);
values.put(People.ContactMethods.DATA, "test@example.com");
values.put(People.ContactMethods.TYPE, People.ContactMethods.TYPE_HOME);
getContentResolver().insert(emailUri, values);   

こんな感じで、追加したい子要素の CONTENT_DIRECTORY を使って
追加用の URI を作成します。あとは普通に行を追加するときと同じです。

このように、行レコードには簡単に子要素を定義することができます。
ここら辺は Ruby On Rails で使われている手法と同じです。
各行が必ず ID カラムを持つという制約を付けることで
リレーショナルデータベースを使いながらオブジェクトデータベースのように
見せかけて使うことができます。

行に対応する RawData

Android では、各行(uri)がそれぞれバイナリデータを持つことができます。
もちろん、通常のデータベースのように Blob を列に使ってバイナリデータを持つことも可能ですが
Android のドキュメントによるとそれは 50K 以内程度の小さいデータの場合に限った方が良いらしいです。

それ以上の大きなバイナリデータを扱う場合は、各行に割り当てられたストリームを使うようにします。

ContentValues values = new ContentValues(3);
values.put(Media.DISPLAY_NAME, "road_trip_1");
values.put(Media.DESCRIPTION, "Day 1, trip to Los Angeles");
values.put(Media.MIME_TYPE, "image/jpeg");
Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);
try {
    OutputStream outStream = getContentResolver().openOutputStream(uri);
    sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream);
    outStream.close();
} catch (Exception e) {
    Log.e(TAG, "exception while writing image", e);
}

このように、まず普通に値を insert した後で、その URI に対応する OutputStream を取得します。
あとはこのストリームにバイナリデータを流し込むだけです。
上の例では Bitmap クラスを使い、画像データを JPEG フォーマットにして格納しています。

データのバッチ更新

大量の行のデータを一括で更新したいような場合、ContentResolver.update() を使います。

データの削除

ContentResolver.delete() に URI を渡すことで、データを削除します。

Content Provider の作成

新しく Content Provider を作る方法を紹介します。

まず、ContentProvider のサブクラスを作成します。
このクラスは、6つの abstract メソッドを持ちます。

以上全てのメソッドは、ContentResolver 経由で異なるプロセスやスレッドから実行されます。
従って、このクラスはスレッドセーフで実装しなければいけません。

また、データの変更があったときには ContentResolver.notifyChange() を呼び出せば
リスナーに通知することができます。
他にも、Provider を使いやすくするいくつかの方法があります。

CONTENT_URI

上でも紹介しましたが、Provider クラスには CONTENT_URI 定数を定義するのが一般的です。

public static final Uri CONTENT_URI = 
  Uri.parse("content://com.example.codelab.transporationprovider");

このように、クラスの Full Qualified Name を全て小文字にするのが作法です。
一般的に URI(URL) に大小文字の区別を付ける(case sensitive)のはあまり良いことではありません。

カラム名

全てのカラム名を、定数として定義しましょう。

_ID は、全てのデータベーステーブルに付けるべきカラム名定数です。
※ カラム名は _id とします

カラム説明

各カラムについて、そのデータ型をきちんとドキュメント化しましょう。
そうしないと、ユーザはそのデータを正しく使えません。

新しいデータ型

もし新しいデータ型を使いたいときは、ContentProvider.getType() が
適切な MIME Type を返すように実装しましょう。

巨大なバイトデータ

もし巨大なバイトデータ、例えば大きな画像イメージなどを提供したい場合は以下のようにします。

そのレコードは、_data という名前のフィールドを持つようにします。
そこに、そのバイトデータを表すデバイス上のファイルパスのリストを格納します。

このフィールドは、クライアントではなく ContentResolver によって読み込むように
意図されたものです。
クライアントが ContentResolver.openInputStream() を呼び出すと、
ContentResolver はこの _data のパスからデータを取得して
それを InputStream として返します。

なぜこのような作りになっているかというと、この処理は通常のクライアントよりも
高い権限が必要な処理だからです。
ContentResolver が仲介することで、クライアントは直接デバイス上のファイルから
データを取得することができるのです。

Content Provider の定義

クラスを作成したら、それを Manifest File 上で定義します。

<provider name="com.example.autos.AutoInfoProvider"
          authorities="com.example.autos.autoinfoprovider" 
          . . . />
</provider>

name にはクラス名、authorities にはURIを記述します。
通常これは CONTENT_URI 定数で定義した値になるはずです。

URI の種類

一つの Content Provider は、様々な種類のデータを返すことができます。
少し例を挙げます。