コンテンツ プロバイダの基本

コンテンツ プロバイダは、データのセントラル リポジトリへのアクセスを管理します。プロバイダは Android アプリの一部であり、多くの場合、データを操作する独自の UI を提供します。ただし、コンテンツ プロバイダは主に、プロバイダ クライアント オブジェクトを使用してプロバイダにアクセスする他のアプリによって使用されます。プロバイダとプロバイダ クライアントは、連携してデータに対する一貫した標準インターフェースを提供し、プロセス間通信と安全なデータアクセスも処理します。

通常、コンテンツ プロバイダを使用する状況は 2 つあります。1 つは別のアプリで既存のコンテンツ プロバイダにアクセスするコードを実装する場合、もう 1 つはアプリで新しいコンテンツ プロバイダを作成して他のアプリとデータを共有する場合です。

このページでは、既存のコンテンツ プロバイダを使用する際の基本について説明します。独自のアプリにコンテンツ プロバイダを実装する方法については、 コンテンツ プロバイダを作成するをご覧ください。

このトピックでは、次のことについて説明します。

  • コンテンツ プロバイダの仕組み。
  • コンテンツ プロバイダからのデータの取得に使用する API。
  • コンテンツ プロバイダへのデータの挿入、データの更新、または削除に使用する API。
  • プロバイダでの作業に役立つその他の API 機能。

概要

コンテンツ プロバイダは外部アプリに対し、リレーショナル データベースのテーブルに似た 1 つ以上のテーブルとしてデータを提供します。行はプロバイダが収集するなんらかのデータのインスタンスを表し、行の各列はインスタンスに対して収集した個々のデータを表します。

コンテンツ プロバイダは、さまざまな API やコンポーネントについて、アプリ内のデータ ストレージ レイヤへのアクセスを調整します。図 1 に示すように、次のようなものがあります。

  • アプリデータへのアクセスを他のアプリと共有する
  • ウィジェットにデータを送信する
  • SearchRecentSuggestionsProvider を使用して、検索フレームワークを介してアプリのカスタム検索候補を返す
  • AbstractThreadedSyncAdapter の実装を使用してアプリデータをサーバーと同期する
  • CursorLoader を使用して UI にデータを読み込む
コンテンツ プロバイダと他のコンポーネントの関係。

図 1. コンテンツ プロバイダと他のコンポーネントとの関係。

プロバイダにアクセスする

コンテンツ プロバイダのデータにアクセスする場合は、アプリの ContextContentResolver オブジェクトを使用し、クライアントとしてプロバイダと通信します。ContentResolver オブジェクトは、ContentProvider を実装するクラスのインスタンスであるプロバイダ オブジェクトと通信します。

プロバイダ オブジェクトはクライアントからデータ リクエストを受け取り、リクエストされたアクションを実施して、結果を返します。このオブジェクトには、プロバイダ オブジェクト(ContentProvider の具象サブクラスのいずれかのインスタンス)内の同じ名前のメソッドを呼び出すメソッドがあります。ContentResolver メソッドは、永続ストレージの基本的な「CRUD」(作成、取得、更新、削除)機能を提供します。

UI から ContentProvider にアクセスするための一般的なパターンでは、CursorLoader を使用してバックグラウンドで非同期クエリを実行します。UI の Activity または Fragment がクエリに対して CursorLoader を呼び出し、それが ContentResolver を使用して ContentProvider を取得します。

これにより、ユーザーはクエリの実行中も UI を引き続き使用できます。このパターンには、図 2 に示すようにさまざまなオブジェクトとのやり取りと、基となるストレージ メカニズムが含まれます。

ContentProvider、他のクラス、ストレージの間のやり取り。

図 2. ContentProvider、他のクラス、ストレージ間のインタラクション。

注: アプリがプロバイダにアクセスするには、通常、マニフェスト ファイルで特定の権限をリクエストする必要があります。この開発パターンについて詳しくは、コンテンツ プロバイダの権限のセクションをご覧ください。

Android プラットフォームの組み込みプロバイダの 1 つに単語リストプロバイダがあります。このプロバイダには、ユーザーが残したい非標準的な単語が格納されます。表 1 は、このプロバイダのテーブルにデータがどのように格納されるかを示しています。

表 1: 単語リストの表の例

word app id frequency locale _ID
mapreduce user1 100 en_US 1
precompiler user14 200 fr_FR 2
applet user2 225 fr_CA 3
const user1 255 pt_BR 4
int user5 100 en_UK 5

表 1 の各行は、標準の辞書にない単語の 1 つのインスタンスを表します。各列は、その単語が最初に検出された言語 / 地域など、その単語のデータを表します。列の見出しは、プロバイダに格納される列の名前です。たとえば、行のロケールを参照するには、その locale 列を参照します。このプロバイダの場合、_ID 列はプロバイダが自動的に維持する主キー列として機能します。

単語リスト プロバイダから単語とその言語 / 地域のリストを取得するには、ContentResolver.query() を呼び出します。query() メソッドにより、単語リスト プロバイダが定義する ContentProvider.query() メソッドが呼び出されます。ContentResolver.query() 呼び出しを次のコード行に示します。

