ContentProvider Basic

内容提供器管理访问结构化的数据。他们压缩数据,提供定义数据安全的机制。内容提供器是标准接口,连接数据在一个进程有代码运行在另一个进程。Content providers are the standard interface that connects data in one process with code running in another process.
当我们想通过内容提供器访问数据,应该使用程序上下文中的ContentResolver对象和提供者交流作为客户端。ContentResolver对象和提供者对象交流,提供者是一个实现ContentProvider的类的实例。提供者对象接收客户端的数据请求,执行请动作,返回结果。
如果我们不打算分享我们的数据给其他应用,不需要开发自己的提供器。然后,我们需要自己的提供器提供在应用中的自定义搜索建议。也需要自己的提供器如果想从我们的应用复制或粘贴复杂数据或文件到其他应用程序。
安卓自己由内容提供器管理数据,比如音频、视频、图片和个人联系人信息。很多啊。
下面开始学习。
Content Provider Basics
一个内容提供器管理访问一个中央处理库。一个提供器是安卓应用的一部分,经常提供自己的UI为了和数据工作。然而,内容提供器通常是为了被其他应用使用,使用一个提供器客户端对象访问提供器。在一起,提供器和提供器客户端提供一致的,标准的接口给数据,也处理进程内的通信和安全数据访问。
讨论如下:
(1) 内容提供器如何工作
(2) 从内容提供器接收数据使用的API
(3) 其他帮助使用提供器的API特定
Overview
一个内容提供器展示数据给外部应用作为一个或多个表格,和关系数据库中的表格类似。一行代表一个类型,每列代表一个片段。
举例,一个安卓平台内置的提供器是用户词典,保存了用户想保存的不标准的单词的拼写。下表说明了在提供器表格中数据可能的样子:
Table 1: Sample user dictionary table. 
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
    在上表,每行代表一个单词的实例,这个单词可能在标准词典中找不到。每列代表单词的一些数据,比如他第一次被遇到的地方。列的头是保存在提供器的列的名称。要参考一行的locale,我们参考他的locale列。对于这个提供器,_ID列担任一个“primary key”列,这个提供器自动维护。
Note: 一个提供器不用要求必须有一个primary key,并且它也不被要求使用_ID作为一个primary key的列名称如果有一个。然后,如果想从提供器绑定数据到一个ListView,其中一个列名必须是_ID.下面会讨论。
A provider isn't required to have a primary key, and it isn't required to use _ID as the column name of a primary key if one is present. However, if you want to bind data from a provider to a ListView, one of the column names has to be _ID. This requirement is explained in more detail in the section Displaying query results. 
Accessing a provider
一个应用使用一个ContentResolver客户端对象访问来自内容提供器的数据。这个对象有调用提供器对象的同名方法的方法,提供器是一个ContentProvider的具体子类。ContentResolver方法提供持久化数据的基本的CRUD操作。
ContentResolver对象在客户端程序的进程,ContentProvider对象在应用中,应用拥有提供器对象,自动处理线程内的通信。ContentProvider也作为一个抽象层在他存储的对象和数据作为表格的外部界面之间。
The ContentResolver object in the client application's process and the ContentProvider object in the application that owns the provider automatically handle inter-process communication. ContentProvider also acts as an abstraction layer between its repository of data and the external appearance of data as tables. 
Note: 要获取一个提供器,我们的应用通常必须在他的manifest文件中要求特定的权限。这个在Content Provider Permission中讨论。
    打个比方,要从用户词典提供器获得一些单词和他们的locales,我们调用ContentResolver.query方法。这个query方法调用用户词典提供器定义的ContentProvider.query方法。下面是一个ContentResolver.query调用:


// Queries the user dictionary and returns results
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
Table 2 shows how the arguments to query(Uri,projection,selection,selectionArgs,sortOrder) match an SQL SELECT statement: 
Table 2: Query() compared to SQL query. 


query() argument SELECT keyword/parameter Notes
Uri FROM table_name Uri maps to the table in the provider named table_name.Uri指向提供器table_name命名的表格
projection col,col,col,... projection is an array of columns that should be included for each row retrieved.  列数组每行返回应该包含(就是返回的列)
selection WHERE col = value selection specifies the criteria for selecting rows. (where 语句,选择的行的标准)
selectionArgs (No exact equivalent. Selection arguments replace ? placeholders in the selection clause.)
sortOrder ORDER BY col,col,... sortOrder specifies the order in which rows appear in the returned Cursor. (排序,指定Cursor中每行的排序)




