Createing a Content Provider:
英文原文:http://developer.android.com/guide/topics/providers/content-provider-creating.html 版本:Android 4.0 r1 译者署名:Rongqi Fan 译者链接:
内容管理者管理对数据的访问。你可以使用一个或多个类还有manifest文件里的元素实现一个提供者。你可以用一个类实现提供者和其它应用程序通信的提供者,它是ContentProvider的子类。尽管对应用程序而言提供者使数据变得可用,但是你也需要一个Activity来允许用户查询、修改提供者管理的数据。
下面是建立一个提供者的基本步骤和需要使用的API。
目录[隐藏]
|
构建提供者之前,先做如下:
1、决定是是否需要一个提供者。考虑如下的特点:
你想向其它应用程序提供复杂的数据或文件。 你想允许用户复制复杂的数据给其它应用程序。 你想通过搜索框架提供定制的搜索建议。
如果用户不和其它应用交互,你不需要使用提供者来操作SQLite数据库。
2、如果你对提供者的基础知识不清楚,可以先阅读Content Provider Basics。
接下来,按如下步骤构建你的提供者:
1、为你的数据设计原始数据存储。提供者通过两种方式提供数据:
文件数据
数据通常需要写入文件,如:图片、音频、视频。文件存储在你的应用程序的私有空间里。响应其它程序的请求,提供者提供文件的句柄。
“结构体”数据
数据通常存储到数据库、数组、相似的结构。以兼容表格的行列的形式储存数据。行代表一个实体,如:一个人或库存的一个项目。列代表这个实体的数据,如:人名、项的价格。通常是储存在SQLite数据库里,但是,你可以使用任何储存的类型。更多关于Android支持的数据类型,阅读Designing Data Storage。
2、定义ContentProvider 的具体实现及所需函数。这个类是你的数据和Android系统的接口。更多的知识,阅读 Implementing the ContentProvider Class。
3、定义提供者的权限字符串、内容URIs、列名。如果你想提供者可以处理intent,定义intent 行为,扩展数据、标志。定义访问数据的权限。你需要在一个分离的合约类里把这些值定义为常量;之后,你把这个类暴露给其它开发人员。更多关于内容URIs的信息在 Designing Content URIs,更多关于intent的信息在Intents and Data Access.。
4、其它可选项,比如:简单数据或可以提供异步数据(在提供者和基于云的数据之间通信)的AbstractThreadedSyncAdapter。
提供者是一个存储数据为结构的形式的接口。在你创建接口之前,你决定如何储存数据。你可以储存成任何一种形式,并且按需要设计读写的接口。
在Android里可用的数据存储技术:
Android系统包含SQLite数据库的API,Android自己的提供者用来存储面向表的数据。 SQLiteOpenHelper类帮助你创建数据库,SQLiteDatabase类是访问数据库的基类。
你不需要使用一个数据库来储存你的数据。提供者对外的表现类似一个关系数据库,是一个表的集合,但是这对于提供者的内部实现来说并不是必须的。
为储存文件数据,Android有一系列面向文件的APIs。更多的信息请阅读。如果你设计的提供者需要处理媒体文件如:音乐、视频,你需要一个结合表数据和文件的提供者。
为了处理基于网络的数据,使用 java.net和 android.net里的类。你可以把基于网络的数据同步到本地数据存储,如:数据库,并且以表或文件的形式提供数据。Sample Sync Adapter是一个简单的说明这种同步类型的例子。
一些关于设计提供者的数据结构的提示:
表数据需要一个“主键”列,提供者维护一个对应每行的唯一的值。你可以使用这个值来联系其它表的相关行(作为“外键”)。尽管你可以给这一列起任何名字,但最好还是叫BaseColumns._ID 。因为,把提供者的查询结果提供给一个 ListView需要获取一个名字是_ID的列。
如果你想提供位图文件或其他大的面向文件的数据,把数据存储在一个文件里并且直接提供而不是通过表提供。如果你这样做了,你需要告诉你的使用者,他们需要使用ContentResolver 文件函数来访问数据。
使用Binary Large OBject (BLOB)数据类型存储大小变化或数据结构变化的数据。例如,使用一个BLOB列来储存量一个protocol buffer 或JSON structure。
你可以使用BLOB来实现一个独立模式的表。这种类型的表,你定义一列作为主键、一个MIME类型的列,其它列定义为BLOB。BLOB列里的数据的意义由MIME列来指定。这样你可以在同一张表里存储不同的数据类型。合约提供者的数据表ContactsContract.Data是一个独立模式的例子。
一个内容URI是提供者标识数据的URI。内容URI包含实体提供者(它的权限)的符号名和一个指向表或文件(一个路径)的名字。可选的id指向表里的一行。 ContentProvider里每个数据访问函数有一个内容URI作为参数;你可以决定访问表、行、文件。
内容URIs的基础在Content Provider Basics里有描述。
设计一个权限 提供者有一个单一的权限作为它在Android里的内部名。为了和别的提供者避免冲突,你使用网络域名权限(反向)作为提供者的权限。因为这个建议对Android包名字总是有效,你可以定义提供者权限作为一个包名的扩展。例如,你的Android包名是com.example.
设计一个路径结构体 开发人员通常从权限创建内容URIs,追加路径来指向单个表。例如,你有两张表:tablestable1 和table2,你从之前的例子合并权限来产生内容URIs com.example.
处理内容URI的IDs 按照约定,提供者提供通过有行ID的内容URI来访问单行,这个ID在URI的最末尾。提供者把这个ID值和表_ID匹配,对匹配的行,执行访问请求。
这个约定有利于为访问提供者设计一个通用的设计模式。应用程序从提供者里查询数据,并且使用 CursorAdapter在ListView 里显示Cursor。CursorAdapter 的定义需要Cursor里有一行是 _ID。
用户点击一行来查看或者修改数据。应用程序从ListView到Cursor获取响应,为这行获取_ID值,附加到内容URI,发送给提高者。提供者为扩展行做查询或修改操作。
内容URI模式 为了帮助你给一个内容URI选择对应的操作方式,提供者的API包含一个类,这个类把内容URI“模式”映射到一个整型数。你在语句里使用整型数来选择内容URI或URIs的匹配模式。
一个内容URI模式匹配内容URIs使用宽字符:
*:匹配任何值、任何长度的字符串。 #:匹配任何长度的数字字符串。
一个设计和编码内容URI处理的例子,考虑一个有com.example.app.provider权限的提供者,承认如下的内容URIs来指向表:
content://com.example.app.provider/table1: A table called table1. content://com.example.app.provider/table2/dataset1: A table called dataset1. content://com.example.app.provider/table2/dataset2: A table called dataset2. content://com.example.app.provider/table3: A table called table3.
提供者也识别附加在内容URIs后的行ID,例如:content://com.example.app.provider/table3/1 表示表table3里的行1。以下的内容URI模式都是可以的:
content://com.example.app.provider/* 匹配提供者的任何内容URI。 content://com.example.app.provider/table2/* 匹配表dataset1 和dataset2的内容URI,不匹配表table1 或table3的内容URI。 content://com.example.app.provider/table3/# 匹配table3表里的单行内容URI,如: content://com.example.app.provider/table3/6 指示了行6。下面的代码段展示了UriMatcher 的函数如果工作。代码处理实体表的URIs不同于单行的URI,使用 content:/// 模式处理表,content:// / / 处理单行。
addURI()函数把权限和路径映射到一个整型数。函数返回一个URI的整型数。一个switch 语句在查询实体项和查询单个记录:
这里有代码,还没有添加 其它类,ContentUris提供处理内容URI的id的函数。类Uri 和Uri.Builder包含解析存在的Uri 类和构建一个新的。
ContentProvider 实例管理其它应用程序请求并且访问结构化的数据集。所有的访问形式本质是调用ContentResolver,调用ContentProvider 的具体函数来访问。
请求函数 你在子类里需要实现抽象类ContentProvider 定义的6个抽象函数。这些函数中,除了onCreate() 外,其它应用程序调用函数来访问你的提供者。
query() 从提供者获取数据。通过参数来选择查询的表,返回行或列,对结果排序。以对象Cursor 的形式返回数据。
insert() 向提供者插入新行。使用参数选择表,获取使用的列值。返回一个新插入行的内容URI。
update() 更新存在的行。使用参数选择表,更新行,获取更新的列值。返回更新的行的数量。
delete() 删除行。使用参数来决心删除的表和行。返回删除的行的数量。
getType() 返回提供者内容的MIME类型。函数在 Implementing Content Provider MIME Types一节里有详细描述。
onCreate() 初始化提供者。Android系统在创建提供者之后就立即调用这个函数。注意,提供者并没有等到ContentResolver 对象需要访问才创建。
注意:这些函数和同名函数ContentResolver 有相同的签名。
你实现这些函数的时候需要做如下考虑:
这些函数,除了onCreate()外,需要考虑线程安全。多线程的知识,阅读Processes and Threads。
避免在onCreate()里做长时间的操作。延缓初始化任务,直到需要的时候。阅读Implementing the onCreate() method 一节。
尽管你需要实现这些函数,你的代码也可以不做任何事情。例如,你想阻止其它程序插入数据,你可以忽略insert()函数返回一个0。
实现查询函数query()
ContentProvider.query()函数必须返回一个Cursor 对象,除非执行失败,抛出异常Exception。如果你使用SQLite数据库存储数据,你可以使用 SQLiteDatabase的函数 query()来返回Cursor 对象。如果没有匹配的行,返回一个Cursor 实例,它的getCount()函数返回值为0。如果在查询中出现内部的错误返回null。
如果你不用和数据库存数据,可以使用Cursor类的子类。例如,MatrixCursor 类实现一个行数据是数组的Object。使用addRow()来添加新行。
Android系统进程间通信可以使用异常。Android在处理查询错误时,以下异常十分有用。
IllegalArgumentException如果你的提供者收到一个无效的内容URI抛出这个异常)
NullPointerException
实现insert()函数
insert() 函数向合适的表里添加行,在ContentValues 参数里设置值。如果ContentValues 里没有行名,提供者使用代码里或者数据库框架里的默认值。
这个函数返回新行的内容URI。使用 withAppendedId()函数,附加新行的_ID (或其它主键)到表内容URI。
实现delete()函数
delete()函数没有物理地删除一行。如果你使用同步适配器,你需要把删除的行标记为“delete”而不是移除行实体。同步适配器会检查删除的行,在提供者上删除它们之前会先在服务上移除。
实现update()函数
update()函数使用 insert()函数相同的ContentValues参数,同delete() 、ContentProvider.query()函数使用相同的selection和selectionArgs 参数。这样就可以允许你在这些函数之前使用相同的代码。
实现onCreate()函数
Android系统在提供者启动的时候调用 onCreate()。函数内你需要快速初始化,延迟数据库的创建和数据加载,直到提供者收到数据请求。如果你在 onCreate()里做长时间的任务,就会减慢提供者的启动。也就会减慢对其它应用程序的响应。
例如,你在函数ContentProvider.onCreate()里创建一个新的SQLiteOpenHelper对象来使用数据库,在打开数据库的时候创建表。这样的话,你第一次调用getWritableDatabase(),自动调用函数SQLiteOpenHelper.onCreate() 。
以下代码说明函数 ContentProvider.onCreate()和SQLiteOpenHelper.onCreate()的相互调用。第一段实现ContentProvider.onCreate():
这里有代码,还没有添加
以下代码实现 SQLiteOpenHelper.onCreate(),包括一个帮助类:
这里有代码,还没有添加
ContentProvider 类有两个函数返回MIME类型:
getType() 你需要实现的请求函数。
getStreamTypes() 如果你的提供者提供文件,你需要实现这个函数。
表的MIME类型 getType() 函数返回一个MIME格式的String (字符串),这个字符串描述内容URI参数返回的数据类型。 Uri 参数可以是一个模式或一个指定的URI;这种情况下,你可以返回和内容URI匹配的模式的数据类型。
通常的数据类型是text、HTML、JPEG, getType() 返回标准的MIME类型。标准类型的信息信息在网站:IANA MIME Media Types 。
指向行或表数据的内容URIs,函数返回一个供应商指定的MIME格式的Android MIME类型:
类型部分:vnd
子类型部分;android.cursor.item/
如果URI是单行:android.cursor.dir/
如果URI是多行:提供者指定部分:vnd.
你提供
例如:一个提供者的权限是com.example.app.provider,它的表名是table1,intable1 表里多行的MIME类型是:
vnd.android.cursor.dir/vnd.com.example.provider.table1
table1表的单行,MIME类型是:
vnd.android.cursor.item/vnd.com.example.provider.table1
文件的MIME类型
如果你的提供者支持文件,实现函数getStreamTypes()。这个函数返回一个文具店MIME类型的字符串,你的提供者可以返回一个给定内容URI。你可以通过MIME类型的过滤参数来过滤MIME类型,你可以返回客户端想要处理的MIME类型。
例如,提供者支持 .jpg, .png, 和 .gif 格式的图片文件。应用程序使用过滤字符串 image/*(一张“图片”)调用ContentResolver.getStreamTypes() ,函数 ContentProvider.getStreamTypes()将返回如下数组:
{ "image/jpeg", "image/png", "image/gif"}
如果应用程序仅仅需要文件 .jpg,它调用ContentResolver.getStreamTypes()的时候使用过滤字符串*\/jpeg。
ContentProvider.getStreamTypes()函数将返回:
{"image/jpeg"}
提供者不支持过滤字符串里的任何MIME类型的文件,getStreamTypes()函数返回null。
合约类是一个定义URIs、列名、MIME类型、其它属于提高者的元文件常量的类public final 。类建立一个在提供者和应用程序之间的合约,以此确定提供者可以正确的访问URIs、列名等等的实际值。
合约类通常使用语义的名字来命名常量,以此帮助开发人员,这样开发人员很少错误的使用列名或URIs。虽然它是一个类,但是他可以包含文档。集成开发环境如:Eclipse,可以自动完成常量名,并且显示相关文档。
开发人员可以访问类的文件,但是,他们也可以使用文件.jar来静态编译。
合约类的例子,如:ContactsContract类和它的嵌套类。
关于Android系统的权限和访问在 Security and Permissions有详细描述。Data Storage 一节也描述如何高效的处理不同介质的安全和权限。简短的说,重点有:
默认情况,数据存储于设备内部,对于应用程序和提供者都是私有的。
你创建的SQLiteDatabase 数据库对提供者和应用程序来说是私有的。
默认把数据存储到外部介质,文件是公有的并且是全局可读的。你不可以使用提供者来限制文件的访问,因为,其它应用程序可以使用API来读写文件。
在设备内部存储介质上打开或创建文件、数据库的函数默认对其它应用程序来说有读写的访问权限。如果你使用内部文件或数据库作为提供者的权限,你给它“全局读”和“全局写”的权限,在提供者的manifest文件里设置的权限不保护你的数据。内部储存介质的文件、数据库访问是私有的,你的提供者的权限你不需要修改。
如果你使用提供者来控制数据访问,你需要把数据储存于内部文件、SQLite数据库、“云端”(例如:远程服务器),你需要保持数据对你的提供者私有。
实现权限
所有的程序可以读写你的提供者,甚至数据是私有的,因为提供者默认没有权限集。在manifest文件里设置提供者的权限,使用
在manifest文件里使用一个或多个
一下列表描述提供者权限的作用域,作用域从提供者开始,之后粒度更小。小粒度的权限优先于大粒度的作用域:
单个读写提供者级权限 对实体提供者的读写权限,通过
分开读写提供者级的权限 实体提供者的读写权限。通过
路径级权限 提供者的内容URI的读、写、读写权限。使用
临时权限 允许应用程序访问的临时权限,甚至应用程序根本没有通常的访问权限。临时访问特性可以减少程序在manifest文件里的权限数量。你可以打开临时权限,只有需要连续访问数据的程序才需要“永久”权限。
考虑你需要实现一个email提供者的权限和应用程序,当你想允许外部的图片显示程序来显示你的图片附件。给图片显示程序访问权限,为图片的内容URI建立临时的权限。设计你的email程序使用户可以显示图片,程序发送一个包含图片内容URI和权限标志的intent给图片显示程序。图片显示程序可以查询你的email提供者并且获取图片,虽然图片显示程序没有读权限。
通过设置
属性值决定提供者的多少访问权。如果属性设置为true,系统获取你的提供者的临时权限,重载任何你的提供者的提供者级或路径级权限。
如果标志设置为false,你需要在
代表临时访问应用程序,一个intent需要包含FLAG_GRANT_READ_URI_PERMISSION 或FLAG_GRANT_WRITE_URI_PERMISSION标志,或者两者都包含。通过函数 setFlags()设置。
如果android:grantUriPermissions属性没有设置,默认就是false。
就像Activity和Service组件一样,ContentProvider 的子类也需要在manifest文件里定义,使用
Android系统获取如下信息:
权限(android:authorities) 系统里的符号名标识提供者。这个属性的描述在 Designing Content URIs一节里。
提供者类名(android:name) 实现 ContentProvider的类名。这个类的更多信息在Implementing the ContentProvider Class。
权限 为其它应用程序指定的访问提供者数据的权限:
android:grantUriPermssions: 临时权限。 android:permission: 单提供者全局读/写权限。 android:readPermission: 提供者全局读权限。 android:writePermission: 提供者全局写权限。
权限和属性的细节描述在Implementing Content Provider Permissions.一节里。
启动和控制属性 这些属性决定Android系统什么时候、如何启动提供者,处理提供者的特性,及其它运行时设置:
android:enabled: 允许系统启动提供者。 android:exported: 运行其它应用程序使用提供者。 android:initOrder: 提供者启动的命令,在同一个进程里和其它提供者的关系。 android:multiProcess: 允许系统在同一个客户端的同一个进程里启动提供者。 android:process: 运行提供者的进程名。 android:syncable: 提供者数据和服务器同步的标识。
在开发者文档的
信息属性
提供者的可选icon和标签:
android:icon::提供者的可绘的icon资源。inSettings > Apps > All在程序列表的提供者标签下。 android:label: 描述提供者或数据的信息的标签。程序列表的标签里。
所有属性的细节在开发者文档的
程序通过intent访问内容提供者。程序不使用或的函数。它发送一个intent来启动Activity,这个Activity是程序的一部分。目标Activity负责获取或显示数据。依赖intent的行为,目标Activity也提示用户修改数据。一个intent也会包含“扩展”数据,Activity会它们显示在UI上;用户在修改数据前选择改变数据。
你想使用intent来确定数据集。你的提供者依赖数据插入、更新、删除记录来严格定义商务逻辑。如果这样,你的程序可以直进修改数据,这样会导致非法数据。如果你想要开发人员使用intent访问,仔细的说明。这样是为什么intent使用程序UI比在代码里试着修改数据要好。
处理输入intent来修改数据,和处理其它intent没有什么不同。学习使用intent,可以阅读 Intents and Intent Filters。