Kotlin

// Queries the UserDictionary and returns results
cursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
        projection,                        // The columns to return for each row
        selectionClause,                   // Selection criteria
        selectionArgs.toTypedArray(),      // Selection criteria
        sortOrder                          // The sort order for the returned rows
)

Java

// Queries the UserDictionary and returns results
cursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    projection,                        // The columns to return for each row
    selectionClause,                   // Selection criteria
    selectionArgs,                     // Selection criteria
    sortOrder);                        // The sort order for the returned rows

表 2 は、query(Uri,projection,selection,selectionArgs,sortOrder) の引数と SQL SELECT ステートメントの一致を示しています。

表 2: query() と SQL クエリの比較

query() 引数 SELECT キーワード / パラメータ Notes
Uri FROM table_name Uri は、table_name という名前のプロバイダのテーブルにマッピングされます。
projection col,col,col,... projection は、取得される各行に含まれる列の配列です。
selection WHERE col = value selection は、行を選択する際の基準を指定します。
selectionArgs 完全に一致するものはありません。選択引数は、選択句の ? プレースホルダを置き換えます。
sortOrder ORDER BY col,col,... sortOrder には、返された Cursor に行が表示される順序を指定します。

コンテンツ URI

コンテンツ URI は、プロバイダのデータを特定する URI です。コンテンツ URI には、プロバイダ全体のシンボリック名(認証局)とテーブルを指す名前(パス)が含まれます。クライアント メソッドを呼び出してプロバイダのテーブルにアクセスする場合、引数のうち 1 つはテーブルのコンテンツ URI です。

上記のコード行では、定数 CONTENT_URI に、単語リストプロバイダの Words テーブルのコンテンツ URI が含まれています。ContentResolver オブジェクトは URI のオーソリティを解析し、既知のプロバイダのシステム テーブルとオーソリティを比較してプロバイダを解決します。その後、ContentResolver はクエリ引数を正しいプロバイダにディスパッチできます。

ContentProvider は、アクセスするテーブルを選択するために、コンテンツ URI のパス部分を使用します。通常、プロバイダには公開する各テーブルのパスがあります。

上記のコード行で、Words テーブルの完全な URI は次のようになります。

content://user_dictionary/words
  • content:// 文字列はスキームです。これは常に存在し、コンテンツ URI であることを識別します。
  • user_dictionary 文字列はプロバイダのオーソリティです。
  • words 文字列は、テーブルのパスです。

多くのプロバイダでは、URI の末尾に ID 値を追加することでテーブル内の 1 行にアクセスできます。たとえば、_ID4 の行を単語リスト プロバイダから取得するには、次のコンテンツ URI を使用します。

Kotlin

val singleUri: Uri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI, 4)

Java

Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

ID 値は、一連の行を取得し、そのうちの 1 つを更新または削除するときによく使用します。

注: Uri クラスと Uri.Builder クラスには、文字列から適切な形式の URI オブジェクトを作成するための便利なメソッドが用意されています。ContentUris クラスには、URI に ID 値を追加するための便利なメソッドが用意されています。上のスニペットでは、withAppendedId() を使用して、単語リストプロバイダのコンテンツ URI に ID を追加しています。

プロバイダからデータを取得する

このセクションでは、単語リスト プロバイダを例に、プロバイダからデータを取得する方法について説明します。

わかりやすくするために、このセクションのコード スニペットは UI スレッドの ContentResolver.query() を呼び出します。ただし、実際のコードでは、別のスレッドで非同期にクエリを実行してください。CursorLoader クラスを使用できます。クラスについて詳しくは、 ローダのガイドをご覧ください。また、コード行はスニペットのみです。完全なアプリが表示されるわけではありません。

プロバイダからデータを取得するには、次の基本的な手順を行います。

  1. プロバイダの読み取りアクセス権をリクエストします。
  2. プロバイダにクエリを送信するコードを定義します。

読み取りアクセス権のリクエスト

プロバイダからデータを取得するには、アプリでプロバイダに対する読み取りアクセス権限が必要です。この権限は実行時にリクエストできません。代わりに、<uses-permission> 要素と、プロバイダが定義する正確な権限名を使用して、この権限が必要であることをマニフェストで指定する必要があります。

マニフェストでこの要素を指定すると、アプリについてこの権限をリクエストします。ユーザーがアプリをインストールすると、このリクエストが暗黙的に付与されます。

使用しているプロバイダの読み取りアクセス権限の正確な名前や、プロバイダによって使用されている他のアクセス権限の名前を確認するには、プロバイダのドキュメントをご覧ください。

プロバイダにアクセスする際の権限の役割については、コンテンツ プロバイダの権限で詳しく説明しています。

単語リスト プロバイダはマニフェスト ファイルで権限 android.permission.READ_USER_DICTIONARY を定義しているため、プロバイダからの読み取りを行うアプリは、この権限をリクエストする必要があります。

クエリを作成する

プロバイダからデータを取得する次のステップは、クエリの作成です。次のスニペットでは、単語リスト プロバイダにアクセスするための変数を定義しています。

