四大組件之ContentProvider之內(nèi)容提供商基礎(chǔ)(譯)

ContentProvider是Android應(yīng)用程序的主要構(gòu)建模塊之一萌狂,可為應(yīng)用程序提供內(nèi)容。它們封裝數(shù)據(jù)并通過單個ContentResolver接口將其提供給應(yīng)用程序 褪测。僅當(dāng)您需要在多個應(yīng)用程序之間共享數(shù)據(jù)時,才需要內(nèi)容提供程序啤挎。例如岂却,聯(lián)系人數(shù)據(jù)由多個應(yīng)用程序使用空镜,并且必須存儲在內(nèi)容提供商中。如果您不需要在多個應(yīng)用程序之間共享數(shù)據(jù)最冰,則可以直接通過使用數(shù)據(jù)庫 SQLiteDatabase典鸡。

當(dāng)通過發(fā)出請求時ContentResolver罚攀,系統(tǒng)將檢查給定URI的權(quán)限潮针,并將請求傳遞給在該權(quán)限下注冊的內(nèi)容提供者术荤。內(nèi)容提供商可以根據(jù)需要解釋其余的URI。的UriMatcher班是解析URI的幫助每篷。

需要實現(xiàn)的主要方法是:

數(shù)據(jù)訪問方法(例如insert(Uri, ContentValues)update(Uri, ContentValues, Bundle))可以同時從多個線程中調(diào)用瓣戚,并且必須是線程安全的。其他方法(例如onCreate())只能從應(yīng)用程序主線程中調(diào)用焦读,并且必須避免執(zhí)行冗長的操作子库。有關(guān)其預(yù)期的線程行為,請參見方法說明矗晃。

請求ContentResolver會自動轉(zhuǎn)發(fā)到適當(dāng)?shù)腃ontentProvider實例刚照,因此子類不必擔(dān)心跨進程調(diào)用的細節(jié)。

開發(fā)人員指南

有關(guān)使用內(nèi)容提供程序的更多信息喧兄,請閱讀 內(nèi)容提供程序 開發(fā)人員指南。

開發(fā)人員指南部分

內(nèi)容提供商基礎(chǔ)

內(nèi)容提供商管理對中央數(shù)據(jù)存儲庫的訪問啊楚。提供程序是Android應(yīng)用程序的一部分吠冤,該應(yīng)用程序通常提供自己的UI來處理數(shù)據(jù)。但是恭理,內(nèi)容提供程序主要旨在供其他應(yīng)用程序使用拯辙,其他應(yīng)用程序使用提供程序客戶端對象訪問提供程序。提供者和提供者客戶端共同為數(shù)據(jù)提供一致的標準接口,該接口還處理進程間通信和安全的數(shù)據(jù)訪問涯保。

通常诉濒,您在以下兩種情況之一中與內(nèi)容提供者合作;您可能想要實現(xiàn)代碼以訪問另一個應(yīng)用程序中的現(xiàn)有內(nèi)容提供者夕春,或者您可能希望在應(yīng)用程序中創(chuàng)建一個新的內(nèi)容提供者以與其他應(yīng)用程序共享數(shù)據(jù)未荒。本主題涵蓋與現(xiàn)有內(nèi)容提供者合作的基礎(chǔ)知識。要了解有關(guān)在自己的應(yīng)用程序中實現(xiàn)內(nèi)容提供者的更多信息及志,請參閱 創(chuàng)建內(nèi)容提供者片排。

本主題描述以下內(nèi)容:

  • 內(nèi)容提供商的工作方式。
  • 您用于從內(nèi)容提供商檢索數(shù)據(jù)的API速侈。
  • 您用于在內(nèi)容提供程序中插入率寡,更新或刪除數(shù)據(jù)的API。
  • 有助于與提供者合作的其他API功能倚搬。

總覽

內(nèi)容提供商將數(shù)據(jù)作為一個或多個表呈現(xiàn)給外部應(yīng)用程序冶共,這些表與關(guān)系數(shù)據(jù)庫中的表相似。行代表提供者收集的某種類型的數(shù)據(jù)的實例每界,并且行中的每一列代表為實例收集的單個數(shù)據(jù)捅僵。

內(nèi)容提供者針對許多不同的API和組件(如圖1所示)協(xié)調(diào)對應(yīng)用程序中數(shù)據(jù)存儲層的訪問,這些API和組件包括:

  • 與其他應(yīng)用程序共享對您的應(yīng)用程序數(shù)據(jù)的訪問
  • 將數(shù)據(jù)發(fā)送到小部件
  • 通過使用以下內(nèi)容的搜索框架為您的應(yīng)用返回自定義搜索建議 SearchRecentSuggestionsProvider
  • 使用的實現(xiàn)將應(yīng)用程序數(shù)據(jù)與服務(wù)器同步 AbstractThreadedSyncAdapter
  • 使用以下命令在用戶界面中加載數(shù)據(jù) CursorLoader
