Google は、黒人コミュニティに対する人種平等の促進に取り組んでいます。取り組みを見る

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

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

コンテンツ プロバイダは通常、2 つのシナリオ(既存のコンテンツ プロバイダにアクセスするためのコードを別のアプリに実装する、または他のアプリとデータを共有するために新しいコンテンツ プロバイダを作成する)のいずれかで使用します。このトピックでは、既存のコンテンツ プロバイダを使用する場合の基本について説明します。独自のアプリにコンテンツ プロバイダを実装する方法の詳細については、コンテンツ プロバイダの作成をご覧ください。

このトピックで説明する内容は次のとおりです。

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

概要

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

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

  • アプリデータへのアクセスを他のアプリと共有する
  • ウィジェットにデータを送信する
  • 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 の各行は、標準の単語リストにない単語のインスタンスを表しています。各列は、該当する単語のデータ(最初に見つかった言語 / 地域など)を表しています。列の見出しは、プロバイダに格納される列の名前です。行の言語 / 地域を調べるには、locale 列を参照します。このプロバイダの場合、_ID 列が「主キー」の列として機能し、プロバイダはこの列を自動的に管理します。

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

Kotlin

// Queries the user dictionary 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 user dictionary 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

query(Uri,projection,selection,selectionArgs,sortOrder) の引数と SQL SELECT ステートメントの対応関係を表 2 に示します。

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

query() 引数 SELECT キーワード / パラメータ 備考
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

ここで、user_dictionary 文字列はプロバイダの認証局、words 文字列はテーブルのパスです。文字列 content://スキーム)は常に存在し、これをコンテンツ URI として識別します。

多くのプロバイダでは、ID 値を URI の末尾に追加することで、テーブル内の 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 値を使用します。

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

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

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

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

プロバイダからデータを取得する基本的な手順は次のとおりです。

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

読み取りアクセス権限をリクエストする

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

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

プロバイダにアクセスする際の権限の役割について詳しくは、コンテンツ プロバイダの権限のセクションをご覧ください。

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

クエリを作成する

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

Kotlin

// A "projection" defines the columns that will be 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 will be 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 String array to contain the selection arguments.
 */
private lateinit var selectionArgs: Array<String>

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

// Remember to 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 may want to call android.util.Log.e() to log this error.
         *
         */
    }
    0 -> {
        /*
         * Insert code here to notify the user that the search was unsuccessful. This isn't
         * necessarily an error. You may 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 will return 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 may want to
     * 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 was unsuccessful. This isn't necessarily
     * an error. You may 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

}

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

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

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

悪意のある入力から保護する

コンテンツ プロバイダが管理するデータが SQL データベース内にある場合、外部の信頼されていないデータを未処理の 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 ステートメントとして扱われるため、プロバイダは基となる SQLite データベース内のすべてのテーブルを消去する可能性があります(プロバイダに SQL インジェクション試行の検出が設定されていない場合)。

この問題を回避するには、? を置換可能なパラメータとして使用する選択句と、選択引数の個別の配列を使用します。こうすると、ユーザー入力は 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 will 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 will 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 may 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 will 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 may 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 will 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 user dictionary 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 user dictionary 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 user dictionary 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 user dictionary content URI
    updateValues,                      // the columns to update
    selectionClause,                   // the column to select on
    selectionArgs                      // the value to compare to
);

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

データを削除する

行の削除は、行データの取得に似ています。削除する行の選択基準を指定すると、削除した行数がクライアント メソッドから返されます。次のスニペットは、appid が「user」に一致する行を削除します。削除した行数がメソッドから返されます。

Kotlin

// Defines selection criteria for the rows you want to delete
val selectionClause = "${UserDictionary.Words.LOCALE} 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 user dictionary 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 user dictionary content URI
    selectionClause,                   // the column to select on
    selectionArgs                      // the value to compare to
);

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

プロバイダのデータ型

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

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

データ型は他にも、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() に適用できます。
  • 非同期クエリ: クエリは個別のスレッドで行う必要があります。これを行う方法の 1 つとして、CursorLoader オブジェクトの使用が挙げられます。ローダのガイドに、この方法の例が記載されています。
  • インテントによるデータアクセス: プロバイダにインテントを直接送信することはできませんが、プロバイダのアプリにはインテントを送信できます。これは通常、プロバイダのデータの変更に最適です。

バッチアクセスとインテントによる変更については、次のセクションで説明します。

バッチアクセス

多数の行を挿入する場合、または同じメソッド呼び出しで複数のテーブルに行を挿入する場合は、プロバイダへのバッチアクセスを使用すると便利です。また一般的に、プロセスの境界を越えて一連の操作をトランザクションとして行う場合に便利です(アトミック操作)。

「バッチモード」でプロバイダにアクセスするには、ContentProviderOperation オブジェクトの配列を作成し、ContentResolver.applyBatch() を使用してコンテンツ プロバイダにディスパッチします。このメソッドには、特定のコンテンツ URI ではなく、コンテンツ プロバイダの認証局を渡します。これにより、配列内の各 ContentProviderOperation オブジェクトが別々のテーブルに対して機能するようになります。ContentResolver.applyBatch() を呼び出すと、結果の配列が返されます。

ContactsContract.RawContacts コントラクト クラスの説明には、バッチ挿入を示すコード スニペットが含まれています。連絡先管理ツールのサンプルアプリでは、ContactAdder.java ソースファイルにバッチアクセスの例が含まれています。

インテントによるデータアクセス

インテントを使用すると、コンテンツ プロバイダに間接的にアクセスできます。アプリにアクセス権限がない場合でも、権限のあるアプリから結果のインテントを取得するか、権限のあるアプリをアクティベートしてユーザーに使用させることで、ユーザーはプロバイダのデータにアクセスできます。

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

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

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

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

たとえば、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 を使用すると、マニフェストで永続的な読み取りアクセス権限をプロバイダにリクエストしていなくても、連絡先プロバイダから連絡先データを読み取ることができます。その後、連絡先の誕生日情報またはメールアドレスを取得し、グリーティング メールを送信できます。

別のアプリを使用する

アクセス権限のないデータをユーザーが簡単に変更できるようにするには、アクセス権限のあるアプリを有効化し、ユーザーにそのアプリを使用してもらうという方法があります。

たとえばカレンダー アプリは、アプリの挿入 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 タイプ文字列には、より複雑な type 値と subtype 値があります。type 値は常に

vnd.android.cursor.dir

(複数行の場合)、または

vnd.android.cursor.item

(1 行の場合)となります。

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

vnd.android.cursor.item/phone_v2

サブタイプの値は単に phone_v2 となっています。

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

content://com.example.trains/Line1

(テーブル Line1 の場合)、プロバイダは次の MIME タイプを返します。

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

次のコンテンツ URI に対して

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

(テーブル Line2 の行 5 の場合)、プロバイダは次の MIME タイプを返します。

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

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

各行のコンテンツ URI については、コンテンツ URI のセクションをご覧ください。