コンテンツ プロバイダは、データのセントラル リポジトリへのアクセスを管理します。プロバイダは Android アプリの一部であり、多くの場合、データを操作する独自の UI を提供します。ただし、コンテンツ プロバイダは主に、プロバイダ クライアント オブジェクトを使用してプロバイダにアクセスする他のアプリによって使用されます。プロバイダとプロバイダ クライアントは、連携してデータに対する一貫した標準インターフェースを提供し、プロセス間通信と安全なデータアクセスも処理します。
通常、コンテンツ プロバイダを使用する状況は 2 つあります。1 つは別のアプリで既存のコンテンツ プロバイダにアクセスするコードを実装する場合、もう 1 つはアプリで新しいコンテンツ プロバイダを作成して他のアプリとデータを共有する場合です。
このページでは、既存のコンテンツ プロバイダを使用する際の基本について説明します。独自のアプリにコンテンツ プロバイダを実装する方法については、 コンテンツ プロバイダを作成するをご覧ください。
このトピックでは、次のことについて説明します。
- コンテンツ プロバイダの仕組み。
- コンテンツ プロバイダからのデータの取得に使用する API。
- コンテンツ プロバイダへのデータの挿入、データの更新、または削除に使用する API。
- プロバイダでの作業に役立つその他の API 機能。
概要
コンテンツ プロバイダは外部アプリに対し、リレーショナル データベースのテーブルに似た 1 つ以上のテーブルとしてデータを提供します。行はプロバイダが収集するなんらかのデータのインスタンスを表し、行の各列はインスタンスに対して収集した個々のデータを表します。
コンテンツ プロバイダは、さまざまな API やコンポーネントについて、アプリ内のデータ ストレージ レイヤへのアクセスを調整します。図 1 に示すように、次のようなものがあります。
- アプリデータへのアクセスを他のアプリと共有する
- ウィジェットにデータを送信する
SearchRecentSuggestionsProvider
を使用して、検索フレームワークを介してアプリのカスタム検索候補を返すAbstractThreadedSyncAdapter
の実装を使用してアプリデータをサーバーと同期するCursorLoader
を使用して UI にデータを読み込む
プロバイダにアクセスする
コンテンツ プロバイダのデータにアクセスする場合は、アプリの Context
の ContentResolver
オブジェクトを使用し、クライアントとしてプロバイダと通信します。ContentResolver
オブジェクトは、ContentProvider
を実装するクラスのインスタンスであるプロバイダ オブジェクトと通信します。
プロバイダ オブジェクトはクライアントからデータ リクエストを受け取り、リクエストされたアクションを実施して、結果を返します。このオブジェクトには、プロバイダ オブジェクト(ContentProvider
の具象サブクラスのいずれかのインスタンス)内の同じ名前のメソッドを呼び出すメソッドがあります。ContentResolver
メソッドは、永続ストレージの基本的な「CRUD」(作成、取得、更新、削除)機能を提供します。
UI から ContentProvider
にアクセスするための一般的なパターンでは、CursorLoader
を使用してバックグラウンドで非同期クエリを実行します。UI の Activity
または Fragment
がクエリに対して CursorLoader
を呼び出し、それが ContentResolver
を使用して ContentProvider
を取得します。
これにより、ユーザーはクエリの実行中も UI を引き続き使用できます。このパターンには、図 2 に示すようにさまざまなオブジェクトとのやり取りと、基となるストレージ メカニズムが含まれます。
注: アプリがプロバイダにアクセスするには、通常、マニフェスト ファイルで特定の権限をリクエストする必要があります。この開発パターンについて詳しくは、コンテンツ プロバイダの権限のセクションをご覧ください。
Android プラットフォームの組み込みプロバイダの 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 ステートメントの一致を示しています。
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 行にアクセスできます。たとえば、_ID
が 4
の行を単語リスト プロバイダから取得するには、次のコンテンツ 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
クラスを使用できます。クラスについて詳しくは、
ローダのガイドをご覧ください。また、コード行はスニペットのみです。完全なアプリが表示されるわけではありません。
プロバイダからデータを取得するには、次の基本的な手順を行います。
- プロバイダの読み取りアクセス権をリクエストします。
- プロバイダにクエリを送信するコードを定義します。
読み取りアクセス権のリクエスト
プロバイダからデータを取得するには、アプリでプロバイダに対する読み取りアクセス権限が必要です。この権限は実行時にリクエストできません。代わりに、<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()
で適用できます。 -
非同期クエリ: 別のスレッドでクエリを実行します。
CursorLoader
オブジェクトを使用できます。ローダのガイドに、この方法の例が記載されています。 - インテントを使用したデータアクセス: プロバイダにインテントを直接送信することはできませんが、プロバイダのアプリには、通常はプロバイダのデータを変更するのに最適な機能としてインテントを送信できます。
以下のセクションでは、インテントを使用したバッチアクセスと変更について説明します。
バッチアクセス
プロバイダへのバッチアクセスは、大量の行を挿入する場合や、同じメソッド呼び出しで複数のテーブルに行を挿入する場合に役立ちます。また、一般に、プロセス境界をまたいで一連のオペレーションをトランザクションとして実行する場合に、アトミック オペレーションと呼ばれます。
バッチモードでプロバイダにアクセスするには、ContentProviderOperation
オブジェクトの配列を作成し、ContentResolver.applyBatch()
を使用してコンテンツ プロバイダにディスパッチします。このメソッドには、特定のコンテンツ URI ではなく、コンテンツ プロバイダのオーソリティを渡します。
これにより、配列内の各 ContentProviderOperation
オブジェクトが別々のテーブルに対して機能するようになります。ContentResolver.applyBatch()
を呼び出すと、結果の配列が返されます。
ContactsContract.RawContacts
コントラクト クラスの説明には、バッチ挿入のコード スニペットが記載されています。
インテントを使用したデータアクセス
インテントを使用すると、コンテンツ プロバイダに間接的にアクセスできます。アプリにアクセス権限がない場合でも、ユーザーがプロバイダ内のデータにアクセスできるようにするには、権限のあるアプリから結果のインテントを取得するか、権限を持つアプリを有効にして、ユーザーにそのアプリでの作業を許可します。
一時的な権限でアクセス権を取得する
適切なアクセス権限を持っていなくても、権限のあるアプリにインテントを送信し、URI 権限を含む結果インテントを受け取ることで、コンテンツ プロバイダのデータにアクセスできます。これは特定のコンテンツ URI の権限であり、権限を受け取るアクティビティが終了するまで効力を持ちます。永続的な権限を持つアプリは、結果のインテントにフラグを設定することで、一時的な権限を付与します。
-
読み取り権限:
FLAG_GRANT_READ_URI_PERMISSION
-
書き込み権限:
FLAG_GRANT_WRITE_URI_PERMISSION
注: これらのフラグは、コンテンツ 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
をリクエストする代わりに、アプリケーションで使用する連絡先をユーザーが制御できるようにします。これを行うには、次の手順を行います。
-
アプリで、メソッド
startActivityForResult()
を使用して、アクションACTION_PICK
と「連絡先」MIME タイプCONTENT_ITEM_TYPE
を含むインテントを送信します。 - このインテントは、ユーザーアプリの「選択」アクティビティのインテント フィルタと一致するため、アクティビティがフォアグラウンドに移動します。
-
選択アクティビティで、更新する連絡先をユーザーが選択します。この場合、選択アクティビティは
setResult(resultcode, intent)
を呼び出して、アプリに返すインテントを設定します。このインテントには、ユーザーが選択した連絡先のコンテンツ URI と、「エクストラ」フラグFLAG_GRANT_READ_URI_PERMISSION
が含まれます。これらのフラグにより、コンテンツ URI が指す連絡先のデータを読み取るための URI 権限がアプリに付与されます。その後、選択アクティビティはfinish()
を呼び出して、制御をアプリに返します。 -
アクティビティがフォアグラウンドに戻り、システムがアクティビティの
onActivityResult()
メソッドを呼び出します。このメソッドは、連絡帳アプリの選択アクティビティによって作成された結果のインテントを受け取ります。 - 結果のインテントのコンテンツ 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 タイプ文字列は、より複雑な type と subtype の値を持ちます。複数行の場合、型の値は常に以下のようになります。
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 をご覧ください。