コンテンツ プロバイダは、データの中央リポジトリへのアクセスを管理します。プロバイダは Android アプリの一部であり、通常、データを処理するための独自の UI を備えています。ただし、コンテンツ プロバイダは主に他のアプリでの使用を意図したものです。アプリはプロバイダ クライアント オブジェクトを使用してプロバイダにアクセスします。さらに、プロバイダとプロバイダ クライアントにはデータを扱うための一貫性のある標準のインターフェースが備わっており、プロセス間の通信や安全なデータアクセスを処理します。
通常、コンテンツ プロバイダを使用するのは次の 2 つのシナリオのいずれかです。別のアプリに既存のコンテンツ プロバイダにアクセスするためのコードを実装するシナリオや、他のアプリとデータを共有するために自分のアプリに新しいコンテンツ プロバイダを作成するシナリオです。このトピックでは、既存のコンテンツ プロバイダを使用する場合の基本について説明します。独自のアプリにコンテンツ プロバイダを実装する方法の詳細については、コンテンツ プロバイダの作成をご覧ください。
このトピックでは、次の項目について説明します。
- コンテンツ プロバイダの仕組み。
- コンテンツ プロバイダからのデータの取得に使用する API。
- コンテンツ プロバイダへのデータの挿入、データの更新や削除に使用する API。
- プロバイダでの作業に役立つその他の API 機能。
概要
外部アプリでは、コンテンツ プロバイダのデータは、リレーショナル データベースで使用する表と同様に、1 つ以上の表として表示されます。行はプロバイダが収集するデータのなんらかのタイプのインスタンスを表しており、行内のそれぞれの列はインスタンスに対して収集した個々のデータを表しています。
コンテンツ プロバイダは、図 1 に示すように、さまざまな API やコンポーネントについてアプリ内のデータ ストレージ レイヤーへのアクセスを調整します。
- 自分のアプリ データへのアクセスを他のアプリと共有する
- ウィジェットにデータを送信する
SearchRecentSuggestionsProvider
を使用して、検索フレームワークを介してアプリのカスタムの検索候補を返すAbstractThreadedSyncAdapter
の実装を使用してアプリ データをサーバーと同期するCursorLoader
を使用して UI にデータを読み込む

図 1. コンテンツ プロバイダと他のコンポーネントの関係
プロバイダにアクセスする
コンテンツ プロバイダのデータにアクセスする場合は、アプリの Context
の ContentResolver
オブジェクトを使用し、クライアントとしてプロバイダとやり取りします。ContentResolver
オブジェクトは、ContentProvider
を実装するクラスのインスタンスである、プロバイダ オブジェクトとやり取りします。プロバイダ オブジェクトはクライアントからのデータ要求を受け取り、要求されたアクションを実行し、結果を返します。このオブジェクトには、プロバイダ オブジェクトの同名のメソッドを呼び出すメソッドが備わっています。これは、ContentProvider
の具象サブクラスのいずれかのインスタンスになります。ContentResolver
メソッドには、永続ストレージの基本的な「CRUD」(作成、取得、更新、削除)機能が備わっています。
UI から ContentProvider
にアクセスするための一般的なパターンは、CursorLoader
を使用してバックグラウンドで非同期クエリを実行することです。UI 内の Activity
または Fragment
は、クエリに対して CursorLoader
を呼び出します。これにより、ContentResolver
を使用して ContentProvider
を取得します。こうして、クエリの実行中に、ユーザーが引き続き UI を使用できるようになります。このパターンには、図 2 に示すようにさまざまなオブジェクトのやり取り、基盤となるストレージ メカニズムが含まれます。