圖1

圖1.內(nèi)容提供者與其他組件之間的關(guān)系盆犁。

訪問提供商

當(dāng)您要訪問內(nèi)容提供程序中的數(shù)據(jù)時命咐,可以使用 ContentResolver應(yīng)用程序中的對象Context作為客戶端與提供程序 進行通信。該 ContentResolver對象與提供者對象(實現(xiàn)的類的實例)進行通信ContentProvider谐岁。提供者對象從客戶端接收數(shù)據(jù)請求醋奠,執(zhí)行請求的操作,然后返回結(jié)果伊佃。該對象的方法調(diào)用提供程序?qū)ο螅ǖ囊粋€具體子類之一的實例)中具有相同名稱的方法ContentProvider窜司。這些 ContentResolver方法提供了持久性存儲的基本“ CRUD”(創(chuàng)建,檢索航揉,更新和刪除)功能塞祈。

ContentProvider從用戶界面 訪問a的常見模式是使用a CursorLoader在后臺運行異步查詢。您的用戶界面中的 ActivityFragment調(diào)用 CursorLoader查詢帅涂,而查詢又 ContentProvider使用ContentResolver议薪。這允許用戶在查詢運行時繼續(xù)對用戶可用。這種模式涉及許多不同對象的交互以及底層存儲機制媳友,如圖2所示斯议。

圖2

圖2. ContentProvider,其他類和存儲之間的交互醇锚。

注意:要訪問提供程序哼御,您的應(yīng)用程序通常必須在其清單文件中請求特定的權(quán)限坯临。在內(nèi)容提供者權(quán)限一節(jié)中將詳細描述這種開發(fā)模式。

Android詞典中的內(nèi)置提供程序之一是用戶詞典恋昼,該詞典存儲用戶想要保留的非標準單詞的拼寫看靠。表1說明了此提供程序的表中的數(shù)據(jù)可能是什么樣的:

樣本用戶字典表

在表1中,每一行代表一個在標準字典中可能找不到的單詞的實例液肌。每列代表該單詞的一些數(shù)據(jù)挟炬,例如首次遇到該單詞的語言環(huán)境。列標題是存儲在提供程序中的列名矩屁。要引用行的語言環(huán)境辟宗,請引用其locale列。對于此提供程序吝秕,該_ID列用作提供程序自動維護的“主鍵”列泊脐。

要從用戶詞典提供者那里獲取單詞及其語言環(huán)境的列表,請致電ContentResolver.query()烁峭。該query()方法調(diào)用ContentProvider.query()用戶詞典提供者定義的 方法容客。以下代碼行顯示了一個 ContentResolver.query()調(diào)用:

//查詢用戶字典并返回結(jié)果
// Queries the user dictionary and returns results
cursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,   // 單詞表的內(nèi)容URI  
    projection,                        // 每行要返回的列   
    selectionClause,                   // 選擇標準  
    selectionArgs,                     // 選擇標準  
    sortOrder);                        // 返回的行的排序順序    

表2顯示了如何query(Uri,projection,selection,selectionArgs,sortOrder)匹配SQL SELECT語句的參數(shù) :

表2

內(nèi)容URI

內(nèi)容URI是一個URI,在一個提供者識別數(shù)據(jù)约郁。內(nèi)容URI包括整個提供程序的符號名稱(它的權(quán)限)和指向表的名稱(路徑)缩挑。當(dāng)您調(diào)用客戶端方法來訪問提供程序中的表時,表的內(nèi)容URI是參數(shù)之一鬓梅。

在前面的代碼行中供置,該常量 CONTENT_URI包含用戶詞典的“單詞”表的內(nèi)容URI。該ContentResolver對象解析出URI的權(quán)限绽快,并通過將權(quán)限與已知提供程序的系統(tǒng)表進行比較來使用它來“解析”提供程序芥丧。然后, ContentResolver可以將查詢參數(shù)分派到正確的提供程序坊罢。

ContentProvider使用內(nèi)容URI的路徑部分選擇表的訪問续担。提供程序通常為它公開的每個表都有一個路徑

在前面的代碼行中活孩,“單詞”表的完整URI為:

content://user_dictionary/words

其中物遇,user_dictionary字符串是提供者的權(quán)限,words字符串是表的路徑憾儒。字符串 content:// (the scheme) 始終存在询兴,并將其標識為內(nèi)容URI。

