标签:
英文原文:http://developer.android.com/guide/topics/providers/content-provider-basics.html
采集日期:2015-01-07
Content Provider 管理着数据库的访问。 Provider 是某个 Android 应用程序的一部分,此类应用程序通常拥有自己的数据操作界面, 不过,Content Provider 主要是供其他应用程序使用的,通过 Provider 客户端对象可以访问到 Content Provider 。 Provider 及其客户端相互合作,提供了标准化、持久化的数据访问接口,并完成进程间通讯和权限控制工作。
本文简要介绍了以下内容:
Content Provider 以数据表的形式向外部应用程序提供数据,这与关系型数据库中的表很类似。 其中,行(row)表示由多个不同类型数据构成的单个实体,每行数据中的列(column)代表实体中的一个数据项。
例如,用户词典就是 Android 系统内置的 Provider 之一,里面记录着用户需要留存的自定义拼写规则的单词。 表1例举了此 Provider 数据表中可以查询的字段信息:
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 |
在表1中,每行代表一个可能无法在标准词典中查到的单词。
每列代表与单词相关的数据,比如首次使用时的地区(语言)。
每列的标题即为存储时的列名称。
引用 locale
列就可以得到每一行数据的地区信息。
这里的 _ID
列被用作“主键”(primary key),并且是由 Provider 自动维护的。
注意: Provider 本身不需要用到主键,主键的名称也不一定要是 _ID
。
但是,如果要把 Provider 作为数据源与
ListView
绑定,则必须有一个列的名称是 _ID
。
详细要求将在 显示查询结果 中描述。
应用程序是通过客户端对象
ContentResolver
访问 Content Provider 的。 此对象中包含一些方法,这些方法将会调用 Provider 对象中的同名方法。而 Provider 对象是
ContentProvider
某个具体子类的实例。
ContentResolver
中的方法内置了基本的“CRUD”(创建、查询、更新、删除(create、retrieve、update 和 delete))功能。
ContentResolver
对象运行于客户端应用的进程中,而
ContentProvider
运行于提供 Provider 应用的进程中,两者会自动完成进程间的通讯。
ContentProvider
还发挥着数据抽象层的作用,负责将内部数据以数据库表的形式提供出来。
注意: 为了访问 Provider,应用程序通常必须在 Manifest 文件中请求相应的权限。 详情请参阅Content Provider 权限一节。
例如,要从 User Dictionary Provider 中读取单词及地区列表,就要用到
ContentResolver.query()
。query()
方法会去调用 User Dictionary Provider 中对应的
ContentResolver.query()
方法。以下代码演示了
ContentResolver.query()
的调用过程:
// 查询用户词典并返回结果
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // 单词表的 Content URI
mProjection, // 需要返回的列
mSelectionClause, // 查询条件
mSelectionArgs, // 查询条件的参数
mSortOrder); // 返回结果的排序要求
表2给出了 query(Uri,projection,selection,selectionArgs,sortOrder)
的参数与 SQL SELECT 语句的对应关系:
query() 参数 | SELECT 关键字/参数 | 说明 |
---|---|---|
Uri |
FROM table_name |
Uri 对应于 table_name 指定的 Provider 数据表名。 |
projection |
col,col,col,... |
projection 是包含返回列名称的数组。 |
selection |
WHERE col = value |
selection 指定查询条件。 |
selectionArgs |
(没有固定值,该查询参数将会替换查询语句中的占位符“?”。) | |
sortOrder |
ORDER BY col,col,... |
sortOrder 指定了返回 Cursor 中各行的显示顺序。 |
Content URI 是一种用于标识 Provider 数据的 URI。 Content URI 包括了整个 Provider 的符号名称(authority)和表名(path)。 调用客户端的方法访问 Provider 数据表时,表的 Content URI 是参数之一。
在前面的代码中,常量 CONTENT_URI
包含了指向用户词典中 “word” 表的 Content URI。 ContentResolver
对象将分离出 URI 中的 authority ,并用它“解析” 出 Provider,这是通过将 authority 与系统记录的已有 Provider 清单进行比较来实现的。 然后 ContentResolver
就可以将查询参数发送给相应的 Provider 了。
ContentProvider
用 Content URI 的 path 部分选择要访问的数据表。 通常, Provider 公开的所有数据表都会带有自己的 path 。
在上述代码中,“word”表的完整 URI 为:
content://user_dictionary/words
这里的字符串 user_dictionary
是 Provider 的 authority 部分, 字符串 words
是数据表的 path 部分。 字符串 content://
(scheme)是必须指定的,以表明这是一个 Content URI。
很多 Provider 提供了对单条记录的访问能力,只要在 URI 后面跟一个 ID 值即可。 例如,要读取用户词典中 _ID
为 4
的数据行,可以使用以下 Content URI:
Uri singleUri =ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
如果已经读取了一些数据,然后需要修改或删除其中的某一条,这时就经常会用到 ID 值了。
注意: Uri
和Uri.Builder
类中已内置了一些工具性的方法,可以由字符串搭建合乎规则的 Uri 对象。 ContentUris
中有一些在 URI 后面追加 ID 值的常用方法。 上述代码就用了 withAppendedId()
把 ID 追加到 UserDictionary 的 Content URI 之后。
本节将介绍从 Provider 读取数据的过程,还是以 User Dictionary Provider 为例。
为了清晰起见,本节中的代码将会调用“UI 线程”中的 ContentResolver.query()
。但是在实际的代码中,应该在单独的线程中实现异步查询。 一种方案是利用 CursorLoader
类,有关细节将在 Loaders 指南中介绍。而且,以下只给出了部分代码,而非一个完整的应用程序。
从 Provider 中读取数据的基本步骤如下所示:
要从 Provider 读取数据,应用程序需要拥有对 Provider 的“读权限”。 在运行时是无法申请该权限的,只能在 Manifest 文件中通过 <uses-permission>
指定。在 Manifest 文件中的定义,实际上是表明此应用程序需要“申请”该权限。 这样用户在安装此应用程序时,就可以明确授权。
在 Provider 的参考文档中,给出了其用到的全部权限的准确名称。
访问 Provider 时权限所起的作用,将在Content Provider 权限一节中详细介绍。
User Dictionary Provider 在其 Manifest 文件中定义了 android.permission.READ_USER_DICTIONARY
权限, 因此要读它的应用程序就必须请求该权限。
接下来是构建查询请求。 以下代码定义了一些变量,在访问 User Dictionary Provider 时将会用到:
// "projection" 定义了要返回的数据列
String[] mProjection =
{
UserDictionary.Words._ID, &n // 对应列名为 _ID 的 Contract Class 常量
UserDictionary.an class="typ">Words.WORD, // 对应列名为 word 的 Contract Class 常量
UserDictionary.an class="typ">Words.LOCALE &nbLOCALE // 对应列名为 local 的 Contract Class 常量
};
// 定义存放查询条件的字符串
String mSelectionClause =an class="pln"> null;<s;
// 初始化存放查询参数的数组
String[]an class="pln"> mSelectionArgs ={""};
接下来的代码演示了 ContentResolver.query()
的使用方法,这里以 User Dictionary Provider 为例。 Provider 客户端查询与 SQL 查询很类似,也包含了需返回的列名、查询条件和排序要求。
查询返回的列名集合对象被称为”投影“( Projection )(即变量 mProjection
)。
查询数据的表达式被拆分为查询条件和查询参数。 查询条件是由逻辑/布尔表达式、列名、数值组成(即变量 mSelectionClause
)。 如果用参数 ?
代替了具体数值,则查询方法将会从查询参数数组(变量 mSelectionArgs
)中读取实际的值。
在以下代码中,如果用户没有输入单词,则查询语句将被置为 null
,这样查询将会返回 Provider 中的所有单词。 如果用户输入了单词,那么查询语句将会是 UserDictionary.Words.WORD + " = ?"
,且查询参数数组中的第一个成员被设为用户输入的单词。
/*
* 定义只有一个成员的字符串数组,用于存放查询参数。
*/
String[] mSelectionArgs ={""};
// 从用户界面读取一个单词
mSearchString = mSearchWord.getText().toString();
// 别忘了在这里添加检查输入内容是否非法或恶意的代码
// 如果单词为空字符串,则读取所有数据
if(TextUtils.isEmpty(mSearchString)){
// 将查询语句设为 null 将返回所有数据
mSelectionClause =null;
mSelectionArgs[0]="";
}else{
// 由用户录入单词构建查询语句
mSelectionClause =UserDictionary.Words.WORD +" = ?";
// 将用户录入的字符串置入查询参数数组中
mSelectionArgs[0]= mSearchString;
}
// 查询数据并返回游标(Cursor)对象
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // 单词表的 Content URI
mProjection, // 需返回的列
mSelectionClause // 为 null 或是用户录入的单词
mSelectionArgs, // 为空或是用户录入的字符串
mSortOrder); // 定义返回数据的排序规则
// 在出错时,某些 Provider 返回 null,另一些会抛出异常
if(null== mCursor){
/*
* 在这里插入处理错误的代码。
* 请勿在这里使用游标!
* 可能需要调用
*/
// 如果游标中没有内容,表示 Provider 没找到匹配的记录。
}elseif(mCursor.getCount()<1){
/*
* 在这里插入通知用户查询失败的代码。
* 这不一定是出错了,可以让用户录入新记录,也可以重新输入查询条件。
*/
}else{
// 在这里插入处理查询结果的代码。
}
查询的语句与以下 SQL 语句类似:
SELECT _ID, word, locale FROM words WHERE word =<userinput> ORDER BY word ASC;
这条 SQL 语句中使用的是真实的列名,而不是 Contract 类常量。
如果 Content Provider 管理的数据存放于 SQL 数据库中,那么在 SQL 语句中插入某些非法信息可能会引发 SQL 注入问题。
请看下面这条查询语句:
// 将用户输入内容拼接在列名之后,构造一条查询语句。
String mSelectionClause = "var = "+ mUserInput;
这时,用户就可以将恶意 SQL 拼接到查询语句中。 比如,用户可以将 mUserInput
输入为“nothing; DROP TABLE *;”,这样查询语句就会成为“var = nothing; DROP TABLE *;
”. 因为查询语句将用作 SQL 语句,所以会导致 Provider 删除底层 SQLite 数据库中的所有数据表(除非 Provider 设置为捕获 SQL 注入 异常)。
为了避免这类问题,可以在查询语句中使用 ?
作为可替代参数,并用另一个数组作为实际的参数值。 这样,用户的输入就与查询直接关联,而不会被解释为 SQL 语句的一部分。 因为不再用作 SQL 语句,用户输入就无法注入恶意 SQL 了。 用户的输入内容不直接用于拼接 SQL 语句,查询语句如下:
// 用可替代参数构造查询语句
String mSelectionClause = "var = ?";
查询参数数组定义如下:
// 定义存放查询参数值的数组
String[] selectionArgs ={""};
在数组中放入一个查询参数值:
// 将查询参数赋为用户的输入值
selectionArgs[0]= mUserInput;
在构造查询时,推荐使用这种将 ?
作为形参、数组提供实参的查询语句,即使不是基于 SQL 数据库的 Provider 也可以使用。
客户端方法 ContentResolver.query()
将返回一个 Cursor
,其中的数据列由对应查询条件的 Projection 指定。 Cursor
对象支持对数据行和数据列的随机读取。通过 Cursor
的内部方法,可以遍历结果数据行、获取每一列的数据类型、读取某一字段的数据并检查其他属性。 某些 Cursor
对象可以在 Provider 的数据发生变化时进行自动更新,或是在 Cursor
数据变动时触发其他监听对象的方法。
注意: 根据建立查询的对象性质, Provider 可以限制对数据列的访问。 比如,联系人 Provider 就不允许 Sync Adapter 访问某些数据列,也就不会在 Activity 和服务中返回这些列。
如果没有找到符合条件的数据, Provider 就会返回一个 Cursor.getCount()
为 0 的 Cursor
对象(即空游标)。
如果发生了内部错误,查询返回的结果将视 Provider 的不同而定。 可能是返回 null
,也可能抛出一个 Exception
。
因为 Cursor
是一个数据行的“列表”,所以一种较好的显示方式就是通过 SimpleCursorAdapter
把它与 ListView
关联起来。
以下代码将延续上面的代码。 创建了一个含有 Cursor
的 SimpleCursorAdapter
对象,并将其设置为一个 ListView
的数据源适配器(Adapter):
// 定义需要从 Cursor 读取并显示出来的数据列
String[] mWordListColumns =
{
UserDictionary.Words.WORD, // 对应 word 列的 Contract 类常量
UserDictionary.Words.LOCALE // 对应 locale 列的 Contract 类常量
};
// 定义 View ID 列表,用于保存 Cursor 返回的一行数据。
int[] mWordListItems ={ R.id.dictWord, R.id.locale};
// 新建一个 SimpleCursorAdapter 对象
mCursorAdapter =newSimpleCursorAdapter(
getApplicationContext(), // 应用程序的 Context 对象
R.layout.wordlistrow, // XML 格式的 Layout,用于 ListView 中每一行的布局
mCursor, // 查询结果
mWordListColumns, // 字符串数组,存放游标中的列名
mWordListItems, // 整形数组,存放行布局中的 View ID
0); // 标志位(一般用不上)
// 设置 ListView 的 Adapter
mWordList.setAdapter(mCursorAdapter);
注意: 要将 Cursor
用作 ListView
的后台数据源,游标必须包含一个名为 _ID
的数据列。 因此,上述查询从“word”表中读取了 _ID
列,当然 ListView
并不会显示这个字段。 这也是大部分 Provider 中的数据表都带有 _ID
列的原因所在。
查询结果不只是简单地用于显示,还可以用来完成其他操作。 比如,可以从用户词典中读取单词并在其他 Provider 中进行检索。 这时就需要遍历 Cursor
中的每行数据:
// 找到列名为“word”的字段编号
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
/*
* 仅当游标可用时才会执行。
* 如果发生内部错误,User Dictionary Provider 将会返回 null。而其他 Provider 可能会抛出异常。
*/
if(mCursor !=null){
/*
* 前进至下一行。
* 在第一次移动之前,“记录指针”为 -1,如果这时读取数据,将会触发异常。
*/
while(mCursor.moveToNext()){
// 读取值
newWord = mCursor.getString(index);
// 在这里插入处理返回单词的代码
...
// while 循环结束
}
}else{
// 如果游标为空或 Provider 抛出异常,在这里插入显示错误的代码。
}
Cursor
中有很多用于读取不同类型数据的“get”方法。 例如,上述代码中用到了 getString()
。还有一个 getType()
方法用于返回字段的类型。
Provider 应用可以设定一些访问权限要求,其他应用程序访问该 Provider 中的数据时必须拥有这些权限。 通过这些权限,用户可以了解并确认某个应用程序将会访问的数据。 根据 Provider 的要求,其他应用需要对访问权限提出申请。 在安装这些应用的时候,最终用户就能看到这些权限请求。
如果 Provider 应用没有设定任何权限要求,其他应用就无权访问该 Provider 中的数据。 不过,该 Provider 内部的组件都是拥有完整的读写权限的,这与权限设定没有关系。
如上所注,读取 User Dictionary Provider 中的数据需要 android.permission.READ_USER_DICTIONARY
权限。 此 Provider 还有另一个 android.permission.WRITE_USER_DICTIONARY
权限用于插入、修改和删除数据。
为了申请 Provider 所需的权限,应用程序可以通过 Manifest 文件中的 <uses-permission>
元素。 当 Android Package Manager 安装应用程序时,用户必须同意所有的权限请求。 只有全部同意,Package Manager 才会继续安装,否则就会退出。
读取 User Dictionary Provider 需要设置以下 <uses-permission>
元素:
<uses-permissionandroid:name="android.permission.READ_USER_DICTIONARY">
在 安全与权限 指南中,将对 Provider 访问权限的效果进行更为详细的介绍。
采用与读操作类似的方式,通过 Provider 客户端和 Provider 的 ContentProvider
之间的连接,还能进行数据修改操作。 然后再调用 ContentResolver
方法,参数中就包含了传给 ContentProvider
对应方法的参数。Provider 及其客户端会自动保证通讯安全并完成进程间通讯。
调用 ContentResolver.insert()
方法可以将数据插入 Provider 到中去。 该方法将在 Provider 中插入新数据行,并返回一个指向改行数据的 Content URI。 以下代码将在 User Dictionary Provider 中插入一条新的单词:
// 定义一个 URI 对象,用于保存插入操作返回的结果
Urian class="pln"> mNewUri;
...
// 定义要插入的对象
ContentValues mNewValues =newContentValues();
/*
* 为每个字段赋值并插入单词数据。
* “put”方法的参数是“列名”和“值”
*/
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, // 用户词典 Content URI
mNewValues // 要插入的值
);
新行的数据存放在一个 ContentValues
对象中, 对象中,这个对象类似于只包含一条数据的游标。 该对象中的各个字段的类型可以各不相同。如果不需要指定值,可以用 ContentValues.putNull()> 方法置为
null
。
上述代码并没有给 _ID
字段赋值,因为这个字段是由系统自动维护的。 Provider 会自动给插入行的 _ID
字段赋一个唯一值,并且通常把它作为表的主键使用。
在 newUri
中返回的 Content URI 唯一标识了新插入的数据行,格式如下:
content://user_dictionary/words/<id_value>
<id_value>
是新插入行的 _ID
字段值。 绝大部分 Provider 都能自动识别这种格式,以便在指定数据行上进行所需的操作。
调用 ContentUris.parseId()
可以读取 Uri
的 _ID
值。
利用 ContentValues
对象可以 对象可以更新一行数据,新数据的给出方式与插入操作类似,查询条件的给出方式与查询请求类似。 更新操作的客户端方法是 ContentResolver.update()> ,只需把要更新的数据字段添加到
ContentValues
对象中即可。如果要清除字段值,请把值赋为 null
。
以下代码将把地区代码为“en”的行全部置为 null
, 返回值是更新成功的行数:
// 定义对象,存放要更新的数据
ContentValuesan class="pln"> mUpdateValues =newContentValues();
// 定义要更新数据的查询条件
String mSelectionClause =UserDictionary.Words.LOCALE + "LIKE ?";
String[] mSelectionArgs ={"en_%"};
// 定义变量,存放更新成功的行数
int mRowsUpdated =0;
...
/*
* 设置更新数据并进行更新
*/
mUpdateValues.putNull(UserDictionary.Words.LOCALE);
mRowsUpdated = getContentResolver().update(
UserDictionary.Words.CONTENT_URI, // 用户词典 Content URI
mUpdateValues // 要更新的字段
&nb mSelectionClause // 查询条件
mSelectionArgs // 查询数据值an class="pln">
);
在调用 ContentResolver.update()
时,同样应该对用户输入内容进行必要的过滤,请阅读 防止非法输入一节。
删除与读取相类似:指定要删除数据的查询条件,客户端方法会返回删除成功的行数。 以下代码将删除 appid 包含 “user”的数据行,并返回删除成功的行数。
// 定义要删除数据的查询条件
String mSelectionClause =UserDictionary.Words.APP_ID +" LIKE ?";
String[] mSelectionArgs ={"user"};
// 定义变量,用于保存返回的删除成功行数
int mRowsDeleted =0;
...
// 删除符合查询条件的单词数据
mRowsDeleted = getContentResolver().delete(
UserDictionary.Words.CONTENT_URI, // 用户词典 Content URI
mSelectionClause // 查询条件
mSelectionArgs // 查询条件值
);
在调用 ContentResolver.delete()
时,同样应该对用户输入内容进行必要的过滤,请阅读 防止非法输入一节。
Content Provider 支持很多种数据类型。 User Dictionary Provider 只支持文本类型,但 Provider 还可以支持以下类型:
Provider 经常用到的另一类数据是二进制大数据对象(Binary Large OBject,BLOB),这实现为 64KB 字节的数组。 具体有哪些数据类型可用,请查阅 Cursor
类的“get”方法。
通常,在 Provider 的文档中都会列出所有字段的数据类型。 User Dictionary Provider 的数据类型列在其 Contract 类 UserDictionary.Words
的参考手册中。(Contract 类在 Contract 类一节中介绍)。 通过调用 Cursor.getType()
方法也可以确定字段的数据类型。
Provider 还Provider 还维护着内部数据对应 Content URI 的 MIME 数据类型信息。 通过 MIME 类型信息,可以知晓应用程序能否处理这些数据,或者根据这些 MIME 信息选择相应的处理方式。 当 Provider 中包含了复杂的数据结构或文件时,通常就要用到 MIME 类型了。 比如,Contact Provider 中的 ContactsContract.Data> 表就用 MIME 类型来标记了联系人的数据。调用
ContentResolver.getType()
可以读取某个 Content URI 对应的 MIME 类型。
MIME 类型参考一节中介绍了标准的 MIME 及自定义 MIME 类型。
在开发应用时,访问 Provider 还有其他三种重要的形式:
ContentProviderOperation
类的一些方法,可以创建批量访问任务,并通过 ContentResolver.applyBatch()
来提交。CursorLoader
对象来实现。在指南 Loaders 中给出了示例。后续章节将介绍如何利用 Intent 批量读取和修改数据。 id="Batch">批量访问
如果要插入大量数据,或是在一次调用时要同时插入多张表的数据,或者要把多个跨进程的操作放入一个事务中完成(原子操作), 这时 Provider 的批量访问将十分有用。
为了能以“批量”方式访问 Provider,需要创建一个 ContentProviderOperation
对象的数组,并通过 ContentResolver.applyBatch()
方法将它传给 Content Provider。 在调用时不是指定某个 Content URI,而是要给出 Content Provider 的 authority。 数组中的每个 ContentProviderOperation
对象可以对不同的数据表进行操作。 ContentResolver.applyBatch()
返回的结果也是数组。
在 Contract 类 ContactsContract.RawContacts
的介绍中 的介绍中,包含了批量插入数据的代码片段。示例应用 Contact Manager的 ContactAdder.java
文件中,也包含了批量访问 Provider 的例子。
如果拥有足够的访问权限,也许会通过 Intent 显示其他应用的数据。 比如,日历应用可以接收 ACTION_VIEW
Intent,用于显示某个日期或事件。 这样就不用自己创建界面来显示日程信息了。 更多信息请参阅 Calendar Provider 指南。
发送 Intent 的应用不一定是与 Provider 关联的应用。 比如,可以从 Contact Provider 中读取一条联系人信息,然后向图片浏览应用发送一个包含了 Content URI 的 ACTION_VIEW
Intent,以便显示联系人的头像。
通过 Intent 通过 Intent 可以直接访问 Content Provider。 即使应用程序不具备访问权限,用户也可访问 Provider 中的数据。 可以是从具备权限的应用中获取一个结果 Intent,也可以启动具备权限的应用并在其中进行操作。
注意: 这两个标志位并不是针对 Provider (Content URI 中包含其 authority)赋读写权限的。 而只是对 URI 本身赋权。
Provider 在其 Manifest 文件中定义了 Content URI 的权限,这通过 <provider>
元素的 android:grantUriPermission
属性和 <provider>
的子元素 <grant-uri-permission>
来实现。关于 URI 权限的机制,将在 安全和权限 指南的“URI 权限”一节中详细介绍。
例如,即使没有 READ_CONTACTS
权限,也可以读取 Contacts Provider 中的联系人信息。 也许在某个发送电子生日贺卡的应用中,会需要用到这一功能。 拥有 READ_CONTACTS
权限可以访问所有的联系人相关信息,但更合适的做法是让用户来控制应用程序可以访问到的联系人。 按照以下步骤可以实现这一点:
startActivityForResult()
方法发送一个 Intent,其中包含的 Action 为 ACTION_PICK
,“contacts” MIME 类型为 CONTENT_ITEM_TYPE
。FLAG_GRANT_READ_URI_PERMISSION
权限,允许应用程序读取该 Content URI 指向的联系人信息。 然后, selection Activity 会调用 finish()
将控制权返回调用方应用。onActivityResult()
方法。该方法将获取由 People 应用的 selection Activity 返回的 Intent。还有一种方法比较简还有一种方法比较简单,也可以让用户修改未获授权的数据,这就是启动另一个具备权限的应用并让用户在其中完成修改工作。
比如,日历应用可以接收 href="http://developer.android.com/reference/android/content/Intent.html#ACTION_INSERT">ACTION_INSERT Intent,这可以启动它的插入界面。 可以在这个 Intent 中附带“extras”数据,用于指定要打开的内置界面。 因为定义一个日程事件的语法比较复杂,所以为了在 Calendar Provider 中插入一条事件,建议通过 ACTION_INSERT
启动日历应用并让用户在其中完成操作。
合约类(Contr合约类(Contract Class)定义了一些常量,以方便应用程序对 Content Provider中 的 Content URI、列名、Intent Action 等对象进行操作。 Contract 类不会跟随 Provider 自动生成,开发 Provider 的人员必须自行定义并提供给其他开发人员使用。 很多 Android 系统自带的 Provider 已经在 android.providere> 包中给出了相应的 Contract 类。
例如,User Dictionary Provider 就带有 Contract 类 UserDictionary
,里面定义了 Content URI 和列名常量。 “words”表的 Content URI 在常量 UserDictionary.Words.CONTENT_URI
中定义。 UserDictionary.Words
类还定 类还定义了列名常量,本文的代码中用到了这些常量。 比如,查询 Projection 可以定义如下:
Stringpan class="pun">[] mProjection =
{
UserDictionary.Words._ID,
UserDictionary.Words.WORD,
UserDictionary.Words.LOCALE
};
Contacts Provider 的另一个 Contract 类为 ContactsContract
,在它的参考文档中给出了示例代码。 ContactsContract.Intents.Insert
是它的 是它的一个子类,其中定义了有关 Intent 及其数据的常量。
Content Provider 可以返回标准的 MIME 媒体类型、自定义 MIME 类型字符串,或是两者都有。
MIME 类型的格式如下:
type/subtype
比如,常见的 MIME 类型 text/html
包含 text
类型和 html
子类型。 如果 Provider 返回该类型的 URI,就表示使用此 URI 的查询将返回带有 HTML 标记的文本。
自定义 MIME 类型字符串也被成为“供应商指定(vendor-specific)”的 MIME 类型,type 值和 subtype 值也更为复杂。 多行数据的 type 值通常为:
vnd.android.cursor.dir
单行数据则为:
vnd.android.cursor.item
subtype 值是由 Prov 值是由 Provider 定义的(provider-specific)。 Android 内置 Provider 的子类型通常都比较简单。 比如,联系人应用在新建一条电话号码数据时,就把其 MIME 类型设置为:
vndpan class="pun">.android.cursor.item/phone_v2
请注意,这里的子类请注意,这里的子类型值即为 phone_v2
其他开发人员也许会基于 Provider 的 authority 和表名创建自己的子类定义规则。 比如,假设某个 Provider 用于保存列车时刻表,其 authority 为 com.example.trains
,其中包括 Line1、Line2 和 Line3 三张表。 对应 Line1 表的 Content URI 为:
content://com.example.trains/Line1
Provider 返回的 MIME 类型为:
vnd.android.cursor.dir/vnd.example.line1
对应 Line2 表第 5 行数据的 URI 为:
content://com.example.trains/Line2/5
Provider 返回的 MIME 类型为:
vnd.android.cursor.item/vnd.example.line2
大部分 Cont大部分 Content Provider 都定义了自己的 Contract 类常量。 比如, Contacts Provider 的 Contract 类 ContactsContract.RawContactsde> 就为单条联系人数据的 MIME 类型定义了常量
CONTENT_ITEM_TYPE
。
关于单行数据的 Content URI 已经在 Content URI 一节中详细介绍过了。
Android开发-API指南-Content Provider基础
标签:
原文地址:http://www.cnblogs.com/popapa/p/android_content-provider-basics.html