図 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
表 2 は、query(Uri,projection,selection,selectionArgs,sortOrder)
の引数と SQL SELECT 文の対応関係を示しています。
表 2. SQL クエリと比較した Query()
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 であることを示します。
多くのプロバイダでは、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 値を使用します。
注:Uri
クラスと Uri.Builder
クラスには、正しい形式の URI オブジェクトを文字列から作成するための、便利なメソッドが用意されています。ContentUris
クラスには、URI に ID 値を付加するための便利なメソッドが用意されています。前述のスニペットは withAppendedId()
を使用して、UserDictionary コンテンツ URI に ID を付加しています。
プロバイダからデータを取得する
このセクションでは、単語リスト プロバイダの例を使い、プロバイダからデータを取得する方法を説明します。
わかりやすくするために、このセクションのコード スニペットは「UI スレッド」の ContentResolver.query()
を呼び出しています。ただし、実際のコードでは、個別のスレッドで非同期にクエリを実行する必要があります。この操作は、CursorLoader
クラスを使用して行うこともできます。このクラスの詳細については、ローダに関するガイドをご覧ください。さらに、コードの行はスニペットのみであり、完全なアプリにはなっていません。
プロバイダからデータを取得するには、次の基本的な手順に従います。
- プロバイダの読み取りアクセスを要求します。
- プロバイダにクエリを送信するコードを定義します。
読み取りアクセス パーミッションを要求する
プロバイダからデータを取得するには、アプリからプロバイダの「読み取りアクセス パーミッション」を要求します。実行時にはこのパーミッションを要求できません。その代わりに、<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 整数(long)
- 浮動小数点
- 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
コントラクト クラスの説明には、バッチ挿入を示したコード スニペットが記載されています。Contact Manager のサンプル アプリでは、ContactAdder.java
ソースファイルでのバッチアクセスの例が紹介されています。
インテント経由のデータアクセス
インテントを使用すれば、コンテンツ プロバイダに間接的にアクセスできます。アプリにアクセス パーミッションがない場合でも、パーミッションを持つアプリから結果のインテントを取得したり、パーミッションを持つアプリをアクティベートしてそのアプリをユーザーに使用させたりすることで、ユーザーがプロバイダのデータにアクセスできるようになります。
一時的なパーミッションによりアクセスを取得する
適切なアクセス パーミッションがない場合でも、パーミッションを持つアプリにインテントを送信し、「URI」パーミッションを含む結果のインテントを受け取ることで、コンテンツ プロバイダのデータにアクセスできます。これらのパーミッションは特定のコンテンツ URI のパーミッションであり、パーミッションを受け取ったアクティビティが終了するまで効力を持ちます。永続的なパーミッションを持つアプリは、次のように結果のインテントにフラグを設定し、一時的なパーミッションを付与します。
-
読み取りパーミッション:
FLAG_GRANT_READ_URI_PERMISSION
-
書き込みパーミッション:
FLAG_GRANT_WRITE_URI_PERMISSION
注:これらのフラグは、認証局がコンテンツ URI に含まれるプロバイダへの全般的な読み取りアクセスと書き込みアクセスを付与するものではありません。アクセスは URI 自身に限定されます。
プロバイダは、<provider>
要素の android:grantUriPermission
属性や、<provider>
要素の <grant-uri-permission>
子要素を使用して、コンテンツ URI の URI パーミッションを自身のマニフェストで定義します。URI パーミッションのメカニズムの詳細については、パーミッションの概要に関するガイドをご覧ください。
たとえば、READ_CONTACTS
パーミッションを持たない場合でも、連絡先プロバイダの連絡先のデータを取得できます。連絡先の誕生日にグリーティング カードをオンラインで送信するアプリなどにこのメカニズムを利用できます。ユーザーのすべての連絡先やそのすべての情報へのアクセス権を付与する READ_CONTACTS
を要求する代わりに、アプリで使用する連絡先をユーザーが制御できるように設定することもできます。そのためには、次の手順を使用します。
-
アプリが、メソッド
startActivityForResult()
を使用して、アクションACTION_PICK
と「連絡先」MIME タイプCONTENT_ITEM_TYPE
を含むインテントを送信します。 - このインテントは連絡帳アプリの「selection」アクティビティのインテント フィルタに一致するため、アクティビティがフォアグラウンドに移動します。
-
selection アクティビティでは、更新する連絡先をユーザーが選択します。この操作が行われると、selection アクティビティが
setResult(resultcode, intent)
を呼び出し、アプリに戻すインテントを設定します。インテントには、ユーザーが選択した連絡先のコンテンツ URI と、「エクストラ」フラグFLAG_GRANT_READ_URI_PERMISSION
が含まれます。これらのフラグがあることで、コンテンツ URI がポイントする連絡先のデータを読み取るための URI パーミッションがアプリに付与されます。その後、selection アクティビティによって、アプリに制御を返すためのfinish()
が呼び出されます。 -
アクティビティがフォアグラウンドに戻り、システムがアクティビティの
onActivityResult()
メソッドを呼び出します。このメソッドは、連絡帳アプリの selection アクティビティによって作成された結果のインテントを受け取ります。 - 結果のインテントのコンテンツ 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
となり、1 つの行の場合は
vnd.android.cursor.item
となります。
subtype はプロバイダ固有の値になります。通常、Android 組み込みプロバイダは単純な subtype を使用します。たとえば、連絡先アプリが電話番号の行を作成すると、行には次の MIME タイプが設定されます。
vnd.android.cursor.item/phone_v2
subtype の値は単純に phone_v2
となっていることがわかります。
他のプロバイダ デベロッパーは、プロバイダの認証局と表名に基づいて独自のパターンの subtype を作成できます。たとえば、列車の時刻表を含むプロバイダについて考えてみます。プロバイダの認証局は 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 のセクションをご覧ください。