官网翻译 Content Provider Basics

Content Provider Basics


Content Provider 管理中央数据仓库访问。Provider是安卓应用的一部分,该应用通常提供自己的UI与数据交互。
然而,Content Provider主要意图是被其他应用使用,其他应用使用provider client对象访问provider。
providers 和 provider clients一起提供了一致且标准的数据接口,并处理进程间通信,以及保证数据安全访问。

本节讨论以下基本内容:
1、content provider 如何工作
2、从content provider中检索数据的API
3、从content provider中插入、修改或删除数据的API
4、促进与content provider交互的其他API

Overview

Content provider把数据以一张或多张表的形式展示给外部应用,这些与关系数据库中的表类似。 一行代表provider收集的某种类型数据的一个实例。行中的每一列表示一个数据实例的各自独立的属性。

例如,安卓平台内建的provider user dictionary 保存了用户需要保存的非标准单词的拼写,表1举例

说明了该provider的数据表现形式:

Table 1: Sample user dictionary table.

wordapp idfrequencylocale_ID
mapreduceuser1100en_US1
precompileruser14200fr_FR2
appletuser2225fr_CA3
constuser1255pt_BR4
intuser5100en_UK5

在表1中,每一行代表一个在标准字典中找不到的单词实例。每一列代表了该单词的一些数据,如locale表示该单词第一次出现的位置。对于该provider,_ID列作为主键列,由provider自动维护。

注意:provider没有要求一定要包含主键,也没有要求一定要用名字“_ID”来作为主键。然而,当你需要将provider中的数据绑定到一个ListView时,一定要有一列名字为"_ID"。 这一要求在Display query results中详细描述。



Accessing a provider

应用使用ContentResolver客户端对象来访问content provider中的数据。这个对象中的方法调用ContentProvider具体子类实例中名字相同的方法。 ContentResolver提供了针对持久化存储的创建、检索、更新、删除功能。

在客户端应用中的ContentResolver对象与在拥有provider应用进程中的ContentProvider对象自动处理进程间通信。
ContentProvider同样在数据仓库和对外呈现的数据表之间扮演抽象层的角色。

注意:为了能够访问provider,你的应用需要在manifest文件中申请权限。这些内容在Content Provider Permissions中详细描述。



例如:为了从用户字典Provider中获取单词和对应的locales列表,你可以调用ContentResolver.query(). query()方法调用
用户字典Provider定义的ContentProvider.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

表2展示了如何将 query(Uri,projection,selection,selectionArgs,sortOrder) 的参数映射到SQL语句。

Table 2: Query() compared to SQL query.

query() argumentSELECT keyword/parameterNotes
UriFROM table_nameUri maps to the table in the provider named table_name.
projectioncol,col,col,...projection is an array of columns that should be included for each row retrieved.
selectionWHERE col = valueselection specifies the criteria for selecting rows.
selectionArgs(No exact equivalent. Selection arguments replace ? placeholders in the selection clause.)
sortOrderORDER BY col,col,...sortOrder specifies the order in which rows appear in the returnedCursor.         

Content URIs

content URI 是用来标识provider中数据的统一资源标识。content URI包含整个provider的符号名字(authority) 和表名。当你调用客户端的方法去访问provider中的一个表时,该表对应的content URI作为一个参数。

在前面的代码里,CONTENT_URI包含用户字典的words表的content URI。ContentResolver对象解析出URI的authority, 并使用它与已知provider的系统表比较,以此识别出具体的provider。然后,ContentResolver可以将query语句分发给正确的provider。


ContentProvider 使用URI中的路径部分来选择需要访问的表项。对于每个provider暴露出来的表项,它通常有一个路径与之对应。在前面的代码段中,words表的完整URI是:

content://user_dictionary/words

 user_dictionary字符串是provider的authority, words字符串是表的路径。字符串content://(the scheme)总是存在的,标识它是一个content URI。

 有些providers允许你通过在URI后面追加一个ID值来访问表中的某一行。例如,为了检索用户字典中_ID为4行,你可以使用这样的URI:

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

 在你已经检索了一组行数据并且需要更新或删除其中的一个的时候,经常需要使用ID字段。


 说明: Uri 和 Uri.Builder类 包含了从字符串构造URI对象的方法。ContentUris包含了在URI后附加ID的方法。 前面的代码片断使用了withAppendedId()在用户字典content URI后附加了id字段。


Retrieving Data from the Provider

