內容供應器會管理資料中央存放區的存取權。提供者 是 Android 應用程式的一部分,通常會提供自己的 UI 來 實體媒介包括儲存空間陣列 傳統硬碟、磁帶和 USB 隨身碟等不過,內容供應器主要供其他地方使用 應用程式,而會使用提供者用戶端物件存取供應器。集結各供應商的力量 和供應商用戶端提供一致的標準介面, 處理序間通訊及保護資料存取。
一般來說,與內容供應器合作的方式有兩種: 程式碼來存取其他應用程式中現有的內容供應器,或建立 在應用程式中建立新的內容供應器,以便與其他應用程式共用資料。
這個頁面 介紹與現有內容供應者合作的基本概念瞭解如何導入 應用程式的內容供應器,請參閱 建立內容供應器。
本主題會說明下列內容:
- 內容供應器的運作方式。
- 您用於從內容供應器擷取資料的 API。
- 您在內容供應器中插入、更新或刪除資料的 API。
- 其他有助於與供應商合作的 API 功能。
總覽
內容供應器會以一或多個資料表形式,將資料提供給外部應用程式 與關聯資料庫中的資料表相似一列代表某種類型的執行個體 代表供應商收集的資料,而資料列中的每一欄都代表 系統針對執行個體收集的資料
內容供應器會協調存取應用程式中的資料儲存層, 可用的 API 和元件數量如圖 1 所示,其中包括:
- 與其他應用程式分享您的應用程式資料存取權
- 將資料傳送至小工具
- 透過搜尋為應用程式傳回自訂搜尋建議
使用
SearchRecentSuggestionsProvider
的架構 - 使用
AbstractThreadedSyncAdapter
- 使用
CursorLoader
載入 UI 中的資料
![內容供應器和其他元件之間的關係。](https://developer.android.com/static/guide/topics/providers/images/content-provider-tech-stack.png?authuser=0000&hl=zh-tw)
圖 1. 內容供應器和其他元件之間的關係。
存取提供者
當您要存取內容供應器中的資料時,可以使用
應用程式的 ContentResolver
物件
Context
:以用戶端形式與供應商通訊。
ContentResolver
物件會與供應器物件通訊
實作 ContentProvider
的類別例項。
提供者
物件接收用戶端的資料要求、執行所要求的動作,然後傳回
也就是預測結果此物件含有的方法,可在提供者物件中呼叫名稱相同的方法。
ContentProvider
具體子類別的一個執行個體。
ContentResolver
方法可提供基本
「CRUD」(建立、擷取、更新及刪除) 永久儲存空間的功能。
透過 UI 存取 ContentProvider
的常見模式會使用
CursorLoader
:在背景執行非同步查詢。
UI 中的 Activity
或 Fragment
會呼叫
CursorLoader
加進查詢,進而取得
使用 ContentResolver
的 ContentProvider
。
這樣一來,使用者在查詢執行期間仍可繼續使用 UI。這個 模式涉及多種不同物件的互動情形,以及基礎模型的 儲存機制,如圖 2 所示。
![ContentProvider、其他類別和儲存空間之間的互動。](https://developer.android.com/static/guide/topics/providers/images/content-provider-interaction.png?authuser=0000&hl=zh-tw)
圖 2. ContentProvider
、其他類別和儲存空間之間的互動。
注意:如要存取供應器,應用程式通常需要要求特定的 權限。如需這個開發模式的詳細說明,請參閱 「內容供應器權限」部分。
「使用者字典提供者」是 Android 平台內建的其中一個供應商, 會儲存使用者想要保留的非標準字詞。表 1 說明 資料可能如以下供應商的表格所示:
表 1:使用者字典資料表範例。
文字 | 應用程式編號 | 頻率 | 語言代碼 | _ID |
---|---|---|---|---|
mapreduce |
使用者 1 | 100 | en_US | 1 |
precompiler |
使用者 14 | 200 | fr_FR | 2 |
applet |
使用者 2 | 225 | fr_CA | 3 |
const |
使用者 1 | 255 | pt_BR | 4 |
int |
使用者 5 | 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
表 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 包含該提供者的符號名稱 (其主管機關),以及 指向資料表的名稱,也就是路徑。撥打電話時 用於存取供應器中資料表的用戶端方法,資料表的內容 URI 是 引數
在程式碼前幾行中,
CONTENT_URI
包含
使用者字典提供者的 Words
表格。ContentResolver
物件會剖析 URI 的授權,並用其來解析供應器,
比較授權與已知供應商的系統表格
接著,ContentResolver
就能將查詢引數分派給正確的
。
ContentProvider
會根據內容 URI 的路徑部分選擇
資料表。提供者通常會為公開的每個資料表都有路徑。
在前幾行程式碼中,Words
資料表的完整 URI 為:
content://user_dictionary/words
content://
字串是一律存在的「配置」 並識別為內容 URIuser_dictionary
字串是供應器的授權。words
字串是資料表的路徑。
許多供應商會附加 ID 值,讓您存取表格中的單一資料列
。例如,如要擷取 _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
類別包含方便將 ID 值附加至
URI。先前的程式碼片段使用 withAppendedId()
,將 ID 附加至使用者字典提供者內容 URI。
從供應商擷取資料
本節說明如何使用「使用者字典提供者」從供應器擷取資料 舉例來說
為求明確起見,本節中的程式碼片段呼叫
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 注入資料。
請考慮使用以下的選取子句:
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,
舉例來說,使用者可以輸入「nothing;」DROP 表格 *;適用於 mUserInput
,而
會產生選取子子句 var = nothing; DROP TABLE *;
由於 選取子句被視為 SQL 陳述式,這可能會導致提供者清除 除非供應商設定了擷取作業 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;
使用 ?
做為可替換參數和陣列陣列的選取子句
即使供應器沒有提供者,也建議您使用選取引數陣列
執行訓練和預測作業
顯示查詢結果
一律使用 ContentResolver.query()
用戶端方法
會傳回 Cursor
,其中包含查詢所指定的資料欄
預測符合查詢選取條件的資料列。A 罩杯
Cursor
物件可隨機讀取其資料列和資料欄
包含。
您可以使用 Cursor
方法疊代處理
結果、判斷各資料欄的資料類型、從資料欄中取得資料,並檢查其他
結果的屬性。
系統會自動導入部分 Cursor
在供應器的資料變更時更新物件,並在觀察器物件中觸發方法
Cursor
變更時或兩者皆有。
注意:提供者可以根據 產生查詢的物件舉例來說,聯絡人供應商會限制某些資料欄的存取權 同步處理轉接器,不會將這類轉接器歸還給活動或服務。
如果沒有符合選取條件的資料列,系統就會提供提供者
會傳回 Cursor
物件,而
Cursor.getCount()
為
0,也就是空白的遊標。
如果發生內部錯誤,查詢結果會因特定的供應器而異。可能會
會傳回 null
,否則可能會擲回 Exception
。
由於 Cursor
是資料列清單,因此顯示
Cursor
的內容是連結至 ListView
透過 SimpleCursorAdapter
。
下列程式碼片段接續了前一段程式碼片段中的程式碼。系統會建立
包含 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);
注意:如要傳回含有 ListView
Cursor
,遊標必須包含名為 _ID
的資料欄。
因此,先前出現的查詢會擷取_ID
Words
資料表,但 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()
方法會傳回指出的值:
資料欄中的資料類型
內容供應器權限
供應商的應用程式可以指定其他應用程式必須具備的權限 存取供應商的資料。這些權限可讓使用者瞭解 錯誤。根據供應商的需求 要求取得供應器所需的權限。使用者會看到 授予的權限
如果供應器的應用程式未指定任何權限,其他應用程式就沒有 供應商資料的存取權 (除非供應商已匯出)。此外,元件 一律具備完整的讀取和寫入權限,無論 指定權限。
「使用者字典提供者」需要
android.permission.READ_USER_DICTIONARY
權限可從中擷取資料。
供應商有個別的 android.permission.WRITE_USER_DICTIONARY
具備插入、更新或刪除資料的權限。
如要取得存取供應器所需的權限,應用程式會透過
<uses-permission>
元素。Android 套件管理員安裝應用程式時,使用者
必須核准應用程式要求的所有權限。如果使用者核准要求
套件管理員會繼續安裝。如果使用者未核准,套件管理員
停止安裝。
下列範例
<uses-permission>
元素會要求讀取使用者字典提供者:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY">
以下詳細說明內容對供應商存取權的影響: 安全性提示。
插入、更新及刪除資料
擷取供應器資料的方式相同,也可使用
供應商用戶端和提供者的 ContentProvider
,可用來修改資料。
您呼叫 ContentResolver
方法時,需要傳遞傳遞至
對應的 ContentProvider
方法供應商和供應商
用戶端會自動處理安全性和處理序間通訊。
插入資料
如要將資料插入供應器,您可以呼叫
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 );
新資料列的資料會歸入單一 ContentValues
物件,
類似於單行遊標。這個物件中的欄不需要包含
同一種資料類型而且您完全不想指定值
透過 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()
。如要進一步瞭解
請參閱「防範惡意輸入」一節。
供應商資料類型
內容供應器可能提供多種不同資料類型。「使用者字典供應商」提供 但供應商也可以提供下列格式:
- 整數
- 長整數 (長)
- 浮點
- 長浮點 (雙精度浮點數)
供應商常用的另一種資料類型是二進位大型物件 (BLOB),
64 KB 位元組陣列。如要瞭解可用的資料類型,請前往
Cursor
類別「get」方法。
供應器中每個資料欄的資料類型通常會列在說明文件中。
「使用者字典提供者」的資料類型列於參考文件中
針對合約類別 UserDictionary.Words
合約類型為
,請參閱「合約課程」一節。
你也可以呼叫 Cursor.getType()
來判斷資料類型。
提供者也會針對他們定義的每個內容 URI 保留 MIME 資料類型資訊。你可以 使用 MIME 類型資訊來瞭解應用程式是否能處理 供應商會根據 MIME 類型提供或選擇處理類型。您通常需要用到 當您與複雜供應商合作時,MIME 類型 資料結構或檔案
例如:ContactsContract.Data
表格,會使用 MIME 類型將儲存在各個聯絡人中的資料類型加上標籤
列。如要取得與內容 URI 相對應的 MIME 類型,請呼叫
ContentResolver.getType()
。
「MIME 類型參考資料」一節將說明 標準和自訂 MIME 類型的語法。
服務供應商的其他存取方式
應用程式開發過程中,有三種替代的供應商存取形式相當重要:
-
批次存取:您可以使用多種方式建立存取呼叫批次,
ContentProviderOperation
類別,然後使用這些參數ContentResolver.applyBatch()
。 -
非同步查詢:在獨立執行緒中執行查詢。你可以
使用
CursorLoader
物件。您可以參考 載入器指南示範 該怎麼做 - 使用意圖存取資料:雖然您無法傳送意圖,但 您就能直接將意圖傳送至供應商的應用程式。 通常功能最適合用來修改供應器的資料。
以下各節將說明使用意圖的批次存取和修改作業。
批次存取權
提供者的批次存取適合插入大量資料列時,用於插入 透過相同方法呼叫多個資料表的資料列,通常用於執行一組 跨程序邊界作業視為交易,稱為「不可部分完成的作業」。
如何以批次模式存取供應器:
建立 ContentProviderOperation
物件的陣列,然後
將這類系統分派給內容供應器
ContentResolver.applyBatch()
。您傳遞了
內容供應器對這個方法的授權,而非特定內容 URI。
讓陣列中的每個 ContentProviderOperation
物件都能順利運作
並套用至其他資料表呼叫 ContentResolver.applyBatch()
會傳回結果陣列。
ContactsContract.RawContacts
合約類別的說明
包括示範批次插入的程式碼片段
使用意圖的資料存取
意圖可以間接存取內容供應器。您還能 存取供應資料 也能從具備權限的應用程式,或是啟用 以及讓使用者在當中進行操作的應用程式。
取得臨時權限的存取權
沒有適當存取權也能存取內容供應器中的資料 將意圖傳送至具有該權限的應用程式 接收包含 URI 權限的結果意圖。 這些特定內容 URI 的權限會持續至收到的活動為止 建立完成具有永久權限的應用程式會授予暫時權限 方法是在結果意圖中設定旗標:
注意:這些旗標不會授予供應器的一般讀取或寫入權限 的內容 URI 中含有授權的主機名稱系統只會針對 URI 本身提出存取要求。
將內容 URI 傳送至其他應用程式時,請至少加入其中一個 旗標這些標記能為收到的應用程式提供下列功能 並指定 Android 11 (API 級別 30) 以上版本:
- 讀取或寫入內容 URI 代表的資料 視意圖中包含的旗標而定
- 取得包裹 瀏覽權限, URI 授權。傳送意圖的應用程式,以及 內容供應器可能是兩個不同的應用程式。
供應器使用
android:grantUriPermissions
屬性的
<provider>
元素
<grant-uri-permission>
子元素的
<provider>
元素。如要進一步瞭解 URI 權限機制,請參閱
Android 中的權限指南。
舉例來說,您可以擷取聯絡人供應程式中聯絡人的資料,即使你沒有
擁有 READ_CONTACTS
權限。建議你
應用程式,傳送電子祝福給聯絡人的生日祝福。而不是
要求 READ_CONTACTS
,以便存取所有
使用者的聯絡人及其所有資訊
可讓使用者控制
聯絡。如要這樣做,請按照以下程序操作:
-
在應用程式中傳送包含該動作的意圖
ACTION_PICK
和「聯絡人」MIME 類型CONTENT_ITEM_TYPE
,使用startActivityForResult()
方法。 - 這個意圖符合 使用者應用程式的「選取」活動進入前景。
-
使用者在選取活動中選取一個
進行更新。發生這種情況時,選取活動會呼叫
setResult(resultcode, intent)
設定要傳回應用程式的意圖。意圖包含內容 URI 使用者選取的聯絡人以及「額外項目」旗標FLAG_GRANT_READ_URI_PERMISSION
。這些旗標會授予 URI 權限,允許應用程式讀取針對該聯絡對象的資料 內容 URI選取活動接著會呼叫finish()
來 並返回應用程式。 -
活動會返回前景,而系統會呼叫活動的
onActivityResult()
方法。這個方法會接收選取活動在所選活動中建立的結果意圖 聯絡人應用程式。 - 使用結果意圖的內容 URI 即可讀取聯絡人的資料 您沒有要求永久的讀取權限, 加入相應的供應商。這樣您就可以取得聯絡人的生日資訊 然後傳送電子祝福。
使用其他應用程式
如果想讓使用者修改您沒有存取權限的資料,也可以是: 啟用具備權限的應用程式,讓使用者在裝置上進行作業。
舉例來說,日曆應用程式接受
ACTION_INSERT
意圖,可讓您啟動
插入使用者介面您可以在套用該意圖的資料
也就是預先填入 UI由於週期性活動的語法很複雜
將活動插入「日曆供應程式」時,必須透過
ACTION_INSERT
,然後讓使用者在此插入事件。
使用輔助應用程式顯示資料
如果您的應用程式「具有」存取權限,您仍然可以使用
意圖在另一個應用程式中顯示資料。舉例來說,日曆應用程式接受
ACTION_VIEW
意圖,顯示特定日期或事件。
如此一來,您無需建立自己的使用者介面,就可以顯示日曆資訊。
如要進一步瞭解這項功能,請參閱
日曆供應程式總覽。
傳送意圖的應用程式不一定是應用程式
與供應商相關聯的名稱舉例來說,您可以透過
與供應商聯絡,然後傳送 ACTION_VIEW
意圖
,內含聯絡人圖片的內容 URI。
合約課程
合約類別定義常數,可協助應用程式使用內容 URI
名稱、意圖動作等
內容供應器的其他功能合約課程無法
自動加入供應商提供者的開發人員必須定義這些項目
以提供給其他開發人員Android 內建許多供應商
平台的 android.provider
套件中有相應的合約類別。
舉例來說,「使用者字典供應商」有一個合約類別
UserDictionary
,包含內容 URI 和資料欄名稱常數。
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
。
這個類別的參考說明文件包含程式碼片段範例。其中一個
子類別 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
,內含資料表
第 1 行、第 2 行和第 3 行。回應第 1 行表的下列內容 URI:
content://com.example.trains/Line1
供應器會傳回下列 MIME 類型:
vnd.android.cursor.dir/vnd.example.line1
回應第 2 行中第 5 列的內容 URI :
content://com.example.trains/Line2/5
供應器會傳回下列 MIME 類型:
vnd.android.cursor.item/vnd.example.line2
大多數內容供應器會針對他們使用的 MIME 類型定義合約類別常數。
聯絡人供應商合約類別 ContactsContract.RawContacts
,
例如,定義常數
CONTENT_ITEM_TYPE
表示 MIME 類型
一個原始聯絡列