内容提供者---基础


原文译自:http://developer.android.com/guide/topics/providers/content-provider-basics.html#ContractClasses



内容目录

  1. 概述
    1. 访问提供者
    2. 内容唯一资源标识符
  2. 从提供者检索数据
    1. 请求读访问权限
    2. 构造查询
    3. 显示查询结果
    4. 从查询结果获取数据
  3. 内容提供者权限
  4. 插入,更新,删除数据
    1. 插入数据
    2. 更新数据
    3. 删除数据
  5. 提供数据类型
  6. 提供访问的替代形式
    1. 批量存取
    2. 通过意图访问数据
  7. 契约类
  8. MIME类型引用



内容提供者管理着对中央数据库的访问。提供者是android应用的组成部分,这类应用通常提供自己的UI以与数据协同工作。然而,内容提供者最主要意在供其它的应用使用,它们使用提供者客户端对象访问提供者提供者提供者客户端一起提供了一个一致的,标准的数据接口,它也处理进程间的通信和安全的数据访问。


该主题描述下列基本知识:


  • 内容提供其如何工作。
  • 用于检索内容提供者数据的API。
  • 用于内容提供者内插入,更新,删除数据的API。
  • 其它方便与提供者协作的API功能。



1. 概述



内容提供者把数据以一个或多个表的形式呈现给外部应用,这些表类似于创建自一个关系数据内的表。一行则代表提供者收集的某些数据类型的一个实例,且行内的每列代表实例的独立数据块(貌似原文有误! each row in the column应改为each column in the row)。


例如,用户词典作为Android平台内建的提供者之一,它存储着用户想要保存的非标准拼写的单词。表一展示了数据在提供者表里可能看上去的样子:


表一:用户词典表样例

word app id frequency locale _ID
mapreduceuser1100en_US1
precompileruser14200fr_FR2
appletuser2225fr_CA3
constuser1255pt_BR4
intuser5100en_UK5


表一中,每行代表一个可能不会在标准词典内找到的单词实例。每列代表那个单词的一些数据,例如locale,它是单词初次出现的语言环境。列的标题是列的名字,它们被存储在提供者内。为了使用行的locale数据,需要引用行的locale列。就这个提供者而言,_ID列作为“主键”列,并由提供者自动维护。


注意提供者不必用有一个主键,并且也不需使用_ID作为主键的列名,如果它存在的话。然而,如果想要把来自提供者的数据绑定到ListView时,其中的一个列名必须是_ID。这个要求在显示查询结果章节里详细地说明。



1.1 访问提供者



应用通过ContentResolver客户端对象从内容提供者存取数据。客户端对象有一些调用提供者对象内同名方法的方法,提供者对象是ContentProvider的具体子类中的一个实例。ContentResolver方法提供了持久存储的基本“CRUD”功能(创建,取回,更新,删除)。


位于客户端应用进程内的ContentResolver对象和位于持有提供者应用内的ContentProvider对象自动地处理进程间通信。ContentProvider对象在它的数据仓库和如同表的数据外观之间也扮演了一个抽象层的角色。


注意:为了访问提供者,应用通常必须在清单文件内请求指定的权限。这点在内容提供者权限章节内将更详细地阐述。


例如,为了从用户词典提供者内获取一个单词和它们的语言环境列表,需要调用ContentResolver.query()。该query()方法调用由用户词典提供者定义的ContentProvider.query()方法。下面的代码行展示了一个ContentResolver.query()调用:


mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
    mProjection,                        // The columns to return for each row
    mSelectionClause                    // Selection criteria
    mSelectionArgs,                     // Selection criteria
    mSortOrder);                        // The sort order for the returned rows


表2显示了 query(Uri,projection,selection,selectionArgs,sortOrder)的参数是如何匹配一个SQL 的SELECT语句:

表2: Query()方法与SQL查询对照。

