内容提供者基础 Content Provider Basics——翻译自developer.android.com
来源:互联网 发布:mac访客模式怎么关闭 编辑:程序博客网 时间:2024/06/05 21:23
#内容提供者基础 Content Provider Basics
content provicer 管理着中心数据仓库的访问。一个provider是Android的应用的一部分,它可以提供数据工作的UI。然而,content provider基本都是被其他的应用访问,使用一个provider客户端对象来访问provider。provider和provider client一同创立了一个持续的标准的数据接口,它可以提供进程内通信以及安全数据访问。
这个主题讲解下面内容的基础部分;
- content provider 是怎样工作的
- 从content provider中获取数据的api
- 从content provider中插入、删除、更新数据的api
- 其他装备了content provider 的功能的api
##一览 Overview
content provider给外部应用的数据以表的形式,类似于关系型数据库。行表示provider汇集的数据类型,行中的列表示一个对象的一块数据。例如一个Android平台内置的provider——用户字典,它保存着用户的一些非标准的词汇的拼写。表1展示了content provider里面的数据的大致样子。
表1 用户词典
***********提示*************
一个provider的关键字不是必须的,即使存在,其名字也可以不是_ID。但如果你想要从provider中获取数据装入到listView中,那么你必须要有一栏为_ID。这个要求的更多细节请参见 Displaying query results一节。
*****************************
访问一个provider
应用可以通过访问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 resultsmCursor = 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查询语句的)。
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中的行的排序方式。
内容的URI
content://user_dictionary/words
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
从provider中检索数据
下面介绍怎样从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 rowString[] 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 clauseString mSelectionClause = null;// Initializes an array to contain selection argumentsString[] 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 nameString 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 parameterString mSelectionClause = "var = ?";
设置选择的参数的数组:
// Defines an array to contain the selection argumentsString[] selectionArgs = {""};在选择数组中赋值如下:
// Sets the selection argument to the user's inputselectionArgs[0] = mUserInput;
使用?占位符,以及一个选择的参数的数组是一个推荐方法,即使不是在SQL数据库中,也推荐这么做。
表示查询结果
ContentResolver.query()的客户端方法总是返回一个包含查询中mProject中指定的栏目,和筛选条件中指定的行的一个cursor。一个Cursor可以为它包含的栏目和行提供随机访问的功能。通过使用Cursor中的方法,你可以迭代Cursor中行,对每一行中的栏目进行判断,获取这些栏目,以及检查结果中的其他的属性。一些Cursor对象中定义了自动更新的方法,在数据库中对应的数据发生变化的时候会自动的更新的奥cursor中,还有一些包含着触发器,当Cursor中的数据发生变化会自动同步到数据库当中。,或者两者都有。*********提示*************
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()方法来返回所指定的栏目的数据类型。
Content Provider的权限许可
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插入一个新单词的例子。
// 定义一个接受插入返回值的uriUri 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 deleteString mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";String[] mSelectionArgs = {"user"};// Defines a variable to contain the number of rows deletedint mRowsDeleted = 0;...// Deletes the words that match the selection criteriamRowsDeleted = 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()中,同样需要注意防范用户输入。
Provider数据类型
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类型的语意。
Provider的访问形式
Batch access组团访问
通过intent访问数据
使用暂时权限来访问
使用另一个应用
协议类
String[] mProjection ={ UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.LOCALE};
MIME类型的引用
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
- 创建一个内容提供者Creating a Content Provider——翻译总结自developer.android.com
- Android内容提供者(Content provider)
- Android Content Provider(内容提供者)
- NFC Basics(基本NFC)——翻译自developer.android.com
- 内容提供者—Content Provider(一)
- 内容提供者—Content Provider(二)
- 黑马程序员-Android基础四大组建内容提供者Content Provider
- Content Provider Basics(内容提供者的基本操作)
- Content Provider Basics 内容提供者的基本操作
- content Provider 内容提供者
- Content Provider内容提供者
- Content Provider 内容提供者
- 内容提供者-Content Provider
- 【Android 开发】:Content Provider (内容提供者) 详解
- Android开发--内容提供者(Content provider)
- Android内容提供者(Content provider)
- Android内容提供者(Content provider)
- 利用Handler来实现UI线程的更新
- Java 使用TreeMap对学生对象的年龄进行升序排序。
- [Paper] Faster R-CNN
- ubuntu下无法保存修改文件
- SSL/TLS协议运行机制的概述
- 内容提供者基础 Content Provider Basics——翻译自developer.android.com
- Python 发邮件服务
- java并发之volatile
- yarn-site.xml相关配置参数
- hdu3974Assign the task(简单树hash,线段树区间更新,单点查询)
- Deep Neural Networks的Tricks
- 百度面试(三)
- python&DBA 一(自动安装热备备份并搭建复制)
- mysql中char和varchar区别