許多提供程序都允許您通過將ID值附加到URI的末尾來訪問表中的一行起趾。例如蕉朵,要檢索行,其_ID4從用戶詞典阳掐,您可以使用此內(nèi)容的URI:

Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

當(dāng)您檢索一組行然后想要更新或刪除其中一個時,通常會使用id值。

注意:所述的UriUri.Builder類包含用于從字符串構(gòu)建合式URI對象方便的方法缭保。本 ContentUris類包含附加ID值到URI方便的方法汛闸。上一個代碼段用于withAppendedId()將ID附加到UserDictionary內(nèi)容URI。

從提供者檢索數(shù)據(jù)

本節(jié)以用戶詞典提供程序為例艺骂,介紹如何從提供程序檢索數(shù)據(jù)诸老。

為了清楚起見,本節(jié)中的代碼段調(diào)用 ContentResolver.query()“ UI線程”钳恕。但是别伏,在實際代碼中,您應(yīng)該在單獨的線程上異步執(zhí)行查詢忧额。一種方法是使用CursorLoader類厘肮,即在更詳細地描述 裝填機。導(dǎo)向另外睦番,代碼行是唯一的代碼段;它們不表現(xiàn)出一個完整的應(yīng)用程序类茂。

要從提供程序檢索數(shù)據(jù),請按照以下基本步驟操作:

  1. 請求提供者的讀取訪問權(quán)限托嚣。
  2. 定義將查詢發(fā)送到提供程序的代碼巩检。

請求讀取訪問權(quán)限

要從提供程序檢索數(shù)據(jù),您的應(yīng)用程序需要提供程序的“讀取訪問權(quán)限”示启。您不能在運行時請求此權(quán)限兢哭。相反,您必須使用<uses-permission> 提供程序定義的元素和確切的權(quán)限名稱夫嗓,在清單中指定需要此權(quán)限 迟螺。當(dāng)您在清單中指定此元素時,實際上是在“請求”應(yīng)用程序的此權(quán)限啤月。用戶安裝您的應(yīng)用程序時煮仇,他們暗中同意此請求。

要查找您正在使用的提供程序的讀取訪問權(quán)限的確切名稱谎仲,以及提供程序使用的其他訪問權(quán)限的名稱浙垫,請參閱提供程序的文檔。

權(quán)限在訪問提供程序中的作用將在“ 內(nèi)容提供程序權(quán)限 ”部分中詳細介紹 郑诺。

用戶詞典提供程序android.permission.READ_USER_DICTIONARY在其清單文件中定義了權(quán)限 夹姥,因此想要從提供程序讀取的應(yīng)用程序必須請求此權(quán)限。

構(gòu)造查詢

從提供程序檢索數(shù)據(jù)的下一步是構(gòu)造查詢辙诞。第一個代碼段定義了一些用于訪問User Dictionary Provider的變量:

//“ projection”定義將為每一行返回的列
字符串[] mProjection = 
{
    UserDictionary.Words._ID辙售,// _ID列名稱的合同類常量
    UserDictionary.Words.WORD,//單詞列名稱的合同類常量
    UserDictionary.Words.LOCALE   //區(qū)域設(shè)置列名稱的合同類常量
};

//定義一個包含選擇子句的字符串
字符串selectionClause = null ; 

//初始化一個數(shù)組以包含選擇參數(shù)
字符串[] selectionArgs = { “” }; 

下一個代碼段以ContentResolver.query()User Dictionary Provider為例飞涂,說明如何使用 旦部。提供程序客戶端查詢類似于SQL查詢祈搜,并且包含一組要返回的列,一組選擇條件和排序順序士八。

查詢應(yīng)返回的一組列稱為投影 (變量mProjection)容燕。

指定要檢索的行的表達式分為選擇子句和選擇參數(shù)。選擇子句是邏輯和布爾表達式婚度,列名和值(變量mSelectionClause)的組合蘸秘。如果指定可替換參數(shù)?而不是值,則查詢方法將從選擇參數(shù)數(shù)組(變量mSelectionArgs)中檢索值蝗茁。

在下一個代碼段中醋虏,如果用戶不輸入單詞,則選擇子句設(shè)置為 null哮翘,查詢將返回提供程序中的所有單詞颈嚼。如果用戶輸入單詞,則選擇子句設(shè)置為UserDictionary.Words.WORD + " = ?"忍坷,選擇參數(shù)數(shù)組的第一個元素設(shè)置為用戶輸入的單詞粘舟。