Kotlin

// A "projection" defines the columns that are returned for each row
private val mProjection: Array<String> = arrayOf(
        UserDictionary.Words._ID,    // Contract class constant for the _ID column name
        UserDictionary.Words.WORD,   // Contract class constant for the word column name
        UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
)

// Defines a string to contain the selection clause
private var selectionClause: String? = null

// Declares an array to contain selection arguments
private lateinit var selectionArgs: Array<String>

Java

// A "projection" defines the columns that are returned for each row
String[] mProjection =
{
    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
    UserDictionary.Words.WORD,   // Contract class constant for the word column name
    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
};

// Defines a string to contain the selection clause
String selectionClause = null;

// Initializes an array to contain selection arguments
String[] selectionArgs = {""};

次のスニペットでは、単語リスト プロバイダの例を使用して、ContentResolver.query() の使用方法を示しています。プロバイダ クライアント クエリは SQL クエリに似ており、返される列のセット、選択条件、並べ替え順序で構成されます。

このクエリによって返される列のセットは射影と呼ばれ、変数は mProjection です。

取得する行を指定する式は、選択句と選択引数に分割されます。選択句は、論理式、ブール式、列名、値の組み合わせです。変数は mSelectionClause です。値の代わりに置換可能なパラメータ ? を指定すると、クエリメソッドは選択引数の配列(変数 mSelectionArgs)から値を取得します。

次のスニペットでは、ユーザーが単語を入力しないと、選択句が null に設定され、クエリはプロバイダのすべての単語を返します。ユーザーが単語を入力すると、選択句が UserDictionary.Words.WORD + " = ?" に設定され、選択引数配列の最初の要素はユーザーが入力した単語に設定されます。

Kotlin

/*
 * This declares a String array to contain the selection arguments.
 */
private lateinit var selectionArgs: Array<String>

// Gets a word from the UI
searchString = searchWord.text.toString()

// Insert code here to check for invalid or malicious input

// If the word is the empty string, gets everything
selectionArgs = searchString?.takeIf { it.isNotEmpty() }?.let {
    selectionClause = "${UserDictionary.Words.WORD} = ?"
    arrayOf(it)
} ?: run {
    selectionClause = null
    emptyArray<String>()
}

// Does a query against the table and returns a Cursor object
mCursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI, // The content URI of the words table
        projection,                       // The columns to return for each row
        selectionClause,                  // Either null or the word the user entered
        selectionArgs,                    // Either empty or the string the user entered
        sortOrder                         // The sort order for the returned rows
)

// Some providers return null if an error occurs, others throw an exception
when (mCursor?.count) {
    null -> {
        /*
         * Insert code here to handle the error. Be sure not to use the cursor!
         * You might want to call android.util.Log.e() to log this error.
         */
    }
    0 -> {
        /*
         * Insert code here to notify the user that the search is unsuccessful. This isn't
         * necessarily an error. You might want to offer the user the option to insert a new
         * row, or re-type the search term.
         */
    }
    else -> {
        // Insert code here to do something with the results
    }
}

Java

/*
 * This defines a one-element String array to contain the selection argument.
 */
String[] selectionArgs = {""};

// Gets a word from the UI
searchString = searchWord.getText().toString();

// Remember to insert code here to check for invalid or malicious input

// If the word is the empty string, gets everything
if (TextUtils.isEmpty(searchString)) {
    // Setting the selection clause to null returns all words
    selectionClause = null;
    selectionArgs[0] = "";

} else {
    // Constructs a selection clause that matches the word that the user entered
    selectionClause = UserDictionary.Words.WORD + " = ?";

    // Moves the user's input string to the selection arguments
    selectionArgs[0] = searchString;

}

// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI, // The content URI of the words table
    projection,                       // The columns to return for each row
    selectionClause,                  // Either null or the word the user entered
    selectionArgs,                    // Either empty or the string the user entered
    sortOrder);                       // The sort order for the returned rows

// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
    /*
     * Insert code here to handle the error. Be sure not to use the cursor! You can
     * call android.util.Log.e() to log this error.
     *
     */
// If the Cursor is empty, the provider found no matches
} else if (mCursor.getCount() < 1) {

    /*
     * Insert code here to notify the user that the search is unsuccessful. This isn't necessarily
     * an error. You can offer the user the option to insert a new row, or re-type the
     * search term.
     */

} else {
    // Insert code here to do something with the results

}

このクエリは、次の SQL ステートメントに似ています。

SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;

この SQL ステートメントでは、コントラクト クラス定数の代わりに実際の列名が使用されます。

悪意のある入力から保護

コンテンツ プロバイダが管理するデータが SQL データベース内にある場合、信頼できない外部のデータを RAW SQL ステートメントに含めると、SQL インジェクションが発生する可能性があります。

次の選択句について考えてみましょう。

Kotlin

// Constructs a selection clause by concatenating the user's input to the column name
var selectionClause = "var = $mUserInput"

Java

// Constructs a selection clause by concatenating the user's input to the column name
String selectionClause = "var = " + userInput;