本节描述如何从provider中检索数据,并且以用户字典provider为例说明。
清晰起见,本节中的代码片断在UI线程中调用ContentResolver.query()。然而在实际代码中,你应该在单独的线程中进行异步查询。你可以使用CursorLoader类来进行异步查询,具体请参考Loader指导。另外,本节中的代码只是片断,并不是一个完整的应用。

检索provider中的数据时,需要遵从如下两步:
1. 请求provider的读权限。
2. 定义发送到provider的代码。


Requesting read access permission


为了检索provider中的数据,你的应用需要该provider的读访问权限。你不能在运行时请求这个权限,你只能在manifest中定义权限许可。定义权限许可使用<uses-permission>元素和该provider定义的权限名字。当你在manifest中指定了<uses-permission>,你实际上在为你的应用请求权限。当用户安装你的应用时,用户隐含地授予了这些权限。


查看provider文档,你可以找到provider提供的读访问等权限的精确的名字。在访问providers中,权限扮演的角色在Content Provider Permissions一节中详细描述。

用户字典provider中在manifest中定义了android.permission.READ_USER_DICTIONARY权限,所以其他应用需要从provider中读数据时,需要申请这个权限。


Constructing the query

接下来,为了从provider中检索数据,构造一个查询。第一个代码片断定义了访问用户字典provider需要的变量,如下:

// 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 = {""};

接下来,以用户字典provider为例,说明如何使用ContentResolver.query()。provider client的查询类似于SQL查询, 包含将返回的一组列的集合、选择准则 和 排序方法。

查询返回的列集合叫做投影( mProjection 变量)。


指定查询范围的表达式,被拆分成了一个选择从句和选择参数。选择从句是逻辑和布尔表达式、列名、值的组合(mSelectionClause变量)。如果你指定了可替换的参数?代替值,查询方法从选择参数数据( mSelectionArgs变量)中检检索值。


在接下来的代码片断中,如果用户没有指定单词,选择子句被设置成null, 查询将返回provider中的所有单词。如果用户指定了单词,选择子句被设置为 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语句中,使用了实际的列名替代了类的常量。


Protecting against malicious input
如果content provider维护的数据是一个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 *;", 将导致选择从句 var = nothing; DROP TABLE *; 由于选择从句被当作SQL语句对待,这将导致provider删除底层Sqlite数据库中的所有表项(除非provider捕获了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;

推荐使用?作为可变参数及选择参数数组作为选择子句,即使provider不是基于SQL数据库的。

Displaying query results

ContentResolver.query() 客户端方法总是返回一个Cursor,它包含符合查询条件的行数据中查询投影中指定的列。Cursor对象可以随机读访问它包含的行和列。使用Cursor的方法,你可以迭代访问结果中的各个行,查看各列的数据类型,获取各列的数据,以及检查结果中的其他属性。Cursor的一些实现当provider的数据变更时自动更新cursor对象,或者触发observer对象中的方法,或者两者都做。


说明:provider可以根据发起查询的对象的特征,限制访问一些列。如联系人provider限制访问一些列以同步适配器,这些列不会返回给activity或service。


如果没有行匹配选择准择,那么provider返回一个空的Cursor对象,该对象的Cursor.getCount为0。


如果provider发生了内部错误,查询结果取决于具体的provider实现。可能返回null或抛出异常。


由于Cursor是一组行的列表,一个显示其内容的好的方式是将Cursor链接到一个ListView,通过SimpleCursorAdapter可以实现链接。接下来的代码与上面的衔接在一起,创建了一个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);

为了在ListView中引用Cursor,Cursor必须包含列名_ID。正是因此,之前的查询检索words表的_ID列,即使ListView并不显示它。这个限制也解释了为什么大多数provider中的表有一个_ID列。


Getting data from query results

除了简单的展示查询结果外,你可以在其他任务中使用查询结果。例如,你可以从用户字典中检索拼写,然后在其他providers中查找他们。为此,你需要迭代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 provider