/*
 * 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語句中,使用實際的列名代替合同類常量佩研。

防止惡意輸入
如果由內(nèi)容提供者管理的數(shù)據(jù)在SQL數(shù)據(jù)庫中柑肴,則將外部不受信任的數(shù)據(jù)包含到原始SQL語句中會導(dǎo)致SQL注入。

考慮以下選擇子句:
//通過將用戶輸入連接到列名來構(gòu)造選擇子句

字符串selectionClause = “ var =” + userInput ;  

如果這樣做旬薯,則允許用戶將惡意SQL連接到您的SQL語句上晰骑。例如,用戶可以輸入“ nothing; DROP TABLE *;”绊序。為mUserInput硕舆,這將導(dǎo)致選擇子句var = nothing; DROP TABLE *;。由于選擇子句被視為SQL語句骤公,因此這可能會導(dǎo)致提供程序擦除底層SQLite數(shù)據(jù)庫中的所有表(除非提供程序設(shè)置為捕獲 SQL注入嘗試)抚官。

為避免此問題,請使用選擇子句阶捆,該子句?用作可替換參數(shù)和單獨的選擇參數(shù)數(shù)組凌节。執(zhí)行此操作時,用戶輸入直接綁定到查詢洒试,而不是被解釋為SQL語句的一部分倍奢。由于未將其視為SQL,因此用戶輸入無法注入惡意SQL垒棋。代替使用串聯(lián)來包含用戶輸入卒煞,請使用以下選擇子句:

//構(gòu)造一個帶有可替換參數(shù)的選擇子句
字符串選擇Clause =“ var =?”;

設(shè)置選擇參數(shù)數(shù)組叼架,如下所示:

//定義一個包含選擇參數(shù)的數(shù)組
字符串[] selectionArgs = {“”};  

?即使提供者不是基于SQL數(shù)據(jù)庫畔裕,也 可以使用用作可替換參數(shù)的選擇子句和選擇參數(shù)數(shù)組的數(shù)組來指定選擇衣撬。

顯示查詢結(jié)果

ContentResolver.query()客戶端方法總是返回一個Cursor含有由查詢的投影為匹配查詢的選擇標準的行指定的列。一個 Cursor對象提供它所包含的行和列的隨機讀取訪問柴钻。使用Cursor方法淮韭,您可以遍歷結(jié)果中的行,確定每列的數(shù)據(jù)類型贴届,從列中獲取數(shù)據(jù),并檢查結(jié)果的其他屬性蜡吧。某些Cursor實現(xiàn)在提供者的數(shù)據(jù)發(fā)生更改時自動更新對象毫蚓,或在更改發(fā)生時自動觸發(fā)觀察者對象中的方法Cursor,或兩者同時發(fā)生昔善。

注意:提供程序可能會根據(jù)進行查詢的對象的性質(zhì)來限制對列的訪問元潘。例如,Contacts Provider限制對某些列的訪問以同步適配器君仆,因此它不會將它們返回到活動或服務(wù)翩概。

如果沒有行符合選擇條件,則提供程序?qū)⒎祷匾粋€Cursor對象返咱,該對象的 Cursor.getCount()值為0(空游標)钥庇。

如果發(fā)生內(nèi)部錯誤,則查詢結(jié)果取決于特定的提供程序咖摹。它可能選擇返回null评姨,也可能會拋出一個Exception

由于a Cursor是行的“列表”萤晴,因此顯示a的內(nèi)容的一種好方法CursorListView 通過a 將其鏈接到a SimpleCursorAdapter吐句。

以下代碼段延續(xù)了上一個代碼段的代碼。它創(chuàng)建一個 SimpleCursorAdapter包含Cursor 查詢檢索到的對象店读,并將該對象設(shè)置為的適配器 ListView

// 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);

注意:要使用來ListView支持a Cursor嗦枢,光標必須包含名為的列_ID。因此屯断,先前顯示的查詢將檢索_ID“單詞”表的列文虏,即使該表ListView未顯示該列。此限制還解釋了為什么大多數(shù)提供程序_ID的每個表都有一個列裹纳。

從查詢結(jié)果中獲取數(shù)據(jù)

您不僅可以顯示查詢結(jié)果择葡,還可以將其用于其他任務(wù)。例如剃氧,您可以從用戶詞典中檢索拼寫敏储,然后在其他提供程序中查找它們。為此朋鞍,您需要遍歷以下行Cursor

// 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實現(xiàn)包含幾個“獲取”方法已添,用于從對象中檢索不同類型的數(shù)據(jù)妥箕。例如,先前的代碼段使用getString()更舞。它們還具有一個getType()返回值的 方法畦幢,該值指示列的數(shù)據(jù)類型。

內(nèi)容提供商權(quán)限

提供者的應(yīng)用程序可以指定其他應(yīng)用程序必須具有的權(quán)限才能訪問提供者的數(shù)據(jù)缆蝉。這些權(quán)限可確保用戶知道應(yīng)用程序?qū)L試訪問哪些數(shù)據(jù)宇葱。根據(jù)提供商的要求,其他應(yīng)用程序會請求他們所需的權(quán)限才能訪問提供商刊头。最終用戶在安裝應(yīng)用程序時會看到請求的權(quán)限黍瞧。

如果提供程序的應(yīng)用程序未指定任何權(quán)限,則其他應(yīng)用程序?qū)o權(quán)訪問提供程序的數(shù)據(jù)原杂。但是印颤,無論指定的權(quán)限如何,提供者的應(yīng)用程序中的組件始終具有完全的讀取和寫入訪問權(quán)限穿肄。

如前所述年局,用戶詞典提供者需要android.permission.READ_USER_DICTIONARY獲得從中檢索數(shù)據(jù)的 權(quán)限。提供者具有android.permission.WRITE_USER_DICTIONARY 插入咸产,更新或刪除數(shù)據(jù)的單獨權(quán)限矢否。

為了獲得訪問提供程序所需的權(quán)限,應(yīng)用程序會<uses-permission> 在清單文件中使用元素來請求它們 锐朴。當(dāng)Android Package Manager安裝該應(yīng)用程序時兴喂,用戶必須批準該應(yīng)用程序請求的所有權(quán)限。如果用戶全部批準焚志,則程序包管理器將繼續(xù)安裝衣迷;否則,將繼續(xù)安裝酱酬。如果用戶不同意壶谒,包管理器將中止安裝。

以下 <uses-permission> 元素請求對用戶詞典提供者的讀取訪問權(quán)限:

<uses-permission android:name="android.permission.READ_USER_DICTIONARY">

權(quán)限對提供程序訪問的影響將在“ 安全和權(quán)限”指南中進行詳細說明 膳沽。

插入汗菜,更新和刪除數(shù)據(jù)

與從提供程序檢索數(shù)據(jù)的方式相同,您還可以使用提供程序客戶端與提供程序的客戶端之間的交互ContentProvider來修改數(shù)據(jù)挑社。您調(diào)用ContentResolver帶有傳遞給的相應(yīng)方法的參數(shù)的方法ContentProvider陨界。提供者和提供者客戶端自動處理安全性和進程間通信。

插入資料

要將數(shù)據(jù)插入提供程序痛阻,請調(diào)用 ContentResolver.insert() 方法菌瘪。此方法將新行插入提供程序,并返回該行的內(nèi)容URI。此代碼段顯示了如何在User Dictionary Provider中插入新單詞:

// 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
);

新行的數(shù)據(jù)將進入一個ContentValues對象俏扩,該對象的形式類似于單行游標糜工。該對象中的列不必具有相同的數(shù)據(jù)類型,并且如果您根本不想指定值录淡,則可以將列設(shè)置為nullusing ContentValues.putNull()捌木。

該代碼段不會添加該_ID列,因為該列是自動維護的嫉戚。提供程序?qū)⑽ㄒ坏闹捣峙浣o_ID添加的每一行刨裆。提供程序通常將此值用作表的主鍵。

返回的內(nèi)容URI newUri標識新添加的行彬檀,格式如下:

content://user_dictionary/words/<id_value>

<id_value>是內(nèi)容的_ID新行崔拥。大多數(shù)提供程序可以自動檢測這種形式的內(nèi)容URI,然后在該特定行上執(zhí)行請求的操作凤覆。

要從_ID返回的值中獲取的值Uri,請調(diào)用 ContentUris.parseId()拆魏。

更新數(shù)據(jù)

要更新行盯桦,可以像使用ContentValues插入一樣使用具有更新值的對象,也可以像查詢一樣使用選擇條件渤刃。您使用的客戶端方法是 ContentResolver.update()拥峦。您只需要為ContentValues要更新的列的對象添加值。如果要清除列的內(nèi)容卖子,請將值設(shè)置為null略号。

以下代碼段將語言環(huán)境為“ en”的所有行更改為語言環(huán)境為的行null。返回值是已更新的行數(shù):

// 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
);

調(diào)用時洋闽,還應(yīng)該清理用戶輸入 ContentResolver.update()玄柠。要了解更多信息,請閱讀防止惡意輸入部分诫舅。

刪除資料

刪除行類似于檢索行數(shù)據(jù):您為要刪除的行指定選擇標準羽利,并且客戶端方法返回已刪除行的數(shù)量。以下代碼段刪除其appid與“ user”匹配的行刊懈。該方法返回已刪除的行數(shù)这弧。

//為要刪除的行定義選擇條件
字符串selectionClause = UserDictionary.Words.APP_ID +“ LIKE?”;
字符串[] selectionArgs = { “ user” }; 

//定義一個變量以包含已刪除的行數(shù)
int rowsDeleted = 0 ; 

...

//刪除符合選擇條件的單詞
rowsDeleted = getContentResolver ().delete(
    UserDictionary.Words.CONTENT_URI 虚汛,//用戶字典內(nèi)容URI   
    selectionClause 匾浪,//要選擇的列                   
    selectionArgs                       //要比較的值
);

調(diào)用時,還應(yīng)該清理用戶輸入 ContentResolver.delete()卷哩。要了解更多信息蛋辈,請閱讀防止惡意輸入部分

提供者數(shù)據(jù)類型

內(nèi)容提供商可以提供許多不同的數(shù)據(jù)類型殉疼。用戶詞典提供程序僅提供文本梯浪,但是提供程序也可以提供以下格式:

  • 整數(shù)
  • 長整數(shù)(長)
  • 浮點
  • 長浮點數(shù)(雙精度)

提供程序經(jīng)常使用的另一種數(shù)據(jù)類型是實現(xiàn)為64KB字節(jié)數(shù)組的二進制大對象(BLOB)捌年。您可以通過查看Cursor類“ get”方法來查看可用的數(shù)據(jù)類型 。

提供程序中每一列的數(shù)據(jù)類型通常會在其文檔中列出挂洛。用戶詞典提供者的數(shù)據(jù)類型在其合同類別的參考文檔中列出UserDictionary.Words(合同類別在“ 合同類別 ”部分中進行了描述)礼预。您還可以通過調(diào)用來確定數(shù)據(jù)類型Cursor.getType()

提供程序還為他們定義的每個內(nèi)容URI維護MIME數(shù)據(jù)類型信息虏劲。您可以使用MIME類型信息來確定您的應(yīng)用程序是否可以處理提供程序提供的數(shù)據(jù)托酸,或者根據(jù)MIME類型選擇處理類型。當(dāng)您使用包含復(fù)雜數(shù)據(jù)結(jié)構(gòu)或文件的提供程序時柒巫,通常需要MIME類型励堡。例如,ContactsContract.Data Contacts Provider中的表使用MIME類型來標記存儲在每一行中的聯(lián)系人數(shù)據(jù)的類型堡掏。要獲取與內(nèi)容URI對應(yīng)的MIME類型应结,請調(diào)用 ContentResolver.getType()

MIME類型參考 部分介紹了標準MIME類型和自定義MIME類型的語法泉唁。

提供者訪問的替代形式

提供者訪問的三種替代形式在應(yīng)用程序開發(fā)中很重要:

以下各節(jié)介紹了通過意向進行的批量訪問和修改劲藐。

批量訪問

對提供程序的批訪問對于插入大量行或在同一方法調(diào)用中的多個表中插入行非常有用八堡,或者通常用于跨事務(wù)邊界執(zhí)行一組作為事務(wù)的操作(原子操作)。

要以“批處理模式”訪問提供程序瘩燥,請創(chuàng)建一個ContentProviderOperation對象數(shù)組秕重,然后使用分配它們到內(nèi)容提供程序 ContentResolver.applyBatch()。您將內(nèi)容提供者的權(quán)限傳遞給此方法厉膀,而不是特定的內(nèi)容URI溶耘。這允許ContentProviderOperation數(shù)組中的每個對象針對不同的表工作。調(diào)用將ContentResolver.applyBatch()返回結(jié)果數(shù)組服鹅。

ContactsContract.RawContacts合同類 的描述包括演示批處理插入的代碼段凳兵。該 聯(lián)系人管理器 示例應(yīng)用程序包含在其批量訪問的示例ContactAdder.java 源文件。

通過意圖進行數(shù)據(jù)訪問

意圖可以提供對內(nèi)容提供商的間接訪問企软。即使您的應(yīng)用程序沒有訪問權(quán)限庐扫,您也可以允許用戶訪問提供程序中的數(shù)據(jù),方法是從具有許可權(quán)的應(yīng)用程序獲取結(jié)果意圖,或者通過激活具有許可權(quán)的應(yīng)用程序并讓用戶在其中工作它形庭。

獲得臨時權(quán)限的訪問

即使沒有適當(dāng)?shù)脑L問權(quán)限铅辞,也可以通過將意圖發(fā)送給具有許可權(quán)的應(yīng)用程序并接收回包含“ URI”許可權(quán)的結(jié)果意圖,來訪問內(nèi)容提供程序中的數(shù)據(jù)萨醒。這些是對特定內(nèi)容URI的許可斟珊,這些許可將持續(xù)到接收它們的活動完成為止。具有永久權(quán)限的應(yīng)用程序通過在結(jié)果意圖中設(shè)置標志來授予臨時權(quán)限:

注意:這些標志不授予對權(quán)限包含在內(nèi)容URI中的提供程序的常規(guī)讀取或?qū)懭朐L問富纸。該訪問僅針對URI本身囤踩。

提供者使用 元素的android:grantUriPermission 屬性以及 <provider>元素的 <grant-uri-permission> 子元素 為其清單中的內(nèi)容URI定義URI權(quán)限 <provider> 。URI權(quán)限機制在“ 權(quán)限”概述指南中有更詳細的 說明晓褪。

例如堵漱,即使您沒有READ_CONTACTS權(quán)限,也可以在“聯(lián)系人提供程序”中檢索聯(lián)系人的數(shù)據(jù)涣仿。您可能想要在向生日當(dāng)天的聯(lián)系人發(fā)送電子問候的應(yīng)用程序中執(zhí)行此操作勤庐。READ_CONTACTS您可以選擇讓用戶控制您的應(yīng)用程序使用哪些聯(lián)系人,而不是請求好港,后者使您可以訪問所有用戶的聯(lián)系人及其所有信息埃元。為此,您使用以下過程:

  1. 你的應(yīng)用程序發(fā)送一個意圖包含操作 ACTION_PICK和“聯(lián)系人” MIME類型 CONTENT_ITEM_TYPE媚狰,使用方法startActivityForResult()
  2. 由于此意圖與People應(yīng)用程序的“選擇”活動的意圖過濾器匹配阔拳,因此該活動將成為前臺崭孤。
  3. 在選擇活動中,用戶選擇要更新的聯(lián)系人糊肠。發(fā)生這種情況時辨宠,選擇活動將調(diào)用 setResult(resultcode, intent) 以建立回饋給您的應(yīng)用程序的意圖。該意圖包含用戶選擇的聯(lián)系人的內(nèi)容URI和“額外”標志 FLAG_GRANT_READ_URI_PERMISSION货裹。這些標志向您的應(yīng)用授予URI權(quán)限嗤形,以讀取內(nèi)容URI指向的聯(lián)系人的數(shù)據(jù)。然后弧圆,選擇活動調(diào)用finish()以將控制權(quán)返回給您的應(yīng)用程序赋兵。
  4. 您的活動返回到前臺,然后系統(tǒng)調(diào)用您活動的 onActivityResult() 方法搔预。此方法接收由“人脈”應(yīng)用中的選擇活動創(chuàng)建的結(jié)果意圖霹期。
  5. 使用結(jié)果意圖中的內(nèi)容URI,即使您未在清單中請求對提供者的永久讀取訪問權(quán)限拯田,也可以從“聯(lián)系提供者”中讀取聯(lián)系人的數(shù)據(jù)历造。然后,您可以獲取聯(lián)系人的生日信息或他們的電子郵件地址,然后發(fā)送電子問候吭产。

使用其他應(yīng)用程序

允許用戶修改您沒有訪問權(quán)限的數(shù)據(jù)的一種簡單方法是激活具有權(quán)限的應(yīng)用程序侣监,然后讓用戶在其中進行工作。

例如臣淤,“日歷”應(yīng)用程序接受一個 ACTION_INSERT意圖橄霉,該意圖使您可以激活應(yīng)用程序的插入UI。您可以以此意圖傳遞“額外”數(shù)據(jù)荒典,應(yīng)用程序?qū)⑵溆糜陬A(yù)填充UI酪劫。由于重復(fù)發(fā)生的事件具有復(fù)雜的語法,因此將事件插入“日歷提供程序”的首選方法是使用激活日歷應(yīng)用程序寺董, ACTION_INSERT然后讓用戶在其中插入事件覆糟。

使用助手應(yīng)用程序顯示數(shù)據(jù)

如果您的應(yīng)用程序確實具有訪問權(quán)限,則您可能仍想使用一種意圖在另一個應(yīng)用程序中顯示數(shù)據(jù)遮咖。例如滩字,“日歷”應(yīng)用程序接受一個 ACTION_VIEW意圖,該意圖顯示特定的日期或事件御吞。這使您可以顯示日歷信息麦箍,而不必創(chuàng)建自己的UI。要了解有關(guān)此功能的更多信息陶珠,請參閱“ 日歷提供程序”指南挟裂。

您向其發(fā)送意圖的應(yīng)用程序不必是與提供程序關(guān)聯(lián)的應(yīng)用程序。例如揍诽,您可以從“聯(lián)系人提供者”中檢索聯(lián)系人诀蓉,然后將ACTION_VIEW包含聯(lián)系人圖像的內(nèi)容URI 的意圖發(fā)送給圖像查看器。

合約類別

合同類定義用于幫助應(yīng)用程序處理內(nèi)容提供??者的內(nèi)容URI暑脆,列名渠啤,意圖動作和其他功能的常量。提供商不自動包含合同類別添吗;提供者的開發(fā)人員必須定義它們沥曹,然后將其提供給其他開發(fā)人員。Android平臺隨附的許多提供程序在軟件包中都有相應(yīng)的合同類android.provider碟联。

例如妓美,用戶詞典提供者具有UserDictionary包含內(nèi)容URI和列名常量的協(xié)定類 ±鸱酰“ words”表的內(nèi)容URI在constant中定義 UserDictionary.Words.CONTENT_URI部脚。的UserDictionary.Words類還包含列名常量,其在該示例段所使用本指南英寸 例如裤纹,查詢投影可以定義為:

String[] projection =
{
    UserDictionary.Words._ID,
    UserDictionary.Words.WORD,
    UserDictionary.Words.LOCALE
};

另一個合同類別ContactsContract適用于聯(lián)系人提供者躏鱼。此類的參考文檔包括示例代碼段。它的子類之一ContactsContract.Intents.Insert是合同類癞季,其中包含意圖和意圖數(shù)據(jù)的常量。

MIME類型參考

內(nèi)容提供者可以返回標準MIME媒體類型或自定義MIME類型字符串呕童,或兩者都返回。

MIME類型具有以下格式

type/subtype

例如淆珊,眾所周知的MIME類型text/html具有text類型和html子類型夺饲。如果提供程序為URI返回此類型,則意味著使用該URI的查詢將返回包含HTML標記的文本施符。

自定義MIME類型字符串往声,也稱為“供應(yīng)商特定的” MIME類型,具有更復(fù)雜的類型和子類型值戳吝。該類型的值總是

vnd.android.cursor.dir

對于多行浩销,或

vnd.android.cursor.item

單行。

該亞型是供應(yīng)商特定的听哭。Android內(nèi)置提供程序通常具有一個簡單的子類型慢洋。例如,當(dāng)“聯(lián)系人”應(yīng)用程序為電話號碼創(chuàng)建一行時陆盘,它將在該行中設(shè)置以下MIME類型:

vnd.android.cursor.item/phone_v2

請注意普筹,子類型的值只是phone_v2。

其他提供程序開發(fā)人員可以根據(jù)提供程序的權(quán)限和表名稱創(chuàng)建自己的子類型模式隘马。例如太防,考慮包含列車時刻表的提供商。提供者的權(quán)限是com.example.trains酸员,并且包含表Line1杏头,Line2和Line3。響應(yīng)內(nèi)容URI

content://com.example.trains/Line1

對于表Line1沸呐,提供程序返回MIME類型

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

響應(yīng)內(nèi)容URI

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

對于表Line2中的第5行,提供程序返回MIME類型

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

大多數(shù)內(nèi)容提供者為其使用的MIME類型定義合同類常量呢燥。ContactsContract.RawContacts例如崭添,Contacts Provider合同類定義CONTENT_ITEM_TYPE了單個原始聯(lián)系人行的MIME類型的常量 。

內(nèi)容URI部分中描述了單行的 內(nèi)容URI叛氨。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呼渣,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子寞埠,更是在濱河造成了極大的恐慌屁置,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仁连,死亡現(xiàn)場離奇詭異蓝角,居然都是意外死亡阱穗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門使鹅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來揪阶,“玉大人,你說我怎么就攤上這事患朱÷沉牛” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵裁厅,是天一觀的道長冰沙。 經(jīng)常有香客問我,道長执虹,這世上最難降的妖魔是什么拓挥? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮声畏,結(jié)果婚禮上撞叽,老公的妹妹穿的比我還像新娘。我一直安慰自己插龄,他們只是感情好愿棋,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著均牢,像睡著了一般糠雨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上徘跪,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天甘邀,我揣著相機與錄音,去河邊找鬼垮庐。 笑死松邪,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哨查。 我是一名探鬼主播逗抑,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寒亥!你這毒婦竟也來了邮府?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤溉奕,失蹤者是張志新(化名)和其女友劉穎褂傀,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體加勤,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡仙辟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年同波,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欺嗤。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡参萄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出煎饼,到底是詐尸還是另有隱情讹挎,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布吆玖,位于F島的核電站筒溃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏沾乘。R本人自食惡果不足惜怜奖,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翅阵。 院中可真熱鬧歪玲,春花似錦、人聲如沸掷匠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽讹语。三九已至钙皮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間顽决,已是汗流浹背短条。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留才菠,地道東北人茸时。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像赋访,于是被迫代替她去往敵國和親可都。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345