文章摘自《解读Content Provider之一》及其续篇
本人制作了一个content provider的实例,有兴趣可以下载看看。android content provider示例程序(简单记账)
综述
Content providers是一个Android应用程序的主要部分,主要是为应用程序提供内容。它对数据进行封装然后通过单一的ContentResolver接口提供给应用程序。只有需要在多个应用程序间共享数据时,content provider才是必须的。例如:有多个应用程序(例如:打电话程序和发短信程序等等)都需要使用联系人的数据,因此我们必须将这些数据存放在content provider。如果不需要在多个应用程序中共享数据,那么可以直接在应用程序中使用SQLiteDatabase。
当程序通过一个ContentResolver接口请求Content provider时,系统就会检查传递进来的URI的权限,并且将请求传递给拥有这些权限的content provider。content provider可以以任意的方式解析URI。UriMatcher类对于解析URIs是非常有帮助的。
对于content provider主要要实现的方法有如下几个:
onCreate():主要是初始化provider;
query(Uri, String[], String, String[], String):讲数据返回给它的调用者
insert(Uri, COntentValues):插入新的数据到content provider
update(Uri, COntentValues, String, String[]):更新content provider中的数据
delete(Uri, String, String[])删除content provider中的数据
getType(Uri):返回content provider中数据的MIME类型
数据存取方法(例如:insert(Uri, ContentValues)和update(Uri, ContentValues, String, String[]))可能会在同一时间被多个线程调用,并且是线程安全的。其他的方法(例如:onCreate())仅仅被程序的主线程调用,而且必须要避免在其中进行耗时的操作。具体的请看方法的描述。
对于ContentResolver将会自动的传给合适的ContentProvider实例,因此子类不需要担心夸进程调用的细节。
开发者指南
对于如何使用content provider的细节,请阅读Content Providers开发者指南。
下面是Content Providers开发者指南:
Content Providers存取数据,并且使这些数据对于所有的程序都是可用的。Android系统中并没有对任何程序都可使用的共同存储区域,因此,这是在多个应用程序间共享数据的唯一方法。
Android系统包含许多的对常见类型数据(音频、视频、图片以及个人联系信息等等)的content providers。你可以在android.provider包中看到一部分的content provider。你可以查询这个content provider中包含的数据(尽管对于其中的一部分数据,你必须要拥有合适的权限)。
如果你想让自己的数据对外部公开,你有两个选择:
1.你可以创建你自己的content provider(一个Content Provider子类)
2.你可以将自己的数据添加到已经存在的content provide,如果存在包含着和你想公开数据的相同类型数据的content provider,并且你拥有权限,你就可以将这些数据写入到这个content provider中。
这篇文档是介绍如何使用content provider。在简洁的讨论了一下基本后,这些讨论覆盖了如何查询一个content provider中的内容,如何修改一个content provider中包含的数据,如何为自己创建一个content provider。
Content Provider 基础知识
content provider如何存储数据是依赖于它的设计的。但是所有的content provider都实现了一个共同的接口用于查询provider并且返回结果--以及:添加,修改和删除数据等。
客户端不会直接使用这个接口,几乎都是通过ContentResolver对象。你可以通过实现在Activity中或其它应用程序组件中的getContentResolver()方法来获得ContentResolver.
ContentResolver cr = getContentResolver();
你可以通过ContentResolver的方法和感兴趣的content providers进行交互。
当一个查询操作初始化后,Android系统识别查询要进行的操作对象,并且确保它处于运行状态。系统实例化了所有的Contentprovider对象:你根本不需要进行这样的操作。实际上,你从来就不会直接和ContentProvider打交道。实际上,对于每一种ContentProvider,系统仅仅有一个实例。但是这一个ContentProvider实例却可以和处于多个程序、多个进程中的ContentResolver对象进行通信。多个进程间的交互是通过ContentResolver类和ContentProvider类进行的。
数据模型
Content providers是像数据库中的表的模型一样暴露自己所包含的数据,每一行都是一条记录,每一列都是一个特定的类型并且有特定的意义。例如:联系人的信息以及他们的电话号码就可能像如下一样存储:
在表中的每一条记录都含有是个数值型的_ID域,每一条记录的该域都是唯一的,用以标示这条记录。IDs可以用来匹配关联表中的记录,例如:在一个表中找到一个联系人的电话号码,在另一个表中找到该联系人的图片。
查询操作可以返回一个Cursor对象,通过这个Cursor对象你可以从一条记录移动到另一条记录,从一个列移动到另一个列来读取每个列值。
对于每一种类型的数据,他都有特定的方法读取。因此,要读取一个域的值,你必须要知道这个域的值的数据类型(关于这方面的详细内容会在查询操作以及Cursor对象中介绍)。
URIs
每一个content provider都暴露了一个公共的URI(包装成一个Uri对象)用来唯一标示它包含的数据集。对于那些包含多数据集(多个表)的content provider它会为每一个数据集提供一个URI。所有由provider暴露的URIs都是以字符串"content://"开头的。"content:"表明这个数据集是由一个content provider提供的。
如果你定义了一个content provider,那么为它的URI定义一个常量是一个比较好的做法,这样就可以简化客户端的编程并且使得将来的更新更加的彻底。Android随着平台一起为所有的providers定义了CONTENT_URI这个常量,例如:对于联系人电话号码的表的URI和存储联系人图片的表的URI(两者都是被Contacts content provider控制)分别如下:
android:provider.Contacts.Phone.CONTENT_URI
android:provider.Contacts.Photos.CONTENT_URI
URI常量在所有与content provider的交互操作中都会被使用到。每一个ContentResolver的方法的第一个参数都是URI。它代表着ContentResolver将会与哪一个provider通信,竟会操作provider中的哪一个表。
查询一个Content Provider
你需要三方面的信息来查询一个xontent provider:
1.标示provider的URI;
2.你希望接收的数据域的名称;
3.你希望操作的数据域的数据类型。
如果你是查询特定的记录,那么你还需要唯一标示这条记录的ID。
执行查询操作
要查询一个content provider,你将会使用到ContentResolver.query(0方法或者是Activity.managedQuery()方法。这两个方法使用同样的参数,都返回一个Cursor对象。然而,managedQuery()方法会使得由activity来管理返回的Cursor对象的生命周期。一个被管理的Cursor对象必须处理所有的细节,例如:在activity处于暂停状态时就要卸载自己,当activity处于重启时,又要开始执行查询操作。 你可以调用Activity.startManagingCursor()方法来管理一个还未被管理的Cursor对象。
query()和managedQuery()方法的第一参数都是provider的URI——CONTENT_URI常量标示了一个特定的ContentProvider以及其数据集。
要严格的查询到一条记录,你必须追加记录的_ID到URI上——也就是,将代表ID的字符串放置在URI的最后字段。例如:如果ID是23,那么URI就如下所示:
content://. . . ./23
这里有一些帮助的方法,特别是ContentUris.withAppendedId()和Uri.withAppendedPath()方法,他们使得追加一个ID到URI上变得更加容易。这两个静态方法都返回追加了ID的Uri对象。因此,如果你想在联系人的数据库中查找到标示为23的这条记录,你可以像下面这样构建一个查询器:
获取的数据是通过一个Cursor对象来呈现的,通过这个Cursor对象,我们可以通过这个结果集来重复的向前向后操作。你仅仅可以通过这个Cursor对象来读取数据。要想添加、修改或者是删除数据,你必须使用ContentReslover对象。
读取检索出来的数据
我们可以通过查询providers返回的Cursor对象来操作返回的数据集。如果在查询的时候指定了ID,那么这个结果集中仅仅包含一条记录。否则他就会包含多条记录(如果没有匹配的数据,那么返回的也有可能为空)。你可以读取记录的特定域,但是你必须知道你要读取的域存放的数据的数据类型,因为Cursor对象对于每一种类型的数据都有不同的方法——例如:getString(),getInt()和getFloat()。(然而,对于大多数的类型,如果你调用读取字符串的方法,那么Cursor对象就会将代表数据的字符串形式返回给你)。Cursor对象允许你从列的索引中回去列名,或者是从列名中获得索引号。
下面的例子演示了从前面的查询操作中读取联系人的名字和电话号码:
修改数据
可以通过如下方法修改被content provider保存的数据:
1.添加新的记录;
2.为已经存在的数据添加新值;
3.批量更新已经存在的记录;
4.删除记录
所有的数据修改操作都可以通过ContentResolver的方法来完成。一些content provider在修改数据的时候要求拥有比读取数据拥有更多的权限。如果没有修改content provider数据的权限,那么ContentResolver的方法就会失效。
添加记录
为了添加新的记录到content provider中,首先在ContentValues对象中设置键-值对,设置的键和content provider中的列名对应,并且值的内容就是我们想为新记录设置值。然后,通过调用ContentResolver.insert()方法,并且将要处理的content provider和设置好的ContentValues键-值对作为参数传递给这个方法。这个方法将会返回新纪录的完整的URI——也就是,要操作的provider的URI加上为新记录追加的ID号。然后,你可以用这个完整的URI来进行查询操作,获得一个指向这个新记录的Cursor,并且利用这个得到的Cursor进行后续的修改记录的操作。如下例:
A.标准的前缀,表明这些数据是被content provider持有的。这个永远不需要修改。
B.URI所属的部分,它是用来鉴别一个content provider的。对于第三方的应用程序,这必须是类的全名(必须是小写)来确保它是唯一的。所属是定义再<provider>元素的authorities属性的: <provider android:name=".TransportationProvider"
android:authorities="com.example.transportationprovider"
. . . >
<provider android:name=".TransportationProvider"
android:authorities="com.example.transportationprovider"
. . . > C.这部分是content provider用来决定是哪些类型的数据被请求。这个可以为空,也可以有几个段那么长。如果一个content provider仅仅暴露一种类型的数据(例如:仅仅是trains类型),那么他就可以为空。如果一个content provider暴露几种类型的数据,包括子类型,那么它就有可能有几段那么长——例如:“land/bus”、“labd/train”、"sea/ship"和“sea/submarine”这四种可能的类型。
D.被查询的特殊记录的ID。如果有,那么这个就是被请求记录的_ID值。如果请求不是对单个记录,那么这个段和斜杠都会被忽略:
content://com.example.transportationprovider/trains
content://com.example.transportationprovider/trains