四大组件之ContentProvider之内容提供商基础(译)

ContentProvider是Android应用程序的主要构建模块之一,可为应用程序提供内容。它们封装数据并通过单个ContentResolver接口将其提供给应用程序 。仅当您需要在多个应用程序之间共享数据时,才需要内容提供程序。例如,联系人数据由多个应用程序使用,并且必须存储在内容提供商中。如果您不需要在多个应用程序之间共享数据,则可以直接通过使用数据库 SQLiteDatabase。

当通过发出请求时ContentResolver,系统将检查给定URI的权限,并将请求传递给在该权限下注册的内容提供者。内容提供商可以根据需要解释其余的URI。的UriMatcher班是解析URI的帮助。

需要实现的主要方法是:

  • onCreate() 用来初始化提供者
  • query(Uri, String[], Bundle, CancellationSignal) 它将数据返回给调用者
  • insert(Uri, ContentValues) 将新数据插入内容提供者
  • update(Uri, ContentValues, Bundle) 更新内容提供者中的现有数据
  • delete(Uri, Bundle) 从内容提供商删除数据
  • getType(Uri) 返回内容提供者中数据的MIME类型

数据访问方法(例如insert(Uri, ContentValues)和 update(Uri, ContentValues, Bundle))可以同时从多个线程中调用,并且必须是线程安全的。其他方法(例如onCreate())只能从应用程序主线程中调用,并且必须避免执行冗长的操作。有关其预期的线程行为,请参见方法说明。

请求ContentResolver会自动转发到适当的ContentProvider实例,因此子类不必担心跨进程调用的细节。

开发人员指南

有关使用内容提供程序的更多信息,请阅读 内容提供程序 开发人员指南。

开发人员指南部分

内容提供商基础

内容提供商管理对中央数据存储库的访问。提供程序是Android应用程序的一部分,该应用程序通常提供自己的UI来处理数据。但是,内容提供程序主要旨在供其他应用程序使用,其他应用程序使用提供程序客户端对象访问提供程序。提供者和提供者客户端共同为数据提供一致的标准接口,该接口还处理进程间通信和安全的数据访问。

通常,您在以下两种情况之一中与内容提供者合作;您可能想要实现代码以访问另一个应用程序中的现有内容提供者,或者您可能希望在应用程序中创建一个新的内容提供者以与其他应用程序共享数据。本主题涵盖与现有内容提供者合作的基础知识。要了解有关在自己的应用程序中实现内容提供者的更多信息,请参阅 创建内容提供者。

本主题描述以下内容:

  • 内容提供商的工作方式。
  • 您用于从内容提供商检索数据的API。
  • 您用于在内容提供程序中插入,更新或删除数据的API。
  • 有助于与提供者合作的其他API功能。

总览

内容提供商将数据作为一个或多个表呈现给外部应用程序,这些表与关系数据库中的表相似。行代表提供者收集的某种类型的数据的实例,并且行中的每一列代表为实例收集的单个数据。

内容提供者针对许多不同的API和组件(如图1所示)协调对应用程序中数据存储层的访问,这些API和组件包括:

  • 与其他应用程序共享对您的应用程序数据的访问
  • 将数据发送到小部件
  • 通过使用以下内容的搜索框架为您的应用返回自定义搜索建议 SearchRecentSuggestionsProvider
  • 使用的实现将应用程序数据与服务器同步 AbstractThreadedSyncAdapter
  • 使用以下命令在用户界面中加载数据 CursorLoader
四大组件之ContentProvider之内容提供商基础(译)_第1张图片
图1

图1.内容提供者与其他组件之间的关系。

访问提供商

当您要访问内容提供程序中的数据时,可以使用 ContentResolver应用程序中的对象Context作为客户端与提供程序 进行通信。该 ContentResolver对象与提供者对象(实现的类的实例)进行通信ContentProvider。提供者对象从客户端接收数据请求,执行请求的操作,然后返回结果。该对象的方法调用提供程序对象(的一个具体子类之一的实例)中具有相同名称的方法ContentProvider。这些 ContentResolver方法提供了持久性存储的基本“ CRUD”(创建,检索,更新和删除)功能。

