Content Provider管理中央存储库的数据的访问, 你在Android程序中实现一个或者多个Provider, 连同清单文件中的元素. 其中一个类实现了ContentProvider子类, 这个子类是你的Provider和其余程序之间的接口.尽管Content Provider意味着让数据对其余程序可见, 固然, 你也能够在你的程序里, 让用户查询和修改由Provider管理的数据.java
这个主题的其余部分是建立Content Provider的基本步骤以及一些API的使用.android
在你开始建立一个Provider以前, 作以下:数据库
1.肯定你须要一个Content Provider. 若是你须要提供一个或多个如下特征, 虚拟须要建立Content Provider:设计模式
你但愿向其余程序提供复杂的数据或者文件数组
你但愿用户从你的程序复制复杂数据到其余程序浏览器
你但愿提供用搜索引擎框架提供自定义搜索提示安全
:若是用途彻底只在你的程序内部, 你就不须要Provider去用SQLite数据库.服务器
2.若是你尚未准备好, 阅读 Content Provider Basics(Content Provider基础)学习更多关于Provider的知识网络
接下来, 遵循如下步骤来构建你的Provider:数据结构
Design the raw storage for your data. A content provider offers data in two ways:
文件数据
数据一般装入文件, 诸如图片, 音频, 或者视频. 将文件存储在你的私人空间. 做为其余程序请求你的文件的响应, 你的Provider提供一个文件的handle.
"格式化"数据
数据一般放入数据库, 数组, 或者相似结构. 将数据存储在一个表格,匹配表的行和列。一行表示一个实体, 如一我的或者一个清单条目. 一列表示实体的一些数据, 如人的名字, 或者条目的价格. 储存这种数据的常见方式是用SQLite 数据库, 但你可使用任何类型的持久性存储.
2.定义ContentProvider类的具体实现及其所需的方法。这个类是你的数据和Android系统的其他部分之间的接口。对于这个类的详细信息,请参阅 Implementing the ContentProvider Class 的部分。
3.定义Provider的受权字符串,其内容的URI,和列名。若是你想Provider的程序处理的Intent,也要定义Intent行动,额外的数据,和标志。还能够定义您将须要的应用程序要访问数据的权限。你应该考虑将全部这些值做为一个单独的contract类中的常量定义; 以后,你将这个类公开给其余开发者。有关URI的更多信息,请参阅部分设计内容的URI。欲了解更多有关意图的信息,请参阅 Intents and Data Access 。
4.添加其余可选件,如样本数据或AbstractThreadedSyncAdapter的实现, 能实现Provider和云端数据的同步
Content Provider是用结构化格式保存的数据接口。 在你建立接口以前, 你必须肯定如何存储数据。你能够将数据储存成任何形式, 而后设计接口在必要时读写数据。
这是一些Android中的一些数据存储技术:
* Android系统提供SQLite数据库的API, 用于Android本身的Provider存储面向表结构的数据。SQLiteOpenHelper类是用来访问数据库的基类。
记住, 你没必要用一个数据库去实现你的存储库。 一个Provider外在表现为一组表格, 就像关系型数据库, 可是这个并非Provider内部实现的必要部分。
对于存储文件数据, Android有多样的面向文件的APIs, 要学习更多关于文件存储的信息, 参阅Data Storage(数据存储)主题。 若是你设计的Provider 提供媒体相关的数据, 如音频和视频, 你可让Provider结合表数据结构数据和文件数据。
对于网络型数据, 用java.net和android.net的类。 你也能够将网络数据同步到本地存储, 如数据库中, 而后提供表数据或者文件。 Sample Sync Adapter示例程序展现这种类型的同步。
这里有一些建议供你设计Provider数据结构:
Table数据常有一个主键列, Provider为每行提供一个惟一的数值。你可使用这个值来联接一行到其余表中的相关行(使用它做为一个“外键”)。尽管你可使用这个列的任何名字, 使用BaseColumns_ID是个好的选择,由于联接到Provider查询一个ListView的结果, 须要一个检索列有name_ID。
若是你想提供的位图图像或其余很是大的文件型数据块, 将数据存储在文件, 而后由它间接地提供,而不是直接存储在一个表。若是你这样作,你须要告诉你的Provider的用户,他们须要使用一个ContentResolver文件的方法来访问数据。
使用二进制大对象(BLOB)数据类型存储数据,不一样大小有不一样的数据结构。例如,你可使用一个BLOB列存储protocol buffer(协议缓冲区)或JSON结构。
您还可使用一个BLOB实现一个单一模式表。在这种类型的表中,你定义一个主键列,一个MIME类型的列,以及一个或多个普通的BLOB列。在“MIME类型”列中的值表示在BLOB列中的数据的含义。这使您能够在同一表存储不一样类型的行。 Contacts Provider 的“数据”表ContactsContract.Data架构的独立表的一个例子。就是schema-independent table(单一模式表)的一个实例。
Content URI是在Provider中标识数据的URI。 Content URIs包含整个Provider的符号名称(它的签名)以及表或者文件的名称(路径)。选配的id部分指向表中的独立的行。 每一个访问ContentProvider的方法的数据以一个Content URI做为一个参数;这容许你指定表, 行以及文件的访问。
Provider一般有个单独的身份认证, 做为其Android内部名称。 为了不与其余Provider冲突, 你应该用完整的互联网域名(反转)做为你Provider认证的基础。 由于这一建议也适用Android的包名, 您能够定义provider身份认证做为包名的扩展。例如, 若是你的Android包名是com.example., 你应该设置Provider的身份认证为com.example..provider.
开发者一般在身份认证后附加指向单独表的路径的方式建立Content URIs。 例如, 若是你有两个表, table1和table2, 你结合前面的例子的认证生成Content URIs com.example..provider/table1 and com.example..provider/table2。路径不局限于单一的segment, 没有必要产生每一个级别的路径表。
按照惯例, 经过接收一个带有行ID在URI末尾的content URI, providers提供访问一个表中单独的行。按照惯例,providers匹配ID值与表的ID列,并执行对行的访问请求的匹配。
本惯例有助于为应用程序访问Provider提供通用的设计模式。应用程序针对Provider作一个查询, 而后用 CursorAdapter在 ListView中显示结果Cursor。 CursorAdapter的定义须要 Cursor中的一列做为_ID.
而后用户选择UI中显示的一行来查看或者修改数据。应用程序从由ListView支持的Cursor获取对应的行。为这行取得_ID值, 将之添加到content URI, 发送访问请求到Provider。
为了帮你选择引入的content URI采起的动做, Provider API引入了个方便的类UriMatcher。将content URI模型映射到数值。您可使用整数值在一个switch语句选择所需的行动为内容的URI或URI相匹配的特定模式。
Content URI模型用通配符匹配Content URIs:
* 匹配字符串中有效字符的长度
* 匹配字符串中数字字符的长度
涉及便携Content URI处理的例子, 考虑一个认证为com.example.app.provider的Provider, 它识别下面指向表的Content 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.
这个Provider也识别这样的Content URIs,将行号加在它们后面, 例如:
content://com.example.app.provider/table3/1 表示table3的第一行
如下的Content URI模型也适用:
content://com.example.app.provider/*
* 匹配Provider中的任意Content URI
content://com.example.app.provider/* /table2/* :
* * 匹配表中dataset1和dataset2的Content URI, 可是不匹配table1或table3的Content URI。
content://com.example.app.provider/table3/#:匹配table3中单一行的Content URI, 如:content://com.example.app.provider/table3/6表示第6行。
下面的代码段显示UriMatcher中的方法如何使用。 这段代码处理整个table的URIs, 不一样与单一行的URIs, 经过使用Content URI模型content:///用于表, content:////对于单一行。
addURI()方法映射一个authority和路径到一个整数。方法android.content.UriMatcher#match(Uri) match()} 返回URI的整数值。用switch语句选择在查询整个表或者查询一条记录。
1 |
public class ExampleProvider extends ContentProvider {... |
另外一个类,ContentUris, 提供了个便利的方法,用于处理Content URIs的id部分。类Uri和Uri.Builder包含便利的方法, 解析存在的Uri对象和建立新的。
ContentProvider实例管理经过从其余应用程序请求的处理的方式访问一个结构化的数据集。全部形式的访问ContentResolver最终调用,而后调用一个具体的方法来访问的ContentProvider。
ContentProvider的抽象类定义了六个抽象方法,您必须实现的做为你的本身的具体子类的一部分。除了onCreate(), 其余的这些方法调用一个客户程序尝试访问你的ContentProvider
query()
* 从您的Provider检索数据。使用参数选择表去查询,返回行和列,按顺序排列结果。返回数据做为Cursor对象。
insert()
* 插入一个新行到您的Provider。使用参数来选择目标表及得到的列值使用。为新插入的行返回一个Content URI。
update()
* 在你的Provider更新现有的行。使用参数选择表和行更新并获得更新的列值。返回更新的行数。
delete()
* 从你的Provider删除行。使用参数选择要删除的表和行。返回删除的行数。
getType()
* 返回Content URI对应的IME类型。这个方法在Implementing Content Provider MIME Types部分作了更详细的阐述。
onCreate()
* 初始化您的provider。Android系统一建立你的Provider就调用这个方法。注意直到ContentResolver对象试图访问时,Provider才建立。
注意,这些方法与ContentResolver中一样命名方法有相同的签名。
这些方法的实现应该遵循如下原则:
除了onCreate(), 全部的这些方法可能被多个线程同时调用,因此他们必须是线程安全的。学习更多关于多线程, 查看Processes and Threads专题
避免在onCreate()作耗时的操做。延迟初始化任务,直到他们真正须要。部 Implementing the onCreate() method章节更细致的讨论了这个问题。
尽管您必须实现这些方法,除了返回预期的数据类型,你的代码能够不作任何事。例如,您可能想要阻止其余应用程序将数据插入某些表。要作到这一点,您能够忽略该insert()调用和返回0。
ContentProvider.query()方法必须返回一个Cursor对象,或者若是它失败了,抛出一个异常。若是您使用的是一个SQLite数据库做为数据存储库,您能够简单地返回一个 SQLiteDatabase 游标query()方法的类SQLiteDatabase。若是查询不匹配任何行,您应该返回一个游标的实例源()方法返回0。若是在查询过程发生了一个内部错误, 你只须要返回null。
若是您没有使用SQLite数据库做为数据存储,使用其中一个Cursor的具体的子类。例如,MatrixCursor类实现一个cursor中的每一行当成一个数组对象。这个类,使用addRow()来添加一个新行。
记住,Android系统必须可以联系各个进程之间的异常。Android能够为下列异常进行有效的查询异常处理:
IllegalArgumentException(若是你的provider接收一个无效的content URI, 能够选择抛出这个异常)
NullPointerException
insert()方法添加一个新行到相应的表,使用在ContentValues的??参数值。若是列名不在ContentValues??参数中,你可能想在您的provider代码,或在您的数据库架构提供了它的默认值。
此方法应该返回新行的content URI。要构造这个,使用withAppendedId()追加新行的_id(或其余主键)值到表的content URI。
delete()方法实际并无从您的数据存储中删除的行。若是您使用的是同步适配器到provider。你应该考虑用一个“delete”标记标记一个被删除的行, 而不是彻底的删除。同步适配器能够检查要被删除的行,从provider删除以前,将他们从服务器中删除。
update()方法须要同insert()方法相同的ContentValues??的参数,和deleted()和ContentProvider.query()用过相同的selection 和 selectionArgs参数。这可能让你复用这些方法之间的代码。
启动Provider时, Android系统调用的onCreate()。您应该在方法中只执行快速初始化任务,并推迟建立数据库和数据加载,直到provider实际收到的数据的请求。若是你在onCreate()作冗长的任务,你将减缓您provider的启动。从而这会减慢从provider到其余应用程序的响应。
例如,若是您使用的是SQLite数据库,你能够在ContentProvider.onCreate()中建立一个新SQLiteOpenHelper对象(),而后首次打开数据库时建立SQL表。为了推进这项工做,你第一次调用getWritableDatabase(),它会自动调用SQLiteOpenHelper.onCreate()方法。
如下两个片断展现ContentProvider.onCreate()和SQLiteOpenHelper.onCreate()之间的相互做用。第一个片断是ContentProvider.onCreate()的实现:
1 |
public class ExampleProvider extends ContentProvider |
下一个片断是SQLiteOpenHelper.onCreate()的实现,包含一个辅助类:
1 |
...// A string that defines the SQL statement for creating a tableprivate static final String SQL_CREATE_MAIN = "CREATE TABLE " + |
ContentProvider类有两个方法返回的MIME类型:
getType()
* 你必须在全部的provider中实现的方法之一。
getStreamTypes()
* 若是您的provider提供文件, 你会但愿实现。
getType()方法返回一个MIME格式的String 描述内容的URI参数返回的数据类型。开放的参数能够是一个模式,而不是一个特定的URI;在这种状况下,你应该返回类型与内容的URI模式相匹配的相关数据。
对于常见的数据类型,如文本,HTML或JPEG的, getType()应返回的数据的标准MIME类型。这些标准类型的完整列表能够在IANA MIME Media Types网站上得到.
对于content URI, 指向表中的数据一行或多行, getType()应该返回一个Android的厂商特定的MIME格式的MIME类型:
类型部分:vnd
子类型部分:
若是URI模型是一个单一的行:android.cursor.item/
若是URI模式是多行:android.cursor.dir/
Provider指定部分: vnd..
您提供和。 的值应该是全球惟一的,对于对应的URI模型,值应该惟一。贵公司的名称或您的应用程序的Android包名称的某些部分是的一个不错的选择。的一个不错的选择是与URI相关联的表的标识的字符串。
例如,若是一个Provider的认证是com.example.app.provider,并将一个表名为table1,为table1中的多个行的MIME类型是:
1 |
vnd.android.cursor.dir/vnd.com.example.provider.table1 |
对于单一行的table1,MIME类型是:
1 |
vnd.android.cursor.item/vnd.com.example.provider.table1 |
若是您的provider提供文件,实现getStreamTypes()。该方法返回一个MIME类型的文件,这个文件是你的provider能够返回一个给定的content URI的String数组。你应该过滤你提供的MIME类型过滤器参数,这样你只返回那些客户端要处理的MIME类型。
例如,考虑provider提供的照片图像, 如:jpg, PNG,gif格式的文件。若是应用程序经过过滤器的字符串image/ * (这是一个“图片”)调用ContentResolver.getStreamTypes(),而后的ContentProvider.getStreamTypes()方法返回数组:
1 |
{ "image/jpeg", "image/png", "image/gif"} |
若是应用程序仅在jpg文件感兴趣,那么它能够调用过滤字符串* / JPEG ContentResolver.getStreamTypes(),ContentProvider.getStreamTypes()应返回:
1 |
{"image/jpeg"} |
若是您的provider没有提供任何过滤字符串要求的MIME类型,getStreamTypes()应该返回null。
Contract类是一个属于provider的public final类,它包含常量定义的URI,列名,MIME类型,和其余元数据。这个类创建的供应商和其余应用程序之间的contract,以确保即便有改变URI的实际值,列名,等等, provider也能够正确访问,。
Contract类能够帮助开发人员,由于它一般有助记符名称的常量,因此provider不太可能使用不正确的值的列名或URIs。由于它是一个类,它能够包含的Javadoc文档。集成开发环境,如Eclipse能够自动完成从Contract类和显示常数的Javadoc的常量名。
provider不能从您的应用程序访问的Contract类的类文件,但他们能够从您提供的.jar文件静态编译到他们的应用程序,。
ContactsContract类和嵌套类Contract类的例子。
Android系统的全部方面的权限和访问中在主题Security and Permissions进行了详细介绍。主题Data Storage还介绍了不一样类型的存储安全和有效的权限。总之,要点以下:
默认状况下,在存储设备的内部存储的数据文件对您的应用程序和Provider是私有的。
您所建立的SQLiteDatabase数据库对您的应用程序和Provider是私有的。
默认状况下,你保存到外部存储的数据文件是公开的。你不能使用一个Content Provider,以限制访问外部存储中的文件,由于其余应用程序可使用其余API读写它们。
打开或建立你内部储存设备上的文件或SQLite数据库, 调用这个方法能给其余程序读或者写他们的方法。若是你使用内部文件或者数据库做为你Provider的存储器,给它“可读”或“可写”访问。在manifest为您的Provider设置的访问权限将不能保护您的数据。内部存储器访问文件和数据库的访问权限是“private”,你的Provider的存储器不该该改变.
若是您要使用的content provider的权限,以控制对数据的访问,那么你应该将数据存储在内部文件,SQLite数据库,或“云”(例如,在远程服务器上)的数据,你应该保持文件和数据库对您的应用程序私有。
全部的应用程序能够读取或写入您的Provider,即便底层数据是私人的,由于默认状况下,你的Provider没有的权限集。要改变这种情况,在您的manifest文件中设置权限,用元素的属性或者子属性。你能够为如下状况设置权限, 适用整个provider, 或者特定的表, 或者单一的记录, 或者前面3项。
你用一个或多个mainifest文件中的元素定义你Provider的属性。为了使您的Provider有特有的限定名,使用Java风格的做用域为android:name属性。例如,指定读权限的com.example.app.provider.permission.READ_PROVIDER.
如下列表说明Provider的权限范围,最开始的适用于整个Provider,而后变得更加细化。更加细化的权限覆盖大范围的权限:
单一读写Provider级别权限
* 一个权限控制读写访问整个Provider,指定了android:权限属性的元素。
分开的读, 写Provider级别权限
* 读权限和写权限为整个Provider。您能够在元素中指定Android:readPermission和androidwritePermission属性。他们覆盖了android:permission的权限。
路径级别权限
* 读,写,或读/写权限做用与您Provider中的Content URI。你指定你想控制每一个URI与“的元素子元素。对于每一个您指定的Content URI,你能够指定一个读/写权限,读权限,写权限,或全部三个。读取和写入权限覆盖读/写权限。此外,路径级别的权限覆盖Provider级别的权限。
临时权限
权限级别授予临时访问的应用程序,即便应用程序没有一般所需的权限。临时访问功能减小了应用程序要求在其清单的权限的数量。当你打开临时权限,只有须要Provider的“永久性”的权限,不断访问你的全部数据。
考虑实施时,你要容许外部图像浏览器应用程序,以显示您提供的照片附件的电子邮件服务提供商和应用,你须要的权限。为了让图像浏览器,没有必要设立临时的权限为内容的照片的URI须要的权限,访问。设计您的电子邮件应用程序,这样,当用户要显示一张照片,应用程序发送一个含有照片的内容URI和权限标志的图像浏览器的意图。图像浏览器就能够查询您的电子邮件提供商检索照片,即便观众不正常读取为您提供的权限。
要打开的临时权限,或者设置了元素的android:grantUriPermissions属性,或将一个或多个子元素添加到您的元素。若是您使用的临时权限,你必须调用Context.revokeUriPermission()每当你从Provider删除Content URI支持,Content URI拥有一个临时的权限。
您的Provider有多少是由访问该属性的值决定。若是该属性设置为true,则系统将给予临时许可给您的整个Provider,覆盖您的Provider级别或路径级别的权限.
若是这个标志设置为false,那么您必须添加子元素到你的元素。每一个子元素指定Content URI或URI临时受权访问。
要指定临时访问权限给应用程序,意图必须包含的的FLAG_GRANT_READ_URI_PERMISSION或FLAG_GRANT_WRITE_URI_PERMISSION标志,或二者兼而有之。这些用setFlags()方法设置。
若是 android:grantUriPermissions属性没有显示,假定为false.
像Activity和Service组件同样,ContentProvider的子类必须定义在其应用的manifest文件中,使用的元素。 Android系统获得元素的如下信息:
认证(android:authorities)
* 符号名称,标识系统内的整个provider。这个属性在章节 Designing Content URIs中作了更详细的描述。
Provider类的名字(android:name)
* 实现ContentProvider的类。这个类在章节 Implementing the ContentProvider Class中作了更详细的描述。
权限
指定权限的属性,其余应用程序必须为了访问Provider的数据:
* android:grantUriPermssions: 临时权限标志。
* android:permission: 单一的provider范围读/写权限
* android:readPermission:Provider范围读权限
android:writePermission:Provider范围写权限
权限及其相应的属性在Implementing Content Provider Permissions章节中作了更详细的描述.
启动和控制属性
* 这些属性决定如何以及什么时候启动Android系统Provider,该Provider的过程特性,以及其余运行时设置:
* android:enabled:标志容许系统开启Provider.
* android:exported:标志容许其余程序使用这个Provider
* android:initOrder:Provider应该开始的顺序, 涉及相同进程中的其余Provider.
* android:multiProcess:标志容许系统做为调用客户端开启同一个进程中的Provider.
* android:process:Provider应该运行的进程的名字.
* android:syncable:标志代表Provider的数据是同服务器同步的.
这个属性是彻底记录在dev指南主题的元素。
信息属性
provider的可选icon和label
* android:icon:一个绘图资源包含Provider的图标。icon显示应用程序列表中的下一个provider标签在Setting > Apps > All.
android:label:描述provider或它们数据或二者的信息标签.这个标签的列表中显示的应用程序中 Settings > Apps > All.
这个属性在dev指南主题的元素中有完整的记录.
应用程序能够经过Intent间接的访问ContentProvider。应用程序不调用任何ContentResolver或ContentProvider的方法。相反,它发送一个启动活动的意图,这每每是Provider的本身的应用程序的一部分。目标Activity是负责在UI上检索和显示的数据。根据Activity的Intent,目标Activity也可能提示用户修改Provider数据。目标活动在UI中显示的意图,也可能包含“额外”的数据,而后在改变Provider的数据以前, 用户能够有选择性的改变数据.
你可能想使用意图访问确保数据的完整性。你的Provider可能会依赖于数据的插入,更新和删除按照严格定义的业务逻辑。若是是这样的状况下,容许其余应用程序直接修改您的数据可能会致使无效的数据。若是你想要开发者使用意图访问,确保彻底的记录它。向他们解释为什么使用本身的应用程序的UI的意图存取比用他们的代码修改数据好。
处理传入的意图修改您Provider数据的意图,处理其余的意图没有什么不一样。你能够了解更多有关使用Intent的主题 Intents and Intent Filters。