第三部分:Android 应用程序接口指南---第一节:应用程序组件---第三章3-1Content Provider基础知识
3-1 Content Provider的基础知识
Content provider用于管理和访问中心仓库的数据。Provider是Android应用程序的一部分,它经常提供一个它自己的UI来用使用数据工作。然而,content provider主要是倾向于被其他应用程序使用,这些应用程序是通过一个provider客户端对象来访问provider。providers和provider客户端共同为数据提供一个一致的、标准的接口,这个接口也处理进程间通信和安全数据访问。
本文接下来将介绍以下内容:
1.content provider是如何工作的。
2.介绍用来取出content provider中数据的API。
3.在content provider中用来插入、更新或删除数据的API。
4.其他有助于与provider一起工作的API特征。
3-1.1 概述
一个content provider提供数据给外部应用程序,就像在关系型数据库中找到一个或多个相似的表格一样。一行代表的是由provider收集的一些数据类型,并且在该列的每一行都代表的是为实例收集的单个数据块。
例如,Android平台里的一个内建provider就好比是用户词典,它存储用户想要保存的非标准单词的拼写。下面的表格3-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 |
表格3-1 用户词典表
在表格3-1中,每一行代表一个在标准词典里可能找不到的单词。每一列代表那个单词的一些数据,就像locale表示第一次遇到的地点。每一列的标题是存储在provider里的列名。对于这个provider来说,_ID列是provider自动维护的一个“主键”列。
注意:provider并不是一定要有一个主键,并且如果provider已经存在一个主键名,那么它不需要用_ID列来作为主键的列名。然而,如果你想把provider中的数据绑定到一个ListView,那么列名中必须有一个是_ID。
1. 访问一个provider
应用程序使用一个ContentResolver客户端对象来访问content provider中的数据。这个对象在provider中有一个相同方法名字的调用,一个ContentProvider子类的具体实例。ContentResolver方法会提供基于持久储存的“CRUD”(创建、取出、更新和删除)函数。ContentResolver对象在客户端应用程序的进程里,而ContentProvider对象是在拥有能自动处理进程间通信的provider的应用程序中。ContentProvider也是一个抽象层,它介于仓库数据和数据的呈现方式之间的一种表格。
注意:要访问一个provider,你的应用程序通常就不得不在它的manifest文件中请求特定的权限。
例如,为了从用户词典Provider中获取单词列表和它们的locales,你可以调用ContentResolver.query()方法。而query()方法会调用由用户词典provider定义的ContentProvider.query()方法。下面就是一个ContentResolver.query()方法的调用,如代码清单3-1所示:
// 查询用户词典并返回结果 mCursor = getContentResolver().query( UserDictionary.Words.CONTENT_URI, // 单词表格的content URI mProjection, // 返回每一行的列 mSelectionClause // 筛选条件 mSelectionArgs, // 筛选条件 mSortOrder); // 为返回的行排序
代码清单3-1
表3-2展示了传递给query(Uri、projection、selection、selectionArgs、sortOrder)的参数是如何与一个SQL SELECT语句相匹配。
query() 参数 |
SELECT keyword/parameter |
Notes |
|
Uri |
FROM table_name |
Uri 映射到provider中的名字为table_name的表格 |
|
projection |
col,col,col,... |
Projection是一个列的数组 ,这个列是每一行中的集合 |
|
selection |
WHERE col =value |
筛选条件 |
|
selectionArgs |
(不用=号的话,可以用?替代成筛选条件) |
筛选条件 |
|
sortOrder |
ORDER BYcol,col,... |
每一行中,列的排序 |
表3-2 Query()与SQL查询的对比
2. Content URIs
一个Content URI表示是一个标识provider中数据的URI。Content URIs包含了整个provider的符号名(它的authority)以及指向表的名称(一条路径)。当你调用一个客户端方法来访问provider中的表格时,在前面的代码行中,常量CONTENT_URI就包含了用户词典里“word”表的content URI。ContentResolver对象会解析出URI的authority,并且会通过比较Authority和已知provider中的系统表,使用这个对象来“resolve(解析)”provider。ContentResolver可以给真正的provider派发查询参数。ContentProvider会用content URI中的路径部分来选择要访问的表。Provider对于它的每一张表通常都有一条路径。
在前面的代码行中,对于“word” 表来说完整的URI如代码清单3-2所示:
content://user_dictionary/words
代码清单3-2
在这个代码中,user_dictionary字符串是provider的authority,而words字符串是表的路径。字符串Content://(scheme)总是存在的并且它被标识为一个content URI。通过在URI的结尾追加一个ID值,许多provider都会允许你访问表中的一个单行。例如,为了从用户词典中取出一个_ID列为4的行,你可以使用下面这个content URI,如代码清单3-3所示:
Uri singleUri = ContentUri.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
代码清单3-3
当你已经取出一个行集合,却想更新或删除它们中的一个时,你就可以使用id值。
注意:Uri和Uri.Builder类包含一些便利方法,这些方法是用来构造字符串中格式合法的Uri对象。ContentUris包含一些用来追加id值到一个URI的便利方法。前面的代码就使用了withAppendedID()方法来把一个id值追加到UserDictionary的content URI。
3-1.2 取得Provider中的数据
下面介绍如何从provider中取出数据,以用户词典provider为例。
为了清楚起见,本节内容里的代码片段中会在“UI线程”上调用ContentResolver.query()方法。然而,在实际代码中,你应该在一个单独的线程上异步地执行查询。具体方法是使用在Loaders那一章中讲到的CursorLoader类。这部分的代码仅仅也只是一小部分,它没有展示出一个完整的应用程序。
为了取出provider中的数据,你可以遵循下面这些基本步骤:
1.请求对provider的读取访问权限。
2.给provider发送查询的代码进行定义。
1. 请求读取访问权限
为了取出provider中的数据,你的应用程序需要provider的“读取访问权限”。你不可以在运行期间请求这个权限;相反,你必须在你的manifest文件中使用<uses-permission>节点,来指定你要的权限,并且指出由provider定义的确切名称。当你指定manifest文件中的这个节点时,你就是在为你的应用程序“请求”这个权限。当用户安装你的应用程序时,他们会显式地给请求授权。用户词典provider会为manifest文件中的android.permission.READ_USER_DICTIONARY权限定义,所以想要读取provider的应用程序必须请求这个权限。
2. 构造查询
取出provider数据的下一个步骤是构造一个查询。下面我们就要为访问用户词典provider(User Dictionary Provider)定义一些变量,如代码清单3-4所示:
// 如表格3-2中的"projection"一样 返回每一行中的所有列 String[] mProjection = { UserDictionary.Words._ID, // _ID column name UserDictionary.Words.WORD, // word column name UserDictionary.Words.LOCALE // locale column name }; // 用于筛选的一个字符串 String mSelectionClause = null; // 初始化数组用于selectionArgs参数 String[] mSelectionArgs = {""};
代码清单3-4
下面的代码清单显示的是如何使用ContentResolver.query,以用户词典provider(User Dictionary Provider)为例。provider客户端查询与SQL查询相类似,并且它包含一组要返回的列、一组选择条件和一些排序。查询应该返回的列集合应该被叫做projection(变量mProjection)。指定要取出的行的表达式被分成一个选择子句和多个选择参数。选择子句由逻辑、Boolean表达式、列名和值(变量mSelection)构成。如果你指定可替代参数?来代替一个值,那么查询方法会取出在选择参数数组(变量mSelection)中的值。在下面的代码中,如果用户不输入一个单词,那么选择子句会变成null,并且查询会返回provider中的所有单词。如果用户输入一个单词,那么选择子句会被设计成UserDictionary.Word.WORD+"=?"并且选择参数数组中的第一个节点会变成用户输入的那个单词,如代码清单3-5所示:
/* * 用于selectionArgs参数. */ String[] mSelectionArgs = {""}; // 用UI中获得一个word mSearchString = mSearchWord.getText().toString(); //如果word为empty字符串,就获得所有 If the word is the empty string, gets everything if (TextUtils.isEmpty(mSearchString)) { // 如果筛选条件为null就返回所有words mSelectionClause = null; mSelectionArgs[0] = ""; } else { //否则匹配用户输入的筛选条件 mSelectionClause = UserDictionary.Words.WORD + " = ?"; // 把用户输入的字符串保存到selectionArgs中 mSelectionArgs[0] = mSearchString; } // 针对表格执行一次查询并返回的一个Cursor对象 mCursor = getContentResolver().query( UserDictionary.Words.CONTENT_URI, mProjection, mSelectionClause mSelectionArgs, mSortOrder); // 如果返回的cursor为null表示错误发生了 if (null == mCursor) { /* *处理错误 */ // 如果Cursor是empty, 表示 provider没有找到匹配的 } else if (mCursor.getCount() < 1) { /* * 提示用户搜索成功,只是没有匹配到合适的。这不是一个错误。你可以在这里为用户提供一些选择,或者提示用户在输入一次 * 其他的内容 */ } else { // 搜索成功并且匹配到合适的,做一些你想要执行的工作把 }
代码清单3-5
这个查询类似于下面这个SQL语句,如代码清单3-6所示:
SELECT _ID, word, frequency, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
代码清单3-6
这个SQL语句使用了实际的列名,而不是类常量。
3. 防止恶意输入
如果由content provider管理的数据是在一个SQL数据库里,那么把外部非信任数据包含进原始SQL语句将会导致SQL的注入行为。所以我们考虑下面这个选择子句,如代码清单3-7所示:
// 根据用户输入的列名构造一个筛选条件 String mSelectionClause = "var = " + mUserInput;
代码清单3-7
如果你真的把外部非信任数据包含进原始SQL语句,那么你就是在允许用户把恶意SQL拼接进你的SQL语句里。例如,用户输入"nothing;DROP TABLE *;" ,把它作为mUserInput变量, 那么选择子句将变成 var = nothing; DROP TABLE *;由于选择子句被看作是一个SQL语句,它可能导致provider会清除底层SQLite数据库中的所有表 (除非provider捕获SQL 的注入行为)。
为了避免这个问题,可以使用一个把?看作是可替代参数的筛选子句和一个单独的用于筛选参数的数组。但当你这样做了,用户输入就会直接绑定到查询,而不会被解析成SQL语句的一部分。因为它的筛选子句不会被看作是一个SQL,所以它不会注入恶意的SQL。我们不会使用拼接来包含用户输入,而是使用下面这个筛选子句,如代码清单3-8所示:
// 用可替换参数构造一个筛选子句 String mSelectionClause = "var = ?";
代码清单3-8
我们应该这样设计筛选参数数组,如代码清单3-9所示:
// 定义一个包含筛选参数的数组 String[] selectionArgs = {""};
代码清单3-9
我们要在筛选参数数组中放入一个值,如代码清单3-10所示:
// 这个值就是用户输入的内容 selectionArgs[0] = mUserInput;
代码清单3-10
使用?作为可替代参数的筛选子句和筛选参数数组都是指定一个筛选的较好方式,即使provider没有基于SQL数据库。
4. 显示查询结果
ContentResolver.query客户端方法总是返回一个Cursor,这个cursor会为匹配查询选择条件的行,把查询projection指定的列包含进去。一个cursor对象会提供对行和列的随意读取访问。使用Cursor方法,你可以遍历结果中的行,决定每一列的数据类型,取出列中的数据并检查结果的其他属性。当provider的数据发生改变时,一些Cursor方法的实现会自动更新对象,或者当Cursor发生改变时,实现一些Cursor方法会触发observer对象中的方法,或者两种情况都会发生。
注意:provider基于制造查询对象的自然属性,可能会限制对列的访问。例如,联系人Provider就会对一些指向同步adapters(适配器)的列限制访问,所以它不会把这些列返回给一个activity或一个service。
如果没有行能匹配选择条件,那么provider返回给Cursor.getCount()方法的Cursor对象会是0(一个空的cursor)。如果一个内部error发生了,那么查询的结果将会依赖于一个特定的provider。它可能选择返回null或者抛出一个Exception。由于一个Cursor是行的一个“list”,所以显示Cursor的contents的一个好方法是通过一个SimpleCursorAdapter把它连接到ListView。下面的代码是前面代码的继续。它创建一个SimpleCursorAdapter对象,这个对象包含由查询取出来的Cursor,并把它设置成一个用于ListView的adapter(适配器),如代码清单3-11所示:
String[] mWordListColumns = { UserDictionary.Words.WORD, UserDictionary.Words.LOCALE }; // 定义一个list中的view ID 用于从每一行中检索Cursor列 int[] mWordListItems = { R.id.dictWord, R.id.locale}; mCursorAdapter = new SimpleCursorAdapter( getApplicationContext(), R.layout.wordlistrow, // ListView mCursor, // 查询返回的结果 mWordListColumns, // cursor 中的一个列名的字符串数组 mWordListItems, // 表示在行布局中的一个int型的view ID数组 0); // Flags (通常不需要) mWordList.setAdapter(mCursorAdapter);
代码清单3-11
注意:为了回到一个带有Cursor的ListView,这个cursor就必须包含一个名为_ID的列。因此,即使ListView不会显示它,这个查询也会显示先前从“word”表中取出的_ID列。这个限制条件也解释了为什么大多数provider在他们的每个表中都有一个_ID列。
5. 从查询结果中获取数据
并不是简单地显示查询结果,你可以把它们用在其他的任务上。例如,你可以从用户词典中取出拼写,然后在其他的provider中找到他们。做这项工作,你要先遍历Cursor中的所有行,如代码清单3-12所示:
// 根据“word”确定列的索引 int index = mCursor.getColumnIndex(UserDictionary.Words.WORD); /* * 一定要检查cursor合法才执行。如果发生一个外部错误,用户此段Provider就会返回null。其他provider可能会抛出异常 * 而不是返回null */ if (mCursor != null) { /* * 移动到cursor的下一行. 因为第一行为-1,如果你不moveToNext,直接使用,则会抛出异常。 */ while (mCursor.moveToNext()) { // 从列中获得值 newWord = mCursor.getString(index); } } else { //发送错误 打印报告 或处理. }
代码清单3-12
为了从对象中取出不同类型的数据,Cursor的实现会包含几个“get”方法。例如,前面的代码就使用了getString()方法。这还有一个getType()方法,它返回一个值,这个值会表明列的数据类型。
3-1.3 Content Provider的权限
一个provider应用程序可以指定其他应用程序必须拥有的权限,以便它们能访问provider中的数据。这些权限可以确保用户知道应用程序要访问的是什么数据。基于provider的需求,其他应用程序会为了能访问到provider而请求它们所需要的权限。当用户安装应用程序时,在最后会看到这些请求权限。
如果provider应用程序不指定任何的权限,那么其他的应用程序就不能访问provider的数据。然而,不管被指定的权限是什么,provider的应用程序组件总是有完全读和写的访问权限。
正如前面所提到的,用户词典provider需要android.permission.READ_USER_DICTIONARY权限来取出它里面的数据。Provider有一个单独的android.permission.WRITE_USER_DITIONARY权限来插入、更新或删除数据。
为了获得访问provider的权限,应用程序会在它的manifest文件中用一个<user-permission>节点来请求他们。当Android包管理器安装应用程序时,用户必须全部批准这些请求。如果用户批准了,包管理器就会继续安装;如果没有批准,那么包管理器就会中止安装。
下面是<user-permission>节点来请求对用户词典provider(User Dictionary Provider)的读取访问权限,如代码清单3-13所示:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY">
代码清单3-13
3-1.4 插入,更新,删除数据
我们能够取出provider中的数据,同样的,你也可以使用一个provider客户端与provider的ContentProvider之间的交互来修改数据。你可以用参数来调用ContentResolver的方法,这些参数被传递给了相应ContentProvider的方法。provider和provider客户端都能自动处理安全问题和进程间通信。
1. 插入数据
要插入数据到一个provider,你可以调用ContentResolver.insert()方法。这个方法会插入一个新的行到provider,并为这个行返回一个cotent URI。下面我们演示如何插入一个新单词到用户词典provider(User Dictionary Provider),如代码清单3-14所示:
// 定义一个新的Uri对象用于去除插入的结果
Uri mNewUri;
...
// 定义一个对象包含新的插入值
ContentValues mNewValues = new ContentValues();
/*
* 键值对的形式把需要的值插入每一列
*/
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 // 插入的值
);
代码清单3-14
新行的数据会进入到一个单一的ContentValues对象,这与一个单行cursor的形式相类似。这个对象的列不需要拥有相同的数据类型,并且如果你根本就不想指定一个值,那么你可以使用ContentValues.putNull()方法把列设置成null。
上面的代码没有添加_ID列,因为这个列是自动维护的。Provider给每个添加的行赋予了一个唯一值。通常provider会使用这个值作为表的主键。
返回到newUri的content URI,可以使用下面的格式,来标识新增的行,如代码清单3-15所示:
content://user_dictionary/words/<id_value>
代码清单3-15
<id_value>是新行、_ID列的contents。大多数provider可以自动检测content URI的形式,然后在特定的行上执行被请求的操作。为了从返回的Uri上获得_ID列的值,你可以调用ContentUris.parseID()方法。
2. 更新数据
要更新一个行,你可以使用一个带有更新值的ContentValues对象,这就正如你处理插入操作一样,选择条件操作,就像你处理查询一样。你使用的客户端方法是ContentResolver.update()。你只需要为正在更新的列,添加一个值到ContentValues对象。如果你想清除一个列的contents,那么你就把值设置成null。
下面我们就把所有locale列中,拥有语言“en”的行改成locale为null。返回值是被更新行的数量,如代码清单3-16所示:
ContentValues mUpdateValues = new ContentValues(); // 根据你想要更新的行,定义筛选条件 String mSelectionClause = UserDictionary.Words.LOCALE + "LIKE ?"; String[] mSelectionArgs = {"en_%"}; // 用于更新行的一个计数 int mRowsUpdated = 0; ... /* * 设置更新的值并更新筛选的words */ mUpdateValues.putNull(UserDictionary.Words.LOCALE); mRowsUpdated = getContentResolver().update( UserDictionary.Words.CONTENT_URI, mUpdateValues mSelectionClause mSelectionArgs );
代码清单3-16
当你调用ContentResolver()方法时,你还应该清除用户输入。
3. 删除数据
删除行就类似于取出行中的数据:你可以为要删除的行指定选择条件,然后客户端方法会返回被删除行的数量。下面我们就删除app id列中是“user”的行,然后方法就返回删除行的数量,如代码清单3-17所示:
// 根据你想要删除的行,定义筛选条件 String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?"; String[] mSelectionArgs = {"user"}; int mRowsDeleted = 0; ... // 删除与筛选条件匹配的words mRowsDeleted = getContentResolver().delete( UserDictionary.Words.CONTENT_URI, mSelectionClause mSelectionArgs );
代码清单3-17
当你调用ContentResolver.delete()方法时,你还应该清除用户输入。
3-1.5 Provider的数据类型
Content provider可以提供许多不同类型的数据。用户词典provider(User Dictionary Provider)只提供文本类型的数据,而provider就可以提供下面这些数据类型:
(1)int
(2)long
(3)float
(4)double
Provider经常使用的另一种数据类型是二进制大型对象(Binary Large OBject),它是作为一个64KB字节数组来实现。你可以通过查看Cursor类的“get”方法看到可用的数据类型。provider中的每一列数据类型通常会列在它的文档里。用户词典provider(User Dictionary Provider)的数据类型会列在它合约类的UserDictionary.Word引用文档里。你还可以通过调用Cursor.getType()方法来确定数据类型。Provider还会为他们定义的每个content URI保留MIME数据类型信息。你可以用MIME数据类型信息来找出你的应用程序是否可以处理provider提供的数据,或者基于MIME类型选择一类数据来处理。当你正在用一个包含复杂数据结构或文件的provider工作时,你通常需要MIME类型。例如,在合约Provider中的ContactsContact.Data表,可以使用MIME类型来为每一行的联系人数据类型做标记。为了获得对应一个content URI的MIME类型,你可以调用ContentResolver.getType()方法。
3-1.6 Provider访问可选表单
Provider有三种可选形式,他们对应用程序的开发都是非常重要的:
1.批处理访问:你可以用ContentProviderOperation类的方法创建一个批处理访问调用,然后用ContentResolver.applyBatch()方法应用它们。
2.异步查询:你应该用一个单独线程来执行查询。具体是使用一个CursorLoader对象。
3.通过intents的数据访问:尽管你不能直接发送一个intent给provider,但你可以发送一个intent给provider应用程序,它最能胜任修改provider的数据。
1. 批处理访问
批处理访问一个provider,对于插入大量行的操作是很有用的,或者调用相同的方法把行插入到多个表中,或者它通常也会跨进程边界执行一组事务操作(一个原子操作)。
为了以“批量模式”访问一个provider,你可以创建一个ContentProvider对象数组,然后用ContentResolver.applyBatch()方法给它们派发给一个content provider。你把content provider的权利传递给这个方法,并没有传递一个特定的content URI,虽然这个URI允许数组中的每个ContentProviderOperation对象来处理一个不同的表。调用ContentResolver.applyBatch()方法会返回一个结果数组。
描述ContactsContact.RawContacts的合约类会包含一个演示批量插入的代码清单。Contact Manager示例应用程序会在它的ContactAdder.java源文件中包含一个批处理访问的例子。
2. 通过intents的数据访问
Intents可以间接访问一个ontent provider。你可以允许用户访问provider中的数据,即使你的应用程序没有访问权,你要么通过获取一个结果intent来访问,这个intent是从一个有权限访问的应用程序返回的,要么就激活一个拥有权限的应用程序,来让用户在应用程序里面工作。
3. 用临时权限获取数据访问
即使你没有合适的访问权限,你也可以给拥有访问权限的应用程序发送一个intent并收回一个包含“URI”权限的结果intent,来访问provider中的数据。这些都是一个特定content URI的权限,这个特定的content URI会一直持续,直到接收它们的activity结束才停止。拥有永久权限的应用程序会通过设置在结果intent中的一个标记来授予临时权限:
·读权限:FLAG_GRANT_READ_URI_PERMISSION
·写权限:FLAG_GRANT_WRITE_URI_PERMISSION
注意:这些标记不会将一般读或写的权限给权利被包含进content URI的provider。访问权只用于URI本身。
Provider可以使用<provider>节点的adroid:grantUriPermission属性和<grant-uri_permission>子节点,为在manifest文件中的content URI定义URI权限。
例如,即使你没有READ_CONTACTS权限,你也可以取出Contract Provider中的联系人数据。你可能想在他或她生日时发送电子贺卡。所以你并不要请求READ_CONTACTS,虽然它可以访问你所有用户联系人及信息,但你更应该让用户来控制你的应用程序使用哪个联系人。下面就是这个方法的步骤:
◆应用程序可以使用startActivity()方法来发送一个intent,这个intent包含动作ACTION_PICK和CONTENT_ITEM_TYPE MIME的“联系人”MIME类型。
◆因为这个intent会将intent filter与联系人app的“选择”activity相匹配,所以activity将回到前台位置。
◆在选择activity中,用户会选择一个联系人来更新。当更新联系人信息时,选择activity会调用setResult(resultcode,intent)方法来配置一个intent,让它返回到你的应用程序。Intent包含用户选择的联系人URI,以及FLAG_GRANT_READ_URI_PERMISSION的“额外”标记。这些标记会给你的应用程序授权,以便它能读取content provider指向的联系人数据。然后选择activity会调用finish()方法来给应用程序返回控制权。
◆activity返回到前台后,系统就会调用activity的onActivityResult()方法。这个方法会接收在联系人应用中由选择activity创建的结果intent。
◆使用结果intent中的content URI,即使你在manifest文件中没有请求对provider的永久读取访问权限,你也可以读取Contacts Provider中的联系人数据。然后你就可以获得联系人他或她的生日信息或电子邮件地址,给他或她发送电子贺卡。
4. 使用另一个应用程序
要允许用户修改你没有访问权的数据,一个简单方法是激活一个拥有权限的应用程序,并让用户在它里面工作。
例如,日历应用程序接收一个ACTION_INSERT intent,这个intent允许你激活应用程序的插入UI。你可以在这个intent中传递“额外”数据,这样应用程序可以用它来预填充这个UI。因为循环事件有一个复杂的语法,所以把事件插入到Calendar Provider的首选方式是用一个ACTION_INSERT来激活日历应用,并且让用户在应用那插入事件。
小扩展:使用一个辅助器应用来显示数据
如果你的应用程序拥有访问权限,你仍然可能要用一个intent来显示另一个应用程序的数据。例如,日历应用程序接收一个ACTION_VIEW intent,这个intent会显示一个特定的日期或事件,它允许你显示日历信息而不必创建自己的UI。接收intent的应用程序并不需要与provider相关联。例如,你可以取出联系人应用程序的一个联系人信息,然后发送一个ACTION_VIEW intent给图片查看器,这个intent包含了联系人图片的content URI。
3-1.7 合约类(Contract Class)
其实我更感觉是内部静态类,合约类定义了一些常量来促进应用程序工作,这些常量是content URIs、列名、intent动作和content provider的其他特征。合约类不会自动包含一个provider,并且provider的开发者必须定义它们,然后让它们对其他开发者可用。许多把Android平台包含进去的provider在abdroid.provider包中都有相对应的合约类。例如,用户词典provider就有一个UserDictionary的合约类,它包含content URI和列名常量。针对“word”表的content URI会在常量UserDictionary.Word.CONTENT_URI中被定义。UserDictionary.Word类也包含了下面这个例子中使用的列名常量。例如,我们可以这样定义一个查询projection(投影),如代码清单3-18所示:
String[] mProjection =
{
UserDictionary.Words._ID,
UserDictionary.Words.WORD,
UserDictionary.Words.LOCALE
};
代码清单3-18
另一个合约类是ContractsContract。ContractsContract.Intents.Insert是它的子类之一,它包含intents和intent数据常量。
3-1.8 MIME类型应用
Content provider可以返回标准MIME媒体类型或自定义的MIME类型字符串,或者两个都返回。
MIME类型格式如代码清单3-19所示:
type/subtype
代码清单3-19
例如,众所周知的text/html的MIME类型,它有text类型和html子类型。如果provider为一个URI返回这个MIME类型,那么就意味着使用这个URI的查询将会返回包含HTML标签的文本。自定义的MIME类型字符串也叫做“vendor-specific”MIME类型,它有更复杂的类型和子类型值。所以对于多行MIME类型的类型值,会总是如代码清单3-20所示:
vnd.android.cursor.dir
代码清单3-20
对于单行的MIME类型,类型值总是如代码清单3-21所示:
vnd.android.cursor.item
代码清单3-21
这个子类型值是provider-specific。Android内置的provider通常都会有一个简单的子类型。例如,当联系人应用程序给一个电话号码创建一行时,它会在这一行上设置下面的MIME类型,如代码清单3-23所示:
vnd.android.cursor.item/phone_v2
代码清单3-23
要注意,这个类型值只是phone_v2。另一个provider开发者会基于provider的权利和表名,来创建它们自己的子类型模式。例如,一个包含列车时刻表的provider。Provider的权利是com.example.trains,并且它包含了表Line1、Line2和Line3.针对表Line1的content URI,provider会返回下面MIME类型,如代码清单3-24、3-25所示:
content://com.example.trains/Line1
代码清单3-24
vnd.android.cursor.dir/vnd.example.line1
代码清单3-25
针对表Line2中第5行的content URI,provider会返回下面的MIME类型,如代码清单3-26、3-27所示:
content://com.example.trains/Line2/5
代码清单3-26
vnd.android.cursor.item/vnd.example.line2
代码清单3-27
大多数的content provider会给他们使用的MIME类型定义合约类常量。例如,联系人provider的ContractsContract.Raw合约类就给一个单行的联系人MIME类型定义了一个CONTENT_ITEM_TYPE常量。
本文来自jy02432443,QQ78117253。是本人辛辛苦苦一个个字码出来的,转载请保留出处,并保留追究法律责任的权利