ContentProvider从用户界面 访问a的常见模式是使用a CursorLoader在后台运行异步查询。您的用户界面中的 Activity或Fragment调用 CursorLoader查询,而查询又 ContentProvider使用ContentResolver。这允许用户在查询运行时继续对用户可用。这种模式涉及许多不同对象的交互以及底层存储机制,如图2所示。

四大组件之ContentProvider之内容提供商基础(译)_第2张图片
图2

图2. ContentProvider,其他类和存储之间的交互。

注意:要访问提供程序,您的应用程序通常必须在其清单文件中请求特定的权限。在内容提供者权限一节中将详细描述这种开发模式。

Android词典中的内置提供程序之一是用户词典,该词典存储用户想要保留的非标准单词的拼写。表1说明了此提供程序的表中的数据可能是什么样的:

四大组件之ContentProvider之内容提供商基础(译)_第3张图片
样本用户字典表

在表1中,每一行代表一个在标准字典中可能找不到的单词的实例。每列代表该单词的一些数据,例如首次遇到该单词的语言环境。列标题是存储在提供程序中的列名。要引用行的语言环境,请引用其 locale列。对于此提供程序,该 _ID列用作提供程序自动维护的“主键”列。

要从用户词典提供者那里获取单词及其语言环境的列表,请致电ContentResolver.query()。该query()方法调用ContentProvider.query()用户词典提供者定义的 方法。以下代码行显示了一个 ContentResolver.query()调用:

//查询用户字典并返回结果
// Queries the user dictionary and returns results
cursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,   // 单词表的内容URI  
    projection,                        // 每行要返回的列   
    selectionClause,                   // 选择标准  
    selectionArgs,                     // 选择标准  
    sortOrder);                        // 返回的行的排序顺序    

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

四大组件之ContentProvider之内容提供商基础(译)_第4张图片
表2

内容URI

内容URI是一个URI,在一个提供者识别数据。内容URI包括整个提供程序的符号名称(它的权限)和指向表的名称(路径)。当您调用客户端方法来访问提供程序中的表时,表的内容URI是参数之一。

在前面的代码行中,该常量 CONTENT_URI包含用户词典的“单词”表的内容URI。该ContentResolver对象解析出URI的权限,并通过将权限与已知提供程序的系统表进行比较来使用它来“解析”提供程序。然后, ContentResolver可以将查询参数分派到正确的提供程序。

在ContentProvider使用内容URI的路径部分选择表的访问。提供程序通常为它公开的每个表都有一个路径

在前面的代码行中,“单词”表的完整URI为:

content://user_dictionary/words

其中,user_dictionary字符串是提供者的权限,words字符串是表的路径。字符串 content:// (the scheme) 始终存在,并将其标识为内容URI。

许多提供程序都允许您通过将ID值附加到URI的末尾来访问表中的一行。例如,要检索行,其_ID4从用户词典,您可以使用此内容的URI:

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

当您检索一组行然后想要更新或删除其中一个时,通常会使用id值。

注意:所述的Uri与Uri.Builder类包含用于从字符串构建合式URI对象方便的方法。本 ContentUris类包含附加ID值到URI方便的方法。上一个代码段用于withAppendedId()将ID附加到UserDictionary内容URI。

从提供者检索数据

本节以用户词典提供程序为例,介绍如何从提供程序检索数据。

为了清楚起见,本节中的代码段调用 ContentResolver.query()“ UI线程”。但是,在实际代码中,您应该在单独的线程上异步执行查询。一种方法是使用CursorLoader类,即在更详细地描述 装填机。导向另外,代码行是唯一的代码段;它们不表现出一个完整的应用程序。

要从提供程序检索数据,请按照以下基本步骤操作:

  1. 请求提供者的读取访问权限。
  2. 定义将查询发送到提供程序的代码。

请求读取访问权限

要从提供程序检索数据,您的应用程序需要提供程序的“读取访问权限”。您不能在运行时请求此权限。相反,您必须使用 提供程序定义的元素和确切的权限名称,在清单中指定需要此权限 。当您在清单中指定此元素时,实际上是在“请求”应用程序的此权限。用户安装您的应用程序时,他们暗中同意此请求。

