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

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

コンテンツ プロバイダは他のアプリがデータを利用できるようにするためのものですが、プロバイダが管理するデータをユーザーがクエリしたり変更したりできるアクティビティをアプリ内に置くことができます。

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

作成を始める前に

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

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

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

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

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

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

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

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

Android で使用できるデータ ストレージ技術には、次のようなものがあります。

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

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

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

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

データ設計上の考慮事項

プロバイダのデータ構造を設計する際のヒントは次のとおりです。

  • テーブルデータには、プロバイダが各行の一意の数値として保持する「主キー」列が常に必要です。この値を使用すると、行を他のテーブルの関連する行にリンクできます(「外部キー」として使用)。この列には任意の名前を使用できますが、プロバイダ クエリの結果を ListView にリンクするには、取得した列のいずれかの名前を _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 を処理する

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

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

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

コンテンツ URI パターン

受信コンテンツ URI に対して実行するアクションを選択できるように、プロバイダ API には、コンテンツ URI パターンを整数値にマッピングするコンビニエンス クラス UriMatcher が含まれています。この整数値は、特定のパターンに一致するコンテンツ URI に対して必要なアクションを選択する switch ステートメントで使用できます。

コンテンツ 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(table31 で識別される行の content://com.example.app.provider/table3/1 など)が付加されている場合、これらのコンテンツ URI を認識します。

使用できるコンテンツ URI パターンは次のとおりです。

content://com.example.app.provider/*
プロバイダの任意のコンテンツ URI と一致します。
content://com.example.app.provider/table2/*
テーブル dataset1 および dataset2 のコンテンツ 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 を単一行の URI とは異なる方法で処理します。

addURI() メソッドは、オーソリティとパスを整数値にマッピングします。メソッド match() は URI の整数値を返します。switch ステートメントは、テーブル全体に対してクエリを実行するか、単一レコードに対してクエリを実行するかを選択します。

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() メソッドを使用して、データ ストレージから行を削除する必要はありません。プロバイダで同期アダプターを使用している場合は、行を完全に削除するのではなく、削除された行を「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 引数によって返されるデータのタイプを記述した String を MIME 形式で返します。Uri 引数には、特定の URI ではなくパターンを指定できます。この場合、パターンに一致するコンテンツ URI に関連付けられているデータタイプを返します。

テキスト、HTML、JPEG などの一般的なタイプのデータの場合、getType() はそのデータの標準の MIME タイプを返します。これらの標準タイプの完全なリストは、IANA MIME メディアタイプのウェブサイトで確認できます。

テーブルデータの 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 形式のファイルとして提供するプロバイダについて考えてみましょう。アプリがフィルタ文字列 image/* を指定して ContentResolver.getStreamTypes() を呼び出した場合、それが「image」の場合、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 に対する読み取り、書き込み、読み取り/書き込み権限。制御する URI は、 <provider> 要素の <path-permission> 子要素で指定します。指定するコンテンツ URI ごとに、読み取り/書き込み権限、読み取り権限、書き込み権限、またはこの 3 つすべてを指定できます。読み取り / 書き込み権限は、読み取り / 書き込み権限よりも優先されます。また、パスレベルの権限はプロバイダ レベルの権限よりも優先されます。
一時的な権限
アプリに通常必要な権限がない場合でも、アプリに一時的なアクセス権を付与する権限レベル。一時アクセス機能を使用すると、アプリがマニフェストでリクエストする必要がある権限の数が減ります。一時的な権限をオンにすると、プロバイダの永続的な権限を必要とするアプリが、すべてのデータに継続的にアクセスします。

たとえば、メール プロバイダとアプリを実装していて、プロバイダから添付された写真を外部の画像ビューア アプリケーションに表示させる場合に必要な権限について考えてみましょう。権限を要求せずに画像ビューアに必要なアクセス権を付与するには、写真のコンテンツ URI に一時的な権限を設定します。

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

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

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

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

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

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

<provider> 要素

ContentProvider のサブクラスは、Activity コンポーネントや Service コンポーネントと同様に、アプリケーションのマニフェスト ファイルで <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> 要素のガイドに記載されています。

注: Android 11 以降をターゲットとしている場合は、パッケージの公開設定に関するドキュメントで詳細な構成要件をご確認ください。

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

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

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

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

開発者にインテント アクセスを使用してもらいたい場合は、その内容を徹底的に文書化してください。コードを使ってデータを変更するよりも、アプリの UI を使用したインテント アクセスの方が優れている理由を説明します。

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

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