コンテンツ プロバイダの作成

コンテンツ プロバイダは、データの中央リポジトリへのアクセスを管理します。Android アプリでは 1 つ以上のクラスとしてプロバイダを実装し、要素をマニフェスト ファイルで実装します。いずれか 1 つのクラスがサブクラス ContentProvider を実装します。これは、プロバイダと他のアプリとの間のインターフェースになります。コンテンツ プロバイダは他のアプリへのデータの提供を意図したものですが、ユーザーがプロバイダに管理されるデータを照会、修正するアクティビティをアプリ内に設定することもできます。

このトピックの残りの部分では、コンテンツ プロバイダと使用する API のリストをビルドするための基本的な手順を挙げていきます。

ビルドを開始する前に

プロバイダのビルドを開始する前に、次の点について検討してください。

  1. コンテンツ プロバイダが必要かどうかを決定します。次のような機能を 1 つ以上提供する場合に、コンテンツ プロバイダをビルドする必要があります。
    • 他のアプリに複雑なデータやファイルを提供する。
    • アプリから他のアプリへの複雑なデータのコピーをユーザーに許可する。
    • 検索フレームワークを使用してカスタムの検索候補を提供する。
    • アプリ データをウィジェットに公開する。
    • AbstractThreadedSyncAdapterCursorAdapterCursorLoader のクラスを実装する。

    独自アプリ内だけで使用し、上記のどの機能も必要ない場合は、データベースや他のタイプの永続ストレージを使用するプロバイダは必要ありません。代わりに、アプリデータの保存ページで説明されているいずれかのストレージ システムを使用できます。

  2. 必要かどうかを決定していない場合は、プロバイダの詳細についてはコンテンツ プロバイダの基本のトピックをご覧ください。

次に、以下の手順に従ってプロバイダをビルドします。

  1. データの未処理のストレージを設計します。コンテンツ プロバイダは次の 2 つの方法でデータを提供します。
    ファイルデータ
    通常、写真、オーディオ、ビデオなどのファイルの形式となるデータです。ファイルはアプリのプライベート スペースに格納します。ファイルに対する別のアプリからの要求に応じて、プロバイダからファイルへのハンドルが提供されます。
    「構造化」データ
    通常、データベース、配列、類似の構造の形式となるデータです。テーブルの行と列に互換性を持つ形式でデータが格納されます。行は、担当者や在庫にあるアイテムなどのエンティティを表します。列は、担当者の名前やアイテムの価格などの、エンティティの一部のデータを表します。一般的にこのタイプのデータは SQLite データベースに格納しますが、任意のタイプの永続ストレージを使用できます。Android システムで使用できるストレージ タイプの詳細は、データ ストレージを設計するをご覧ください。
  2. ContentProvider クラスの具体的な実装とそれに必要なメソッドを定義します。このクラスは、データと Android システムのそれ以外の部分とのインターフェースとなります。このクラスの詳細は、ContentProvider クラスを実装するセクションをご覧ください。
  3. プロバイダの認証局の文字列、コンテンツ URI、列名を定義します。プロバイダのアプリでインテントを処理する場合は、インテント アクション、エクストラ データ、フラグも定義します。さらに、データにアクセスするアプリに必要なパーミッションも定義します。これらの値はすべて定数として個別のコントラクト クラスに定義するようにします。そうしておくと、後で他のデベロッパーに対してこのクラスを公開できます。コンテンツ URI の詳細は、コンテンツ URI を設計するセクションをご覧ください。インテントの詳細は、インテントとデータアクセス セクションをご覧ください。
  4. サンプルデータや、プロバイダとクラウドベースのデータの間でデータを同期する AbstractThreadedSyncAdapter の実装などの、その他のオプション部分を追加します。

データ ストレージを設計する

コンテンツ プロバイダは、構造化された形式で保存されているデータへのインターフェースです。インターフェースを作成する前に、データの格納方法を決定しておく必要があります。データは任意の形式で保存でき、必要に応じてデータの読み取りと書き込みを行うためのインターフェースを設計できます。