これを行うと、ユーザーが悪意のある SQL を SQL ステートメントに連結する可能性があります。たとえば、mUserInput に対して「nothing; DROP TABLE *;」と入力すると、選択句 var = nothing; DROP TABLE *; になります。

選択句は SQL ステートメントとして扱われるため、プロバイダが SQL インジェクションの試行をキャッチするように設定されていない限り、基盤となる SQLite データベース内のすべてのテーブルがプロバイダによって消去される可能性があります。

この問題を回避するには、? を置換可能なパラメータとして使用する選択句と、選択引数の個別の配列を使用します。このようにして、ユーザー入力は SQL ステートメントの一部として解釈されるのではなく、クエリに直接バインドされます。SQL として扱われないため、ユーザー入力によって悪意のある SQL が挿入されることはありません。ユーザー入力を含めるために連結を使用するのではなく、次の選択句を使用します。

Kotlin

// Constructs a selection clause with a replaceable parameter
var selectionClause = "var = ?"

Java

// Constructs a selection clause with a replaceable parameter
String selectionClause =  "var = ?";

選択引数の配列を次のようにセットアップします。

Kotlin

// Defines a mutable list to contain the selection arguments
var selectionArgs: MutableList<String> = mutableListOf()

Java

// Defines an array to contain the selection arguments
String[] selectionArgs = {""};

選択引数の配列に、次のように値を設定します。

Kotlin

// Adds the user's input to the selection argument
selectionArgs += userInput

Java

// Sets the selection argument to the user's input
selectionArgs[0] = userInput;

選択を指定するには、プロバイダが SQL データベースに基づいていなくても、? を置換可能なパラメータとして使用し、選択引数の配列を使用する選択句を使用することをおすすめします。

クエリ結果を表示する

ContentResolver.query() クライアント メソッドは、常に Cursor を返します。これには、クエリの選択基準に一致する行のクエリの射影で指定される列が含まれます。Cursor オブジェクトは、そこに含まれる行と列へのランダム読み取りアクセスを提供します。

Cursor メソッドを使用すると、結果の行の反復処理、各列のデータ型の特定、列からのデータの取得、結果の他のプロパティの確認を行うことができます。

一部の Cursor 実装では、プロバイダのデータが変更されたときにオブジェクトを自動的に更新したり、Cursor が変更されたときにオブザーバー オブジェクトのメソッドをトリガーしたり、またはその両方を行うことができます。

注: プロバイダは、クエリを実行するオブジェクトの性質に基づいて列へのアクセスを制限できます。たとえば、連絡先プロバイダは、一部の列の同期アダプターへのアクセスを制限しているため、アクティビティやサービスに列が返されません。

選択条件に一致する行がない場合、プロバイダは、Cursor.getCount() が 0 の Cursor オブジェクト(空のカーソル)を返します。

内部エラーが発生した場合、クエリの結果はプロバイダによって異なります。null が返されるか、Exception がスローされます。

Cursor は行のリストであるため、Cursor の内容を表示するには、SimpleCursorAdapter を使用して ListView にリンクすることをおすすめします。

次のスニペットは、前のスニペットのコードの続きです。クエリで取得した Cursor を含む SimpleCursorAdapter オブジェクトを作成し、このオブジェクトを ListView のアダプターに設定します。

Kotlin

// Defines a list of columns to retrieve from the Cursor and load into an output row
val wordListColumns : Array<String> = arrayOf(
        UserDictionary.Words.WORD,      // Contract class constant containing the word column name
        UserDictionary.Words.LOCALE     // Contract class constant containing the locale column name
)

// Defines a list of View IDs that receive the Cursor columns for each row
val wordListItems = intArrayOf(R.id.dictWord, R.id.locale)

// Creates a new SimpleCursorAdapter
cursorAdapter = SimpleCursorAdapter(
        applicationContext,             // The application's Context object
        R.layout.wordlistrow,           // A layout in XML for one row in the ListView
        mCursor,                        // The result from the query
        wordListColumns,                // A string array of column names in the cursor
        wordListItems,                  // An integer array of view IDs in the row layout
        0                               // Flags (usually none are needed)
)

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter)

Java

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] wordListColumns =
{
    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
};

