为了清晰起见,本节中的代码片段在UI线程上调用ContentResolver()方法,但是,在实际代码中,应该在一个独立的线程中执行异步查询。执行异步查询的方法之一是使用CursorLoader类,这个类在装载器(Loader)指南中进行过比较详细的描述。而且这里的代码也只是代码片段,它们不是一个完整应用程序的展示。
按照以下基本步骤从提供器中获取数据:
1.给提供器申请读访问权限;
2.定义发送给提供器的查询代码。
申请读访问权限
要从一个提供器中获取数据,针对对应的提供器,应用程序需要“读访问权限”。你不能在运行时申请这个权限,而是要在清单文件中使用
要找到你要使用的提供器的读访问许可的确切的名字,以及提供器使用的其他的访问权限的名字,请看对应的提供器的文档。
在正在访问的提供器中的许可的角色会在“内容提供器权限”一节中进行更详细的描述。
用户字典提供器在它的清单文件中定义了android.permission.READ_USER_DICTIONARY许可,因此如果应用程序想要从这个提供器中读取数据,就必须申请这个权限。
构建查询
接下来是要构建一个获取提供器中数据的查询。首先为了访问用户字典提供器定义了一些变量:
// A "projection" defines the columns that will be returned for each row
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
};
// Defines a string to contain the selection clause
String mSelectionClause = null;
// Initializes an array to contain selection arguments
String[] mSelectionArgs = {""};
下面的代码片段使用用户字典提供器作为例子来演示如何使用ContentResolver.query()方法。提供器的客户端查询跟一个SQL查询类似,它包含了要返回的列的集合、选择条件的集合和排序标准。
查询应该返回的列的集合被叫做投影(projection)(它在变量mProjection中定义)。
指定要获取的行的表达式被分成了选择子句和选择参数两部分。选择子句是一个逻辑和布尔表达式的组合(包含列名和值,上例中在mSelection变量中定义)。如果你在选择子句中指定了可用一个值来替换的参数”?”,那么查询方法就会获取与选择参数数组(变量mSelectionArgs)中指定的值相匹配的数据。
在下面的代码片段中,如果用户没有在输入域输入内容,那么选择子句被设置为null,查询就会返回提供器中的所有行。如果用户输入了一些内容,那么选择子句被设置为UserDictionary.Words。word + “ = ?”,选择参数数组中的第一个元素被设置为用户输入的内容。
/*
* This defines a one-element String array to contain the selection argument.
*/
String[] mSelectionArgs = {""};
// Gets a word from the UI
mSearchString = mSearchWord.getText().toString();
// Remember to insert code here to check for invalid or malicious input.
// If the word is the empty string, gets everything
if (TextUtils.isEmpty(mSearchString)) {
// Setting the selection clause to null will return all words
mSelectionClause = null;
mSelectionArgs[0] = "";
} else {
// Constructs a selection clause that matches the word that the user entered.
mSelectionClause = " = ?";
// Moves the user's input string to the selection arguments.
mSelectionArgs[0] = mSearchString;
}
// Does a query against the table and returns a Cursor object
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
// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
/*
* Insert code here to handle the error. Be sure not to use the cursor! You may want to
* call android.util.Log.e() to log this error.
*
*/
// If the Cursor is empty, the provider found no matches
} else if (mCursor.getCount() < 1) {
/*
* Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
* an error. You may want to offer the user the option to insert a new row, or re-type the
* search term.
*/
} else {
// Insert code here to do something with the results
}
这个查询类似以下SQL语句
SELECT _ID, word, frequency, locale FROM words WHERE word =
在这个SQL语句中,约定的类的常量被实际的列名替代了。
防止恶意输入
如果内容提供器的数据是用一个SQL数据库来管理的,那么就能把外部的非法数据包含到SQL语句中,从而导致SQL注入(一种把恶意代码插入到字符串中来攻击关系性数据库的方式)。
研究以下这个选择子句:
// Constructs a selection clause by concatenating the user's input to the column name
String mSelectionClause = "var = " + mUserInput;
如果你这样做了,就允许用户把恶意的SQL拼装到你的SQL语句中。如,用户可以给变量mUserInput输入“nothing;DROP TABLE *”,这将导致var = nothing; DROP TABLE *“出现在选择子句中。因此把选择子句作为一个SQL语句来处理,这可能导致提供器删除SQLite数据库中的所有的表(除非提供器进行捕获SQL注入的尝试)。
要避免这个问题,使用用“?”作为可替换参数的选择子句和一个独立的选择参数数组,这样做的时候,用户输入被直接绑定到查询而不是做为SQL语句的一部分来解释。因为它不被看做是SQL,所以用户输入就不能注入恶意的SQL。下面的这个例子就是使用了包括用户输入的字符串拼装方式,而不是使用选择子句方式:
// Constructs a selection clause with a replaceable parameter
String mSelectionClause = "var = ?";
以下是建立选择参数数组的方式:
// Defines an array to contain the selection arguments
String[] selectionArgs = {""};
以下是把用户输入放入选择参数数组的方式:
// Sets the selection argument to the user's input
selectionArgs[0] = mUserInput;
使用“?”做为可替换参数的选择子句和选择参数数据是指定一个选择条件的首选方法,即使提供器不是机SQL数据。
显示查询数据
ContentResolver.query()客户端方法始终返回一个Cursor对象,这个对象包含了跟查询条件匹配的行和由查询投影指定的列。Cursor对象给它所包含的行和列提供了随机的对访问。使用Cursor对象方法,你可以遍历结果中的所有行、判断每一列的数据类型、获取一列数据以及检查结果的其他属性。某些Cursor对象还实现了在提供器数据改变时自动更新对象,或者在Cursor对象改变时触发一个观察者对象中的方法,或者都是。
注意:基于构造查询对象的特性,提供器可以限制访问某些列。例如,通信录提供器对异步适配器就限制其访问某些列,因此这些列不能返回给Activity或service。
如果没有数据行跟选择条件匹配,那么调用提供器返回Cursor对象的getCount()方法,结果是0(空的Cursor对象)。
如果发生了内部错误,查询结果依赖与特定的提供器,它可以选择返回null,也可以抛出一个异常。
因为Cursor对象一个行的列表,通过SimpleCursorAdapter对象把Cursor对象和一个ListView对象进行关联是显示内容的好方法。
以下代码片段是前面代码片段的继续,它创建了一个包含被查询返回的Cursor对象的SimpleCursorAdapter对象,并且把这个设置成ListView对象的适配器。
// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mWordListColumns =
{
UserDictionary.Words.WORD, // Contract class constant containing the word column name
UserDictionary.Words.LOCALE // Contract class constant containing the locale column name
};
// Defines a list of View IDs that will receive the Cursor columns for each row
int[] mWordListItems = { R.id.dictWord, R.id.locale};
// Creates a new SimpleCursorAdapter
mCursorAdapter = new SimpleCursorAdapter(
getApplicationContext(), // The application's Context object
R.layout.wordlistrow, // A layout in XML for one row in the ListView
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)
// Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);
注意:要完成ListView和Cursor的一一对应,Cursor对象就必须包含一个名叫_ID的列。正因为如此,在前面显示的查询中要获取“words”表的_ID列,即使ListView对象不显示这列。这种限制就解释了为什么大多数提供器的每个表都有一个_ID列。
从查询结果中获得数据
你不但可以简单的显示查询结果,而且能够在其他任务中使用它们。例如,你能够从用户字典中获取拼写,然后在其他的提供器中查看。你可以迭代Cursor中所有行来做这件事情。
// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
/*
* Only executes if the cursor is valid. The User Dictionary Provider returns null if
* an internal error occurs. Other providers may throw an Exception instead of returning null.
*/
if (mCursor != null) {
/*
* Moves to the next row in the cursor. Before the first movement in the cursor, the
* "row pointer" is -1, and if you try to retrieve data at that position you will get an
* exception.
*/
while (mCursor.moveToNext()) {
// Gets the value from the column.
newWord = mCursor.getString(index);
// Insert code here to process the retrieved word.
...
// end of while loop
}
} else {
// Insert code here to report an error if the cursor is null or the provider threw an exception.
}
Cursor的类实现为从对象中获取不同类型的数据包含了几个“get”方法。例如,前面代码片段中的getString()方法。同时还有返回指定列的数据类型的getType()方法。