Content URIs统一资源标识符(Uniform Resource Identifier,或URI)
    一个内容URI是一个确定提供器中数据的URI。内容提供器的URI包括整个提供器(its authority)的象征性名字和一个指向一个表格的名称(a path)。当我们调用一个客户端方法取得内容提供器中的一个表格,这个表格的content URI是其中一个参数。
A content URI is a URI that identifies data in a provider. Content URIs include the symbolic name of the entire provider (its authority) and a name that points to a table (a path). When you call a client method to access a table in a provider, the content URI for the table is one of the arguments. 
在之前的代码中,常量CONTENT_URI包含用户字典的“words”表的content URI。ContentResolver对象解析出URI的authority(指向这个提供器),然后使用它去决定提供器通过和系统已知的提供器表格对比这个authority。ContentResolver然后可以分派查询参数给正确的提供器。
ContentResolver使用content URI的path部分去选择要访问的表。一个提供器通常为每一个暴露的表格提供一个path。
在上面的代码,“words”表格的全URI是:
content://user_dictionary/words


“user_dictionary”字符串是提供器的authority,“words”字符串是表的路径。“content://”(the scheme)字符串永远存在,并且标识这个是一个content URI。
    许多内容提供器允许我们访问表中的一行数据通过在URI末尾增加一个ID值。举例,从用户词典活动_ID为4的一行,应该这样使用URI:
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);


我们经常使用id 值当你获取多行然后想更新或者删除其中一行。
Note: Uri和Uri.Builder类,包含方便的访问构造一个复合规则的Uri对象从String。ContentUris包含方便的方法添加id值给URI。之前的片段使用withAppendId方法去增加一个id给content URI。
Retrieving Data from the Provider
________________________________________
这部分讨论如何从提供器获得数据,使用用户词典提供器作为例子。
为了清晰,这部分代码片段调用ContentResolver.query在UI线程。在实际代码,然后,应该在一个独立的线程异步执行查询。一种去这么做的方法是使用CursorLoader类,在Loader中讨论。同理,代码段仅仅是片段,他们不展示一个完全的应用。
去从提供器获得数据,跟随下面的基本步骤:
1. 为提供器请求读权限Request the read access permission for the provider. 
2. 定义一个代码,发送查询给提供器Define the code that sends a query to the provider. 
Requesting read access permission
要从提供器获得数据,我们的应用需要为提供器“read access permission”。
我们不能在运行时请求这个权限,相反,必须在manifest中指定权限,使用<uses-permission>元素和提供器定义的准确的权限。当我们在manifest中指定这个元素,我们实际上在我们的应用请求这个权限。当用户安装我们的应用,他们默认允许这个请求。
为了找到我们使用的提供器的准确的可读权限名称,和这个提供器使用的其他访问权限,访问Provider文档。
访问提供器的权限会在Content Provider Permissions详细讨论。
用户词典提供器在manifest文件定义权限android.permission.READ_USER_DICTIONARY,所以,一个应用想从这个提供器读必须请求这个权限。
Constructing the query
   从提供器获得数据的下一步是构造一个查询。第一个片段定义了一些访问用户词典提供器的变量:
// 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查询类似,并且它包含要返回的一些列,一些选择标准和一个排序。
查询应该返回的列的结合叫projection(映射,mProjection变量)。
指定获取返回的行的表达式被分割成一个选择子句(selection clause)和选择参数(select arguments)。选择子句是逻辑和布尔表达式,列名称和值(mSelectionClause变量)的组合,如果指定可替换的变量 ? 代替值,查询方法从选择参数数组获得值(mSelectionArgs变量)。
The expression that specifies the rows to retrieve is split into a selection clause and selection arguments. The selection clause is a combination of logical and Boolean expressions, column names, and values (the variable mSelectionClause). If you specify the replaceable parameter ? instead of a value, the query method retrieves the value from the selection arguments array (the variable mSelectionArgs). 
    在下一个片段,如果用户不输入一个单词,选择子句被设置为null,同时这个查询返回提供器的所有单词。如果用户输入一个单词,选择子句被设置成
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 = UserDictionary.Words.WORD + " = ?";


    // 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, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
在这个SQL语句,真实的列名被使用而不是抽象类包含。
In this SQL statement, the actual column names are used instead of contract class constants. 
Protecting against malicious input
    如果内容提供器管理的数据是在一个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数据库为基础。
