コンテンツ プロバイダを作成する

コンテンツ プロバイダは、データのセントラル リポジトリへのアクセスを管理します。プロバイダは、マニフェスト ファイル内の要素とともに、1 つ以上のクラスとして Android アプリに実装します。クラスの一つは、プロバイダと他のアプリ間のインターフェースである ContentProvider のサブクラスを実装しています。

コンテンツ プロバイダは他のアプリでデータを使用できるようにすることを目的としていますが、プロバイダによって管理されるデータをユーザーがクエリしたり変更したりできるアクティビティをアプリ内に組み込むことができます。

このページでは、コンテンツ プロバイダを作成する基本的なプロセスと、使用する API のリストについて説明します。

構築を開始する前に

プロバイダの作成を開始する前に、次の点を考慮してください。

  • コンテンツ プロバイダが必要かどうかを判断します。以下の機能の 1 つ以上を提供する場合は、コンテンツ プロバイダを作成する必要があります。
    • 複雑なデータやファイルを他のアプリに提供したい。
    • ユーザーが複雑なデータをアプリから他のアプリにコピーできるようにしたい場合。
    • 検索フレームワークを使用してカスタム検索候補を提供する必要があります。
    • アプリケーション データをウィジェットに公開したいと考えています。
    • AbstractThreadedSyncAdapter クラス、CursorAdapter クラス、または CursorLoader クラスを実装する場合。

    完全に独自のアプリケーション内で使用し、上記の機能のいずれも必要ない場合、データベースやその他のタイプの永続ストレージを使用するプロバイダは必要ありません。代わりに、データ ストレージとファイル ストレージの概要で説明されているストレージ システムのいずれかを使用できます。

  • まだご覧になっていない場合は、 コンテンツ プロバイダの基本をお読みになり、プロバイダとその仕組みの詳細をご確認ください。

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

  1. データの RAW ストレージを設計する。コンテンツ プロバイダは次の 2 つの方法でデータを提供します。
    ファイルデータ
    写真、音声、動画など、通常はファイルに保存されるデータ。ファイルはアプリケーションのプライベート スペースに保存します。別のアプリからのファイルに対するリクエストに応じて、プロバイダはファイルへのハンドルを提供できます。
    「構造化データ」
    通常は、データベース、配列、または同様の構造に格納されるデータ。行と列のテーブルと互換性のある形式でデータを保存します。行は、人物や在庫のアイテムなどのエンティティを表します。列は、人の名前やアイテムの価格など、エンティティのデータを表します。このタイプのデータを保存する一般的な方法は SQLite データベースに格納することですが、任意のタイプの永続ストレージを使用できます。Android システムで利用可能なストレージ タイプについて詳しくは、 データ ストレージを設計するのセクションをご覧ください。
  2. ContentProvider クラスの具体的な実装と必要なメソッドを定義します。このクラスは、データと Android システムの他の部分との間のインターフェースです。このクラスの詳細については、ContentProvider クラスを実装するをご覧ください。
  3. プロバイダの権限文字列、コンテンツ URI、列名を定義します。プロバイダのアプリでインテントを処理する場合は、インテントのアクション、エクストラのデータ、フラグも定義します。また、データにアクセスするアプリケーションに必要な権限も定義します。これらの値をすべて別のコントラクト クラスで定数として定義することを検討してください。後でこのクラスを他のデベロッパーに公開できます。コンテンツ URI について詳しくは、コンテンツ URI を設計するをご覧ください。 インテントの詳細については、インテントとデータアクセスのセクションをご覧ください。
  4. サンプルデータや、プロバイダとクラウドベースのデータ間でデータを同期できる AbstractThreadedSyncAdapter の実装など、他のオプション要素を追加します。

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

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

Android で利用できるデータ ストレージ テクノロジーには、次のようなものがあります。

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

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

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

: 下位互換性のない変更をリポジトリに加える場合は、リポジトリに新しいバージョンをマークする必要があります。また、新しいコンテンツ プロバイダを実装するアプリのバージョン番号を上げることも必要です。この変更を行うことで、互換性のないコンテンツ プロバイダを含むアプリを再インストールしようとすると、システムのダウングレードによってシステムがクラッシュしなくなります。

データ設計上の考慮事項

プロバイダのデータ構造を設計する際のヒントをいくつか紹介します。

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

    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 を処理する

慣例として、プロバイダの行は ID 値を持つコンテンツ URI を URI の末尾で受け入れることで、テーブル内の 1 行へのアクセスを提供します。また、慣例として、プロバイダは ID 値をテーブルの _ID 列と照合し、一致する行に対してリクエストされたアクセスを実行します。