// Defines a list of View IDs that receive the Cursor columns for each row
int[] wordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
cursorAdapter = new SimpleCursorAdapter(
    getApplicationContext(),               // The application's Context object
    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
    mCursor,                               // The result from the query
    wordListColumns,                       // A string array of column names in the cursor
    wordListItems,                         // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter);

注: Cursor を使用して ListView を返すには、カーソルに _ID という名前の列を含める必要があります。このため、上記のクエリでは、ListView では表示されませんが、Words テーブルの _ID 列が取得されます。ほとんどのプロバイダで各テーブルに _ID 列があるのは、この制限の理由にも当てはまります。

クエリ結果からデータを取得する

クエリ結果を表示するだけでなく、他のタスクにも使用できます。たとえば、単語リスト プロバイダからスペルを取得して、他のプロバイダでそのスペルを検索できます。そのためには、次の例のように Cursor 内の行を反復処理します。

Kotlin

/*
* Only executes if the cursor is valid. The User Dictionary Provider returns null if
* an internal error occurs. Other providers might throw an Exception instead of returning null.
*/
mCursor?.apply {
    // Determine the column index of the column named "word"
    val index: Int = getColumnIndex(UserDictionary.Words.WORD)

    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you get an
     * exception.
     */
    while (moveToNext()) {
        // Gets the value from the column
        newWord = getString(index)

        // Insert code here to process the retrieved word
        ...
        // End of while loop
    }
}

Java

// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);

/*
 * Only executes if the cursor is valid. The User Dictionary Provider returns null if
 * an internal error occurs. Other providers might throw an Exception instead of returning null.
 */

if (mCursor != null) {
    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you get an
     * exception.
     */
    while (mCursor.moveToNext()) {

        // Gets the value from the column
        newWord = mCursor.getString(index);

        // Insert code here to process the retrieved word
        ...
        // End of while loop
    }
} else {

    // Insert code here to report an error if the cursor is null or the provider threw an exception
}

Cursor の実装には「get」メソッドがいくつか用意されており、オブジェクトからさまざまなタイプのデータを取得できます。たとえば上記のスニペットでは getString() を使用しています。また、列のデータ型を示す値を返す getType() メソッドも使用しています。

コンテンツ プロバイダの権限

プロバイダのアプリで、他のアプリがプロバイダのデータにアクセスするために必要な権限を指定できます。これらの権限は、アプリがアクセスしようとしているデータをユーザーに知らせるものです。他のアプリは、プロバイダの要件に基づき、プロバイダにアクセスするために必要な権限をリクエストします。エンドユーザーがアプリをインストールするとき、リクエストされた権限が表示されます。

プロバイダのアプリで権限が指定されていない場合、プロバイダがエクスポートされない限り、他のアプリはプロバイダのデータにアクセスできません。さらに、指定された権限に関係なく、プロバイダのアプリのコンポーネントは常に完全な読み取り / 書き込みアクセス権を持ちます。

単語リスト プロバイダからデータを取得するには、android.permission.READ_USER_DICTIONARY 権限が必要です。プロバイダには、データの挿入、更新、削除を行うための個別の android.permission.WRITE_USER_DICTIONARY 権限があります。

プロバイダにアクセスするために必要な権限を取得するには、アプリのマニフェスト ファイルで <uses-permission> 要素を使用して権限をリクエストします。Android Package Manager がアプリをインストールする際、ユーザーはアプリがリクエストするすべての権限を承認する必要があります。ユーザーが承認すると、Package Manager はインストールを続行します。ユーザーが承認しないと、Package Manager はインストールを停止します。

次のサンプル <uses-permission> 要素は、単語リストプロバイダへの読み取りアクセス権をリクエストしています。

<uses-permission android:name="android.permission.READ_USER_DICTIONARY">

権限がプロバイダ アクセスに与える影響については、セキュリティに関するヒントで詳しく説明しています。

データの挿入、更新、削除

プロバイダからデータを取得する場合と同じ方法で、プロバイダ クライアントとプロバイダの ContentProvider の間のやり取りを使用してデータを変更します。対応する ContentProvider のメソッドに渡す引数を使用して ContentResolver のメソッドを呼び出します。プロバイダとプロバイダのクライアントは、セキュリティとプロセス間通信を自動的に処理します。

データの挿入

プロバイダにデータを挿入するには、ContentResolver.insert() メソッドを呼び出します。このメソッドは、プロバイダに新しい行を挿入し、その行のコンテンツ URI を返します。次のスニペットは、単語リスト プロバイダに新しい単語を挿入する方法を示しています。

Kotlin

// Defines a new Uri object that receives the result of the insertion
lateinit var newUri: Uri
...
// Defines an object to contain the new values to insert
val newValues = ContentValues().apply {
    /*
     * Sets the values of each column and inserts the word. The arguments to the "put"
     * method are "column name" and "value".
     */
    put(UserDictionary.Words.APP_ID, "example.user")
    put(UserDictionary.Words.LOCALE, "en_US")
    put(UserDictionary.Words.WORD, "insert")
    put(UserDictionary.Words.FREQUENCY, "100")

}

newUri = contentResolver.insert(
        UserDictionary.Words.CONTENT_URI,   // The UserDictionary content URI
        newValues                           // The values to insert
)

Java

// Defines a new Uri object that receives the result of the insertion
Uri newUri;
...
// Defines an object to contain the new values to insert
ContentValues newValues = new ContentValues();

/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value".
 */
newValues.put(UserDictionary.Words.APP_ID, "example.user");
newValues.put(UserDictionary.Words.LOCALE, "en_US");
newValues.put(UserDictionary.Words.WORD, "insert");
newValues.put(UserDictionary.Words.FREQUENCY, "100");

newUri = getContentResolver().insert(
    UserDictionary.Words.CONTENT_URI,   // The UserDictionary content URI
    newValues                           // The values to insert
);

新しい行のデータは 1 つの ContentValues オブジェクトに格納されます。これは 1 行カーソルの形式に似ています。このオブジェクトの列は同じデータ型である必要はありません。また、値をまったく指定しない場合は、ContentValues.putNull() を使用して列を null に設定できます。

上のスニペットでは _ID 列は追加されていません。この列は自動的に維持されるためです。プロバイダは、追加されるすべての行に _ID の一意の値を割り当てます。プロバイダは通常、この値をテーブルの主キーとして使用します。

newUri で返されるコンテンツ URI は、新しく追加された行を次の形式で識別します。

content://user_dictionary/words/<id_value>

<id_value> は、新しい行の _ID のコンテンツです。ほとんどのプロバイダは、この形式のコンテンツ URI を自動的に検出し、その特定の行に対してリクエストされたオペレーションを実施できます。

返された Uri から _ID の値を取得するには、ContentUris.parseId() を呼び出します。

データの更新

行を更新するには、クエリの場合と同様に、挿入および選択基準の場合と同様に、更新された値を含む ContentValues オブジェクトを使用します。使用するクライアント メソッドは ContentResolver.update() です。更新する列の ContentValues オブジェクトに値を追加するだけで済みます。列のコンテンツを消去する場合は、値を null に設定します。

次のスニペットでは、言語 / 地域が "en" であるすべての行のロケールが null に変更されます。戻り値は、更新された行の数です。

Kotlin

// Defines an object to contain the updated values
val updateValues = ContentValues().apply {
    /*
     * Sets the updated value and updates the selected words.
     */
    putNull(UserDictionary.Words.LOCALE)
}

// Defines selection criteria for the rows you want to update
val selectionClause: String = UserDictionary.Words.LOCALE + "LIKE ?"
val selectionArgs: Array<String> = arrayOf("en_%")

// Defines a variable to contain the number of updated rows
var rowsUpdated: Int = 0
...
rowsUpdated = contentResolver.update(
        UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
        updateValues,                      // The columns to update
        selectionClause,                   // The column to select on
        selectionArgs                      // The value to compare to
)

Java

// Defines an object to contain the updated values
ContentValues updateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String selectionClause = UserDictionary.Words.LOCALE +  " LIKE ?";
String[] selectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int rowsUpdated = 0;
...
/*
 * Sets the updated value and updates the selected words.
 */
updateValues.putNull(UserDictionary.Words.LOCALE);

rowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
    updateValues,                      // The columns to update
    selectionClause,                   // The column to select on
    selectionArgs                      // The value to compare to
);