Displaying query results
    ContentResolver.query客户端方法总是返回一个Cursor包含由查询的projection为符合筛选标注的行指定的列。一个Cursor对象提供他包含的行和列的随机访问。使用Cursor方法,可以遍历结果中的行,判断每列的数据类型,获得一列的数据,检查结果的其他属性。一些Cursor实现自动更新对象当提供器数据改变,或者触发一个监听器的方法当Cursor改变,或者全部。
Note: 注意,一个提供器可能限制列的访问基于产生这个查询的对象的性质。举例,联系人提供器限制了一些同步适配器的列的访问,所有他不会返回给活动或服务。
    如果没有行匹配选择 标准,提供器返回一个Cursor.getCount为哦的Cursor(一个空Cursor)

如果一个内部的错误发生,查询的结果取决于特别的提供器。他可能选择返回null,或者抛出一个异常。
因为Cursor是行的list,一个好的方式去展示Cursor的内容是通过一个SimpleCursorAdapter连接到ListView上。
    下面的片段继续上面的代码片段,创建一个SimpleCursorAdapter对象包含查询获得的Cursor,然后把这个对象设置为一个ListView的adapter。
    // 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);
Note: 为了返回一个ListView包含一个Cursor,cursor必须包含一个列叫做_ID.因为这个,查询展示之前获得的_ID列为这个“word”表,即使ListView不展示他。这个限制也解释了为什么大多数的提供器为每一个表提供一个 _ID列。
Getting data from query results
出来简单展示查询结果,可以使用它们做其他任务,比如,可以检索它们的拼写从用户词典然后在其他提供器查看它们。为了这样做,遍历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的实现包含几个get方法从对象获得不同类型的数据。举例,上一个片段使用getString。它们也有一个getType方法返回一个暗示这个列的数据类型的值。
Content Provider Permissions
________________________________________
一个提供器程序可以指定一个权限,其他应用程序必须拥有去访问提供器数据。这些权限确保用户知道一个应用将要尝试访问什么数据。以提供器的要求为基础,其他程序请求他们需要的权限为了访问提供器。终端用户看到这些请求权限,当他们安装这个应用。
如果一个提供器程序没有指定任何权限,其他应用程序没有办法访问提供器的数据。然后,提供器程序中的组件拥有全部的读写访问,不管指定的权限
    正如之前提到的,用户词典提供器请求android.permission.READ_USER_DICTIONARY 权限去获得数据。提供器有一个独立的android.permission.WRITE_USER_DICTIONARY 权限,去插入,更新或修改数据。
为了需要获得访问一个提供器的权限,一个应用在他的manifest文件中国使用<uses-permission>元素请求权限。当安卓包管理器安装这个应用,用户必须同意应用请求的所有权限。如果用户同意所有,包管理继续安装,如果不同意,放弃安装。
下面<uses-permission>元素要求读权限给用户词典提供器。
<uses-permission android:name="android.permission.READ_USER_DICTIONARY">
更多信息,查看安全和权限指导。
The impact of permissions on provider access is explained in more detail in the Security and Permissions guide.
Inserting, Updating, and Deleting Data
________________________________________
    我们从提供器获得数据的同样的方式,我们也使用提供器客户端和提供器的ContentProvider直接的交互修改数据。可以调用ContentResolver的方法随着参数,参数会被传递到ContentProvider的响应方法。提供器和提供器客户端自动处理安全和进程内的通信。
In the same way that you retrieve data from a provider, you also use the interaction between a provider client and the provider's ContentProvider to modify data. You call a method of ContentResolver with arguments that are passed to the corresponding method of ContentProvider. The provider and provider client automatically handle security and inter-process communication. 
Inserting data
    为了插入数据到一个提供器,调用ContentResolver.insert方法。这个方法插入新的一行到提供器中,并且为那一行返回一个content URI。这个片段展示了如何插入一行新数据到用户词典提供器。
// Defines a new Uri object that receives the result of the insertion
Uri mNewUri;  定义一个新的Uri对象,接收插入语句的结果


...
定义一个对象,包含新的数据去插入。
// Defines an object to contain the new values to insert
ContentValues mNewValues = new ContentValues();