query() 参数
SELECT 关键字/参数 说明
UriFROM table_name Uri 映射到提供者内名为table_name的表。
projectioncol,col,col,...  projection 是一个列(名)数组,被取回的每行中应该包含它们。
selectionClauseWHERE col = value selectionClause 指定了选择行的条件。
selectionArgs(没有确切的等价物。selectionArags参数替换选择子句内的?占位符。)
sortOrderORDER BY col,col,...  sortOrder 指定了行在返回的Cursor内的顺序。


1.2 内容唯一资源标识符


内容唯一资源标识符是一个URI,它标识了提供者内的数据。内容唯一资源标识符包括完整的提供者符号名(它的授权authority))和指向表的名字(一个路径(path))。当调用客户端方法访问提供者内表时,该表的内容唯一资源标识符是重参数之一。


先前的代码行里,常量CONTENT_URI包含了用户词典的“word”表的内容唯一资源标识符。ContentResolver对象解析出该标识符的授权(authority),并使用它到一个已知提供者的系统表进行比较来“解析”该提供者。然后,ContentResolver 可以把查询参数分发给正确的提供者


ContentProvider使用URI的路径部分来选择访问的表。提供者通常有它保留的每个表的路径。在先前的代码行里,“word”表的完整URI是:content://user_dictionary/words


user_dictionary”字符串是提供者的授权,“words”字符串是表的路径。字符串“content://”(方案)是经常存在的,并标识这里作为一个内容唯一资源标识符。


通过在URI后面追加一个ID值,很多提供者允许访问表内的一个单独行。例如,为了从用户词典内取回_ID值是4的行,可以使用这个URI:


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


当已取回了一组行并且接着想要更新或删除它们中的一个时,常常会使用id值。


注意UriUri.Builder类包含一些方便的方法用以从字符串构造格式规范的Uri对象。ContentUris包含了向URI追加id值的方法。上面的代码段使用withAppendedId()向用户词典URI追加了一个id。




2. 从提供者检索数据



这章节以用户词典提供者作为例子, 阐述如何从提供者检索数据。


为了清晰起见,该章节的代码段在“UI 线程”上调用ContentResolver.query()。然而,在实际的代码里,应该在一个单独的线程里执行异步地查询操作。一种这样做的方法是使用CursorLoader类,它在 Loaders指南内有更详细的介绍。同时,代码行只是片段而已;它们没有展示一个完成应用。


为了从提供者检索数据,遵循以下基本步骤:

  1. 请求提供者的读访问权限。
  2. 定义向提供者发送查询的代码。




2.1  请求提供者的读访问权限



为了从提供者检索数据,应用需要请求提供者的“读访问权限”。不能在运行时请求该权限,相反,必须在清单文件里使用<uses-permission>元素和由提供者定义确切权限名来指定应用需要此权限。当在清单文件里指定这个元素时,实际上等同于为应用“请求”权限。当用户安装应用时,他们暗中地同意了权限请求。


为了查找正在使用的提供者的确切读访问权限,以及提供者使用的其它访问权限名,请查阅提供者的文档。访问提供者的权限作用在内容提供者权限章节有更详细地介绍。


用户词典提供者定义的权限android.permission.READ_USER_DICTIONARY位于它的清单文件里,所以,一个想要从该提供者读取数据的应用必须请求这个权限。




2.2  构造查询



检索提供者数据的下一步是构造查询。为了访问用户词典提供者这个代码片段定义了一些变量:


// 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 mSelectionClause = null;

// Initializes an array to contain selection arguments
String[] mSelectionArgs = {""};



下一个代码段展示了以用户词典提供者为例如何使用ContentResolver.query()。提供者客户端查询类似于SQL查询,它包含了一个待返回列的集合,一个选择字句集合及一个排列顺序。


查询返回的列的集合称之为投射(mProjection变量).

指定了返回行的表达式被拆分成选择字句和选择参数。选择字句是一个逻辑,布尔表达式,列名和值(mSelection变量)的组合。如果指定了可替代参数?而不使用值,查询方法从选择参数数组(mSelectionArgs变量)中返回值。


在下一个代码段里,如果用户不键入一个单词,那么选择字句被设置为空,并且查询返回提供者内所有的单词。如果用户键入一个单词,则选择字句被设置成UserDictionary.Words.Word + " = ?",并且选择参数数组的第一个元素被设置成用户键入的单词。