ContentResolver.update() の呼び出し時にユーザー入力をサニタイズします。詳細については、悪意のある入力から保護するをご覧ください。

データを削除する

行の削除は、行データの取得と同様です。削除する行の選択条件を指定すると、クライアント メソッドは削除された行の数を返します。次のスニペットは、アプリ ID が "user" と一致する行を削除します。削除した行数がメソッドから返されます。

Kotlin

// Defines selection criteria for the rows you want to delete
val selectionClause = "${UserDictionary.Words.APP_ID} LIKE ?"
val selectionArgs: Array<String> = arrayOf("user")

// Defines a variable to contain the number of rows deleted
var rowsDeleted: Int = 0
...
// Deletes the words that match the selection criteria
rowsDeleted = contentResolver.delete(
        UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
        selectionClause,                   // The column to select on
        selectionArgs                      // The value to compare to
)

Java

// Defines selection criteria for the rows you want to delete
String selectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] selectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int rowsDeleted = 0;
...
// Deletes the words that match the selection criteria
rowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
    selectionClause,                   // The column to select on
    selectionArgs                      // The value to compare to
);

ContentResolver.delete() の呼び出し時にユーザー入力をサニタイズします。詳細については、悪意のある入力から保護するをご覧ください。

プロバイダのデータタイプ

コンテンツ プロバイダは、さまざまなデータ型を提供できます。単語リスト プロバイダはテキストのみを提供しますが、次の形式も提供できます。

  • 整数
  • 長整数(long)
  • 浮動小数点数
  • long 浮動小数点(double)

プロバイダがよく使用するもう 1 つのデータ型は、64 KB バイト配列として実装されたバイナリ ラージ オブジェクト(BLOB)です。使用可能なデータ型は、Cursor クラスの「get」メソッドで確認できます。

プロバイダの各列のデータ型は通常、そのドキュメントに記載されています。単語リスト プロバイダのデータ型は、そのコントラクト クラス UserDictionary.Words のリファレンス ドキュメントに記載されています。コントラクト クラスについては、コントラクト クラスのセクションをご覧ください。 Cursor.getType() を呼び出すことでもデータ型を判断できます。

プロバイダは、定義する各コンテンツ URI の MIME データ型情報も保持します。MIME タイプ情報を使用して、プロバイダが提供するデータをアプリが処理できるかどうかを確認したり、MIME タイプに基づいて処理タイプを選択したりできます。MIME タイプは通常、複雑なデータ構造やファイルを含むプロバイダを扱う際に必要になります。

たとえば連絡先プロバイダの ContactsContract.Data テーブルでは、MIME タイプを使用して、各行に格納されている連絡先データのタイプにラベルを付けます。コンテンツ URI に対応する MIME タイプを取得するには、ContentResolver.getType() を呼び出します。

