当前位置:   article > 正文

android Content Provider详解二

query string injection: android provider

从Provider取得data

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

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

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

1请求provider的读权限.

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

请求读权限

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

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

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

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

构建请求

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


//"projection"定义了要返回的各列们
String[]mProjection=
{
UserDictionary.Words._ID,//Contractclassconstantforthe_IDcolumnname
UserDictionary.Words.WORD,//Contractclassconstantforthewordcolumnname
UserDictionary.Words.LOCALE//Contractclassconstantforthelocalecolumnname
};

//定义一个包含"select"条款的字符串
StringmSelectionClause=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,//ThecontentURIofthewordstable
mProjection,//Thecolumnstoreturnforeachrow
mSelectionClause//Eithernull,orthewordtheuserentered
mSelectionArgs,//Eitherempty,orthestringtheuserentered
mSortOrder);//Thesortorderforthereturnedrows

//有些provider在出错时返回null,有抛出异常
if(null==mCursor){
/*
*在此插入代码处理错误.记住不要使用游标!你可能要调用
*android.util.Log.e()把错误记录的日志
*
*/
//如果游标是空的,找不到匹配的provider
}elseif(mCursor.getCount()<1){

/*
*在此插入代码来通知用户,查找不成功.这也不能完全算是个错误.你可能想为用户提供插入一个新行或重新输入查询单词的选项
*/

}else{
//在此插入代码,利用返回的结果做想做的事

}

查询与下面的SQL语句等价:

SELECT_ID,word,frequency,localeFROMwordsWHEREword=<userinput>ORDERBYwordASC;

在此SQL语句中,以实际的列名代替了内置的类别常量.

防止恶意输入

如果被contentprovider管理的数据是一个SQL数据库,在原始的SQL语句中包含不可信的数据会导致SQL注入

思考以下选择条款:

//通过连接用户输入到列名来构造一个选择条款
StringmSelectionClause="var="+mUserInput;

如果你这样做,你就允许用户连接恶意的SQL语句到你的SQL语句中.例如,用户可以输入"nothing;DROPTABLE*;",这将在选择条款中变为var=nothing;DROPTABLE*;..既然选择条款被作为SQL语句,这就可能导致provider删除SQLite数据库中的所有的表(除非provider被设置成捕获SQLinjection阴谋).

要避免此问题,应使用一个运用?作为可替换参数的选择条款和一个作为选择参数的数组.当你这样做时,用户输入被直接绑定到查询而不是被解释为SQL语句的一部分.因为它不被认为是SQL,于是用户输入就不能注入恶意SQL.使用以下选择条款来代替连接用户输入的那个:

//构造一个带有占位符的选择条款
StringmSelectionClause="var=?";

像这样建立起选择参数数组:

//定义一个数组来容纳选择参数
String[]selectionArgs={""};

像这样把一个值置入选择参数数组中:

//Setstheselectionargumenttotheuser'sinput
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,//Contractclassconstantcontainingthewordcolumnname
UserDictionary.Words.LOCALE//Contractclassconstantcontainingthelocalecolumnname
};

//定义一个ViewID组成的列表,它们将接收每行的Cursor列的值
int[]mWordListItems={R.id.dictWord,R.id.locale};

//CreatesanewSimpleCursorAdapter
mCursorAdapter=newSimpleCursorAdapter(
getApplicationContext(),//Theapplication'sContextobject
R.layout.wordlistrow,//ListView的一行的layout
mCursor,//Theresultfromthequery
mWordListColumns,//Astringarrayofcolumnnamesinthecursor
mWordListItems,//AnintegerarrayofviewIDsintherowlayout
0);//Flags(usuallynoneareneeded)

//将适配器设置给ListView
mWordList.setAdapter(mCursorAdapter);

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

从查询结果中获取数据

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


//获取叫做"word"的列的序号
intindex=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()方法,用它可以返回的值代表了数据的类型.


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/981964
推荐阅读
相关标签
  

闽ICP备14008679号