この規則にすることで、プロバイダにアクセスするアプリの一般的な設計パターンを容易に実装できます。アプリはプロバイダに対してクエリを実行し、CursorAdapter を使用して結果の CursorListView に表示します。CursorAdapter の定義では、Cursor のいずれかの列が _ID である必要があります。

次に、データを確認または変更するために、表示されている行のいずれかを UI から選択します。アプリは、ListView をサポートする Cursor から対応する行を取得し、この行の _ID 値を取得してコンテンツ URI に追加し、アクセス リクエストをプロバイダに送信します。プロバイダは、ユーザーが選択した行に対してクエリまたは変更を行うことができます。

コンテンツ URI パターン

受信コンテンツ URI に対して実行するアクションを選択できるように、プロバイダ API には、コンテンツ URI パターンを整数値にマッピングする便利なクラス UriMatcher が用意されています。switch ステートメントで整数値を使用して、特定のパターンに一致するコンテンツ 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 という名前のテーブル。

プロバイダは、これらのコンテンツ URI に行 ID が付加されている場合も認識します(table31 によって識別される行の content://com.example.app.provider/table3/1 など)。

次のようなコンテンツ URI パターンが考えられます。

content://com.example.app.provider/*
プロバイダ内のすべてのコンテンツ URI と一致します。
content://com.example.app.provider/table2/*
テーブル dataset1dataset2 のコンテンツ URI と一致しますが、table1 または table3 のコンテンツ URI とは一致しません。
content://com.example.app.provider/table3/#
table3 の単一行のコンテンツ URI と一致します(6 で識別される行の content://com.example.app.provider/table3/6 など)。

次のコード スニペットは、UriMatcher のメソッドの仕組みを示しています。 このコードは、コンテンツ URI パターン content://<authority>/<path>(テーブルの場合)と 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 the content URI patterns that the provider
     * recognizes. 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 isn't recognized,
                // 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 the content URI patterns that the provider
         * recognizes. For this snippet, only the calls for table 3 are shown.
         */

        /*
         * Sets the integer value for multiple rows in table 3 to one. 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 isn't recognized, do some error handling here
        }
        // Call the code to actually do the query
    }

もう 1 つのクラス ContentUris には、コンテンツ URI の id 部分を操作するための便利なメソッドが用意されています。Uri クラスと Uri.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() メソッドは、コンテンツ URI 引数によって返されるデータのタイプを表す MIME 形式の String を返します。Uri 引数には、特定の URI ではなくパターンを指定できます。この場合、パターンに一致するコンテンツ URI に関連付けられたデータのタイプを返します。

テキスト、HTML、JPEG などの一般的なタイプのデータの場合、getType() はそのデータの標準の MIME タイプを返します。これらの標準タイプの一覧については、IANA MIME メディアタイプをご覧ください。

テーブルデータの行を指すコンテンツ 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 形式のファイルとして写真画像を提供するプロバイダについて考えてみましょう。アプリが「画像」としてフィルタ文字列 image/* を指定して ContentResolver.getStreamTypes() を呼び出すと、ContentProvider.getStreamTypes() メソッドは配列を返します。

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

アプリが JPG ファイルのみを扱う場合は、フィルタ文字列 *\/jpeg を指定して ContentResolver.getStreamTypes() を呼び出すことができます。getStreamTypes() は次のものを返します。

{"image/jpeg"}

プロバイダがフィルタ文字列でリクエストされた MIME タイプを提供していない場合、getStreamTypes()null を返します。

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

コントラクト クラスは、URI、列名、MIME タイプ、プロバイダに関連するその他のメタデータの定数定義を含む public final クラスです。このクラスは、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 つの権限。 <provider> 要素の android:permission 属性で指定します。
プロバイダ レベルの読み取り権限と書き込み権限を個別に付与
プロバイダ全体に対する読み取り権限と書き込み権限。この属性は、 <provider> 要素の android:readPermission 属性と android:writePermission 属性で指定します。これらは、 android:permission で必要な権限よりも優先されます。
パスレベルの権限
プロバイダのコンテンツ URI に対する読み取り、書き込み、または読み取り/書き込み権限。 <provider> 要素の <path-permission> 子要素を使用して、制御する各 URI を指定します。指定するコンテンツ URI ごとに、読み取り/書き込み権限、読み取り権限、書き込み権限、またはその両方を指定できます。読み取りおよび書き込み権限は、読み取り/書き込み権限よりも優先されます。また、パスレベルの権限はプロバイダ レベルの権限よりも優先されます。
一時的な権限
アプリに一時的にアクセス権を付与する権限レベル。通常は必要な権限がアプリに付与されていなくてもかまいません。一時アクセス機能により、アプリがマニフェストでリクエストする必要がある権限の数を削減できます。一時的な権限をオンにした場合、プロバイダの永続的な権限を必要とするアプリは、継続的にすべてのデータにアクセスするアプリのみになります。

たとえば、メール プロバイダとアプリを実装していて、外部の画像ビューア アプリケーションにプロバイダからの写真添付ファイルを表示できるようにする場合に必要な権限について検討してください。権限がなくても画像ビューアに必要なアクセス権を付与するには、写真のコンテンツ URI に一時的な権限を設定します。

ユーザーが写真を表示しようとしたときに、写真のコンテンツ URI と権限フラグを含むインテントが画像ビューアに送信されるようにメールアプリを設計します。画像ビューアは、メール プロバイダに対して通常の読み取り権限がなくても、写真を取得するためにメール プロバイダにクエリを送信できます。

一時的な権限を有効にするには、 <provider> 要素の android:grantUriPermissions 属性を設定するか、1 つ以上の <grant-uri-permission> 子要素を <provider> 要素に追加します。プロバイダから一時的な権限に関連付けられているコンテンツ URI のサポートを削除する場合は、必ず Context.revokeUriPermission() を呼び出します。

属性の値によって、プロバイダをどの程度アクセス可能にするかが決まります。 この属性を "true" に設定した場合、システムによりプロバイダ全体に一時的な権限が付与され、プロバイダ レベルまたはパスレベルの権限で必要とされるその他の権限はオーバーライドされます。

このフラグを "false" に設定した場合は、<grant-uri-permission> 子要素を <provider> 要素に追加します。各子要素には、一時的なアクセス権が付与されるコンテンツ URI または URI を指定します。

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

android:grantUriPermissions 属性が存在しない場合、"false" であると見なされます。

<provider> 要素

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

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

権限とそれに対応する属性について詳しくは、コンテンツ プロバイダの権限を実装するのセクションをご覧ください。

起動属性と制御属性
これらの属性により、Android システムがプロバイダを起動する方法とタイミング、プロバイダのプロセス特性、その他のランタイム設定が決まります。
  • android:enabled: システムがプロバイダを起動できるようにするフラグ。
  • android:exported: 他のアプリがこのプロバイダを使用できるようにフラグを設定します。
  • android:initOrder: このプロバイダが開始された順序(同じプロセス内の他のプロバイダとの比較)
  • android:multiProcess: システムが呼び出し元のクライアントと同じプロセスでプロバイダを起動できるようにするフラグ
  • android:process: プロバイダが実行されるプロセスの名前
  • android:syncable: プロバイダのデータをサーバー上のデータと同期させることを示すフラグ

これらの属性については、 <provider> 要素のガイドで詳しく説明しています。

情報属性
プロバイダのアイコンとラベル(省略可):
  • android:icon: プロバイダのアイコンを含むドローアブル リソース。このアイコンは、[設定] > [アプリ] > [すべて] のアプリのリストで、プロバイダのラベルの横に表示されます。
  • android:label: プロバイダ、そのデータ、またはその両方を記述する情報ラベル。このラベルは、[設定] > [アプリ] > [すべて] のアプリのリストに表示されます。

これらの属性については、 <provider> 要素のガイドで詳しく説明しています。

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

アプリは Intent を使用して、コンテンツ プロバイダに間接的にアクセスできます。 アプリは ContentResolverContentProvider のメソッドを呼び出しません。代わりに、アクティビティを開始するインテントを送信します。アクティビティは、多くの場合、プロバイダ独自のアプリの一部です。デスティネーション アクティビティは、UI でのデータの取得と表示を担当します。

インテントのアクションに応じて、デスティネーション アクティビティは、プロバイダのデータを変更するようユーザーに促すこともできます。インテントには、デスティネーション アクティビティが UI に表示する「エクストラ」データが含まれる場合もあります。ユーザーはこのデータを使用してプロバイダ内のデータを変更することもできます。

インテント アクセスはデータの整合性に役立ちます。プロバイダによっては、厳密に定義されたビジネス ロジックに従ってデータの挿入、更新、削除が行われている場合があります。その場合、他のアプリがデータを直接変更できるようにすると、無効なデータにつながる可能性があります。

デベロッパーがインテント アクセスを使用できるようにするには、その詳細を明記します。アプリの UI を使用したインテント アクセスの方が、アプリのコードを使用してデータを変更するよりも優れた理由を説明します。

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

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