MIME タイプのリファレンス セクションでは、標準とカスタムの両方の MIME タイプの構文について説明しています。

別の形式のプロバイダ アクセス

アプリ開発では、次の 3 つの代替形式のプロバイダ アクセスが重要です。

以下のセクションでは、インテントを使用したバッチアクセスと変更について説明します。

バッチアクセス

プロバイダへのバッチアクセスは、大量の行を挿入する場合や、同じメソッド呼び出しで複数のテーブルに行を挿入する場合に役立ちます。また、一般に、プロセス境界をまたいで一連のオペレーションをトランザクションとして実行する場合に、アトミック オペレーションと呼ばれます。

バッチモードでプロバイダにアクセスするには、ContentProviderOperation オブジェクトの配列を作成し、ContentResolver.applyBatch() を使用してコンテンツ プロバイダにディスパッチします。このメソッドには、特定のコンテンツ URI ではなく、コンテンツ プロバイダのオーソリティを渡します。

これにより、配列内の各 ContentProviderOperation オブジェクトが別々のテーブルに対して機能するようになります。ContentResolver.applyBatch() を呼び出すと、結果の配列が返されます。

ContactsContract.RawContacts コントラクト クラスの説明には、バッチ挿入のコード スニペットが記載されています。

インテントを使用したデータアクセス

インテントを使用すると、コンテンツ プロバイダに間接的にアクセスできます。アプリにアクセス権限がない場合でも、ユーザーがプロバイダ内のデータにアクセスできるようにするには、権限のあるアプリから結果のインテントを取得するか、権限を持つアプリを有効にして、ユーザーにそのアプリでの作業を許可します。

一時的な権限でアクセス権を取得する

適切なアクセス権限を持っていなくても、権限のあるアプリにインテントを送信し、URI 権限を含む結果インテントを受け取ることで、コンテンツ プロバイダのデータにアクセスできます。これは特定のコンテンツ URI の権限であり、権限を受け取るアクティビティが終了するまで効力を持ちます。永続的な権限を持つアプリは、結果のインテントにフラグを設定することで、一時的な権限を付与します。

注: これらのフラグは、コンテンツ URI にオーソリティが含まれているプロバイダへの全般的な読み取りアクセス権または書き込みアクセス権を付与するものではありません。アクセスは URI 自体に限定されます。

別のアプリにコンテンツ URI を送信する場合は、これらのフラグを少なくとも 1 つ含めます。このフラグは、インテントを受け取り、Android 11(API レベル 30)以降をターゲットとするアプリに次の機能を提供します。

  • インテントに含まれるフラグに応じて、コンテンツ URI が表すデータに対して読み取りまたは書き込みを行います。
  • URI オーソリティに一致するコンテンツ プロバイダを含むアプリでパッケージの公開設定を取得する。インテントを送信するアプリとコンテンツ プロバイダを含むアプリが、2 つの異なるアプリである場合があります。

プロバイダは、<provider> 要素の android:grantUriPermissions 属性と <provider> 要素の <grant-uri-permission> 子要素を使用して、コンテンツ URI の URI 権限をマニフェストで定義します。URI 権限メカニズムについて詳しくは、Android での権限ガイドをご覧ください。

たとえば、READ_CONTACTS 権限がない場合でも、連絡先プロバイダの連絡先データを取得できます。これは連絡先の誕生日にグリーティング メールを送信するアプリで利用できます。ユーザーのすべての連絡先とすべての情報にアクセスできる READ_CONTACTS をリクエストする代わりに、アプリケーションで使用する連絡先をユーザーが制御できるようにします。これを行うには、次の手順を行います。

  1. アプリで、メソッド startActivityForResult() を使用して、アクション ACTION_PICK と「連絡先」MIME タイプ CONTENT_ITEM_TYPE を含むインテントを送信します。
  2. このインテントは、ユーザーアプリの「選択」アクティビティのインテント フィルタと一致するため、アクティビティがフォアグラウンドに移動します。
  3. 選択アクティビティで、更新する連絡先をユーザーが選択します。この場合、選択アクティビティは setResult(resultcode, intent) を呼び出して、アプリに返すインテントを設定します。このインテントには、ユーザーが選択した連絡先のコンテンツ URI と、「エクストラ」フラグ FLAG_GRANT_READ_URI_PERMISSION が含まれます。これらのフラグにより、コンテンツ URI が指す連絡先のデータを読み取るための URI 権限がアプリに付与されます。その後、選択アクティビティは finish() を呼び出して、制御をアプリに返します。
  4. アクティビティがフォアグラウンドに戻り、システムがアクティビティの onActivityResult() メソッドを呼び出します。このメソッドは、連絡帳アプリの選択アクティビティによって作成された結果のインテントを受け取ります。
  5. 結果のインテントのコンテンツ URI を使用すると、マニフェストで永続的な読み取りアクセス権限をプロバイダにリクエストしていなくても、連絡先プロバイダから連絡先データを読み取ることができます。これで連絡先の誕生日情報またはメールアドレスを取得して、挨拶メッセージを送信できます。