Android には、次のように、いくつかのデータ格納テクノロジーがあります。

  • 構造化データを使用している場合は、SQLite などのリレーショナル データベース、または LevelDB などの非リレーショナル キー値データストアを検討してください。オーディオ、画像、ビデオメディアなどの非構造化データを使用している場合は、データをファイルとして格納することを検討してください。複数のさまざまなストレージを組み合わせて、必要に応じて 1 つのコンテンツ プロバイダを使用してそれらを公開できます。
  • Android システムは Room 永続ライブラリとやり取りできます。このライブラリは、Android の独自プロバイダがテーブル指向のデータを格納するために使用する SQLite データベース API へのアクセスを提供します。このライブラリを使用してデータベースを作成するには、Room 永続ライブラリのガイドで説明するように RoomDatabase のサブクラスをインスタンス化します。

    リポジトリを実装するのに、データベースを使用する必要がありません。外部的には、プロバイダはリレーショナル データベースに似た一連のテーブルとして表示されますが、プロバイダの内部実装をそのような形式にする必要はありません。

  • ファイルデータを格納するために、Android にはファイル指向のさまざまな API があります。ファイル ストレージの詳細は、データ ストレージのトピックをご覧ください。音楽やビデオのようにメディア関連のデータを提供するプロバイダを設計する場合は、テーブルデータとファイルを組み合わせるプロバイダを作成できます。
  • まれに、1 つのアプリに対して複数のコンテンツ プロバイダを実装することでメリットが得られることがあります。たとえば、あるコンテンツ プロバイダを使用してウィジェットと一部のデータを共有し、他のアプリと共有するために別のデータセットを公開できます。
  • ネットワーク ベースのデータで作業する場合は、java.netandroid.net のクラスを使用します。また、ネットワーク ベースのデータをデータベースなどのローカル データストアに同期させ、データをテーブルやファイルとして提供することもできます。基本の同期アダプタのサンプル アプリでは、このタイプの同期を示しています。

:後方互換性がないリポジトリに変更を加えた場合は、そのリポジトリに新しいバージョン番号を付ける必要があります。また、新しいコンテンツ プロバイダを実装するアプリのバージョン番号を増やす必要があります。この変更を行うことで、互換性のないコンテンツ プロバイダを持つアプリを再インストールしようとしたときに、システムがダウングレードしてクラッシュするのを防ぐことができます。

データ設計上の考慮事項

次に、プロバイダのデータ構造を設計する際のヒントを示します。

  • テーブルデータには、プロバイダが各行の一意の数値として保持する「プライマリキー」列が常に必要です。この値を使用すると、行を他のテーブルの関連する行にリンクできます(「外部キー」として使用)。この列には任意の名前を設定できますが、プロバイダ クエリの結果を ListView にリンクさせるには、取得する列のいずれかの名前が _ID になっている必要があるため、BaseColumns._ID を使用することをお勧めします。
  • ビットマップ画像やサイズが非常に大きなその他のファイル指向のデータを提供する場合は、テーブルにデータを直接格納するのではなく、データをファイルに格納し、データを間接的に提供します。その場合、データにアクセスするには、ContentResolver ファイル メソッドの使用が必要であることをプロバイダのユーザーに通知する必要があります。
  • サイズや構造が異なるデータを格納するには、バイナリラージ オブジェクト(BLOB)データタイプを使用します。たとえば、Protocol BufferJSON 構造を格納するには、BLOB 列を使用します。

    さらに、BLOB を使用してスキーマに依存しないテーブルを実装できます。このタイプのテーブルでは、プライマリキー列、MIME タイプ列、1 つ以上の汎用列を BLOB として定義します。BLOB 列内のデータの内容は、MIME タイプ列の値によって示されます。これにより、同じテーブルにさまざまな行タイプを格納できます。連絡先プロバイダの「データ」テーブル ContactsContract.Data は、スキーマに依存しないテーブルの例です。

コンテンツ URI を設計する

コンテンツ URI は、プロバイダのデータを特定する URI です。コンテンツ URI には、プロバイダ全体の識別名(認証局)とテーブルやファイルをポイントする名前(パス)が含まれます。オプションの ID 部分は、テーブル内の個々の行を指します。ContentProvider のそれぞれのデータアクセス メソッドは引数としてコンテンツ URI を使用します。これにより、アクセスするテーブル、行、ファイルを決定できます。

コンテンツ URI の基本については、コンテンツ プロバイダの基本のトピックをご覧ください。

認証局を設計する

通常、プロバイダは、Android 内部の名前として使用される認証局を 1 つ持ちます。他のプロバイダとの競合を避けるためには、(逆に)プロバイダの認証局の基礎としてインターネット ドメインの所有権を使用する必要があります。この推奨事項は Android パッケージ名にもあてはまるため、プロバイダを含むパッケージの名前の拡張子として、プロバイダの認証局を定義できます。たとえば、Android パッケージ名が com.example.<appname> の場合、プロバイダに認証局 com.example.<appname>.provider を付与する必要があります。

