本文翻译自android官方文档,结合自己测试,整理如下。
content provider管理数据的访问,我们可以在自己的应用程序中实现一个或多个自定义的provider(通过继承抽象类ContentProvider),当然这些provider需要在manifest文件中注册。尽管content provider是用来为其它程序来访问数据的,但是在自己程序中的activities显然可以对这些数据进行处理。
确定是否需要提供content provider。若有以下一种或多种需求的话需要创建content provider:
若在程序内部使用SQLite数据库,则不需要provider。
接下来,通过下列步骤创建provider(先简单的总结,后续详细介绍):
为数据设计存储方式,content provider提供两种方式:
需要继承抽象类ContentProvider,并且覆盖必要的方法。这个类是我们的数据和其他程序交互的接口。
在提供provider之前,我们必须要确定我们的数据该如何存储,当然存储方式我们可以任意指定,然后再针对该存储方式设计provider。
有下列几种存储方式:
BaseColumns._ID
,这样的话,在ListView就能很方便的检索。内容URI是能够识别provider中数据的URI,包括权限(authority)和路径(path)。权限找到provider,路径找到表或文件。还可以有一个id,能够表示某一行。
权限用于区分不同程序,一般为了避免冲突,都会采用包名的形式命名。例如包名为:com.example.sywyg
,则该权限就可以为:com.example.sywyg.provider
。
URI是权限加路径的方式来查找指定的表。路径是区分同一程序中表或者其它形式(例如文件)的,可以直接添加在权限后面。例如table1和table2,则形成的URI分别为:com.example.sywyg.provider/table1
和com.example.sywyg.provider/table2
。
最后内容URI需要在权限和路径前加上content://
表示内容URI。例如,一个标准的内容URI写法如下:content://com.example.sywyg.provider/table1
。
将ID追加到URI后面的话就可以检索到表中的指定的行,ID对应的列名为_ID。
UriMatcher类映射内容URI模式到一个integer类型数,我们可以使用该值进行模式匹配。
URI模式通过通配符匹配:
假设权限为:com.example.app.provider
,识别下面的URI对应的表:
content://com.example.app.provider/table1: 表为 table1.
content://com.example.app.provider/table2/dataset1: 表为 dataset1.
content://com.example.app.provider/table2/dataset2: 表为 dataset2.
content://com.example.app.provider/table3: 表为 table3.
若带有ID同样可以识别:
content://com.example.app.provider/table3/1
表table3中的第1行
下面的URI模式:
content://com.example.app.provider/*
匹配provider中的任意URI
content://com.example.app.provider/table2/*
将会匹配表dataset1和dataset2,但是不会匹配table1或table3。
content://com.example.app.provider/table3/#
将会匹配table3中的任意行。
content://com.example.app.provider/table3/6
将会匹配table3中的第6行。
总结起来,URI标准就是:content:///或content:////,前者针对表,后者针对指定行。
我们可以借助UriMatcher类快速实现内容URI的匹配。常用代码如下:
ContentProvider类能够管理我们provider中的数据,外界通过ContentResovler可以调用对应的ContentProvider方法实现操作数据。因此,我们必须要提供相应的方法来操作数据。
我们需要实现以下方法,才能方便ContentResovler访问数据。
query()
insert()
update()
delete()
getType()
onCreate()
可以看到对于上述几个操作数据的方法,在ContentResovler有同样名称的方法。
在覆盖方法时需注意一下几点:
onCreate()
之外的方法都要注意多线程安全问题。onCreate()
进行长时间的操作。直到需要时再初始化对应的内容。query()
方法该方法会返回一个Cursor对象,或者失败的话抛出异常。若没有查到对应的行则应该返回一个getCount()
方法为0的Cursor对象。只有在内部出现错误是才返回null。若使用SQLite数据库保存数据的话,可以直接调用SQLiteDatabase类的query()
方法返回Cursor对象。若不使用的话,就要使用Cursor类的具体子类。
在查询时可能会抛出下列异常:
若访问的是SQLite数据库,则query()
简单实现代码如下:
public class ExampleProvider extends ContentProvider {
private static final UriMatcher sUriMatcher;
sUriMatcher.addURI("com.example.app.provider", "table3", 1);
sUriMatcher.addURI("com.example.app.provider", "table3/#", 2);
// 参数为ContentResovler调用query()方法传递过来的
public Cursor query(
Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String sortOrder) {
switch (sUriMatcher.match(uri)) {
// 对应table3
case 1:
if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
break;
// 对应带有id的table3
case 2:
selection = selection + "_ID = " uri.getLastPathSegment();
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 实际查询SQLite数据库语句
}
insert()
方法插入方法将新的一行添加到指定的表中,数据来自于参数ContentValues 传递的数据,若某一列没有指定,则提供默认值(该默认值取决于provider或数据库本身)。
该方法会返回新行的URI。我们可以通过ContentUris的withAppendId()
方法在URI后添加主键ID(通常是_ID)来构建该URI。直接通过parse()
方法也行。
update()
方法和插入类似,不再介绍。
delete()
方法删除指定的行。该方法不需要真的删除某行,若我们使用同步适配器的话,可以考虑先把要删除的数据进行标记删除。该同步适配器可以在从provider中真的删除数据之前能够检查出删除的行,并把这些排除,实现假删除。
getType()
方法将在下面部分详细讲解。
onCreate()
方法当创建provider时,系统调用onCreate()
方法。我们应该保证在这里初始化的内容是必须的且能够快速执行,对于不是必须的且耗时的可以在需要时再初始化。例如数据库创建以及数据加载可以在真正请求操作数据时再执行。若太耗时的话,provider启动就会耗时,显然这会影响回应请求该provider的程序。
ContentProvider有两个方法能返回类型:
getType()
getStreamTypes()
getType()
方法返回一个MIME格式的字符串,用来描述参数URI对应的 数据类型。参数Uri可以匹配一个模式而不是指定的URI,这样我们应该返回和匹配模式的URIs相关的数据类型。
对于常见的类型:text,HTML,JPEG,getType()
方法应该返回标准的MIME类型。
对于指定一行或多行的URIs,getType()
方法应该返回android指定的MIME格式:
com.example.app.provider
,表名为:table1,则table1多行的MIME类型为: vnd.android.cursor.dir/vnd.com.example.provider.table1
vnd.android.cursor.item/vnd.com.example.provider.table1
若provider提供文件的话,需要实现getStreamTypes()
方法。该方法返回一个MIME类型的字符串数组,根据给定的URI。我们应该根据MIME类型过滤参数过滤MIME类型,以便返回客户端想处理的MIME类型。
例如,provider提供图片文件:.jpg,.png和.gif格式。若一个程序调用了getStreamTypes()
使用过滤参数image/*,那么该方法就会返回:
{ "image/jpeg", "image/png", "image/gif"}
若程序只需要.jpg的话,可以使用过滤参数*\/jpeg,则方法返回:
{"image/jpeg"}
若provider不提供类型的话,方法返回null。
一般需要一个public final类型的相关类来定义常量:URIs,列名,MIME或者其他和provider相关的信息。该类使provider和其它程序建立一种关系,能够确保provider被正确地获取。
对于其他程序来说,可以通过我们提供的.jar文件来操作这个相关类,进而实现操作provider。
需要注意一下几点:
若我们想使用content provider权限控制数据的读取,我们需要把数据存储在内部文件,SQLite数据库或服务器中,并且确保这些文件和数据库是私有的。
默认情况下,provider没有设置许可,所有的程序都能获取provider数据。我们可以在mainfest文件中<provider>
标签的属性或子标签配置。许可可以针对整个provider或者特定的表或者特定的记录配置。
声明许可使用<permission>
标签,例如:
<permission android:name="com.example.app.provider.permission.READ_PROVIDER">
下面描述了provider的详细许可设置:
<provider>
标签中的android:permission
属性中设置。<provider>
标签中的android:readPermission
属性中设置读许可;在<provider>
标签中的android:writePermission
属性中设置写许可。这两个许可优于android:permission设置的许可。<provider>
标签中的<path-permission>
子标签中设置。该级别权限优于上面的两个许可。临时许可(Temporary permission)
授予程序临时获取数据的许可。
在<provider>
标签中的android:grantUriPermissions
属性中设置,或者在<provider>
标签中的<grant-uri-permission>
子标签中添加一个或多个。
若使用了临时许可,每当从provider移除对一个URI的支持时,必须调用Context.revokeUriPermission()
,该URI和临时许可相关。若该属性设置了true,则系统支持授权临时许可,且会覆盖其它任何许可(provider级别或路径级别的)。
若设置了false,就需要在<provider>
标签中的<grant-uri-permission>
子标签中添加一个或多个。每一个子标签指定一个或多个URIs有临时被访问的许可。
为了在一个程序中委托临时访问许可,intent必须包含FLAG_GRANT_READ_URI_PERMISSION
和FLAG_GRANT_WRITE_URI_PERMISSION
中的一个或两个flags(通过setFlags()
设置)。
若没设置android:grantUriPermissions
属性,则被认为是false。
<provider>
标签我们知道四大组件都需要在mainfest文件中配置,ContentProvider的实现类通过<provider>
标签配置。该标签中还包括一些重要的属性和子标签:
android:authorities
android:name
Permission
上面已经详细描述,主要包括:
- `android:grantUriPermssions`;
- `android:permission`;
- `android:readPermission`;
- `android:writePermission`。
启动和控制属性
android:enabled
是否允许实例化android:exported
外部是否能使用android:initOrder
integer值,表示在同一个进程中被初始化的顺序,值越大越早被初始化android:multiProcess
是否允许在多个进程中实例化android:process
所在的进程android:syncable
信息属性
android:icon
图标android:label
名称ContentResovler和ContentProvider之间的协作关系以查询SQLite数据库为例进行描述:
ContentResovler对象的query()
方法中的参数URI,通过URI中的权限authority可以找到对应的ContentProvider实现类,对该类实例化并调用query()
方法,在query()
方法中通过UriMatcher.match()
方法匹配Uri,匹配成功后交给SQLite数据库的查询方法,并返回Cursor,然后通过ContentProvider实例返回该Cursor给调用者。可以看到通过权限可以确定一个provider的,因此一个程序中可以包含多个providers。
遗留问题:
解读Android之ContentProvider(2)创建自己的Provider
原文地址:http://blog.csdn.net/wangyongge85/article/details/47057369