別のアプリケーションを使用

アクセス権限のないデータをユーザーが変更できるようにするもう 1 つの方法は、権限のあるアプリを有効にして、ユーザーがそのアプリで作業できるようにすることです。

たとえば、カレンダー アプリは、アプリの挿入 UI をアクティブにできる ACTION_INSERT インテントを受け付けます。このインテントに「追加」のデータを渡すと、アプリはこのデータを使用して UI を事前入力します。定期的な予定は構文が複雑であるため、カレンダー プロバイダにイベントを挿入する場合は、ACTION_INSERT でカレンダー アプリを有効にしてからユーザーにイベントを挿入してもらうことをおすすめします。

ヘルパーアプリを使用してデータを表示する

アプリにアクセス権限がある場合でも、インテントを使用して別のアプリにデータを表示することがあります。たとえば、カレンダー アプリは、特定の日付やイベントを表示する ACTION_VIEW インテントを受け付けます。これにより、独自の UI を作成しなくても、カレンダーの情報を表示できます。この機能について詳しくは、カレンダー プロバイダの概要をご覧ください。

インテントを送信するアプリは、プロバイダに関連付けられているアプリでなくてもかまいません。たとえば、連絡先プロバイダから連絡先を取得して、連絡先の画像のコンテンツ URI を含む ACTION_VIEW インテントを画像ビューアに送信できます。

コントラクト クラス

コントラクト クラスは、アプリでコンテンツ URI、列名、インテント アクション、コンテンツ プロバイダのその他の機能を利用する際に役立つ定数を定義します。コントラクト クラスは、プロバイダに自動的に含まれません。プロバイダのデベロッパーは、これらを定義して他のデベロッパーが利用できるようにする必要があります。Android プラットフォームに含まれる多くプロバイダでは、対応するコントラクト クラスがパッケージ android.provider にあります。

たとえば単語リスト プロバイダには、コンテンツ URI と列名の定数を含むコントラクト クラス UserDictionary があります。Words テーブルのコンテンツ URI は、定数 UserDictionary.Words.CONTENT_URI で定義されます。UserDictionary.Words クラスには、このガイドのサンプル スニペットで使用される列名の定数も含まれています。たとえば、クエリの射影は次のように定義できます。

Kotlin

val projection : Array<String> = arrayOf(
        UserDictionary.Words._ID,
        UserDictionary.Words.WORD,
        UserDictionary.Words.LOCALE
)

Java

String[] projection =
{
    UserDictionary.Words._ID,
    UserDictionary.Words.WORD,
    UserDictionary.Words.LOCALE
};

別のコントラクト クラスは、連絡先プロバイダの ContactsContract です。このクラスのリファレンス ドキュメントには、コード スニペットの例が記載されています。サブクラスの 1 つである ContactsContract.Intents.Insert は、インテントとインテント データの定数を含むコントラクト クラスです。

MIME タイプのリファレンス

コンテンツ プロバイダは、標準の MIME メディアタイプ、カスタムの MIME タイプ文字列、またはその両方を返すことができます。

MIME タイプの形式は次のとおりです。

type/subtype

たとえば、よく利用される MIME タイプ text/html には、text タイプと html サブタイプがあります。プロバイダが URI に対してこのタイプを返した場合、その URI を使用するクエリは HTML タグを含むテキストを返します。

ベンダー固有の MIME タイプとも呼ばれるカスタム MIME タイプ文字列は、より複雑な typesubtype の値を持ちます。複数行の場合、型の値は常に以下のようになります。

vnd.android.cursor.dir

単一行の場合、型の値は常に以下のようになります。

vnd.android.cursor.item

subtype はプロバイダ固有です。通常、Android 組み込みプロバイダは単純なサブタイプを使用します。たとえば連絡先アプリで電話番号の行を作成すると、その行に次の MIME タイプが設定されます。

vnd.android.cursor.item/phone_v2

サブタイプの値は phone_v2 です。

他のプロバイダ デベロッパーは、プロバイダのオーソリティとテーブル名に基づいて、独自のパターンのサブタイプを作成できます。たとえば、列車の時刻表を含むプロバイダについて考えてみます。プロバイダのオーソリティは com.example.trains であり、テーブル Line1、Line2、Line3 が含まれています。テーブル Line1 に関する次のコンテンツ URI のレスポンス。

content://com.example.trains/Line1

プロバイダから次の MIME タイプが返されます。

vnd.android.cursor.dir/vnd.example.line1

次のテーブル Line2 の行 5 のコンテンツ URI に対するレスポンス:

content://com.example.trains/Line2/5

プロバイダから次の MIME タイプが返されます。

vnd.android.cursor.item/vnd.example.line2

ほとんどのコンテンツ プロバイダは、使用する MIME タイプのコントラクト クラス定数を定義します。たとえば連絡先プロバイダのコントラクト クラス ContactsContract.RawContacts は、1 つの未加工連絡先行の MIME タイプに定数 CONTENT_ITEM_TYPE を定義します。

1 行のコンテンツ URI については、コンテンツ URI をご覧ください。