パス構造を設計する

デベロッパーは通常、個々のテーブルを指すパスを末尾に追加して認証局からコンテンツ URI を作成します。たとえば、table1table2 の 2 つのテーブルがある場合、前の例の認証局を組み合わせてコンテンツ URI com.example.<appname>.provider/table1com.example.<appname>.provider/table2 を作成します。パスは 1 つのセグメントに限定されず、パスの各レベルにテーブルを作成する必要はありません。

コンテンツ URI ID を処理する

慣例として、URI の末尾にある行の ID 値を持つコンテンツ URI を受け取ることで、プロバイダはテーブル内の 1 つの行へのアクセスを提供します。さらに、慣例として、プロバイダは ID 値をテーブルの _ID 列とマッチングし、一致する行に対して要求されたアクセスを実行します。

この慣例により、プロバイダにアクセスするアプリの一般的なパターンを簡単に設計できます。アプリはプロバイダにクエリを実行し、CursorAdapter を使用して、取得した CursorListView に表示します。CursorAdapter の定義には、Cursor のいずれかの列を _ID に設定する必要があります。

その後、ユーザーはデータの確認や修正のために、表示された行のいずれかを UI から選択します。アプリは ListView を返す Cursor から対応する行を取得し、この行の _ID 値を取得し、その値をコンテンツ URI の末尾に追加して、プロバイダにアクセス要求を送ります。その後、プロバイダは、ユーザーが選択した行にクエリや修正を実行します。

コンテンツ URI パターン

受け取ったコンテンツ URI に対するアクションを簡単に選択できるように、プロバイダ API には UriMatcher という便利なクラスが用意されています。このクラスはコンテンツ URI「パターン」を正数値にマッピングします。この整数値を switch 文で使用することで、特定のパターンに一致する 1 つ以上のコンテンツ URI に目的のアクションを選択できます。

コンテンツ URI パターンは、次のワイルドカード文字を使用するコンテンツ URI に一致します。

  • *:任意の長さの任意の有効な文字で構成される文字列に一致します。
  • #:任意の長さの任意の数字で構成される文字列に一致します。

コンテンツ URI 処理の設計とコーディングの例として、テーブルを指す次のコンテンツ URI を認識する認証局 com.example.app.provider を持つプロバイダを考えてみます。

  • content://com.example.app.provider/table1:table1 という名前のテーブル。
  • content://com.example.app.provider/table2/dataset1:dataset1 という名前のテーブル。
  • content://com.example.app.provider/table2/dataset2:dataset2 という名前のテーブル。
  • content://com.example.app.provider/table3:table3 という名前のテーブル。

さらに、行 ID が末尾に追加されていると、プロバイダではこれらのコンテンツ URI が認識されます。たとえば、table31 で特定される行は content://com.example.app.provider/table3/1 になります。

次のようなコンテンツ URI パターンを使用できます。

content://com.example.app.provider/*
プロバイダの任意のコンテンツ URI に一致します。
content://com.example.app.provider/table2/*:
テーブル dataset1dataset2 のコンテンツ URI に一致しますが、table1table3 のコンテンツ URI には一致しません。
content://com.example.app.provider/table3/#:table3 の 1 つの行のコンテンツ URI に一致します。6 で特定される行は content://com.example.app.provider/table3/6 になります。

次のコード スニペットでは、UriMatcher のメソッドが機能する仕組みを示します。このコードでは、テーブルにコンテンツ URI パターン content://<authority>/<path> を使用し、1 つの行に content://<authority>/<path>/<id> を使用することで、表全体の URI と 1 つの行の URI を異なる方法で処理します。

メソッド addURI() は認証局とパスを整数値にマッピングします。メソッド match() は URI に整数値を返します。次のように、switch 文によって、クエリをテーブル全体に実行するか、1 つのレコードに実行するかが決まります。

Kotlin