要查找您正在使用的提供程序的读取访问权限的确切名称,以及提供程序使用的其他访问权限的名称,请参阅提供程序的文档。

权限在访问提供程序中的作用将在“ 内容提供程序权限 ”部分中详细介绍 。

用户词典提供程序android.permission.READ_USER_DICTIONARY在其清单文件中定义了权限 ,因此想要从提供程序读取的应用程序必须请求此权限。

构造查询

从提供程序检索数据的下一步是构造查询。第一个代码段定义了一些用于访问User Dictionary Provider的变量:

//“ projection”定义将为每一行返回的列
字符串[] mProjection = 
{
    UserDictionary.Words._ID,// _ID列名称的合同类常量
    UserDictionary.Words.WORD,//单词列名称的合同类常量
    UserDictionary.Words.LOCALE   //区域设置列名称的合同类常量
};

//定义一个包含选择子句的字符串
字符串selectionClause = null ; 

//初始化一个数组以包含选择参数
字符串[] selectionArgs = { “” }; 

下一个代码段以ContentResolver.query()User Dictionary Provider为例,说明如何使用 。提供程序客户端查询类似于SQL查询,并且包含一组要返回的列,一组选择条件和排序顺序。

查询应返回的一组列称为投影 (变量mProjection)。

指定要检索的行的表达式分为选择子句和选择参数。选择子句是逻辑和布尔表达式,列名和值(变量mSelectionClause)的组合。如果指定可替换参数?而不是值,则查询方法将从选择参数数组(变量mSelectionArgs)中检索值。

在下一个代码段中,如果用户不输入单词,则选择子句设置为 null,查询将返回提供程序中的所有单词。如果用户输入单词,则选择子句设置为UserDictionary.Words.WORD + " = ?",选择参数数组的第一个元素设置为用户输入的单词。