/*
 * This defines a one-element String array to contain the selection argument.
 */
String[] mSelectionArgs = {""};

// Gets a word from the UI
mSearchString = mSearchWord.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(mSearchString)) {
    // Setting the selection clause to null will return all words
    mSelectionClause = null;
    mSelectionArgs[0] = "";

} else {
    // Constructs a selection clause that matches the word that the user entered.
    mSelectionClause = " = ?";

    // Moves the user's input string to the selection arguments.
    mSelectionArgs[0] = mSearchString;

}

// 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
    mProjection,                       // The columns to return for each row
    mSelectionClause                   // Either null, or the word the user entered
    mSelectionArgs,                    // Either empty, or the string the user entered
    mSortOrder);                       // 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, frequency, locale FROM words WHERE word = <userinput> ORDER BY word ASC;


在这个SQL语句里,使用的是实际的列名,而不是契约类的常量。


包含免受恶意输入


如果由内容提供者管理的数据在SQL数据库里,把外部不可信数据包含进原始SQL语句可能导致SQL注入攻击。

考虑这个选择字句:


// Constructs a selection clause by concatenating the user's input to the column name
String mSelectionClause =  "var = " + mUserInput;

如果你这样做了,那就是允许用户接连怀疑恶意的SQL到你的SQL语句上。例如, 用户可能键入“nothing;DROP TABLE*;”对于mUserInput来说,它产生一个选择字句var = nothing;DROP TABLE*;.因为选择字句被看做是一个SQL语句,这有可能导致提供者在底层的SQLite数据库中删除所有的表(除非提供者被设置可以捕获SQL注入攻击的企图)。


为了避免该问题,使用一个把?作为一个可替代的参数的选择字句及一个单独选择参数数组。当这样做时,用户的输入被直接绑定到查询上,而不被解释作为SQL语句的一部分。因为它没有被视为SQL, 所以用户的输入不能注入恶意的SQL。替代使用连接来包含用户输入,可以使用如下的选择字句:


// Constructs a selection clause with a replaceable parameter
String mSelectionClause =  "var = ?";


像这样设置选择参数数组:


// Defines an array to contain the selection arguments
String[] selectionArgs = {""};



像这样在选择数组中放置一个值:


// Sets the selection argument to the user's input
selectionArgs[0] = mUserInput;



一个使用?作为可替代参数的选择字句和一个选择参数数组是指定选择的首选方式,即便提供者不是基于SQL数据库。



2.3 显示查询结果



ContentResolver.query()客户端方法通常返回一个游标(Cursor), 它包含了由查询的projection参数为可以匹配查询选择条件的行而指定的列。游标Cursor 对象提供随意读取访问它包含的行和列。使用游标(Cursor)方法可以遍历结果内所有的行,判定每列的数据类型,提取列内的数据,及检查结果的其他属性。


某些游标实现在提供者数据变化时自动更新对象,或者当游标改变时触发观察者方法,或者二者兼备。


注意:基于查询对象的性质,提供者可能限制对列的访问。例如,联系人提供者(Contacts Provider)限制同步适调器对某些列进行访问。所以它不会把这些列返回给活动或服务。

 

如果没有符合选择标准的行,提供者返回一Cursor.getCount()为0(空游标)的游标Cursor对象。


如果发生内部错误,查询结果依具体的提供者而定。它可能返回空,或者抛出异常。


因为游标 Cursor是一个行的“列表”, 一个显示游标内容的好方法是通过SimpleCursorAdapter把游标链接到列表视图(ListView)上。


接下来的代码片段继续了先前的代码段的代码。它创建一个SimpleCursorAdapter对象(包含查询取回的游标(Cursor)),然后再把该对象设置为列表视图(ListView)的适调器:


// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mWordListColumns =
{
    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[] mWordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
mCursorAdapter = 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
    mWordListColumns,                      // A string array of column names in the cursor
    mWordListItems,                        // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);


注意:为使游标Cursor支持列表视图ListView,游标必须包含一个名为_ID的列。因为这点,先前显示的查询取回“word”表的_ID列,即使ListView没有显示它。这点限定也解释了为什么大部分提供者的每个表都有一个_ID列。




2.4 从查询结果获取数据



不只是简单地显示查询结果,还可以使它们用于其他的任务。例如,可以从用户词典检索拼写,然后在其他的提供者里查阅它们。为做到这点,遍历游标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.
}


游标的实现包括几个“get”方法以取回不同类型的数据。例如,上一个的代码段使用getString()方法。它们也有一个getType(),其返回一个表明列的数据类型的值。



3. 内容提供者权限



提供者应用能够指定权限,其他的应用要想访问提供者数据则必须拥有这些权限。这些权限确保用户知道其他应用将试图访问什么样的数据。基于提供者应用的要求,其他应用请求权限以达到访问提供者的目的。最终用户在安装应用时会看到被请求的权限。


如果提供者应用不指定任何权限,那么其他的应用则没有权限访问提供者的数据。然而,不管是否被指定权限,提供者应用内的组件通常拥有充分的读写权限。


如上所述,用户词典提供者要求有android.permission.READ_USER_DICTIONARY权限方可从其内取出数据。该提供者有单独的android.permission.WRITE_USER_DICTIONARY权限为了插入,更新,或删除数据。


为获得必要的权限访问提供者,应用以它清单文件里<uses-permission>元素来请求它们。当Android包管理器安装该应用时,用户需要同意应用请求的全部权限。如果用户同意了它们,包管理器方可继续进行安装;如果用户不同意它们,包管理器中止安装。


下面的<uses-permission>元素请求读访问用户词典提供者:


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

权限对提供者访问的影响在 安全性与权限(Security and Permissions)指南里有更详细的解释。




4.  插入,更新和删除数据



以从提供者取数据的方式一样,可以使用提供者客户端与提供者的ContentProvider 间的交互来修改数据。以传递给ContentProvider方法的参数来调用对应的ContentResolver方法。提供者和提供者客户端将自动地处理安全和进程间通信。



4.1 插入数据



为把数据插入进提供者里, 调用ContentResolver.insert()方法。该方法把一个新的行插入进提供者并返回该行的内容唯一资源标示符(URI)。下面的代码段展示了如何把一个新的单词插入到用户词典提供者内:


// Defines a new Uri object that receives the result of the insertion
Uri mNewUri;

...

// Defines an object to contain the new values to insert
ContentValues mNewValues = new ContentValues();

/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value"
 */
mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
mNewValues.put(UserDictionary.Words.WORD, "insert");
mNewValues.put(UserDictionary.Words.FREQUENCY, "100");

mNewUri = getContentResolver().insert(
    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
    mNewValues                          // the values to insert
);


新行的数据放进一个单独 ContentValues对象,形式上类似于单行游标。该对象内的列不必拥有相同的数据类型,并且如果根本不想指定一个值,可以使用ContentValues.putNull()把列设置为空。


上面的代码段没有添加_ID列,因为该列是自动被维护的。提供者为每个被添加的行赋予唯一的_ID值。提供者通常使用该值作为表的主键。


newUri 中返回的内容唯一资源标示符(URI)标识最新插入的行,通过如下的格式:


content://user_dictionary/words/<id_value>

<id_value>是新行的_ID内容。大部分提供者能够自动地探测到内容URI的此种格式,然后在特定的行上执行被请求的操作。

为了从返回的Uri里得到_ID值,可以调用ContentUris.parseId().




4.2 更新数据



为更新一行,就像处理插入和查询一样,使用携带有被更新值的ContentValues对象及选择标准。客户端使用ContentResolver.update()方法。只需要做的是为正在更新的列把值添加到ContentValues对象。如果想清除列的内容,把值设置为空。


接下来的代码段把所有当地语言为“en”的行的当地语言改为空。返回值为被更新行的数目:


// Defines an object to contain the updated values
ContentValues mUpdateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
String[] mSelectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int mRowsUpdated = 0;