private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    /*
     * The calls to addURI() go here, for all of the content URI patterns that the provider
     * should recognize. For this snippet, only the calls for table 3 are shown.
     */

    /*
     * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
     * in the path
     */
    addURI("com.example.app.provider", "table3", 1)

    /*
     * Sets the code for a single row to 2. In this case, the "#" wildcard is
     * used. "content://com.example.app.provider/table3/3" matches, but
     * "content://com.example.app.provider/table3 doesn't.
     */
    addURI("com.example.app.provider", "table3/#", 2)
}
...
class ExampleProvider : ContentProvider() {
    ...
    // Implements ContentProvider.query()
    override fun query(
            uri: Uri?,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
    ): Cursor? {
        var localSortOrder: String = sortOrder ?: ""
        var localSelection: String = selection ?: ""
        when (sUriMatcher.match(uri)) {
            1 -> { // If the incoming URI was for all of table3
                if (localSortOrder.isEmpty()) {
                    localSortOrder = "_ID ASC"
                }
            }
            2 -> {  // If the incoming URI was for a single row
                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query
                 */
                localSelection += "_ID ${uri?.lastPathSegment}"
            }
            else -> { // If the URI is not recognized
                // You should do some error handling here.
            }
        }

        // call the code to actually do the query
    }
}

Java

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        /*
         * The calls to addURI() go here, for all of the content URI patterns that the provider
         * should recognize. For this snippet, only the calls for table 3 are shown.
         */

        /*
         * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
         * in the path
         */
        uriMatcher.addURI("com.example.app.provider", "table3", 1);

        /*
         * Sets the code for a single row to 2. In this case, the "#" wildcard is
         * used. "content://com.example.app.provider/table3/3" matches, but
         * "content://com.example.app.provider/table3 doesn't.
         */
        uriMatcher.addURI("com.example.app.provider", "table3/#", 2);
    }
...
    // Implements ContentProvider.query()
    public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder) {
...
        /*
         * Choose the table to query and a sort order based on the code returned for the incoming
         * URI. Here, too, only the statements for table 3 are shown.
         */
        switch (uriMatcher.match(uri)) {


            // If the incoming URI was for all of table3
            case 1:

                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // If the incoming URI was for a single row
            case 2:

                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query
                 */
                selection = selection + "_ID = " + uri.getLastPathSegment();
                break;

            default:
            ...
                // If the URI is not recognized, you should do some error handling here.
        }
        // call the code to actually do the query
    }

別のクラス ContentUris には、コンテンツ URI の一部である id で作業するための便利なメソッドが備わっています。クラス UriUri.Builder には、既存の Uri オブジェクトを解析して新しいオブジェクトをビルドするための便利なメソッドがあります。

ContentProvider クラスを実装する

ContentProvider インスタンスは、他のアプリからの要求を処理することで、一連の構造化されたデータへのアクセスを管理します。どのような形式でアクセスする場合も、最終的には ContentResolver が呼び出されます。その後 ContentProvider の具象メソッドが呼び出され、アクセスを取得します。

必須メソッド

抽象クラス ContentProvider は 6 つの抽象メソッドを定義しますが、これらのメソッドは独自の具象サブクラスの一部として実装する必要があります。次のように、onCreate() を除くこれらのすべてのメソッドは、コンテンツ プロバイダへのアクセスを試みるクライアント アプリによって呼び出されます。

query()
プロバイダからデータを取得します。引数を使って、クエリを実行するテーブル、返す行と列、結果の並び順を選択します。データは Cursor オブジェクトとして返されます。
insert()
プロバイダに新しい行を挿入します。引数を使って、挿入先のテーブルを選択し、使用する列の値を取得します。新たに挿入した行のコンテンツ URI が返されます。
update()
プロバイダ内の既存の行を更新します。引数を使って、更新するテーブルと行を選択し、更新された列の値を取得します。更新された行の数が返されます。
delete()
プロバイダから行を削除します。引数を使って、削除するテーブルと行を選択します。削除された行の数が返されます。
getType()
コンテンツ URI に対応する MIME タイプを返します。このメソッドの詳細は、コンテンツ プロバイダ MIME タイプを実装するセクションをご覧ください。
onCreate()
プロバイダを初期化します。プロバイダが作成されると、Android システムがこのメソッドを即座に呼び出します。ContentResolver オブジェクトがアクセスを試みるまでは、プロバイダは作成されません。

これらのメソッドが持つ署名は、同じ名前の ContentResolver メソッドの署名と同じになります。

これらのメソッドを実装する場合は、次の点を考慮する必要があります。

  • onCreate() を除くこれらのすべてのメソッドは、複数のスレッドを使用して同時に呼び出すことができるため、スレッドに対応した設定にする必要があります。複数のスレッドの詳細は、プロセスとスレッドをご覧ください。
  • onCreate() では冗長な操作は行わないようにします。本当に必要になるまでは、タスクの初期化は行わないようにします。詳細は、onCreate() メソッドを実装するをご覧ください。
  • これらのメソッドの実装は必須ですが、コードでは予測されたデータタイプを返す以外の操作は必要ありません。たとえば、他のアプリが一部のテーブルにデータを挿入できないように設定するとします。その場合、insert() への呼び出しを無視して 0 を返します。