/*设置每列的值和插入的单词。Put方法的参数是列名和值。
 * 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对象,和一行的cursor的格式类似。对象中的列不需要有同样的数据类型,如果我们不想指定一个值,可以使用ContentValues.putNull设置一列为null。
片段没有添加_ID列,因为这列是自动维护的。提供器为添加的每一列分配一个唯一的_ID值。提供器通常使用这个值作为表的primary key。
newUri中返回的content URI确定新添加的行,使用下面的格式:
content://user_dictionary/words/<id_value>


<id_value>的新行的_ID。大多数提供器可以自动发现这种格式的content URI,然后在特殊行执行请求操作。
    要从返回的Uri获得_ID的值,调用ContentUris.parseId
Updating data
要修改一行,可以使用一个带有更新数值的ContentValue对象,就像在插入的时候那样,和一个选择条件就像在查询中一样。这个客户端方法我们要用的是ContentResolver.update。我们仅仅为我们要更新的列添加值给ContentValues对象.如果想清空一列的内容,设置值为null。
接下来的片段改变所有locale有语言“en”的行变为locale为null。返回值的更新的行数:
// 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(),应检查用户输入,防止SQL注入。 


Deleting data
删除行和获取行信息类似:指定想删除行的选择条件,然后客户端方法返回删除的行数。接下来的片段删除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 (),应检查用户输入,防止SQL注入。 
Provider Data Types
________________________________________
    内容提供器能提供许多不同数据类型。用户词典提供器仅仅提供文本,但是提供器也可以提供如下格式:
integer 
long integer (long) 
floating point 
long floating point (double) 
    另一个提供器常用的数据类型是Binary Large Object(BLOB)实现作为一个64KB比特数组。可以查看可用的数据类型通过查看Cursor类的get方法。
    提供器中的每行的数据类型通常在他的文档列出来。用户词典提供器被列在他的合同类UserDictionary.Words的参考文档。我们也可以通过调用Cursor.getType判断数据类型。
提供器也为他们定义的每一个Conten URI维护一个MIME数据类型信息。我们可以使用MIME类型信息去找出是否我们的应用可以处理提供器提供的数据,或者去以MIME类型为基础选择一个处理类型。我们通常需要MIME类型当我们和一个包含复杂数据结构或文件的提供器工作。举例,ContactsContract.Data表在Contacts Provider中使用MIME类型去标注保存在每一行的contact数据类型。获得响应一个content URI的MIME类型,调用ContentResolver.getType方法。
MIME Type 参考描述了标准的和自定义的MIME类型的语法。
Alternative Forms of Provider Access(提供器访问的替代格式)
________________________________________
Three alternative forms of provider access are important in application development: 
    在应用开发中,提供器访问的三个替代性格式是重要的。
Batch access: 可以使用ContentProviderOperation类中的方法创建一批访问调用,然后使用ContentResolver.applyBatch应用他们。 
Asynchronous queries:应该在一个分离的线程执行查询,一个这样做的方法是使用一个CursorLoader对象。Loader指导演示了如何做。 
Data access via intents:虽然不能直接给一个提供器发送一个intent,可以发送intent给提供器程序,提供器程序通常是修改提供器数据最准备充分的。 Although you can't send an intent directly to a provider, you can send an intent to the provider's application, which is usually the best-equipped to modify the provider's data. 
Batch access
提供器的批访问非常有用,在同一个方法调用插入大量行或者在多个表插入行,或者通常执行一系列跨进程边界操作作为一个事务(一个原子操作)。
为了以“batch mode”访问一个提供器,我们创建一个ContentProviderOperation的对象的数组,然和使用ContentResolver.applyBatch方法把他们分发给一个内容提供器。我们传递内容提供器的authority给这个方法,而不是一个具体的content URI(只指定到authority,不需要path??)。这允许数组中的每一个ContentProviderOperation对象和一个不同的表工作(work against a different table)。一个ContentResolver.applyBatch的调用返回一个结果数组。
ContactsContract.RawContacts合同类的描述包含一个代码段,展示了batch插入。ContactManager例子,包含一个批量访问。
Data access via intents
    Intents可以提供一个内容提供器的间接的访问。我们允许用户访问提供器中的数据即使我们的应用程序没有访问权限,要么通过从一个有权限的应用获得一个结果意图返回,要么激活一个有权限的应用,让用户和这个应用工作。
Getting access with temporary permissions
    我们可以访问一个提供器的数据,即使我们没有适当的访问权限,通过发送一个intent给一个有权限的应用程序,接收返回的结果意图包含“URI”权限。这些权限是为一个特殊content URI持续到接收他们的活动finished。有永久权限的应用通过在结果意图设置一个flag授予临时权限。
Read permission: FLAG_GRANT_READ_URI_PERMISSION 
Write permission: FLAG_GRANT_WRITE_URI_PERMISSION 
Note: 这些标志,不提供通常的,authority包含在content URI的提供器的读写访问。仅仅是为URI自己。??
These flags don't give general read or write access to the provider whose authority is contained in the content URI. The access is only for the URI itself. 
一个提供器在他的manifest文件中为content URI定义URI权限,使用<provider>元素的<android:grantUriPermission>属性和<provider>元素的子元素<grant-uri-permission>.URI机制在安全和权限指导讨论。
举例,在Contacts Provider可以获得contact的数据,即使我们没有READ_CONTACTS权限。我们可能想这样做在一个应用程序在联系人的生日发送问候。不请求访问所有的用户联系人和所有他们信息的READ_CONTACTS权限,我们更喜欢让用户决定哪个联系人被这个应用使用。为了实现这个,使用下面的步骤:(没有演示代码?)
1. 我们的应用使用startActivityForResult发送一个intent包含ACTION_PICK动作和“contacts”MIME类型CONTENT_ITEM_TYPE.   Your application sends an intent containing the action ACTION_PICK and the "contacts" MIME type CONTENT_ITEM_TYPE, using the method startActivityForResult(). 
2. 因为这个意图匹配People app的“selection”活动,这个活动来到最前。   
3. 在这个活动中,用户选择一个联系人去更新。当这个发送的时候,这个活动调用setResult(resultcode, intent)设置一个返回给我们应用的intent。这个intent包含用户选择的联系人的content URI,和额外标志FLAG_GRANT_READ_URI_PERMISSION。这些标志授予URI权限给我们的app读取content URI指向的联系人数据。这个选择活动然后调用finish,返回控制给我们的应用。 
4. 我们的活动回到最前面,然后系统调用我们活动的onActivityResult方法,这个方法接收People app中的选择活动创建的结果意图。


5. 使用结果意图中的content URI,我们可以从联系人提供器读取数据,即使我们在manifest中没请求永久的读访问权限。然后可以获得联系人的生日和他的邮件地址,然后发送祝福。


Using another application
一个简单的方法允许用户修改没有访问权限的数据的激活一个有权限的应用,让用户在那里工作。
举例,一个Calendar应用接收一个ACTION_VIEW intent,
For example, the Calendar application accepts an ACTION_INSERT intent, which allows you to activate the application's insert UI. You can pass "extras" data in this intent, which the application uses to pre-populate the UI. Because recurring events have a complex syntax, the preferred way of inserting events into the Calendar Provider is to activate the Calendar app with an ACTION_INSERT and then let the user insert the event there. 
Displaying data using a helper app
如果我们的应用有访问权限,我们仍然可能想使用一个intent去展示数据在另一个应用。举例,Calendar应用接收一个ACTION_VIEW intent,展示一个特别的数据或事件。这允许我们展示Calendar信息不创建自己的UI。参考Calender Provider指导。
这个我们发送给intent的应用不用和提供器关联。举例,我们可以获得一个联系人从联系人提供器,然后发送一个ACTION_VIEW intent包含为联系人的图像的content URI 到一个image viewer .
The application to which you send the intent doesn't have to be the application associated with the provider. For example, you can retrieve a contact from the Contact Provider, then send an ACTION_VIEW intent containing the content URI for the contact's image to an image viewer. 







Contract Classes
________________________________________
一个合同类定义常量,帮助应用和一个内容提供器的content URIs,列名,intent actio和其他特征工作。合同类不自动包含进一个提供器,提供器开发者必须定义他们,然后让他们可以被其他开发者可用。安卓平台的许多提供器有相应的合同类在android.provider包中。
举例来说,用户词典提供器有一个合同类UserDictionary包含content URI和列名常量。“words”表中的content URI定义在常量UserDictionary.Words.CONTENT_URI.UserDictionary.Words类也包含列名常量,在示例片段中使用。举例,一个查询projection可以被定义为:
String[] mProjection =
{
    UserDictionary.Words._ID,
    UserDictionary.Words.WORD,
    UserDictionary.Words.LOCALE
};

你可能感兴趣的:(ContentProvider Basic)