...

/*
 * Sets the updated value and updates the selected words.
 */
mUpdateValues.putNull(UserDictionary.Words.LOCALE);

mRowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mUpdateValues                       // the columns to update
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);


当调用 ContentResolver.update()时依然需要审查用户的输入。要了解更多,阅读防范恶意输入章节。


4.3 删除数据



删除行类似于行数据的取回:为想要删除的行指定选择标准,然后客户端方法返回被删除行的数目。接下来的代码段删除其appid匹配“user”的行。方法返回被删除行的数目。


// Defines selection criteria for the rows you want to delete
String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] mSelectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int mRowsDeleted = 0;

...

// Deletes the words that match the selection criteria
mRowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);


当调用 ContentResolver.delete()时依然需要审查用户输入。为了解更多,阅读防范恶意输入章节。




5. 提供者数据类型




内容提供者能够提供很多不同的数据类型。用户词典提供者仅提供了文本(text),但是提供者也能提供以下格式:

  • 整型
  • 长整型(long)
  • 浮点
  • 长浮点 (double)


提供者常常使用的其他数据类型是二进制大对象(BLOB), 该对象被实现为64KB字节的数组。通过查看游标Cursor类的“get”方法,会看到可用的数据类型。


提供者内每列的数据类型通常被列在它的文档里。用户词典提供者的数据类型在它的契约类

UserDictionary.Words (契约类在Contract Classes章节讲述)参考文档里列出。可以调用Cursor.getType()来确定数据类型。


提供者也维护着它们定义的每个内容唯一资源标识符URI的MIME数据类型信息。可以使用MIME类型信息来得知应用是否可以处理来自提供者数据,或者选择一个基于MIME类型的处理类型。当与含有复杂数据结构或文件的提供者协作时,通常需要MIME类型。例如,联系人提供者内的ContactsContract.Data 使用MIME类型来标注存储在每行内的联系方式数据类型。为得到内容唯一资源标识符URI对应的MIME类型,调用ContentResolver.getType()


MIME类型参考  章节描述了标准的和自定义的MIME类型语法。  




6. 提供者访问的替代形式




三个替代的提供者访问形式在应用开发中是很重要的:

  • 异步查询: 需要在一个单独的线程里进行查询。 做到这点的一种方式是使用CursorLoader 对象。Loaders指南内的实例展示了如何做到这点。
  • 通过意图访问数据: 尽管不能把意图直接发送给提供者,但是可以想提供者应用发送意图,提供者应用通常是修改提供者数据的最好装备。


接下来的章节阐述经由意图的批量访问和修改。


6.1 批量访问


对提供者的批量访问对于插入大量行数,或者通过相同的方法把行插入多个表,或者通常作为事务来执行一组跨进程边界的操作(原子操作)都十分有用。


为以"批量模式"访问提供者,需要创建一组ContentProviderOperation对象,然后使用ContentResolver.applyBatch()把他们分派给内容提供者。把内容提供者的权限(authority)传递给该方法,而不是特定的内容URI, 该方法允许组内的每个ContentProviderOperation对象针对不同的表工作。对ContentResolver.applyBatch()的调用返回一组结果。


ContactsContract.RawContacts契约类里包括一个展示批量插入的代码段。 联系人管理器(Contact Manager)示例应用在它的ContactAdder.java文件里包含一个批量访问的例子。



6.2 通过意图访问数据


意图能够提供对内容提供者的间接访问。你可以允许使用者访问提供者数据,即使你的应用不具有访问权限,要么从拥有权限的应用获得一个结果意图,要么启动一个拥有权限的应用并让使用者在其内工作。


获得临时访问权限


通过向拥有权限的应用发送意图,并回收到含有“URI”权限的结果意图。即使不具有适当的访问权限,也能访问内容提供者的数据。这些特定内容URI的权限持续到接受它们的活动被结束为止。具有永久权限的应用通过在结果意图里设置标志赋予临时权限:



注意:这些标志不为其权限含在内容URI里的提供者提供一般的读写访问。访问只是针对URI它自己的。


