コンテンツ プロバイダは、データのセントラル リポジトリへのアクセスを管理します。プロバイダは Android アプリの一部であり、多くの場合、データを操作する独自の UI を提供します。ただし、コンテンツ プロバイダは主に、プロバイダ クライアント オブジェクトを使用してプロバイダにアクセスする他のアプリで使用されます。また、プロバイダとプロバイダ クライアントはデータを扱うための一貫した標準的なインターフェースを提供し、プロセス間の通信と安全なデータアクセスも処理します。
コンテンツ プロバイダは通常、2 つのシナリオ(既存のコンテンツ プロバイダにアクセスするためのコードを別のアプリに実装する、または他のアプリとデータを共有するために新しいコンテンツ プロバイダを作成する)のいずれかで使用します。
このページでは、既存のコンテンツ プロバイダを使用する場合の基本について説明します。独自のアプリにコンテンツ プロバイダを実装する方法については、 コンテンツ プロバイダを作成するをご覧ください。
このトピックで説明する内容は次のとおりです。
- コンテンツ プロバイダの仕組み。
- コンテンツ プロバイダからのデータの取得に使用する 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 の各行は、標準の単語リストにない単語のインスタンスを表しています。各列は、該当する単語のデータの一部(その単語が最初に見つかったロケールなど)を表しています。列の見出しは、プロバイダに格納される列の名前です。たとえば、ある行の言語 / 地域を参照するには、その 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
query(Uri,projection,selection,selectionArgs,sortOrder)
の引数と SQL SELECT ステートメントの対応関係を表 2 に示します。
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
content://
文字列は常に存在するスキームで、コンテンツ URI として識別します。user_dictionary
文字列はプロバイダの認証局です。words
文字列はテーブルのパスです。
多くのプロバイダでは、ID 値を URI の末尾に追加することで、テーブル内の 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 値を使用します。
注: 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 データベースにある場合、信頼できない外部のデータが未加工の 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 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
という名前の列を含める必要があります。このため、前述のクエリは Words
テーブルの _ID
列を取得しますが、ListView
には表示されません。また、この制限があることから、ほとんどのプロバイダは各テーブルに _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()
メソッドも使用しています。
クエリ結果リソースを解放する
Cursor
オブジェクトが不要になった場合は、関連付けられたリソースがすぐに解放されるように、閉じる必要があります。これは、close()
を呼び出すか、Java プログラミング言語の try-with-resources
ステートメントまたは Kotlin プログラミング言語の use()
関数を使用して行えます。
コンテンツ プロバイダの権限
プロバイダのアプリでは、他のアプリがプロバイダのデータにアクセスするために必要な権限を指定できます。これらの権限により、ユーザーはアプリがアクセスしようとしているデータを把握できます。他のアプリは、プロバイダの要件に基づき、プロバイダにアクセスするために必要な権限をリクエストします。エンドユーザーがアプリをインストールするとき、リクエストされた権限が表示されます。
プロバイダのアプリで権限が指定されていない場合、他のアプリはプロバイダのデータにアクセスできません(プロバイダがエクスポートされている場合を除く)。また、プロバイダのアプリのコンポーネントは、指定された権限に関係なく、常に完全な読み取りと書き込みのアクセス権を持ちます。
単語リスト プロバイダがデータを取得するには、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)
- 浮動小数点
- 長精度浮動小数点(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()
に適用できます。 -
非同期クエリ: クエリは個別のスレッドで実行します。
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 値があります。複数行の場合、type 値は常に次のようになります。
vnd.android.cursor.dir
1 行の場合、type 値は常に次のようになります。
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
を定義します。
単一行のコンテンツ URI については、コンテンツ URI セクションをご覧ください。