可以指定其他应用访问本provider数据所需要的权限。这些权限保证了用户知晓应用需要访问什么样的数据。基于provider的要求,其他应用请求他们访问provider需要的权限。在安装应用的时候,最终用户将看到应用请求的权限。
 
 如果一个provider应用没有指定任何权限,其他应用则无权访问这个provider的数据。然而 provider所在的应用中的其他组件有完全的读写访问权,不管是否指定了权限。
 
 如前面描述的,用户字典provider需要 android.permission.READ_USER_DICTIONARY权限来从它检索数据。 provider有单独的对应插入、更新和删除的android.permission.WRITE_USER_DICTIONARY权限。
 
 为了获取访问provider的权限,应用在manifest中使用<uses-permission>申请权限。当安卓包管理器安装应用时,用户必须批准应用申请的所有权限。如果用户同意所有请限,包管理器才继续安装。如果用户没有同意,包管理器则中止安装。
 
 如下,使用<uses-permission>元素请求用户字典的读权限。

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

 Inserting, Updating, and Deleting Data

 与从provider中检索数据相同,你可以通过provider客户端与ContentProvider交互以进行数据修改。调用ContentResolver中相应的方法,方法中的参数将传到ContentProvider对应的方法中。provider和 provider客户端 自动处理安全和进程间通信。

 Inserting data

 调用ContentResolver.insert()方法向provider中插入数据。这个方法向provider中插入一条新数据行,并且返回该行数据对应的content URI。如下的代码片段显示了如何插入一个新单词到用户字典provider中:

// 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对象,在形式上类似于一行的Cursor。在这个对象中的各个列不需要具有相同的数据类型,或许你根本不想指定一个数据,你可以使用ContentValues.putNull()将某一列设成null。

上面的代码中没有增加一个_ID列,因为该列是provider自动维护的。provider为每一个添加的行指定了唯一的_ID值。provider通常用这个值来做表的主键。

返回的Content URI标识添加的行,格式如下:

content://user_dictionary/words/<id_value>
<id_value>是新增加列的_ID列对应的值。大多数的provider可以自动检测出这种类型的Content URI,然后在该行上做特定的请求操作。


可以使用ContentUris.parseId()获取返回的Uri中的_ID值。


Updating data

更新行时,用ContentValues对象保存待更新数据,就像插入操作一样。选择准则和查询操作相同。客户端用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()时,你需要检查用户输入。关于这方面更多的内容,在上面的Protecting against malicious input中已经讲过,请参考。

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()时,你需要检查用户输入。关于这方面更多的内容,在上面的Protecting against malicious input中已经讲过,请参考。

Provider Data Types

Content provider提供了多种不同的数据类型。用户字典provider只提供了文本,providers也可以提供如下数据格式:
  • integer
  • long integer(long)
  • floating point
  • long floating point(double)
providers经常使用的另一个数据类型是Binary Large OBject(BLOB),它实现成64KB字节数组。你可以使用Cursor的get方法查看可用的数据类型。
provider每列的数据类型通常列在它的文档里。你也可以通过Cursor.getType获取数据类型。
providers同样为每个他们定义的content URI维护MIME数据类型信息。你可以使用MIME类型信息来确定你的应用是否可以处理providers提供的数据, 或选择一个基于MIME类型处理的类型。在与包含复杂数据结构或文件的provider交互时,你通常需要使用MIME类型。例如,Contacts provider中的ContactsContract.Data表使用MIME类型来标记存储在每一行中的联系人类型。可以调用ContentResolver.getType()获取content URI对应的MIME类型。
MIME Type Reference节描述了标准的和自定义的MIME类型的语法。

Alternative Forms of Provider Access

在应用开发中,访问provider的三种替代形式很重要。
批量访问: 你可以通过ContentProviderOperation类的方法创建一组访问调用,然后调用ContentResolver.appBatch应用他们。
异步查询:你应该在单独的线程中进行查询操作。方法之一是使用CursorLoader对象。在安卓官网的Loaders章节中举例说明如何使用。
通过Intent访问数据:尽管你不能直接发送Intent给provider,你可以发送intent到provider所在的应用,这通常是修改provider数据的最齐全的方法。
下面描述批量访问和能过intents修改。

Batch access

批量访问provider是有用的,如插入大量的行、在相同的方法调用中插入若干行到多个表、或作为主个事务跨进程完成一组操作(原子操作)。

以批量模式访问provider,你需要创建一个ContentProviderOperation对象的数组,然后使用ContentResolver.applyBatch()分发他们到content provider。你需要向applyBatch中传入content provider的authority,而不是特定的content URI。这允许每一个ContentProviderOperatioin对象针对不同的表进行操作。ContentResolver.applyBatch返回一个结果数组。

Data access via intents