通过使用<provider>元素的android:grantUriPermission属性,以及<provider>元素的<grant-uri-permission>子元素,提供者在它的清单文件里为内容URIs定义URI权限。在安全和权限指南的“URI权限”章节里有关于URI权限机制更详细的说明。


例如,即使没有READ_CONTACTS权限,也可以从联系人提供者内取出一个联系人数据。你可能会这样做,应用可在他或她的生日时向联系人发送电子贺卡。最好让用户控制哪些联系人可被用于应用,而不是请求READ_CONTACT ,因为它可以访问用户的所联系人及他们的全部信息。为做到这点,使用如下过程:


  1. 应用使用startActivityForResult()方法发送一个含有动作ACTION_PICK“联系人”MIME类型   

    CONTENT_ITEM_TYPE 的意图。

  2. 在选择活动(people应用里的)里,用户选择一个联系人更新。这种情况下,选择活动调用setResult(resultcode, intent) 设置一个反馈给应用的意图。该意图包含用户选择的联系人内容URI, 及“extras”标志FLAG_GRANT_READ_URI_PERMISSION。这些标志给应用授予URI权限,以读取由内容URI指定的联系人数据。
  3. 活动返回到前台,接着,系统调用活动的onActivityResult()方法。该方法接收由选择活动创建的结果意图。
  4. 通过结果意图内的内容URI,可以读取联系人提供者内的联系人数据,尽管没有在清单文件里向提供者请求持久的读访问权限。接着,获取联系的生日信息或者他或她的email地址,然后发送电子贺卡。

使用其他的应用


一个允许用户修改没有访问权限的数据的简单方法是激活一个拥有权限的应用并让用户在此开展工作。

例如,日历应用接受 ACTION_INSERT意图,它允许你激活日历应用的插入UI。可以把“额外”数据放置在此意图

里,日历应用使用它来预填充UI。因为周期性事件的语法复杂,因此把事件插入进日历提供者的首选方法是通过

ACTION_INSERT激活日历应用,然后让用户在那里插入事件。



7.契约类



契约类定义了一些常量,他们有助于应用与内容URIs, 列名,意图动作,以及内容提供者其他特性的协同工作。

内容提供者不会自动包含契约类;提供者的开发人员必须定义他们,然后使他们对其他开发者可用。在android.provider包里,由Android平台包含的许多提供者都有相对应的契约类。


例如,用户词典提供者有一个UserDictionary契约类,它包含内容URI和列名常量。“word”表的内容URI是通过常

UserDictionary.Words.CONTENT_URI来定义的。UserDictionary.Words也包含列名常量,它们被用在本指南里

的实例代码片段中。例如,查询投射可被定义为:


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

联系人提供者的另一个契约类是ContactsContract。这个类的参考文档内包含了实例代码片段。该类的子类之一,ContactsContract.Intents.Insert,是一个包含了意图常量和意图数据的契约类。





8. MIME类型引用



内容提供者能够返回标准的MIME媒体类型,或者是自定义MIME类型字符串,或是它们两种。

MIME类型的格式如下:


type/subtype

例如,众所周知的MIME类型text/html拥有text类型及html子类型。如果提供者为URI返回此类型,这意味着一个使

用那个URI的查询将返回包含有(html)HTML标签的(text)文本。


自定义MIME类型字符串也被称之为“厂商特定”的MIME类型,它有复杂的类型子类型值。该类型值通常是


vnd.android.cursor.dir

用于多行,或者是


vnd.android.cursor.item

于单行。


子类型是提供者特定的。平台内置的提供者通常有一个简单的子类型。例如,当联系人应用为一电话号码创建一行

时,它在行内设置如下的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内第五行对应的内容URI是


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

该提供者返回MIME类型


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


大部分内容提供者为它们使用的MIME类型定义了契约类常量。例如,联系人契约类ContactsContract.RawContacts一单个的原始联系行定义了CONTENT_ITEM_TYPE常量。


Content URIs章节里介绍单行内容URIs。




                                                                                                                                                                                                                                                                                                                           2012年7月27日    毕。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值