当前位置: 代码迷 >> Android >> android Content Provider详解2
  详细解决方案

android Content Provider详解2

热度:118   发布时间:2016-05-01 17:34:27.0
android Content Provider详解二

从Provider取得data

本节讲述了如何从provider取得数据,使用用户词典作为例子.

为了清析易懂,本节中调用ContentResolver.query()的代码片断置于"UI 线程"中.但是,在实际代码中,你应该在另一个线程执行查询动作,这样做的一种方法是使用CursorLoader 类.而,那几行示例代码仅是片断,它们不能展示一个完整的应用.

要从provider取得data,须依如下步骤:

请求provider的读权限.

定义发送请求到provider的代码.

请求读权限

要从一个provider中获取数据,你的应用需要对目标provider具有"读权限".你不能在运行时请求此权限,而只能在manifest文件中使用 <uses-permission> 元素指定你的权限需求.当你在manifest中指定此元素时,你实际上就是在为你的应用请求这个权限.当用户安装你的应用时,就表示同意了这个权限请求.

要找到你使用的provider读权限的所对应的准确名字,以及其它用于provider的权限的名字,请浏览provider的文档.

关于操作provider的权限的角色的更多信息,请见Content Provider权限一节.

用户词典Provider在它的manifest 中定义了android.permission.READ_USER_DICTIONARY 权限,所以一个想读取它内容的应用必须请求此权限.

构建请求

获取数据的下一步是构建一个请求(query).这里的第一个代码片段定义了一些用于操作用户词典Provider的变量:


// "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 + " = ?" 并且选择参数(数组)的第一项被设置为用户输入的单词.

/*
 * 定义一个一维的字符串数组来容纳选择参数们
 */
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会禁止同步适配器操作某些列,所以它不会把它们返回给一个activityservice

如果没有符合选择条件的行,provider返回一个Cursor 对象,其Cursor.getCount() 0 (一个空cursor)

如果一个内部错误发生,查询结果会因provider的不同而不同.它可能返回null,也可能抛出一个Exception

既然一个Cursor 是行组成的"列表",那么一个和显示Cursor 内容的好方法就是把它链接到一个ListView 上,通过SimpleCursorAdapter

下面的代码片段是衔接前面的代码来的.它创建一个SimpleCursorAdapter 对象,包含有查询返回的Cursor ,然后设置这个对象为ListView的适配器.

// 定义要从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支持ListViewcursor必须包含一个叫做_ID的列,因此,上面所示的查询从"单词"表中取出了_ID ,当ListView 可以不显示它.这条限制同时也解释了为毛大多数provider在它们的表中都具有一个_ID 列.

从查询结果中获取数据

你可以使用查询结果做更多是事情,而不是仅简单地显示它们.比如,你可以从用户词典中获取拼法然后在其它provider中查找它们.要这样做,你需在Cursor中迭代所有的行.


// 获取叫做"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 {

    // 如果cursornull或前面抛出了异常,在处插入代码报告错误.
}

Cursor 的实现包含了多个"get" 方法,用于从对象中获取不同类型的数据.例如,前面的代码片段使用getString().它们也具有一个getType() 方法,用它可以返回的值代表了数据的类型.


  相关解决方案