/*
 * 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 = ORDER BY word ASC;
在此SQL语句中,使用实际的列名代替合同类常量。

防止恶意输入
如果由内容提供者管理的数据在SQL数据库中,则将外部不受信任的数据包含到原始SQL语句中会导致SQL注入。

考虑以下选择子句:
//通过将用户输入连接到列名来构造选择子句

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

如果这样做,则允许用户将恶意SQL连接到您的SQL语句上。例如,用户可以输入“ nothing; DROP TABLE *;”。为mUserInput,这将导致选择子句var = nothing; DROP TABLE *;。由于选择子句被视为SQL语句,因此这可能会导致提供程序擦除底层SQLite数据库中的所有表(除非提供程序设置为捕获 SQL注入尝试)。

为避免此问题,请使用选择子句,该子句?用作可替换参数和单独的选择参数数组。执行此操作时,用户输入直接绑定到查询,而不是被解释为SQL语句的一部分。由于未将其视为SQL,因此用户输入无法注入恶意SQL。代替使用串联来包含用户输入,请使用以下选择子句:

//构造一个带有可替换参数的选择子句
字符串选择Clause =“ var =?”;

设置选择参数数组,如下所示:

//定义一个包含选择参数的数组
字符串[] selectionArgs = {“”};  

?即使提供者不是基于SQL数据库,也 可以使用用作可替换参数的选择子句和选择参数数组的数组来指定选择。

显示查询结果

的ContentResolver.query()客户端方法总是返回一个Cursor含有由查询的投影为匹配查询的选择标准的行指定的列。一个 Cursor对象提供它所包含的行和列的随机读取访问。使用Cursor方法,您可以遍历结果中的行,确定每列的数据类型,从列中获取数据,并检查结果的其他属性。某些Cursor实现在提供者的数据发生更改时自动更新对象,或在更改发生时自动触发观察者对象中的方法Cursor,或两者同时发生。

注意:提供程序可能会根据进行查询的对象的性质来限制对列的访问。例如,Contacts Provider限制对某些列的访问以同步适配器,因此它不会将它们返回到活动或服务。

如果没有行符合选择条件,则提供程序将返回一个Cursor对象,该对象的 Cursor.getCount()值为0(空游标)。

如果发生内部错误,则查询结果取决于特定的提供程序。它可能选择返回null,也可能会抛出一个Exception。

由于a Cursor是行的“列表”,因此显示a的内容的一种好方法Cursor是ListView 通过a 将其链接到a SimpleCursorAdapter。

以下代码段延续了上一个代码段的代码。它创建一个 SimpleCursorAdapter包含Cursor 查询检索到的对象,并将该对象设置为的适配器 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未显示该列。此限制还解释了为什么大多数提供程序_ID的每个表都有一个列。

从查询结果中获取数据

您不仅可以显示查询结果,还可以将其用于其他任务。例如,您可以从用户词典中检索拼写,然后在其他提供程序中查找它们。为此,您需要遍历以下行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实现包含几个“获取”方法,用于从对象中检索不同类型的数据。例如,先前的代码段使用getString()。它们还具有一个getType()返回值的 方法,该值指示列的数据类型。

内容提供商权限

提供者的应用程序可以指定其他应用程序必须具有的权限才能访问提供者的数据。这些权限可确保用户知道应用程序将尝试访问哪些数据。根据提供商的要求,其他应用程序会请求他们所需的权限才能访问提供商。最终用户在安装应用程序时会看到请求的权限。

如果提供程序的应用程序未指定任何权限,则其他应用程序将无权访问提供程序的数据。但是,无论指定的权限如何,提供者的应用程序中的组件始终具有完全的读取和写入访问权限。

如前所述,用户词典提供者需要android.permission.READ_USER_DICTIONARY获得从中检索数据的 权限。提供者具有android.permission.WRITE_USER_DICTIONARY 插入,更新或删除数据的单独权限。

为了获得访问提供程序所需的权限,应用程序会 在清单文件中使用元素来请求它们 。当Android Package Manager安装该应用程序时,用户必须批准该应用程序请求的所有权限。如果用户全部批准,则程序包管理器将继续安装;否则,将继续安装。如果用户不同意,包管理器将中止安装。

以下 元素请求对用户词典提供者的读取访问权限:


权限对提供程序访问的影响将在“ 安全和权限”指南中进行详细说明 。

插入,更新和删除数据

与从提供程序检索数据的方式相同,您还可以使用提供程序客户端与提供程序的客户端之间的交互ContentProvider来修改数据。您调用ContentResolver带有传递给的相应方法的参数的方法ContentProvider。提供者和提供者客户端自动处理安全性和进程间通信。

插入资料

要将数据插入提供程序,请调用 ContentResolver.insert() 方法。此方法将新行插入提供程序,并返回该行的内容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
);

新行的数据将进入一个ContentValues对象,该对象的形式类似于单行游标。该对象中的列不必具有相同的数据类型,并且如果您根本不想指定值,则可以将列设置为nullusing ContentValues.putNull()。

该代码段不会添加该_ID列,因为该列是自动维护的。提供程序将唯一的值分配给_ID添加的每一行。提供程序通常将此值用作表的主键。

返回的内容URI newUri标识新添加的行,格式如下:

content://user_dictionary/words/

是内容的_ID新行。大多数提供程序可以自动检测这种形式的内容URI,然后在该特定行上执行请求的操作。

要从_ID返回的值中获取的值Uri,请调用 ContentUris.parseId()。

更新数据

要更新行,可以像使用ContentValues插入一样使用具有更新值的对象,也可以像查询一样使用选择条件。您使用的客户端方法是 ContentResolver.update()。您只需要为ContentValues要更新的列的对象添加值。如果要清除列的内容,请将值设置为null

以下代码段将语言环境为“ en”的所有行更改为语言环境为的行null。返回值是已更新的行数:

// 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”匹配的行。该方法返回已删除的行数。

//为要删除的行定义选择条件
字符串selectionClause = UserDictionary.Words.APP_ID +“ LIKE?”;
字符串[] selectionArgs = { “ user” }; 

//定义一个变量以包含已删除的行数
int rowsDeleted = 0 ; 

...

//删除符合选择条件的单词
rowsDeleted = getContentResolver ().delete(
    UserDictionary.Words.CONTENT_URI ,//用户字典内容URI   
    selectionClause ,//要选择的列                   
    selectionArgs                       //要比较的值
);

调用时,还应该清理用户输入 ContentResolver.delete()。要了解更多信息,请阅读防止恶意输入部分。

提供者数据类型

内容提供商可以提供许多不同的数据类型。用户词典提供程序仅提供文本,但是提供程序也可以提供以下格式:

  • 整数
  • 长整数(长)
  • 浮点
  • 长浮点数(双精度)

提供程序经常使用的另一种数据类型是实现为64KB字节数组的二进制大对象(BLOB)。您可以通过查看Cursor类“ get”方法来查看可用的数据类型 。

提供程序中每一列的数据类型通常会在其文档中列出。用户词典提供者的数据类型在其合同类别的参考文档中列出UserDictionary.Words(合同类别在“ 合同类别 ”部分中进行了描述)。您还可以通过调用来确定数据类型Cursor.getType()。

提供程序还为他们定义的每个内容URI维护MIME数据类型信息。您可以使用MIME类型信息来确定您的应用程序是否可以处理提供程序提供的数据,或者根据MIME类型选择处理类型。当您使用包含复杂数据结构或文件的提供程序时,通常需要MIME类型。例如,ContactsContract.Data Contacts Provider中的表使用MIME类型来标记存储在每一行中的联系人数据的类型。要获取与内容URI对应的MIME类型,请调用 ContentResolver.getType()。

MIME类型参考 部分介绍了标准MIME类型和自定义MIME类型的语法。

提供者访问的替代形式

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

  • 批量访问:您可以使用ContentProviderOperation类中的方法创建一批访问调用,然后使用进行应用 ContentResolver.applyBatch()。
  • 异步查询:您应该在单独的线程中进行查询。一种方法是使用CursorLoader对象。加载程序指南中的示例 演示了如何执行此操作。
  • 通过意向访问数据:尽管您不能直接向提供者发送意图,但是您可以将意图发送到提供者的应用程序,这通常是修改提供者数据的最佳装备。

以下各节介绍了通过意向进行的批量访问和修改。

批量访问

对提供程序的批访问对于插入大量行或在同一方法调用中的多个表中插入行非常有用,或者通常用于跨事务边界执行一组作为事务的操作(原子操作)。

要以“批处理模式”访问提供程序,请创建一个ContentProviderOperation对象数组,然后使用分配它们到内容提供程序 ContentResolver.applyBatch()。您将内容提供者的权限传递给此方法,而不是特定的内容URI。这允许ContentProviderOperation数组中的每个对象针对不同的表工作。调用将ContentResolver.applyBatch()返回结果数组。

ContactsContract.RawContacts合同类 的描述包括演示批处理插入的代码段。该 联系人管理器 示例应用程序包含在其批量访问的示例ContactAdder.java 源文件。

通过意图进行数据访问

意图可以提供对内容提供商的间接访问。即使您的应用程序没有访问权限,您也可以允许用户访问提供程序中的数据,方法是从具有许可权的应用程序获取结果意图,或者通过激活具有许可权的应用程序并让用户在其中工作它。

获得临时权限的访问

即使没有适当的访问权限,也可以通过将意图发送给具有许可权的应用程序并接收回包含“ URI”许可权的结果意图,来访问内容提供程序中的数据。这些是对特定内容URI的许可,这些许可将持续到接收它们的活动完成为止。具有永久权限的应用程序通过在结果意图中设置标志来授予临时权限:

  • 阅读权限: FLAG_GRANT_READ_URI_PERMISSION
  • 写入权限: FLAG_GRANT_WRITE_URI_PERMISSION

注意:这些标志不授予对权限包含在内容URI中的提供程序的常规读取或写入访问。该访问仅针对URI本身。

提供者使用 元素的android:grantUriPermission 属性以及 元素的 子元素 为其清单中的内容URI定义URI权限 。URI权限机制在“ 权限”概述指南中有更详细的 说明。

例如,即使您没有READ_CONTACTS权限,也可以在“联系人提供程序”中检索联系人的数据。您可能想要在向生日当天的联系人发送电子问候的应用程序中执行此操作。READ_CONTACTS您可以选择让用户控制您的应用程序使用哪些联系人,而不是请求,后者使您可以访问所有用户的联系人及其所有信息。为此,您使用以下过程:

  1. 你的应用程序发送一个意图包含操作 ACTION_PICK和“联系人” MIME类型 CONTENT_ITEM_TYPE,使用方法startActivityForResult()。
  2. 由于此意图与People应用程序的“选择”活动的意图过滤器匹配,因此该活动将成为前台。
  3. 在选择活动中,用户选择要更新的联系人。发生这种情况时,选择活动将调用 setResult(resultcode, intent) 以建立回馈给您的应用程序的意图。该意图包含用户选择的联系人的内容URI和“额外”标志 FLAG_GRANT_READ_URI_PERMISSION。这些标志向您的应用授予URI权限,以读取内容URI指向的联系人的数据。然后,选择活动调用finish()以将控制权返回给您的应用程序。
  4. 您的活动返回到前台,然后系统调用您活动的 onActivityResult() 方法。此方法接收由“人脉”应用中的选择活动创建的结果意图。
  5. 使用结果意图中的内容URI,即使您未在清单中请求对提供者的永久读取访问权限,也可以从“联系提供者”中读取联系人的数据。然后,您可以获取联系人的生日信息或他们的电子邮件地址,然后发送电子问候。

使用其他应用程序

允许用户修改您没有访问权限的数据的一种简单方法是激活具有权限的应用程序,然后让用户在其中进行工作。

例如,“日历”应用程序接受一个 ACTION_INSERT意图,该意图使您可以激活应用程序的插入UI。您可以以此意图传递“额外”数据,应用程序将其用于预填充UI。由于重复发生的事件具有复杂的语法,因此将事件插入“日历提供程序”的首选方法是使用激活日历应用程序, ACTION_INSERT然后让用户在其中插入事件。

使用助手应用程序显示数据

如果您的应用程序确实具有访问权限,则您可能仍想使用一种意图在另一个应用程序中显示数据。例如,“日历”应用程序接受一个 ACTION_VIEW意图,该意图显示特定的日期或事件。这使您可以显示日历信息,而不必创建自己的UI。要了解有关此功能的更多信息,请参阅“ 日历提供程序”指南。

您向其发送意图的应用程序不必是与提供程序关联的应用程序。例如,您可以从“联系人提供者”中检索联系人,然后将ACTION_VIEW包含联系人图像的内容URI 的意图发送给图像查看器。

合约类别

合同类定义用于帮助应用程序处理内容提供​​者的内容URI,列名,意图动作和其他功能的常量。提供商不自动包含合同类别;提供者的开发人员必须定义它们,然后将其提供给其他开发人员。Android平台随附的许多提供程序在软件包中都有相应的合同类android.provider。

例如,用户词典提供者具有UserDictionary包含内容URI和列名常量的协定类 。“ words”表的内容URI在constant中定义 UserDictionary.Words.CONTENT_URI。的UserDictionary.Words类还包含列名常量,其在该示例段所使用本指南英寸 例如,查询投影可以定义为:

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类型,具有更复杂的类型和子类型值。该类型的值总是

vnd.android.cursor.dir

对于多行,或

vnd.android.cursor.item

单行。

该亚型是供应商特定的。Android内置提供程序通常具有一个简单的子类型。例如,当“联系人”应用程序为电话号码创建一行时,它将在该行中设置以下MIME类型:

vnd.android.cursor.item/phone_v2

请注意,子类型的值只是phone_v2。

其他提供程序开发人员可以根据提供程序的权限和表名称创建自己的子类型模式。例如,考虑包含列车时刻表的提供商。提供者的权限是com.example.trains,并且包含表Line1,Line2和Line3。响应内容URI

content://com.example.trains/Line1

对于表Line1,提供程序返回MIME类型

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

响应内容URI

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

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

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

大多数内容提供者为其使用的MIME类型定义合同类常量。ContactsContract.RawContacts例如,Contacts Provider合同类定义CONTENT_ITEM_TYPE了单个原始联系人行的MIME类型的常量 。

内容URI部分中描述了单行的 内容URI。

你可能感兴趣的:(四大组件之ContentProvider之内容提供商基础(译))