Android开发ContentProvider应用程序的使用方法
概述
Content Provider 以数据表的形式向外部应用程序提供数据,这与关系型数据库中的表很类似。 其中,行(row)表示由多个不同类型数据构成的单个实体,每行数据中的列(column)代表实体中的一个数据项。
例如,用户词典就是 Android 系统内置的 Provider 之一,里面记录着用户需要留存的自定义拼写规则的单词。 表1例举了此 Provider 数据表中可以查询的字段信息:
表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
在表1中,每行代表一个可能无法在标准词典中查到的单词。 每列代表与单词相关的数据,比如首次使用时的地区(语言)。 每列的标题即为存储时的列名称。 引用 locale 列就可以得到每一行数据的地区信息。 这里的 _ID 列被用作"主键"(primary key),并且是由 Provider 自动维护的。
注意: Provider 本身不需要用到主键,主键的名称也不一定要是 _ID 。 但是,如果要把 Provider 作为数据源与 ListView 绑定,则必须有一个列的名称是 _ID 。 详细要求将在 显示查询结果中描述。 访问 Provider
应用程序是通过客户端对象 ContentResolver 访问 Content Provider 的。 此对象中包含一些方法,这些方法将会调用 Provider 对象中的同名方法。而 Provider 对象是 ContentProvider 某个具体子类的实例。 ContentResolver 中的方法内置了基本的"CRUD"(创建、查询、更新、删除(create、retrieve、update 和 delete))功能。
ContentResolver 对象运行于客户端应用的进程中,而 ContentProvider 运行于提供 Provider 应用的进程中,两者会自动完成进程间的通讯。 ContentProvider 还发挥着数据抽象层的作用,负责将内部数据以数据库表的形式提供出来。
注意: 为了访问 Provider,应用程序通常必须在 Manifest 文件中请求相应的权限
例如,要从 User Dictionary Provider 中读取单词及地区列表,就要用到 ContentResolver.query() 。 query() 方法会去调用 User Dictionary Provider 中对应的 ContentResolver.query() 方法。以下代码演示了 ContentResolver.query() 的调用过程: 1 // 查询用户词典并返回结果 2 mCursor = getContentResolver().query( 3 UserDictionary.Words.CONTENT_URI, // 单词表的 Content URI 4 mProjection, // 需要返回的列 5 mSelectionClause, // 查询条件 6 mSelectionArgs, // 查询条件的参数 7 mSortOrder); // 返回结果的排序要求
表2给出了 query(Uri,projection,selection,selectionArgs,sortOrder) 的参数与 SQL SELECT 语句的对应关系:
表2: Query() 与 SQL 查询的对比
query() 参数
SELECT 关键字/参数
说明
Uri
FROM *table_name*
Uri 对应于 table_name 指定的 Provider 数据表名。
projection
*col,col,col,...*
projection 是包含返回列名称的数组。
selection
WHERE *col* = *value*
selection 指定查询条件。
selectionArgs
(没有固定值,该查询参数将会替换查询语句中的占位符"?"。)
sortOrder
ORDER BY *col,col,...*
sortOrder 指定了返回 Cursor 中各行的显示顺序。 Content URI
Content URI 是一种用于标识 Provider 数据的 URI。 Content URI 包括了整个 Provider 的符号名称( authority )和表名( path )。 调用客户端的方法访问 Provider 数据表时,表的 Content URI 是参数之一。
在前面的代码中,常量 CONTENT_URI 包含了指向用户词典中 "word" 表的 Content URI。 ContentResolver 对象将分离出 URI 中的 authority ,并用它"解析" 出 Provider,这是通过将 authority 与系统记录的已有 Provider 清单进行比较来实现的。 然后 ContentResolver 就可以将查询参数发送给相应的 Provider 了。
ContentProvider 用 Content URI 的 path 部分选择要访问的数据表。 通常, Provider 公开的所有数据表都会带有自己的 path 。
在上述代码中,"word"表的完整 URI 为: content://user_dictionary/words
这里的字符串 user_dictionary 是 Provider 的 authority 部分, 字符串 words 是数据表的 path 部分。 字符串 content:// ( scheme )是必须指定的,以表明这是一个 Content URI。
很多 Provider 提供了对单条记录的访问能力,只要在 URI 后面跟一个 ID 值即可。 例如,要读取用户词典中 _ID 为 4 的数据行,可以使用以下 Content URI: Uri singleUri =ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
如果已经读取了一些数据,然后需要修改或删除其中的某一条,这时就经常会用到 ID 值了。
注意: Uri 和 Uri.Builder 类中已内置了一些工具性的方法,可以由字符串搭建合乎规则的 Uri 对象。 ContentUris 中有一些在 URI 后面追加 ID 值的常用方法。 上述代码就用了 withAppendedId() 把 ID 追加到 UserDictionary 的 Content URI 之后。 从 Provider 读取数据
本节将介绍从 Provider 读取数据的过程,还是以 User Dictionary Provider 为例。
为了清晰起见,本节中的代码将会调用"UI 线程"中的 ContentResolver.query() 。但是在实际的代码中,应该在单独的线程中实现异步查询。 一种方案是利用 CursorLoader 类,而且,以下只给出了部分代码,而非一个完整的应用程序。
从 Provider 中读取数据的基本步骤如下所示: 申请读取 Provider 的权限。 编写向 Provider 发送查询请求的代码。 申请读取权限
要从 Provider 读取数据,应用程序需要拥有对 Provider 的"读权限"。 在运行时是无法申请该权限的,只能在 Manifest 文件中通过 指定。在 Manifest 文件中的定义,实际上是表明此应用程序需要"申请"该权限。 这样用户在安装此应用程序时,就可以明确授权。
在 Provider 的参考文档中,给出了其用到的全部权限的准确名称。
User Dictionary Provider 在其 Manifest 文件中定义了 android.permission.READ_USER_DICTIONARY 权限, 因此要读它的应用程序就必须请求该权限。 构建查询
接下来是构建查询请求。 以下代码定义了一些变量,在访问 User Dictionary Provider 时将会用到: 1 // "projection" 定义了要返回的数据列 2 String[] mProjection = 3 { 4 UserDictionary.Words._ID, &n // 对应列名为 _ID 的 Contract Class 常量 5 UserDictionary.an class="typ">Words.WORD, // 对应列名为 word 的 Contract Class 常量 6 UserDictionary.an class="typ">Words.LOCALE &nbLOCALE // 对应列名为 local 的 Contract Class 常量 7 }; 8 9 // 定义存放查询条件的字符串 10 String mSelectionClause =an class="pln"> null; mSelectionArgs ={""};
接下来的代码演示了 ContentResolver.query() 的使用方法,这里以 User Dictionary Provider 为例。 Provider 客户端查询与 SQL 查询很类似,也包含了需返回的列名、查询条件和排序要求。
查询返回的列名集合对象被称为"投影"( Projection )(即变量 mProjection )。
查询数据的表达式被拆分为查询条件和查询参数。 查询条件是由逻辑/布尔表达式、列名、数值组成(即变量 mSelectionClause )。 如果用参数 ? 代替了具体数值,则查询方法将会从查询参数数组(变量 mSelectionArgs )中读取实际的值。
在以下代码中,如果用户没有输入单词,则查询语句将被置为 null ,这样查询将会返回 Provider 中的所有单词。 如果用户输入了单词,那么查询语句将会是 UserDictionary.Words.WORD + " = ?" ,且查询参数数组中的第一个成员被设为用户输入的单词。 1 /* 2 * 定义只有一个成员的字符串数组,用于存放查询参数。 3 */ 4 String[] mSelectionArgs ={""}; 5 6 // 从用户界面读取一个单词 7 mSearchString = mSearchWord.getText().toString(); 8 9 // 别忘了在这里添加检查输入内容是否非法或恶意的代码 10 11 // 如果单词为空字符串,则读取所有数据 12 if(TextUtils.isEmpty(mSearchString)){ 13 // 将查询语句设为 null 将返回所有数据 14 mSelectionClause =null; 15 mSelectionArgs[0]=""; 16 17 }else{ 18 // 由用户录入单词构建查询语句 19 mSelectionClause =UserDictionary.Words.WORD +" = ?"; 20 21 // 将用户录入的字符串置入查询参数数组中 22 mSelectionArgs[0]= mSearchString; 23 24 } 25 26 // 查询数据并返回游标(Cursor)对象 27 mCursor = getContentResolver().query( 28 UserDictionary.Words.CONTENT_URI, // 单词表的 Content URI 29 mProjection, // 需返回的列 30 mSelectionClause // 为 null 或是用户录入的单词 31 mSelectionArgs, // 为空或是用户录入的字符串 32 mSortOrder); // 定义返回数据的排序规则 33 34 // 在出错时,某些 Provider 返回 null,另一些会抛出异常 35 if(null== mCursor){ 36 /* 37 * 在这里插入处理错误的代码。 38 * 请勿在这里使用游标! 39 * 可能需要调用 40 */ 41 // 如果游标中没有内容,表示 Provider 没找到匹配的记录。 42 }elseif(mCursor.getCount()<1){ 43 44 /* 45 * 在这里插入通知用户查询失败的代码。 46 * 这不一定是出错了,可以让用户录入新记录,也可以重新输入查询条件。 47 */ 48 49 }else{ 50 // 在这里插入处理查询结果的代码。 51 52 }
查询的语句与以下 SQL 语句类似: SELECT _ID, word, locale FROM words WHERE word = ORDER BY word ASC;
这条 SQL 语句中使用的是真实的列名,而不是 Contract 类常量。 防止非法输入
如果 Content Provider 管理的数据存放于 SQL 数据库中,那么在 SQL 语句中插入某些非法信息可能会引发 SQL 注入问题。
请看下面这条查询语句: // 将用户输入内容拼接在列名之后,构造一条查询语句。 String mSelectionClause = "var = "+ mUserInput;
这时,用户就可以将恶意 SQL 拼接到查询语句中。 比如,用户可以将 mUserInput 输入为"nothing; DROP TABLE *;",这样查询语句就会成为" var = nothing; DROP TABLE *; ". 因为查询语句将用作 SQL 语句,所以会导致 Provider 删除底层 SQLite 数据库中的所有数据表(除非 Provider 设置为捕获 SQL 注入异常)。
为了避免这类问题,可以在查询语句中使用 ? 作为可替代参数,并用另一个数组作为实际的参数值。 这样,用户的输入就与查询直接关联,而不会被解释为 SQL 语句的一部分。 因为不再用作 SQL 语句,用户输入就无法注入恶意 SQL 了。 用户的输入内容不直接用于拼接 SQL 语句,查询语句如下: // 用可替代参数构造查询语句 String mSelectionClause = "var = ?";
查询参数数组定义如下: // 定义存放查询参数值的数组 String[] selectionArgs ={""};
在数组中放入一个查询参数值: // 将查询参数赋为用户的输入值 selectionArgs[0]= mUserInput;
在构造查询时,推荐使用这种将 ? 作为形参、数组提供实参的查询语句,即使不是基于 SQL 数据库的 Provider 也可以使用。 显示查询结果
客户端方法 ContentResolver.query() 将返回一个 Cursor ,其中的数据列由对应查询条件的 Projection 指定。 Cursor 对象支持对数据行和数据列的随机读取。通过 Cursor 的内部方法,可以遍历结果数据行、获取每一列的数据类型、读取某一字段的数据并检查其他属性。 某些 Cursor 对象可以在 Provider 的数据发生变化时进行自动更新,或是在 Cursor 数据变动时触发其他监听对象的方法。
注意: 根据建立查询的对象性质, Provider 可以限制对数据列的访问。 比如,联系人 Provider 就不允许 Sync Adapter 访问某些数据列,也就不会在 Activity 和服务中返回这些列。
如果没有找到符合条件的数据, Provider 就会返回一个 Cursor.getCount() 为 0 的 Cursor 对象(即空游标)。
如果发生了内部错误,查询返回的结果将视 Provider 的不同而定。 可能是返回 null ,也可能抛出一个 Exception 。
因为 Cursor 是一个数据行的"列表",所以一种较好的显示方式就是通过 SimpleCursorAdapter 把它与 ListView 关联起来。
以下代码将延续上面的代码。 创建了一个含有 Cursor 的 SimpleCursorAdapter 对象,并将其设置为一个 ListView 的数据源适配器(Adapter): 1 // 定义需要从 Cursor 读取并显示出来的数据列 2 String[] mWordListColumns = 3 { 4 UserDictionary.Words.WORD, // 对应 word 列的 Contract 类常量 5 UserDictionary.Words.LOCALE // 对应 locale 列的 Contract 类常量 6 }; 7 8 // 定义 View ID 列表,用于保存 Cursor 返回的一行数据。 9 int[] mWordListItems ={ R.id.dictWord, R.id.locale}; 10 11 // 新建一个 SimpleCursorAdapter 对象 12 mCursorAdapter =newSimpleCursorAdapter( 13 getApplicationContext(), // 应用程序的 Context 对象 14 R.layout.wordlistrow, // XML 格式的 Layout,用于 ListView 中每一行的布局 15 mCursor, // 查询结果 16 mWordListColumns, // 字符串数组,存放游标中的列名 17 mWordListItems, // 整形数组,存放行布局中的 View ID 18 0); // 标志位(一般用不上) 19 20 // 设置 ListView 的 Adapter 21 mWordList.setAdapter(mCursorAdapter);
注意: 要将 Cursor 用作 ListView 的后台数据源,游标必须包含一个名为 _ID 的数据列。 因此,上述查询从"word"表中读取了 _ID 列,当然 ListView 并不会显示这个字段。 这也是大部分 Provider 中的数据表都带有 _ID 列的原因所在。 从查询结果中读取数据
查询结果不只是简单地用于显示,还可以用来完成其他操作。 比如,可以从用户词典中读取单词并在其他 Provider 中进行检索。 这时就需要遍历 Cursor 中的每行数据: 1 // 找到列名为"word"的字段编号 2 int index = mCursor.getColumnIndex(UserDictionary.Words.WORD); 3 4 /* 5 * 仅当游标可用时才会执行。 6 * 如果发生内部错误,User Dictionary Provider 将会返回 null。而其他 Provider 可能会抛出异常。 7 */ 8 9 if(mCursor !=null){ 10 /* 11 * 前进至下一行。 12 * 在第一次移动之前,"记录指针"为 -1,如果这时读取数据,将会触发异常。 13 */ 14 while(mCursor.moveToNext()){ 15 16 // 读取值 17 newWord = mCursor.getString(index); 18 19 // 在这里插入处理返回单词的代码 20 21 ... 22 23 // while 循环结束 24 } 25 }else{ 26 27 // 如果游标为空或 Provider 抛出异常,在这里插入显示错误的代码。 28 }
Cursor 中有很多用于读取不同类型数据的"get"方法。 例如,上述代码中用到了 getString() 。还有一个 getType() 方法用于返回字段的类型。
本文源代码获取方式:私信 发送 "底层源码" 即可 免费获取
官宣!张本智和全家入籍日本,母亲一度不放弃中国籍,最终妥协WTT支线赛捷克奥洛穆茨站比赛将在8月23日至28日进行,日本乒协公布运动员教练员名单,张本智和的母亲赫然在列。过往,张本智和的母亲张凌表示,自己不改国籍,因为怀念自己的家乡。但现
严惩!中华台北男排输球又输人,赛后发言挑战国人底线男排亚洲杯比赛正在泰国进行,原本男排比赛在咱们国内算不上非常关注度很高的比赛,此前中国男排时隔多年之后重返南开世界联赛,最终虽然没有能够打进淘汰赛,取得了第13名的成绩,但是战胜了
好男人人设崩塌?孔卡再婚,新娘是其训练师的前未婚妻作为中超昔日的第一中场,有天体之王之称的恒大名宿孔卡迎来了第二段婚姻。日前,39岁的孔卡在个人社交媒体上晒出照片,称自己与新一任妻子完成了婚礼仪式,并且领证成功。据巴西媒体报道,孔
巩晓彬指导担任篮协新职务,一个令裁判瑟瑟发抖的男人一直以来,国内联赛的裁判都让大家纷纷吐槽不专业,一场精彩的比赛时常让裁判抢了风头,球员们和教练也是敢怒不敢言,一说话裁判准给你技术犯规,但是,有一个人可不触裁判,时常在比赛中硬怼裁
中国男篮将迎来8月2这场恶战,能否获胜归来?从目前来看,中国男篮仍然是处在低谷状态,两场赢下了中国台北队,并不能算得上什么,而两场输给了澳大利亚,也是成为了球迷预料中的事情,因此,杜锋现在所执教的中国男篮仍然是要居危思危,并
湖人内部评估浓眉价值,决定是否送走浓眉湖人内部评估浓眉价值一位匿名高管表示,湖人内部的一些人士想要衡量浓眉的交易价值,这很大程度上取决于詹姆斯是否会选择留队。新赛季赛程表暂缓公布据美媒报道,有消息人士称新赛季NBA完整
湖人内部评估浓眉价值,决定是否送走浓眉湖人内部评估浓眉价值一位匿名高管表示,湖人内部的一些人士想要衡量浓眉的交易价值,这很大程度上取决于詹姆斯是否会选择留队。新赛季赛程表暂缓公布据美媒报道,有消息人士称新赛季NBA完整
揭示探索人类第六感公元2016年,美国地球物理学家从研究实验中发现人类具有感应磁场的第六感,历来许多物种也都能对地球磁场做出反应,细胞中可能存在1种能接收磁场信息的物质。过去科学家认为人类不可能感觉
揭示探索人类第六感公元2016年,美国地球物理学家从研究实验中发现人类具有感应磁场的第六感,历来许多物种也都能对地球磁场做出反应,细胞中可能存在1种能接收磁场信息的物质。过去科学家认为人类不可能感觉
比普通闪电强100倍的巨大闪电射流冲向太空没有专门的仪器研究巨大的喷气机。一位市民设法用他的微光相机捕捉到了这一事件。我们现在有了第一张巨大的闪电喷射的3D地图。据报道,佐治亚理工学院(GTRI)的研究人员分析了更多数据,
比普通闪电强100倍的巨大闪电射流冲向太空没有专门的仪器研究巨大的喷气机。一位市民设法用他的微光相机捕捉到了这一事件。我们现在有了第一张巨大的闪电喷射的3D地图。据报道,佐治亚理工学院(GTRI)的研究人员分析了更多数据,
刚和同事们一起听了一本书,才明白为什么日本人总是自觉清扫卫生下班前,和同事们一起听了一本关于大扫除的书,深有感触,大家也都分享了感想,确实值得深思。还记得卡塔尔世界杯八强时,日本队出人意料地力压西班牙和德国,小组第一出线。然后在八分之一决赛
荣耀全场景新品发布会,80GT性价比很高!12月26日晚,荣耀举行了全场景新品发布会,带来了预热已久的荣耀80GT以及多款新品,下面一起来看看吧!1。荣耀平板V8Pro屏幕方面,V8Pro搭载12。1英寸全面屏,屏幕分辨率
2022跨境电商海外厮杀北美欧洲东南亚三大战场风云录年终盘点撰文长风编辑李可馨在TemuSHEIN进入美国之前,几美元可能做不了什么,但在这些跨境电商到来后,几美元的用途立刻就变得不容小觑。要知道,Temu上大部分的日用家居类商品都在10美
湖人做出三点改变!詹姆斯说威少会影响比赛,又对其他人提出要求湖人队在浓眉哥遭遇伤病之后,状态不佳,虽然詹姆斯已经连续7场打出30的得分表现,但糟糕的阵容配置让湖人经历了4连败,所以对阵魔术队的比赛,有很多人都不看好湖人队,可是结果很是鼓舞人
湖人队,我们来啦巴特勒阿德巴约将复出战湖人!头条创作挑战赛今天,在詹姆斯威少的带领下,湖人队全民皆兵,6人得分上双,以129110大胜魔术队,结束了自己的4连败。下一个对手将是2020年总决赛的老冤家,迈阿密热火队。虽然湖人
生活随笔整个身体处于一个闷葫芦的状态,那种不见天日的感觉,被笼罩在其中,不知道什么时候,心情就会被影响,压抑到极点。每天晚上的随笔时间,可能,有着治愈的功效吧,此刻,久违了的被幸福包围的时
女人念念不忘的男人,不是初恋在头条看见彼此文大梦希望我的文字,可以治愈你的不安生命中的第一次,总是最让人怀念的。当岁月的手推动着生活中的一切都变了模样,会渴望回到最初的最初,那种只有美好没有经历过的曾经。爱上
美媒2022年最严重的7次航天挫折2022年最严重的火箭发射失败案例表明,航天飞行仍具有很大难度2022年有很多航天飞行的成功故事,但也有很多航天任务未取得预定效果。火箭需要成千上万个零部件才能直达太空,哪怕一个问
这个小行星撞击模拟器,可以体验行星撞地球阅读文章之前,请点击关注,方便您回来查看内容,以及参与大家的互动,感谢您给予我码字动力!来自Neal。Fun的一款网络应用,它可以让你体验行星撞击地球的模拟。一颗1,500英尺高的
重要提醒!就在明天!今年最后一次明天,上弦月与明亮木星浪漫同框天文科普专家介绍,12月29日晚天宇将上演木星合月,这是2022年最后一场行星合月。届时只要天气晴好,我国公众可在南方天空欣赏到明亮木星与一轮上弦月近
心虚了?阿波罗共往月球上扔了809件垃圾,如今立法不让别人靠近很多人总是陷入阿波罗登月造假的阴谋论里不能自拔,认为美国人根本没有登上月球。但实际上,美国6次载人登月的遗迹早就被其他国家的探测器拍到了,包括我们的嫦娥二号,也拍到了阿波罗15号的