标签:
表1 用户词典
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 |
***********提示*************
一个provider的关键字不是必须的,即使存在,其名字也可以不是_ID。但如果你想要从provider中获取数据装入到listView中,那么你必须要有一栏为_ID。这个要求的更多细节请参见 Displaying query results一节。
*****************************
应用可以通过访问ContentResolver对象来访问content provider。这个对象中的方法和provider对象的中的同名,是ContentProvider的子类的具体实现。ContentResolver方法中包含了维护数据的增删改查操作。
在客户应用进程中的ContentProvider对象和在拥有provider的应用的进程中的ContentProvider对象会自动处理进程内的交流。ContentProvider还会作为在数据仓库和比如说表这样的数据形式的之间的抽象层。
**********提示******************
访问contentprovider你的你需要在manifest中申请特殊的权限。在Content Provider Permissions会有更详细的讨论。
*********************************
举个例子,获取从用户字典中获取词汇和对应的现场,你可以调用ContentResolver.query()方法。这里面的query()方法会调用ContentProvider的query()方法,它要由用户词典提供者定义。下面的代码展示了一个ContentProvider.query()的调用。
// Queries the user dictionary and returns results mCursor = getContentResolver().query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table mProjection, // The columns to return for each row mSelectionClause // Selection criteria mSelectionArgs, // Selection criteria mSortOrder); // The sort order for the returned rows表2展示了query(Uri,projection,selection,selectionArgs,sortOrder)中的参数是如何对应 SQL SELECT statement(SQL查询语句的)。
query() argument | SELECT keyword/parameter | Notes |
---|---|---|
Uri |
FROM table_name |
Uri 对应的provider中的名为table_name的表。 |
projection |
col,col,col,... |
projection 每一行中需要查询的栏目名字构成的数组。 |
selection |
WHERE col =value |
selection 选择行的选择条件的说明。 |
selectionArgs |
(没有准确的 SQL语句中的对应,表示的是在选择从句中的?的占位。) | |
sortOrder |
ORDER BYcol,col,... |
sortOrder 返回的Cursor中的行的排序方式。 |
content://user_dictionary/words
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
下面介绍怎样从provider中检索数据,使用用户字典(User Dictionary Provider)作为例子。
******************************
为了便于说明,这一节中的代码片中调用的ContentResolver.query()都是在UI线程中调用的。在实际的应用中,你应该异步地在另一个线程中进行查询。一种方法是使用CursorLoader类,在Loader的教程中有更多的说明。并且,这些代码只是一个片段,并没有展现一个整个的应用。
想要从provider中检索数据,要遵循下面的基本步骤:
1.为provider请求读权限。
2.定义向provider发送查询的代码。
要从provider中获取数据,你的应用怒要provider的 读访问权限。必不能在运行时获得这样的权限,只可以在manifest文件中声明,使用<uses-permission>元素,以及在provider中定义的名字。当你在manifest中声明这个元素以后,就是在想你的应用请求权限了。当用户安装了你的应用的时候,他就隐式地授予了这些权限。
在User Dictionary Provider 在manifest文件在中定义了android.permission.READ_USER_DICTIONNARY权限,所以想要从这个provider中读取数据的应用就要请求这个权限。
// 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()方法,还是使用User Dictionary Provider作为例子。一个ContentProvider查询和一个SQL查询类似,都包含要获取的栏目,筛选标准以及结果排序的顺序。
查询中要返回的栏目叫做projection(英语中有投影的意思)(mProjection变量)。
说明那些行要查询的表达式,则被分成了selection clause 和 selection arguments两部分。selection clause 是一个逻辑和布尔表达式,栏目的名字,以及值(mSelectionClause)的结合。如果你使用了占位符?而不是数值,那么查询方法会从来选择参数数组中获取值(mSelectArgs 变量)。
下一个代码片中,如果用户没有输入一个单词,selection clause就会被设置成null,从而查询会返回所有的provider中的单词。如果用户输入了一个单词,那么selection clause就会设定为“UserDictionary.Words.WORD+"=?”,同时selection arguments 数组中的第一个元素会被设定为用户输入的这个单词。
/*
* 这里定义一个只有一个元素的String数组来表示selection argument
*/
String[] mSelectionArgs = {""};
// 从UI中获取一个输入
mSearchString = mSearchWord.getText().toString();
// 记得检查用户的输入是否合法或者是否是恶意代码
// 如果获得是一个空的String,就查询所有的内容
if (TextUtils.isEmpty(mSearchString)) {
// 把筛选标砖的从句设为null,就会返回所有行
mSelectionClause = null;
mSelectionArgs[0] = "";
} else {
// 构建一个和用户输入相对应的查询选择从句
mSelectionClause = UserDictionary.Words.WORD + " = ?";
//把用户输入放入到选择的参数的数组中
mSelectionArgs[0] = mSearchString;
}
// 对指定的的表进行查询并在Cursor中返回
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // The content URI 表的
mProjection, // The 每一行指定的栏目
mSelectionClause // 是空或者是用户用户输入的单词
mSelectionArgs, // 是空或者是用户输入的字符串
mSortOrder); // 返回的一组行的排序顺序
// 如果发生了一些错误,就会返回一个null的Cursor,或者是抛出异常
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.在这里处理错误,一定要确保不要使用这个cursor,你可以调用Log.e来记录这些错误
* */// 如果返回的Cursor中没有元素,说明没有对应的内容} 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, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
如果content Provider管理的 是一个SQL数据库,在SQL语句中包含不信任的外部数据可以导致SQL注入。
思考下面的选择从句:
// Constructs a selection clause by concatenating the user‘s input to the column name String mSelectionClause = "var = " + mUserInput;如果你这样做,就留存了用户进行连接恶意SQL代码到你的SQL语句中的漏洞。例如,用户可以输入“nothing;DROP TALBE *;”给变量mUserInput, 会导致这个选择的从句变成了var=nothing;DROP TABLE *;。以为这个选择的语句会被当做一个SQL语句,所以这可能会导致provider擦出除了对应的SQLitedatabase中的所有的表。(除非这个providerprovider设置了捕获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数据库中,也推荐这么做。
*********提示*************
provider可能会限制对于一些栏目的访问,基于进行查询的对象的一些特性。例如,Contacts Provider会只允许同步适配器访问以下栏目,这样的话就不会想activity或者service返回这些栏目。
***************************
如果没有行和选择中的标准相互对应,那么就会返回一个Cursor.getCount()方法为0的Cursor对象。(一个空的cursor)
如果内部发生了异常,那么返回的结果取决于具体的provider,通常会返回一个指向null 的cursor或者抛出异常 。
因为Cursor对象的组织就如同一个行的列表,所以用来表达内容的简单的办法就是使用SimpleCursorAdaper来把它和一个ListView进行关联。
下面是一个承接上面代码片的新代码片。其中创建了SimpleCurcorAdaper的对象,其中包含了通过查询获得的Cursor对象,并且设置一个ListView来适配这个对象。
// 定义了Cursor中的,我们想要表示 栏目的名字的数组。
String[] mWordListColumns ={ UserDictionary.Words.WORD, // 协议来中的包含word栏目名字的常量。 UserDictionary.Words.LOCALE // 协议类中locale栏目的常量};// 定义一个接受从Cursor中获取的栏目的内容的View的数组int[] mWordListItems = { R.id.dictWord, R.id.locale};// 创建一个SimpleCursorAdaptermCursorAdapter = new SimpleCursorAdapter( getApplicationContext(), // 应用的上下文对象 R.layout.wordlistrow, // ListView中一行的布局的xml布局 mCursor, // 查询的结果 mWordListColumns, // cursor需要展示的栏目的名字 mWordListItems, // 行布局中接收数据的view的id的int数组 0); // 标志位,通常不需要// 设置ListView的适配器mWordList.setAdapter(mCursorAdapter);
*************提示*******************
想要从Cursor构建一个ListView,cursor中一定要包含一个名为_ID的字段。正式因为这样,所以之前的查询都要包含_ID字段,尽管在ListView中并不需要显示。这个限制也解释了为什么contentProider表大都包含_ID字段。
**************************************
预期仅仅是显示查询结果,你可以把它们用作其他任务中。例如你可以在用户字典里面检索拼写,然后在其他的provider里面进行查找。你可以在Cursor的行上面迭代来完成。
// 确定一个叫做“word”的栏目的索引号 int index = mCursor.getColumnIndex(UserDictionary.Words.WORD); /* * Only executes if the cursor is valid. The User Dictionary Provider returns null if * a只有在Cursor合法的时候才可以执行。如果获得Cursor过程中发生内部错误,其他的provider可能会抛出异常而不是返回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 *移动到Cursor的的下一行。当你移动cursor之前,它默认的指针的位置是-1。如果你在这个位置读数据的话就会发生异常 */ while (mCursor.moveToNext()) { // 从这一栏中中获取数据。 newWord = mCursor.getString(index); // 这里插入处理检索出的单词的代码。 ... // 循环体结束 } } else { // 如果cursor为null或者发生异常,那么在这里插入处理的代码。 }Cursor的构建里面包含了很多get类型的方法,以从对象中获取不同类型的信息。例如上面的代码片里面就是使用了getString()方法。同样,还有getType()方法来返回所指定的栏目的数据类型。
provider可以指定其他应用想要访问这个provider必须要声明的权限。这个许可保证了使用者知道自己将访问哪一个provider。基于这个要求,其他的应用就要通过请求响应的权限来访问provider。终端用户在安装这个应用的时候就可以看见权限请求。
如果一个provider没有指定一个权限许可,那么其他的应用是不可以访问该provider的。但是,无论是否声明权限,provider的同一个应用的其他组件拥有provider的完全读写权限。
如同之前提到的那样,User Dictionary Provider需要请求android.permission.READ_USER_DICTIONARY的权限,来从中获取数据。provider还有另外一个分离的权限android.permission.WRITE_USER_DICTIONARY来进行插入更新和删除数据。
要获取想访问的provider的权限,应用通过在manifest文件当中使用<uses-permission>标签。当Android Package Manager安装应用时,用户必须要允许所有应用请求的权限。如果用户用户允许了所有,安装才可以继续进行,如果不允许,Android Packet Manager就中断安装。
下面的<user-permission>标签请求了访问User Dictionary Provider的权限。
<uses-permission android:name="android.permission.READ_USER_DICTIONARY">
和你从provider中获取数据的方式类似,你在客户端组件和ContentProvider之间使用同样的方式来修改数据。你通过调用ContentProvider的一个方法,同时还有一些参数,这些参数会被传入到ContentProvider的对应方法当中。provider和provider客户端之间就会自动处理安全的跨进程的通信。
可以使用ContentResolver.insert()方法来插入数据。这个方法在ContentProvider中插入一行数据,并返回这行数据的uri。下面的代码片展示了向User Dictionary Provider插入一个新单词的例子。
// 定义一个接受插入返回值的uri Uri mNewUri; ... // 定义一个包含要插入值的对象 ContentValues mNewValues = new ContentValues(); /* * Sets the values of each column and inserts the word. The arguments to the "put" * method are "column name" and "value"为插入的数据设定每一栏的值,put方法的两个参数分别为栏目的名字和插入的值 */ 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, // user dictionary content 的uri mNewValues // 要插入的值 );
新的要插入的值被放入到ContentValues的对象当中,在形式上和只有一行的Cursor类似。对象中的栏目不必是相同的类型,并且如果你不想给某一栏设定值,你可以使用ContentValues.putNull()来设定为null。
代码片中没有插入_ID一栏,因为这一栏会被自动插入。provider会给每一个插入的数据设定惟一的_ID。provider通常使用这个值作为表格的主键。
返回的指示新插入行的content URI是下面的这种形式
content://user_dictionary/words/<id_value>
想要从返回的Uri中获取_ID的值,可以调用ContentUris.parseId()方法。
更新数据的一行,使用的方法和参数类似于你插入数据,并且如同你查找数据那样设定选取行的标准。客户端你使用的方法是ContentResolver.update()。你只需要把你要更新的数据添加到ContentValues对象的当中即可。如果你想清除某一栏的数据,就把对应的栏目设置为null。
下面的代码片就是把local语言为“en”的行都更新为null。返回值是更新了的行的数量。
// 定义包含要更新的值的对象 ContentValues mUpdateValues = new ContentValues(); // 定义选择你要更新行的标准 String mSelectionClause = UserDictionary.Words.LOCALE + "LIKE ?"; String[] mSelectionArgs = {"en_%"}; // 定义返回的更新的行目数量 int mRowsUpdated = 0; ... /* * 设定更新的值,并更新选定的单词 */ mUpdateValues.putNull(UserDictionary.Words.LOCALE); mRowsUpdated = getContentResolver().update( UserDictionary.Words.CONTENT_URI, // the user dictionary content URIcontent的uri mUpdateValues // the columns to update更新的栏目 mSelectionClause // the column to select on选择的行 mSelectionArgs // the value to compare to对应上一句的值 );当你调用ContentResolver.update()方法的时候,你需要防范用户的输入。更多关于防范的注意,请阅读Protecting against malicious input。
删除数据行和检索很类似:你指定筛选要删除的行的标准,接着返回删除的行的数目。下面的代码片删除了appid为“user”的行。方法返回了删除的行的书目。
// Defines selection criteria for the rows you want to delete String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?"; String[] mSelectionArgs = {"user"}; // Defines a variable to contain the number of rows deleted int mRowsDeleted = 0; ... // Deletes the words that match the selection criteria mRowsDeleted = getContentResolver().delete( UserDictionary.Words.CONTENT_URI, // the user dictionary content URI mSelectionClause // the column to select on mSelectionArgs // the value to compare to );在调用ContentResolver.delete()中,同样需要注意防范用户输入。
ContentProvider可以提供不同种类的数据类型。User Dictionary Provider里面仅仅提供了text类型,但是provider还可以提供下面的类型。
- integer
- long integer(long)
- floating point
- long floating point (double)
还有一种provider经常使用的数据是Binary Large OBject(BLOB),由一个64kb的byte数据构成。你可以通过查看Cursor类的get方法来看支持的数据类型。
每一栏的数据类型都会在provider的文档中列举。User Dictionary Provider的数据类型,在他的协议类UserDictionary.Words的参考文档中列举。(Contract Classes中有关于它的介绍)。你也可以通过调用Cursor.getType()来确定数据类型。
Provider同样可以维护每一个定义的URI对应的MIME类型信息。你可以使用MIME类型信息来确定你的应用是否支持provider提供的类型,或者是基于MIME类型选择一种处理的类型。当你使用包含复杂的数据结构或者文件的provider的时候,你总是需要使用MIME类型。例如,ContactsProvider中的ContactsContarct.Data表,就使用MIME类型来标明每一行中存储的数据的类型。想要获取MIME类型对应的content URI,可以调用ContentResolver.getType()。
MIME Type Reference描述了标准和用户MIME类型的语意。
String[] mProjection = { UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.LOCALE };
type/subtype
vnd.android.cursor.dir
vnd.android.cursor.item
vnd.android.cursor.item/phone_v2
content://com.example.trains/Line1
vnd.android.cursor.dir/vnd.example.line1
content://com.example.trains/Line2/5
vnd.android.cursor.item/vnd.example.line2
内容提供者基础 Content Provider Basics——翻译自developer.android.com
标签:
原文地址:http://blog.csdn.net/wallezhe/article/details/51221135