从Provider取得data
本节讲述了如何从provider取得数据,使用用户词典作为例子.
为了清析易懂,本节中调用ContentResolver.query()的代码片断置于"UI 线程"中.但是,在实际代码中,你应该在另一个线程执行查询动作,这样做的一种方法是使用CursorLoader 类.而,那几行示例代码仅是片断,它们不能展示一个完整的应用.
要从provider取得data,须依如下步骤:
1 请求provider的读权限.
2 定义发送请求到provider的代码.
请求读权限
要从一个provider中获取数据,你的应用需要对目标provider具有"读权限".你不能在运行时请求此权限,而只能在manifest文件中使用 <uses-permission> 元素指定你的权限需求.当你在manifest中指定此元素时,你实际上就是在为你的应用请求这个权限.当用户安装你的应用时,就表示同意了这个权限请求.
要找到你使用的provider读权限的所对应的准确名字,以及其它用于provider的权限的名字,请浏览provider的文档.
关于操作provider的权限的角色的更多信息,请见Content Provider权限一节.
用户词典Provider在它的manifest 中定义了android.permission.READ_USER_DICTIONARY 权限,所以一个想读取它内容的应用必须请求此权限.
构建请求
获取数据的下一步是构建一个请求(query).这里的第一个代码片段定义了一些用于操作用户词典Provider的变量:
下一个代码片段演示了如何使用ContentResolver.query(),将用户词典Provider作为一个例子.一个provider客户端查询极像一个SQL查询,它包含了要返回的一坨column们,一堆筛选条件,和一个排序方式.
查询返回的column集合被称作projection (变量 mProjection).
指定返回的列的语句被分解为选择条款和选择参数两部分.选择条款是逻辑和布尔表达式,列名以及值的组合体(变量mSelection).如果你在其中指定了使用 ? 来代表一个值,查询方法就会从选择参数部分取得这个值(变量mSelectionArgs).
在下一个代码片段中,如果用户没有输入单词,选择条款就被设为null,并且查询会反回所provider中所有的单词.如果用户输入了单词,选择条款就被设置为UserDictionary.Words.Word + " = ?" 并且选择参数(数组)的第一项被设置为用户输入的单词.
查询与下面的SQL语句等价:
SELECT _ID, word, frequency, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
在此SQL 语句中,以实际的列名代替了内置的类别常量.
防止恶意输入
如果被content provider管理的数据是一个SQL 数据库,在原始的SQL语句中包含不可信的数据会导致SQL注入.
思考以下选择条款:
// 通过连接用户输入到列名来构造一个选择条款
String mSelectionClause = "var = " + mUserInput;
如果你这样做,你就允许用户连接恶意的SQL语句到你的SQL语句中.例如,用户可以输入"nothing; DROP TABLE *;" ,这将在选择条款中变为 var = nothing; DROP TABLE *;..既然选择条款被作为SQL语句,这就可能导致provider删除SQLite数据库中的所有的表(除非provider被设置成捕获SQL injection 阴谋).
要避免此问题,应使用一个运用?作为可替换参数的选择条款和一个作为选择参数的数组.当你这样做时,用户输入被直接绑定到查询而不是被解释为SQL语句的一部分.因为它不被认为是SQL,于是用户输入就不能注入恶意SQL.使用以下选择条款来代替连接用户输入的那个:
// 构造一个带有占位符的选择条款
String mSelectionClause = "var = ?";
像这样建立起选择参数数组:
// 定义一个数组来容纳选择参数
String[] selectionArgs = {""};
像这样把一个值置入选择参数数组中:
// Sets the selection argument to the user's input
selectionArgs[0] = mUserInput;
一个使用?作为占位符的选择条款+一个选择参数数组是指定一个选择器的最佳方式,即使provider不是基于SQL数据库的.
显示查询结果
客户端方法ContentResolver.query() 总是返回一个包含所查询的列们的Cursor .一个Cursor 对象提供了随机的读取它所包含的行和列的能力.使用Cursor 的方法们,你可以迭代结果中的行,决定每列的数据类型,从列获得数据,以及检测结果的其它属性.一些Cursor 的实现会在provider的数据改变时自动更新,或在Cursor 改变时触发监听者的方法,或者两者都支持.
注: 一个provider可能跟据构建查询的对象的性质限制对某些列的操作.例如,联系人Provider会禁止同步适配器操作某些列,所以它不会把它们返回给一个activity或service.
如果没有符合选择条件的行,provider返回一个Cursor 对象,其Cursor.getCount() 为0 (一个空cursor).
如果一个内部错误发生,查询结果会因provider的不同而不同.它可能返回null,也可能抛出一个Exception.
既然一个Cursor 是行组成的"列表",那么一个和显示Cursor 内容的好方法就是把它链接到一个ListView 上,通过SimpleCursorAdapter.
下面的代码片段是衔接前面的代码来的.它创建一个SimpleCursorAdapter 对象,包含有查询返回的Cursor ,然后设置这个对象为ListView的适配器.
注:要使Cursor支持ListView,cursor必须包含一个叫做_ID的列,因此,上面所示的查询从"单词"表中取出了_ID ,当然ListView 可以不显示它.这条限制同时也解释了为毛大多数provider在它们的表中都具有一个_ID 列.
从查询结果中获取数据
你可以使用查询结果做更多是事情,而不是仅简单地显示它们.比如,你可以从用户词典中获取拼法然后在其它provider中查找它们.要这样做,你需在Cursor中迭代所有的行.
Cursor 的实现包含了多个"get" 方法,用于从对象中获取不同类型的数据.例如,前面的代码片段使用getString().它们也具有一个getType() 方法,用它可以返回的值代表了数据的类型.
本节讲述了如何从provider取得数据,使用用户词典作为例子.
为了清析易懂,本节中调用ContentResolver.query()的代码片断置于"UI 线程"中.但是,在实际代码中,你应该在另一个线程执行查询动作,这样做的一种方法是使用CursorLoader 类.而,那几行示例代码仅是片断,它们不能展示一个完整的应用.
要从provider取得data,须依如下步骤:
1 请求provider的读权限.
2 定义发送请求到provider的代码.
请求读权限
要从一个provider中获取数据,你的应用需要对目标provider具有"读权限".你不能在运行时请求此权限,而只能在manifest文件中使用 <uses-permission> 元素指定你的权限需求.当你在manifest中指定此元素时,你实际上就是在为你的应用请求这个权限.当用户安装你的应用时,就表示同意了这个权限请求.
要找到你使用的provider读权限的所对应的准确名字,以及其它用于provider的权限的名字,请浏览provider的文档.
关于操作provider的权限的角色的更多信息,请见Content Provider权限一节.
用户词典Provider在它的manifest 中定义了android.permission.READ_USER_DICTIONARY 权限,所以一个想读取它内容的应用必须请求此权限.
构建请求
获取数据的下一步是构建一个请求(query).这里的第一个代码片段定义了一些用于操作用户词典Provider的变量:
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
|
// "projection" 定义了要返回的各列们
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
};
// 定义一个包含"select"条款的字符串
String mSelectionClause =
null
;
// 初始化一个包含"select"参数的字符串
String[] mSelectionArgs = {
""
};
|
下一个代码片段演示了如何使用ContentResolver.query(),将用户词典Provider作为一个例子.一个provider客户端查询极像一个SQL查询,它包含了要返回的一坨column们,一堆筛选条件,和一个排序方式.
查询返回的column集合被称作projection (变量 mProjection).
指定返回的列的语句被分解为选择条款和选择参数两部分.选择条款是逻辑和布尔表达式,列名以及值的组合体(变量mSelection).如果你在其中指定了使用 ? 来代表一个值,查询方法就会从选择参数部分取得这个值(变量mSelectionArgs).
在下一个代码片段中,如果用户没有输入单词,选择条款就被设为null,并且查询会反回所provider中所有的单词.如果用户输入了单词,选择条款就被设置为UserDictionary.Words.Word + " = ?" 并且选择参数(数组)的第一项被设置为用户输入的单词.
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
/**
* 定义一个一维的字符串数组来容纳选择参数们
*/
String[] mSelectionArgs = {
""
};
// 从界面中获取一个单词
mSearchString = mSearchWord.getText().toString();
// 记住要在此插插入代码检查不合法的或恶意的输入.
// 如果单词是空的,则获取所有数据
if
(TextUtils.isEmpty(mSearchString)) {
// 设置选择条款为null就会返回所有单词
mSelectionClause =
null
;
mSelectionArgs[
0
] =
""
;
}
else
{
// 构造一个匹配用户输入的单词的选择条款
mSelectionClause =
" = ?"
;
// 将用户输入的单词置于选择参数中
mSelectionArgs[
0
] = mSearchString;
}
// 执行查询并返回游标对象
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
// 有些provider在出错时返回null,有抛出异常
if
(
null
== mCursor) {
/**
* 在此插入代码处理错误.记住不要使用游标! 你可能要调用
* android.util.Log.e()把错误记录的日志
*
*/
// 如果游标是空的,找不到匹配的provider
}
else
if
(mCursor.getCount() <
1
) {
/*
* 在此插入代码来通知用户,查找不成功.这也不能完全算是个错误.你可能想为用户提供插入一个新行或重新输入查询单词的选项
*/
}
else
{
在此插入代码,利用返回的结果做想做的事
}
|
查询与下面的SQL语句等价:
SELECT _ID, word, frequency, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
在此SQL 语句中,以实际的列名代替了内置的类别常量.
防止恶意输入
如果被content provider管理的数据是一个SQL 数据库,在原始的SQL语句中包含不可信的数据会导致SQL注入.
思考以下选择条款:
// 通过连接用户输入到列名来构造一个选择条款
String mSelectionClause = "var = " + mUserInput;
如果你这样做,你就允许用户连接恶意的SQL语句到你的SQL语句中.例如,用户可以输入"nothing; DROP TABLE *;" ,这将在选择条款中变为 var = nothing; DROP TABLE *;..既然选择条款被作为SQL语句,这就可能导致provider删除SQLite数据库中的所有的表(除非provider被设置成捕获SQL injection 阴谋).
要避免此问题,应使用一个运用?作为可替换参数的选择条款和一个作为选择参数的数组.当你这样做时,用户输入被直接绑定到查询而不是被解释为SQL语句的一部分.因为它不被认为是SQL,于是用户输入就不能注入恶意SQL.使用以下选择条款来代替连接用户输入的那个:
// 构造一个带有占位符的选择条款
String mSelectionClause = "var = ?";
像这样建立起选择参数数组:
// 定义一个数组来容纳选择参数
String[] selectionArgs = {""};
像这样把一个值置入选择参数数组中:
// Sets the selection argument to the user's input
selectionArgs[0] = mUserInput;
一个使用?作为占位符的选择条款+一个选择参数数组是指定一个选择器的最佳方式,即使provider不是基于SQL数据库的.
显示查询结果
客户端方法ContentResolver.query() 总是返回一个包含所查询的列们的Cursor .一个Cursor 对象提供了随机的读取它所包含的行和列的能力.使用Cursor 的方法们,你可以迭代结果中的行,决定每列的数据类型,从列获得数据,以及检测结果的其它属性.一些Cursor 的实现会在provider的数据改变时自动更新,或在Cursor 改变时触发监听者的方法,或者两者都支持.
注: 一个provider可能跟据构建查询的对象的性质限制对某些列的操作.例如,联系人Provider会禁止同步适配器操作某些列,所以它不会把它们返回给一个activity或service.
如果没有符合选择条件的行,provider返回一个Cursor 对象,其Cursor.getCount() 为0 (一个空cursor).
如果一个内部错误发生,查询结果会因provider的不同而不同.它可能返回null,也可能抛出一个Exception.
既然一个Cursor 是行组成的"列表",那么一个和显示Cursor 内容的好方法就是把它链接到一个ListView 上,通过SimpleCursorAdapter.
下面的代码片段是衔接前面的代码来的.它创建一个SimpleCursorAdapter 对象,包含有查询返回的Cursor ,然后设置这个对象为ListView的适配器.
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
// 定义要从Cursor取出的并要加载到view中的列们
String[] mWordListColumns =
{
UserDictionary.Words.WORD,
// Contract class constant containing the word column name
UserDictionary.Words.LOCALE
// Contract class constant containing the locale column name
};
// 定义一个View ID组成的列表,它们将接收每行的Cursor列的值
int
[] mWordListItems = { R.id.dictWord, R.id.locale};
// Creates a new SimpleCursorAdapter
mCursorAdapter =
new
SimpleCursorAdapter(
getApplicationContext(),
// The application's Context object
R.layout.wordlistrow,
// ListView的一行的layout
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)
// 将适配器设置给ListView
mWordList.setAdapter(mCursorAdapter);
|
注:要使Cursor支持ListView,cursor必须包含一个叫做_ID的列,因此,上面所示的查询从"单词"表中取出了_ID ,当然ListView 可以不显示它.这条限制同时也解释了为毛大多数provider在它们的表中都具有一个_ID 列.
从查询结果中获取数据
你可以使用查询结果做更多是事情,而不是仅简单地显示它们.比如,你可以从用户词典中获取拼法然后在其它provider中查找它们.要这样做,你需在Cursor中迭代所有的行.
[Java]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// 获取叫做"word"的列的序号
int
index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
/*
* 仅在cursor有效时执行下面语句.如果发生内部错误,用户词典Provider返回null.
* 其它provider可能抛出一个异常而不是返回null.
*/
if (mCursor != null) {
/*
* 移到cursor中的下一行.在第一次移动之前,
* "行指针" 为-1,并且,如果你想获取那个位置的数据,你将得到一个异常
*/
while
(mCursor.moveToNext()) {
// 从列中获取值.
newWord = mCursor.getString(index);
// 在此插入代码处理获取到的单词
...
// 循环结束
}
}
else
{
// 如果cursor为null或前面抛出了异常,在处插入代码报告错误.
}
|
Cursor 的实现包含了多个"get" 方法,用于从对象中获取不同类型的数据.例如,前面的代码片段使用getString().它们也具有一个getType() 方法,用它可以返回的值代表了数据的类型.