query() メソッドを実装する

ContentProvider.query() メソッドは Cursor オブジェクトを返す必要があります。失敗した場合は、Exception をスローします。SQLite データベースをデータ ストレージとして使用する場合は、SQLiteDatabase クラスのいずれかの query() メソッドが返す Cursor を返します。クエリがどの行にも一致しない場合は、getCount() メソッドが 0 を返す Cursor インスタンスを返す必要があります。クエリプロセス中に内部エラーが発生した場合にのみ null を返します。

SQLite データベースをデータ ストレージとして使用しない場合は、Cursor クラスのいずれかの具象サブクラスを使用します。たとえば、MatrixCursor クラスは、各行が Object の配列となるカーソルを実装します。このクラスでは、addRow() を使って新しい行を追加します。

Android システムは、プロセスの境界をまたいで Exception を送信できるように設定する必要があることにご注意ください。Android では、クエリエラーを処理するのに便利な、次の例外を送信できます。

insert() メソッドを実装する

insert() メソッドは、ContentValues 引数の値を使用して、適切なテーブルに新しい行を追加します。列名が ContentValues 引数にない場合は、プロバイダ コードがデータベース スキーマのいずれかで、デフォルト値を設定できます。

このメソッドは新しい行のコンテンツ URI を返します。作成するには、withAppendedId() を使用して、新しい行の _ID(または他のプライマリキー)の値をテーブルのコンテンツ URI の末尾に追加します。

delete() メソッドを実装する

delete() メソッドでは、データ ストレージから行を物理的に削除する必要はありません。プロバイダで同期アダプタを使用する場合は、行全体を削除するのではなく、削除された行に「削除」フラグを付けるようにします。同期アダプタは削除された行を探して、プロバイダから削除される前に、サーバーから行を削除できます。

update() メソッドを実装する

update() メソッドは insert() が使用するのと同じ ContentValues 引数、delete()ContentProvider.query() が使用するのと同じ selection 引数と selectionArgs 引数を使用します。これにより、これらのメソッド間でコードを再利用できます。

onCreate() メソッドを実装する

Android システムはプロバイダを起動する際に onCreate() を呼び出します。このメソッドでは迅速に実行できる初期化タスクのみを実行するようにし、プロバイダが実際にデータの要求を受け取るまでは、データベースの作成とデータの読み込みを保留します。onCreate() で冗長なタスクを行う場合は、プロバイダの起動が遅くなります。同様に、プロバイダから他のアプリへの応答も遅くなります。

次の 2 つのスニペットは、ContentProvider.onCreate()Room.databaseBuilder() との間のやり取りを表しています。このスニペットは、ContentProvider.onCreate() の実装を示しています。これによって、データベース オブジェクトがビルドされ、データアクセス オブジェクトへのハンドルが作成されます。

Kotlin

// Defines the database name
private const val DBNAME = "mydb"
...
class ExampleProvider : ContentProvider() {

    // Defines a handle to the Room database
    private lateinit var appDatabase: AppDatabase

    // Defines a Data Access Object to perform the database operations
    private var userDao: UserDao? = null

    override fun onCreate(): Boolean {

        // Creates a new database object.
        appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build()

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.userDao

        return true
    }

    ...

    // Implements the provider's insert method
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Java

public class ExampleProvider extends ContentProvider

    // Defines a handle to the Room database
    private AppDatabase appDatabase;

    // Defines a Data Access Object to perform the database operations
    private UserDao userDao;

    // Defines the database name
    private static final String DBNAME = "mydb";

    public boolean onCreate() {

        // Creates a new database object.
        appDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build();

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.getUserDao();

        return true;
    }

    ...

    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

ContentProvider MIME タイプを実装する

ContentProvider クラスには、MIME タイプを返すための次の 2 つのクラスがあります。

getType()
任意のプロバイダに実装する必要がある必須メソッドの 1 つです。
getStreamTypes()
ファイルを提供するプロバイダの場合に、実装が考えられるメソッドです。

テーブルの MIME タイプ

getType() メソッドは、MIME 形式で String を返します。これは、コンテンツ URI 引数によって返されるデータのタイプを表します。Uri 引数には特定の URI ではなくパターンを指定することもできます。この場合、パターンに一致するコンテンツ URI に関連付けられているデータのタイプを返す必要があります。

テキスト、HTML、JPEG といった一般的なタイプのデータの場合、getType() では該当するデータの標準的な MIME タイプを返す必要があります。利用できるこれらの標準的なタイプの詳細なリストは、IANA MIME Media Types のウェブサイトをご覧ください。

テーブルデータの 1 つ以上の行を指すコンテンツ URI の場合、getType() は、Android のベンダー固有の MIME 形式で MIME タイプを返す必要があります。

