本文为个人读书笔记,大部分为书中内容摘要。仅供记录和分享学习中遇到的需要留意的问题,如有相关版权问题请及时通知作者。
ContentProvider保存和获取数据并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式。在Android中,没有提供所有应用共同访问的公共存储区域。
Content Provider概述
Content Provider内部如何保存数据由其设计者决定。但是所有的Content Provider都实现一组通用的方法用来提供数据的增、删、改、查功能。
客户端通常不会直接使用这些方法,大多数是通过ContentResolver对象实现对Content Provider的操作。可以通过调用Activity或其他应用程序组件的实现类中的getContentResolver()方法来获得Content Provider。
eg:ContentResolver cr=getContentReolver();
使用ContentResolver提供的方法可以获得Content Provider中任何感兴趣的数据。
当开始查询时,Android系统确认查询的目标Content Provider并确保它正在运行。系统会初始化所有Content Provider类的对象,开发人员不必完成此类操作。实际上,开发人员根本不会直接使用ContentProvider类的对象。通常每个类型的ContentProvider仅有一个单独的实例,但是该实例能与位于不同应用程序和进程的多个ContentResolver类对象通信。不同进程之间的通信由ContentProvider类和ContentResolver类处理。
数据模型
ContentProvider使用基于数据库模型的简单表格来提供其中的数据,每行代表一条记录,没列代表特定类型和含义的数据。
每条记录包含一个数值型的 _ID字段,用于在表格中唯一标识该记录。
查询返回一个Cursor对象,它能遍历各行各列来读取各个字段的值。对于各个类型的数据,它都提供了专用的方法。因此,为了读取字段的数据,开发人员必须知道当前字段包含的数据类型。
URI的用法
每个Content Provider提供公共的URI(使用URI类包装)来唯一标识其数据集。管理多个数据集(表格)的Content Provider为每个都提供了单独的URI。所有为provider提供的URI都已“content://“作为前缀,“content://“模式表示数据由Content Provider来管理。
如果自定义ContentProvider,则应该为其URI也定义一个常量来简化客户端代码。Android为当前平台提供的ContentProvider定义了CONTENT_URI常量。匹配电话号码到联系人表格的URI:android.provider.Contacts.Phones.CONTENT_URI。匹配保存联系人照片表格的URI:android.provider.Contacts.Photos.CONTENT_URI
URI常量用于所有与ContentProvider的交互中。每个ContentResolver方法使用URI作为其第一个参数。它标识ContentResolver应该使用哪个provider及其中的哪个表格。
content:// com.xx.xxx /dba /001
A B C D
A :标准前缀,标识该数据由ContentProvider管理。永远不用修改。
B:URI的authority部分,标识该ContentProvider。对于第三方应用,该部分应该是完整的类名来保证唯一性。在<provider>元素的authorities属性中声明authority。
C:ContentProvider路径部分,用于木额定哪类数据被请求。如果ContentProvider仅提供一种数据类型,这部分可以没有。如果provider提供几种类型,把偶哦子类型,这部分可以由几部分组成。
D:被请求的特定记录的ID值。这是被请求记录的_ID值。如果请求不仅限于单条记录,该部分及其前面的斜线应该删除
ContentProvider的几种操作
Android系统为常用数据类型提供了很多预定义的Content Provider,大都位于android.provider包中。开发人员可以查询这些provider以获得其中包含的信息。
Android系统提供的常见ContentProvider
ContentProvider | 功能 |
Browser | 读取或修改书签、浏览历史或网络搜索 |
CallLog | 查看或更新通话记录 |
Contacts | 获取、修改或保存联系人信息 |
LiveFolders | 由ContentProvider提供内容的特定文件夹 |
MediaStore | 访问声音、视频、图片 |
Setting | 馋看和获取蓝牙设置、铃声和其他设备偏好 |
SearchRecentSuggestion | 能被配置以使用查找意见provider操作 |
SyncStateContract | 用于使用数据组账号关联数据的ContentProvider约束。希望使用标准方式保存数据的provider可以使用 |
UserDictionary | 可预测文本输入。应用程序和输入法能增加数据到该字典。单词能关联频率信息和本地化信息 |
查询数据
开发人员需要3条信息查询ContentProvider中的数据
1、标识该Content Provider的URI
2、需要查询的数据字段名称
3、字段中数据的类型
如果查询特定的记录,还需要提供该记录的ID
查询ContentProvider中的数据,需要使用ContentResolver.query()或Activity.messageQuery()方法。这两个方法使用相同的参数,都返回Cursor对象。然而,managedQuery()方法导致Activity管理Cursor的生命周期。托管的Cursor处理所有的细节,例如当Activity暂停时卸载自身,当Activity重启时加载自身。调用Activity.startManagingCursor()方法可以让Activity管理未托管的Cursor对象。
query()和managedQuery()方法的第一个参数是provider的URI,即标识特定ContentProvider和数据集的CONTENT_URI常量。
为了限制仅返回一条记录,可以在URI结尾增加该记录的_ID值,即将匹配ID值的字符串作为URI路径部分的结尾片段。
ContentUris.withAppendedID()和URI.withAppendenPath()等辅助方法,可以将ID增加到URI。这两个方法都是静态方法并返回一个增加了ID的Uri对象。
query()和managedQuery()方法其他参数用来更加细致地限制查询结果:
1、应返回的数据列名称。null表示返回全部。否则仅返回列出的列。全部预定义Content Provider为其列都定义了常量。例如android.provider.Contacts.Phone类定义了_ID、NUMBER、BUMBER_KEY、NAME。
2、决定哪些行被返回的过滤器,格式类似SQL的WHERE语句。null值表示返回全部行(除非URI限制查询结果限制为单行记录)
3、选择参数
4、返回记录的排序器,格式类似SQL的ORDER BY语句。null表示已默认顺序返回记录,可能是无序的。
5、查询返回一组零条或多条数据库记录。列明、默认顺序和数据类型对每个Content Provider都是特别的。但是每个provider都有一个_ID列,它为每条记录保存唯一的数值ID。每个provider也能使用_COUNT包干返回结果中记录的行数,该值在各行都是相同的。
6、获得数据使用Cursor对象处理,它能向前或者向后遍历整个结果集。开发人员可以使用它来读取数据。增加、修改、删除数据则必须使用ContentResolver对象
增加记录
首先在ContentValues对象中建立键值对映射,这里每个键匹配ContentProvider中的列名,每个值是该列中希望增加的值。然后调用CotentResolver.insert()方法并传递给它provider的URI参数和ContentValues映射。该方法返回新记录的完整URI,即增加了新纪录ID的URI。开发人员可以使用该URI来查询并获取该记录的Cursor,以便修改该记录。
增加新值
增加记录到Contacts数据库的最佳方式是增加保存新数据的表名到代表记录的URI,然后使用组装好的URI来增加新数据。每个Contacts表格以CONTENT_DIRECTORY常量的方式提供名称作为该用途。
开发人员可以调用使用byte数组作为参数的ContentValues.put()方法向表格中增加少量二进制数据。适用于小图标的图片、短音频片段等。如果需要增加大量二进制数据,保存代表数据的content:URI到表格,然后使用文件URI调用ContentResolver.openOutputStream()方法。这导致ContentProvider保存数据到文件并在记录的隐藏字段保存文件路径。
批量更新数据
使用ContentResolver.update()方法并提供需要修改的列名和值。
删除记录
单条记录:调用ContentResolver.delete()方法并提供特定行的URI
多条记录:调用ContentResolver.delete()方法并提供删除记录类型的URI和一个SQL WHERE语句。
【13.6】
根据需要Content Provider
开发人员共享自己的数据,有两个选择:
1、创建自定义的Content Provider(一个ContentProvider类的子类);
2、如果有预定义的provider,管理相同的数据类型并且有写入权限,则可以向其中增加数据。
如果自定义Content Provider,则开发人员需要完成以下操作:
1、建立数据存储系统。大多数Content Provider使用Android文件存储方法或者SQLite数据库保存数据,但是开发人员可以使用任何方式存储。Android提供了SQLiteOpenHelper类帮助创建数据库,SQLiteDatabase类管理数据库。
2、继承ContentProvider类来提供数据访问方式。
3、在应用程序的AndroidManifest文件中声明Content Provider。
继承ContentProvider类
开发人员定义ContentProvider类的子类以便使用ContentResolver和Cursor类来共享数据。原则上,需要实现ContentProvider类定义的以下6个抽象方法:
public boolean onCreate()
public Cursor query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder)
public Uri insert(Uri uri,ContentValues values)
public int update(Uri uri,ContentValues values,String selection,String[])
public int delete(Uri uri,String selection,String[] selectionArgs)
public String geType(Uri uri)
方法 | 说明 |
onCreate() | 初始化provider |
query() | 返回数据给调用者 |
insert() | 插入新数据到Cotent Provider |
update() | 更新Content Provider中已经存在的数据 |
delete() | 从Content Provider中删除数据 |
getType() | 返回Content Provider数据的MIME类型 |
query()方法必须返回Cursor对象,用于遍历查询结果。Cursor自身是一个接口,但是Android提供了一些该接口的实现类
由于这些ContentProvider方法能被位于不同进程和线程的不同ContentResolver对象调用,它们必须以线程安全的方式实现。
此外,开发人员如果想调用ContentResolver.notifyChange()方法以便在数据修改时通知监听器。
除了定义子类自身,还应采取一些其他措施以便简化客户端工作并让类更加易用:
1、定义public static final Uri CONTENT_URI变量。该字符串表示自定义的Content Provider处理的完整的content:URI。开发人员必须为该值定义唯一的字符串。最佳的解决方式是使用Content Provider的完整类名。
如果provider包含子表,也应该为各个子表定义URI。这些URI应该有相同的authority(因为它标识Cotent Provider)然后使用路径进行区分。
2、定义Content Provider将返回给客户端的列名。如果开发人员使用底层数据库,这些列名通常与SQL数据库列名相同。同样定义public static String常量,客户端用它们来指定查询中的列和其他指令。确保包含为“_ID”的整数列用来作为记录的ID值。无论记录中其他字段是否唯一,开发人员都应该包含该字段。如果打算使用SQLite数据库,_ID字段应该是: INTEGER PRIMARY KEY AUTOINCREMENT类型
3、仔细注释每列的数据类型,客户端需要使用这些信息来读取数据。
4、如果开发人员正在处理新数据类型,则必须定义新的MIME类型以便在ContentProvider.getType()方法实现中返回。
5、如果开发人员提供的byte数据太大而不能放到表格中,提供给客户端的字段应该包含content:URI字符串。
声明Content Provider
在应用程序的AndroidManifest.xml文件中定义<provider>元素。没有在配置文件中声明的Content Provider对于Android系统不可见。
name属性的值是ContentProvider类的子类的完整名称。authorities属性是provider定义的content:URI中authority部分。authorities属性删除了content:URI中的路径部分。
其他<provider>属性能设置读写数据的权限,提供显示给用户的图表或文本,启用或禁用provider等。如果数据不需要在多个运行着的ContentProvider间同步,则设置multiprocess为true。这允许在各个客户端进程创建一个provider实例,从而避免执行IPC。