Intents可以间接访问content provider。你允许用户访问provider中的数据,即使你的应用没有访问权限,要么通过从一个有权限的应用中获取一个结果intent,要么激活一个有权限的应用并让用户在这个应用中处理工作。
Getting access with temporary permissions
在没有访问权限的时候访问content provider中的数据,可以能过发送一个intent到一个有权限的应用,该应用返回包含URI权限的结果intent。这些指定content URI的权限在接收该URI权限的应用结束时终止。拥有永久权限的应用通过在结果intent中设置的标志来授予临时权限。
  • Read permission: FLAG_GRANT_READ_URI_PERMISSION
  • Write permission: FLAG_GRANT_WRITE_URI_PERMISSION
说明:这个标志不授予指定provider的通用的读写权限,访问仅奶于URI本身。
provider在manifest中为content URI定义URI权限,定义方式是在<provider>标签中使用android:grantUriPermission属性和<grant-uri-permission>子标签。
举例说明,即使你没有READ_CONTACTS权限,你可以从Contacts Provider中检索一个联系人的数据。如你想要在一个应用中在一个联系人生日的时候向他发送祝福。应用不用请求READ_CONTACTS权限,你更倾向于让用户选择你的应用可以使用哪些联系人。如下步骤可以实现:
1. 你的应用使用startActivityForResult()方法,发送一个包含ACTION_PICK和contacts MIME类型CONTENT_ITEM_TYPE 的intent。
2. 这个intent会匹配到联系人应用选择界面组件,该界面组件被调度到前台。
3. 在界面组件中,用户选择了联系人。界面组件此时,调用setResult(resultCode, intent)设置返回到你的应用的intent。这个intent包含用户选择的联系人的content URI,并在extras flags标记FLAG_GRANT_READ_URI_PERMISSION。 这些标志授权你的应用读访问该content URI指向的联系人数据。 然后,选择界面组件调用finish()将控制权返回到你的应用。
4. 你的应用返回到前台,系统调用你应用界面组件的onActivityResult()函数。该方法收到在选择界面组件中创建的结果intent。
5. 根据结果intent中的content URI,你可以读取Contacts Provider中对应联系人的数据。此时,你并没有在manifest中申请读访问provider的永久权。但,你仍然可以获取到该联系人的生日和email信息,并发送邮件。

Using another application
允许用户访问你没有权限的数据的一种简单方法是,激活一个有权限的应用,让用户在这个应用中完成工作。

例如,Calendar应用接收ACTION_INSERT intent, 允许你激活该应用的插入界面。你可以在intent中传送extras数据,插入界面将预加载这些数据。 由于递归事件语法复杂,比较好的方式是使用ACTION_INSERT激活Calendar应用,然后让用户在这个应用中插入事件。


Contract Classes

契约类定义了一系列常量辅助应用与provider交互,包括content URI, 列名,intent actions等。契约类不是provider自动包含的。provider开发者需要定义他们并提供给其他开发者。大数据android平台的provider有对应的契约类在包android.provider中。
如,用户字典provider有契约类 UserDictionary ,UserDictionary中包含content URI和列名常量。words表的content URI定义成常量UserDictionary.Words.CONTENT_URI。UserDictionary.Words同样包含一些列名常量,他们在前面的代码中已经使用过,如查询投影可以定义为:

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

MIME Type Reference

content providers 可以返回标准的MIME介质类型,也可以返回自定义的MIME类型字符串,或者两者一起返回。
MIME类型格式如下:

type/subtype
如,熟知的MIME类型text/html有text类型和html子类型。如果一个provider返回这个类型的URI, 意味着使用该URI查询将返回包含HTML标签的文本。

自定义的MIME类型字符串也叫做“vendor-specific”MIME类型,有更复杂的类型和子类型。这些类型值经常是:

针对多行数据的:

vnd.android.cursor.dir

针对单行数据的:

vnd.android.cursor.item

子类型是provider自定义的。安卓内建的provider通常拥有一个简单的子类型。如,联系人应用为一个电话号码创建了一行,将该行设置成如下类型:

vnd.android.cursor.item/phone_v2

子类型就是简单的phone_v2

其他provider开发者可以根据provider的authority和表名,创建他们自己的子类型模式。如,一个包含train timetables的provider,authority是com.example.trains, 包含Line1, Line2,和Line3三个表。表1的content URI是:

content://com.example.trains/Line1
对应的MIME类型是:

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

表2第5行的content URIj是

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

对应的MIME类型是:

vnd.android.cursor.item/vnd.example.line2
大多数的content provider为他们使用的MIME类型定义契约类常量。 如,Contacts Provider契约类 ContactsContract.RawContacts定义了单行联系人对应的MIME常量CONTENT_ITEM_TYPE。



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值