  • タイプ部分: vnd
  • サブタイプ部分:
    • URI パターンが 1 つの行の場合: android.cursor.item/
    • URI パターンが複数の行の場合: android.cursor.dir/
  • プロバイダ固有の部分: vnd.<name>.<type>

    <name><type> を指定します。<name> の値はグローバルに一意の値とし、<type> の値は対応する URI パターンに一意のものとする必要があります。<name> には、企業名や、アプリの Android パッケージ名の一部を使うことをお勧めします。<type> には、URI に関連付けられているテーブルを特定する文字列を使うことをお勧めします。

たとえば、プロバイダの認証局が com.example.app.provider の場合、テーブル名は table1 となり、table1 の複数行の MIME タイプは次のようになります。

vnd.android.cursor.dir/vnd.com.example.provider.table1

table1 の 1 つの行の場合は、MIME タイプは次のようになります。

vnd.android.cursor.item/vnd.com.example.provider.table1

ファイルの MIME タイプ

ファイルを提供するプロバイダの場合は、getStreamTypes() を実装します。このメソッドは、プロバイダが特定のコンテンツ URI に対して返すことができるファイルの MIME タイプの String 配列を返します。クライアントで処理する MIME タイプのみを返すには、MIME タイプフィルタ引数によって、提供する MIME タイプをフィルタする必要があります。

たとえば、写真画像を .jpg.png.gif 形式で提供するプロバイダについて考えてみます。アプリが ContentResolver.getStreamTypes() をフィルタ文字列 image/*(なんらかの形式の「画像」)で呼び出す場合、ContentProvider.getStreamTypes() メソッドは次のような配列を返します。

{ "image/jpeg", "image/png", "image/gif"}

アプリの対象が .jpg ファイルのみであり、アプリが ContentResolver.getStreamTypes() をフィルタ文字列 *\/jpeg で呼び出す場合、ContentProvider.getStreamTypes() は次の項目を返します。

{"image/jpeg"}

プロバイダが、フィルタ文字列で要求した MIME タイプを提供していない場合、getStreamTypes()null を返します。

コントラクト クラスを実装する

コントラクト クラスは public final クラスであり、URI、列名、MIME タイプ、プロバイダに関連するその他のメタデータを含みます。このクラスは、URI や列名などの実際の値が変更された場合にも、プロバイダに正しくアクセスできることを保証することで、プロバイダとその他のアプリとの間にコントラクトを構築します。

さらに、通常、コントラクト クラスではその定数にニーモニック名が設定されているため、デベロッパーが列名や URI に誤った値を使用しにくいようになっています。クラスであるため、Javadoc ドキュメントを含めることができます。Android Studio などの統合型開発環境では、コントラクト クラスから定数名が自動的に設定され、定数の Javadoc が表示されます。

デベロッパーはアプリからコントラクト クラスのクラスファイルにアクセスできませんが、提供する .jar ファイルからクラスファイルをアプリに静的にコンパイルできます。

ContactsContract クラスとそのネストされたクラスは、コントラクト クラスの例です。

コンテンツ プロバイダ パーミッションを実装する

Android システムのあらゆる領域に対するパーミッションとクラスについては、セキュリティとパーミッションのトピックをご覧ください。また、データ ストレージのトピックにも、ストレージのさまざまなタイプに有効なセキュリティとパーミッションについて記載されています。重要な点をまとめると次のようになります。

  • デフォルトでは、端末の内部ストレージに格納されるデータファイルは、アプリとプロバイダでのみ使用できます。
  • 作成する SQLiteDatabase データベースは、自分のアプリとプロバイダにのみ公開されます。
  • デフォルトでは、外部ストレージに保存するデータファイルはパブリックとなり、誰もが読み取ることができます。他のアプリは別の API 呼び出しを使用してファイルの読み取りと書き込みを実行できるため、コンテンツ プロバイダで外部ストレージにあるファイルへのアクセスは制限できません。
  • 端末の内部ストレージにあるファイルや SQLite データベースを開いたり作成したりするメソッド呼び出しは、他のすべてのアプリに読み取りと書き込みの両方のアクセスを付与してしまう可能性があります。内部ファイルやデータベースをプロバイダのリポジトリとして使用する場合に、「誰でも読み取り可能」や「誰でも書き込み可能」なアクセスを付与してしまうと、マニフェストで設定したプロバイダのパーミッションでデータを保護できなくなってしまいます。内部ストレージのファイルとデータベースへのデフォルトのアクセスは「プライベート」になっているため、プロバイダのリポジトリの場合はこの設定を変更しないでください。

コンテンツ プロバイダ パーミッションを使用してデータへのアクセスを制御する場合は、内部ファイル、SQLite データベース、「クラウド」(リモート サーバーなど)にデータを格納し、ファイルやデータベースを自分のアプリにのみ公開するように設定します。

パーミッションを実装する

デフォルトではプロバイダにはパーミッションが設定されていないため、基礎となるデータがプライベートに設定されている場合でも、すべてのアプリは自分のプロバイダからの読み取りとプロバイダへの書き込みを実行できます。これを変更するには、 <provider> 要素の属性や子要素を使用して、マニフェスト ファイルでプロバイダのパーミッションを設定します。プロバイダ全体に適用するパーミッションを設定することもできますし、特定のテーブル、特定のレコード、これら 3 つすべてに適用するパーミッションを設定することもできます。

マニフェスト ファイルで 1 つ以上の <permission> 要素を使用して、プロバイダのパーミッションを定義します。自分のプロバイダに独自のパーミッションを作成するには、 android:name 属性に Java スタイルのスコーピングを使用します。たとえば、読み取りパーミッションの名前を com.example.app.provider.permission.READ_PROVIDER とします。

次のリストは、プロバイダ パーミッションの範囲を示しています。プロバイダ全体に適用するパーミッションから始まり、より詳細な範囲のものになっています。より広い範囲を持つパーミッションよりも、範囲が限定されるパーミッションの方が優先されます。

プロバイダ レベルで 1 つの読み取りと書き込みのパーミッション
1 つのパーミッションでプロバイダ全体の読み取りと書き込みのアクセスの両方を制御します。 <provider> 要素の android:permission 属性で指定します。
プロバイダ レベルで別々の読み取りと書き込みパーミッション
プロバイダ全体の読み取りパーミッションと書き込みパーミッションです。これらのパーミッションは <provider> 要素の android:readPermission 属性と android:writePermission 属性で指定します。これらは、 android:permission で要求するパーミッションよりも優先されます。
パスレベルのパーミッション
プロバイダのコンテンツ URI の読み取り、書き込み、読み取り / 書き込みパーミッションです。 <provider> 要素の <path-permission> 子要素を使用して、制御対象の各 URI を指定します。指定する各コンテンツ URI に対して、読み取り / 書き込みパーミッション、読み取りパーミッション、書き込みパーミッション、3 つすべてを指定できます。読み取りパーミッションと書き込みパーミッションは、読み取り / 書き込みパーミッションに優先します。また、パスレベルのパーミッションはプロバイダ レベルのパーミッションに優先します。
一時的なパーミッション
アプリへの一時的なアクセスを付与するパーミッション レベルです。アプリに通常必要とするパーミッションが付与されていない場合でも付与できます。一時的なアクセス機能により、アプリのマニフェストに必要なパーミッションの数を減らせます。一時的なパーミッションを有効にすると、プロバイダへの「永続的な」パーミッションを必要とするアプリのみが、継続的にすべてのデータにアクセスできます。

プロバイダからの写真の添付ファイルを外部の画像ビューア アプリで表示する場合に、メール プロバイダとアプリに実装するパーミッションを考えてみます。パーミッションを要求しなくても、画像ビューアに必要なアクセスを付与するには、写真のコンテンツ URI に一時的なパーミッションを設定します。ユーザーが写真を表示しようとしたときに、アプリから写真のコンテンツ URI とパーミッション フラグを含むインテントを画像ビューアに送信するようにメールアプリを設計します。ビューアにプロバイダの通常の読み取りパーミッションが付与されていない場合でも、画像ビューアはメール プロバイダにクエリを実行して写真を取得します。

一時的なパーミッションを有効にするには、 <provider> 要素の android:grantUriPermissions 属性を設定するか、1 つ以上の <grant-uri-permission> 子要素を <provider> 要素に追加します。一時的なパーミッションを使用する場合、プロバイダからのコンテンツ URI のサポートを削除したときは常に Context.revokeUriPermission() を呼び出す必要があります。そうすると、コンテンツ URI が一時的なパーミッションに関連付けられます。

属性の値によって、プロバイダへのアクセスレベルが決まります。属性を true に設定すると、システムによってプロバイダ全体への一時的なパーミッションが付与されます。このパーミッションは、プロバイダ レベルやパスレベルのパーミッションで必要になるその他のパーミッションよりも優先されます。

このフラグを false に設定する場合は、 <grant-uri-permission> 子要素を <provider> 要素に追加する必要があります。それぞれの子要素は、一時的なアクセスが付与される 1 つ以上のコンテンツ URI を指定します。

一時的なアクセスをアプリに委任するには、インテントに FLAG_GRANT_READ_URI_PERMISSION フラグか FLAG_GRANT_WRITE_URI_PERMISSION フラグ、またはその両方を含める必要があります。これらは、setFlags() メソッドで設定します。

android:grantUriPermissions 属性がない場合は、false であると仮定されます。

<provider> 要素

Activity コンポーネントや Service コンポーネントと同様に、 <provider> 要素を使用して ContentProvider のサブクラスをアプリのマニフェスト ファイルに定義する必要があります。Android システムは要素から次の情報を取得します。

認証局(android:authorities
システム内のプロバイダ全体を特定する識別名です。この属性の詳細は、コンテンツ URI を設計するセクションをご覧ください。
プロバイダ クラス名( android:name
ContentProvider を実装するクラスです。このクラスの詳細は、ContentProvider クラスを実装するセクションをご覧ください。
パーミッション
他のアプリがプロバイダのデータにアクセスするのに必要なパーミッションを指定する属性です。

パーミッションとそれに対応する属性の詳細は、コンテンツ プロバイダ パーミッションを実装するセクションをご覧ください。

起動と制御の属性
これらの属性は、Android システムの起動方法とそのタイミング、プロバイダのプロセスの特性、その他の実行時の設定を決定します。
  • android:enabled:システムにプロバイダの起動を許可するフラグです。
  • android:exported:他のアプリにこのプロバイダの使用を許可するフラグです。
  • android:initOrder:同じプロセス内の他のプロバイダに対して、このプロバイダを起動する順番です。
  • android:multiProcess:システムに呼び出しクライアントと同じプロセスでのプロバイダの起動を許可するフラグです。
  • android:process:プロバイダを実行するプロセスの名前です。
  • android:syncable:プロバイダのデータをサーバー上のデータと同期することを示すフラグです。

属性に関する詳細は、開発ガイドの <provider> 要素に関するトピックに記載されています。

情報に関する属性
プロバイダのオプションのアイコンとラベルです。
  • android:icon:プロバイダのアイコンを含むドローアブル リソースです。[Settings] > [Apps] > [All] にあるアプリのリストのプロバイダ ラベルの横に表示されるアイコンです。
  • android:label:プロバイダ、データ、その両方を説明する情報ラベルです。[Settings] > [Apps] > [All] にあるアプリのリストに表示されるラベルです。

属性に関する詳細は、開発ガイドの <provider> 要素に関するトピックに記載されています。

インテントとデータアクセス

Intent を使用すると、アプリはコンテンツ プロバイダに間接的にアクセスできます。アプリは、ContentResolverContentProvider のメソッドを呼び出しません。その代わりに、アクティビティを起動するインテントを送信します。これは通常、プロバイダ独自のアプリの一部となっています。対象のアクティビティは UI のデータの取得と表示を担当します。インテントのアクションに応じて、対象のアクティビティにより、ユーザーにプロバイダのデータの修正を求めるメッセージが表示されることがあります。さらに、インテントには、対象のアクティビティが UI に表示する「エクストラ」データが含まれることがあります。ユーザーは使用前に、このデータを変更してプロバイダのデータを修正することもできます。

インテント アクセスを使用して、データの整合性を保証できます。プロバイダのデータの挿入、更新、削除は、厳格に定義されたビジネス ロジックによって規定されることがあります。この場合、他のアプリにデータを直接修正できるように許可すると、無効なデータが発生することがあります。デベロッパーがインテント アクセスを使用する場合は、すべての操作を完全に文書化します。コードを使用してデータを修正するよりも、独自のアプリの UI を使用したインテント アクセスの方が優れている理由を説明します。

プロバイダのデータを修正するための受信インテントの処理は、その他のインテントの処理と同じです。インテントの使用についての詳細は、インテントとインテント フィルタをご覧ください。

このページに関連するサンプルコードについては、Note Pad のサンプル アプリをご覧ください。

その他の関連情報については、カレンダー プロバイダをご覧ください。