前言
content provider提供了一种访问结构化数据的方式。他通过封装数据进行安全的数据访问。content provider是跨进程访问数据的标准接口。
当你想访问content Provider的数据的时候,使用Context对象中的ContentResolver对象。这个对象和content provider的一个对象进行交互。provider对象接收客户端的查询等请求,并返回结果。
如果你的应用不需要和其他的应用共享数据,那么可以不必开发content provider。但你你需要给你自己应用的客户提供搜索建议。如果你想要复制复杂的数据或者文件到另外的应用中,那么你同样需要content provider。
Android本身包含了一些content provider 例如音频,视频,图片,联系人等。他们在android.provider包的参考文档中列举着。provider使用一些限制,对其他的应用提供访问。
创建一个Content Provider
content provider 管理了一个中心数据仓库的访问。你可以用一个或者多个类来实现一个provider,同时需要在manifest文件中进行声明。其中的一个类必须继承自Content Provider类,它是你的provider和其他应用交互的接口。尽管content provider的目的是让其他的应用可以访问你的数据,但是你可以在你的应用中使用,让用户通过它来查询和修改数据。
下面是创建一个 content provider的基本的步骤,和一些api的用法。
开始构造之前
当你开始构造一个provider之前,进行下面的工作。
1.判断你是否需要一个provider。如果你需要下面的一个或者几个功能,那么你需要构建content provider。
- 你需要向其他应用提供复杂数据和文件。
- 你的用户需要从你的app中复制复杂的数据到其他的app中。
- 你需要使用搜索框架向用户提供搜索建议。
你不需要使用content provider来使用SQLite数据库,如果使用仅仅局限在你的应用当中的话。
2.确保你先阅读了Content Provider Basics.
接下来,按照下面步骤来构建Content Provider
1.给你的数据设置原始存储。content provider通过两个方式提供数据访问。
- 文件数据
数据通常都是以文件的形式,比如说照片,音频,视频。这些文件存储在你的应用私有空间。provider可以提供这些文件的入口,来回应从其他应用的访问这些文件的请求。
- 结构化数据
通常是以数据库、序列或者类似的结构的数据。吧哲学数据存储到拥有行和列的表的形式当中。一行表示一个实体,比如说一个人或者货物清单中的一个物体。一栏表示了这个实体的一些数据,比如说人的名字或者物品的价格。常用的存储种数据的方式就是SQLite数据库,当然也可以使用其他类型的和持续化存储。更多的android平台的存储类型,可以参见Design Data Storage一节。
2.定义ContentProvider类的具体实现,以及实现里面的方法。这是你的数据和android中其他部分进行交互的接口。更多的关于这个类的说明,参见Implementing the ContentProvider Class一节。
3.定义provider的职权字符串,contentURI,和栏目名称。如果你想要provider所在的应用处理intnet,那么定义intent的actions,extra data,以及标志位。同样要定义要访问你的provider需要的权限许可。你应该把所有的值定义到不同的协议类当中,并把协议类暴露给其他开发者。更多content URI的内容可以参见Designing Content URIs,更多intent内容可以参见 Intents and Data Access。
4.添加其他的可选的部分。比如示例数据或者一个AbstractThreadedSyncAdapter的实现。这个类用来同步provider和云端数据。
设计数据存储
content provider是一个提供格式化数据存储的接口。在创建接口之前,必须要先决定怎样存储数据。你可以使用你任何想用的存储形式,然后设计必要的数据读写接口。
下面是一下可用的android的数据存储技术。
- android系统包含一种SQLite数据库api,android自己的provider可以使用它来存储面向表哥的数据。SQLiteOpenHelper类可以帮助你创建数据库,并且SQLiteDataBase类是访问数据库的基础的类。
记住,你不必要使用数据库来构架你的数据仓库。一个provider从外表上就如同一个表的集合,类似一个关系型数据库,但在provider的内部没有必要这么做。
-android有很多的面向文件的存储api来存储文件数据。关于文件存储的更多内容可以阅读Data Storage。如果你的provider想要提供媒体有关的数据,比如音乐和视频,你可以创建一个表数据和文件数据结合的provider。
-如果要使用基于网络的数据,那么使用包java.net和android.net。你可以把基于网络的数据同步到本地,比如数据库,然后以数据库或者文件的形式呈现它们。Sample Sync Adapter就展示了这种类型的同步。
数据设计的注意事项
下面是设计你的provider的数据结构的一些提示:
- 表类型数据一定要有一个主键 primary key,这样provider可以为每一行维护一个独有的数值。你可以使用这个值来关联这个表中的一行数据和其他表中的一行数据(作为外键)。尽管你可以给这一栏其任意的名字,但是最好还是叫做BaseColoums._ID是最好的,因为在连接provider的检索结果和listView的时候需要检索一样名为_ID的栏目。
- 如果你要存储位图或者其他的大块文件在provider里面,吧这些数据存储成文件然后间接地提供访问,而不要直接把它们存到表里面。同时你需要告诉用户使用ContentResolver的文件方法来访问数据。
- 使用Binary Large OBject(BLOB)数据类型来存储变长数据或者变结构数据。比如说使用BLOB栏来存储协议缓存类型(protocol buffer)或者JSON 结构。
你也可以使用BLOB来构建一个模式独立的表。在这种表里面,你需要定义一个主键,一个MIME格式的栏目,其余的都使用BLOB类型。BLOB栏目中的数据的意义在MIME类型中指示。这可以让你在同一个表中存储不同类型的数据。Contacts Provider中的数据表ContactsContract.Data就是一个模式独立的表的例子。
设计Content URI
content URI是在provider当中指明数据的一个URI。Content URI中包括了一个整个provider(又叫职权authority)的符号名称和一个指向表说着文件的名字(又叫路径)。可选的id部分指向一个表中的行。每一个ContentProvider的方法都要接受一个content URI作为参数,这让你可以分辨要访问的表,行或者文件。
content URI的基础在 Content Provider Basics中进行讨论。
设计一个职权authority
一个provider通常有一个authority,担任Android内部的名字。为了避免和其他的provider冲突,你应该使用互联网域名的倒叙作为你的provider的authority的基础。对于android的表明也是这样建议的,所以你可以把provider的名字定义为android中包含provider的包的名字的拓展。例如如果你的android 的包名是com.example.<appname>,你应该使用的authority名为com.exmaple.<appname>.provider.
设计一个路径结构
开发者通常在authority的基础上添加路径来构造指向表个体的content URI。例如,如果你拥有两个表table1和table2,那么按照上面说的规则就可以命名content URI为com.example.<appname>.provider/table1和com.example.<appname>.provider/table2。路径不仅仅局限于一个片段,并且不必path的每一层都有一个表。
处理 content URI的ID
按照惯例,provider通过接受一个content URI,其末尾附加了ID值,来提供对于表中的一行的访问。同样,通常provider将ID的值和表中的_ID栏目相对应,并且基于对应的行来处理请求。
这个惯例有利于一个通常的访问provider的一个模式。app查询provider 获得一个Cursor,并且在ListView中通过使用CursorAdapter来显示。CursorAdapter的顶用中需要一个Cursor中有一栏的为_ID。
用户从现实的UI中选择要修改或者查看的行。应用则获取到ListView背后的Cursor中对应的行,获取其中的_ID值,把它加到content URI当中,并给provider发送这个访问请求。接着provider就可以查询或者修改用户指定的这一行了。
content URI的模式
为了帮助你选择对于content URI应该选择哪种操作,provider API包含了一个便捷的类叫做UriMatcher,它映射content URI到整形数值。你接着可以在switch语句中选择对于对应的URI进行的操作或者对应模式的一些个URI进行的操作。
content URI模式使用通配符来匹配contentURI。
- *:对应一个任何字符任意长度的字符串。
- #:对应任意长度的数字字符的字符串。
下面使用一个authority为com.example.app.provider的uri来作为例子,URI对应着怎样的表,来说明设计和编码处理content 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的表。
provider同样可以识别包含行ID的URI。例如 content://com.example.app.provider/table3/1 表示的是table3中标记为1的行。
下面的content URI 模式也是有可能的:
content://com.example.app.provider/*
用来匹配provider中的任何content URI。
content://com.example.app.provider/table2/*
匹配表dataset1或者dataset2中的所有URI,但是不会匹配table1或者table3。
content://com.example.app.provider/table3/# :匹配表table3中的一个行,比如content://com.example.provider/table3/6 对应的就是行“6”。
下面是一个展示UriMatcher 怎样工作的代码片。这段代码处理整个表格的URI而不是URI对应的一行,通过使用URI 模式content://<authority>/<path>对应表,以及content://<authority>/<path>/<id>对应行。
方法 addURI()吧一个authority和路径映射到一个整型值。方法match()返回URI对应的整型值。switch语句来决定是查询整个表格还是单一一条记录。
public class ExampleProvider extends ContentProvider { ... // 创建一个UriMatcher对象。 private static final UriMatcher sUriMatcher; ... /* * The calls to addURI() go here, for all of the content URI patterns that the provider * should recognize. For this snippet, only the calls for table 3 are shown.
* 这里调用addURI,来设定所有provider要识别的uri模式。这个代码片只显示table3的调用。 */ ... /* * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used * in the path
*设置table3表中的行的整型值为1。注意到这里在path里面没有使用通配符。 */ sUriMatcher.addURI("com.example.app.provider", "table3", 1); /* * Sets the code for a single row to 2. In this case, the "#" wildcard is * used. "content://com.example.app.provider/table3/3" matches, but * "content://com.example.app.provider/table3 doesn‘t.
*设定对于一个行的查询的整型值为2.这个例子里面是用了#通配符。这时候content://com.example.app.provider/table3/3会匹配。但是content://com.example.app.provider/table3不会匹配。 */ sUriMatcher.addURI("com.example.app.provider", "table3/#", 2); ... // Implements ContentProvider.query() 构造query函数 public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ... /* * Choose the table to query and a sort order based on the code returned for the incoming * URI. Here, too, only the statements for table 3 are shown.
*基于传来的的uri进行查询和排序。这里只列举table3的语句。
* */ switch (sUriMatcher.match(uri)) { // If the incoming URI was for all of table3 如果uri对应的是table3中所有的 case 1: if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; break; // If the incoming URI was for a single row 如果uri对应的是table3中的一行。 case 2: /* 如果uri对应的是一个单行,那么就会提供一个_ID字段。从URI中获取最后的path部分就是_ID字段。接着把这个值添加到查询语句的where从句中。 * Because this URI was for a single row, the _ID value part is * present. Get the last path segment from the URI; this is the _ID value. * Then, append the value to the WHERE clause for the query */ selection = selection + "_ID = " uri.getLastPathSegment(); break; default: ... // If the URI is not recognized, you should do some error handling here.如果uri没有识别,那么你需要在这里处理一些错误。 } // call the code to actually do the query }
还有一个类,ContentUris, 提供了使用content URI中的id的便捷方法。Uri类和Uri.Builder包解析存在的Uri对象和建立新对象的便捷方法。
构建 ContentProvider 类
ContentProvider通过处理来自其他应用的请求来管理对于结构化数据的访问。所有类型的访问都会调用Content Resolver,这个对象会调用Content Provider的一个具体方法类访问。
请求方法
Content Provider抽象类定义了六个抽象方法,在你实现自己的具体类的时候一定要进行实现。出了onCreate()方法以外的其他方法都是要被要获取你provider数据的客户端应用访问的。
query()
从provider当中检索数据,参数指定要查询单词表,和要返回的行和栏目,以及结果的排序顺序。以Cursor对象的形式进行返回。
insert()
向你的provider中插入新的一行。使用参数来确定要处理的表和选择的栏目。返回最新插入到行的content URI。
update()
更新你的provider中的行。使用参数来获取要处理的表,和要更新的行,已经更新的栏目数据。返回更新了的行数。
delete()
从你的provider中删除行。使用参数来确定要处理的表格和要删除的行。染回删除的行的个数。
getTyte()
返回一个contentURI对应的MIME类型。这个方法的更多参见Implementing Content Provider MIME Types。
onCreate()
初始化一个provider。android系统在你创建provider以后立即调用这个方法。要注意,只有Content Resolver试图访问时,你的provider才会被创建。
注意到这些方法和同名的ContentProvider方法有着相同的名。
你的方法实现应该注意到一下几点:
- 除了onCreate方法以外,其他的方法都有可能被很多歌线程访问。所以一定要线程安全。更多的多线程的内容参见 Process and Thread。
- 避免在onCreate中进行长时间操作。如果不是必要的话,要尽量推迟初始化操作。更多内容参见 Implementing the onCreate() method。
- 尽管你必须要实现这些方法,但是必要的任务只有在方法中返回需要的数据类型。例如你想防止其他的应用在你的表中插入数据,你可以忽略insert的调用,只返回0。
构造query()方法 Implementing the query() method
ContentProvider.query()方法要么成功返回一个Cursor对象,要么失败抛出异常。如果你的provider使用的是SQLite那么你可以直接返回数据库查询以后返回的Cursor对象。如果你的查询没有匹配到任何的行,你需要返回一个getCount方法返回为0的Cursor对象。只有查询中发生内部错误的时候,你应该返回null。
如果没有使用SQLite作为你的数据存储,那么你应该使用Cursor的一个具体子类。例如使用MarixCursor,它的每行是一个Object对象构成的数组。使用这个类的时候,用addRow()方法来添加新行。
记住,Android系统一定要能跨越进程来和Exception进行通信。android可以处理下面对于处理query错误有帮助的异常。
- IlleagalArgumentException(如果你的rprovider接收到了一个不合法的content URI你可以抛出这个)
- NullPointerException
构建插入insert()方法
insert方法使用Content Values的参数值来添加对应表中的行。如果某个栏目的名字在ContentValue里面没有,你可以在provider的代码或者数据库的模式中提供默认的值。
这个方法要返回插入的行的content URI。构建这个URI要把新行的_ID(或者是其他的主键)插入到表的content URI当中,使用withAppendedId()方法。
构建delete()方法
delete方法未必要真的从你的存储设备进行物理上的删除。如果你的provider使用了sync adapter,你应该考虑在这一行上标记上delete的标志位,而不是彻底 删除一行。sync adpter可以检查删除了的行,并再从provider删除之前,把它们从server中移除。
构建update()方法
update方法使用和insert方法相同的ContentValue参数,使用和delete方法和ContentProvider方法相同的selection从句和从句参数。这一让你在不同方法之间复用代码。
构建onCreat()方法
Android系统在启动provider的时候会调用onCreate方法。你应该只在里面进行执行快的初始化任务,而吧数据库初始化和数据加载推迟到街都到访问请求的 时候。如果你方法中进行长时间操作,会降低你的provider的启动速度,这会让你provider响应其他应用的速度变慢。
例如,你可以在ContentProvider.onCreate当中创建一个SQLiteOpenHelper对象,当你第一次打开数据的时候在创建SQL表。你第一次调用getWritableDatabase()的时候,他就会自动调用SQLiteHelper.onCreate方法。
下面两个代码片显示了ContentProvider.onCreate和SQLiteOpenHelper.onCreate()之间的交互。第一个代码片是ContentProvider.onCreate()的构造。
public class ExampleProvider extends ContentProvider /* * Defines a handle to the database helper object. The MainDatabaseHelper class is defined * in a following snippet.定义处理数据库的数据库帮助对象,数据库帮助对象在第二代码片中定义。 */ private MainDatabaseHelper mOpenHelper; // Defines the database name private static final String DBNAME = "mydb"; // Holds the database object private SQLiteDatabase db; public boolean onCreate() { /* * Creates a new helper object. This method always returns quickly. * Notice that the database itself isn‘t created or opened * until SQLiteOpenHelper.getWritableDatabase is called
创建一个帮助者的对象,这个方法返回的很快。在这个里面数据库本身没有创建或者打开,只有在SQLiteOpenHelper.getWritableDatabase()被调用的时候才会创建。
*/ mOpenHelper = new MainDatabaseHelper( getContext(), // the application context DBNAME, // the name of the database) null, // uses the default SQLite cursor 1 // the version number ); return true; } ... // Implements the provider‘s insert method构建provider的插入方法 public Cursor insert(Uri uri, ContentValues values) { // Insert code here to determine which table to open, handle error-checking, and so forth 这里的代码是判断哪个表被插入,以及错误检查等等 ... /* * Gets a writeable database. This will trigger its creation if it doesn‘t already exist. *获取一个可写的数据库,如果不存在则会触发创建。 */ db = mOpenHelper.getWritableDatabase(); } }
下一个代码片是SQLiteOpenHelper.onCreate的构建,包含下面的帮助类。
... // A string that defines the SQL statement for creating a table private static final String SQL_CREATE_MAIN = "CREATE TABLE " + "main " + // Table‘s name "(" + // The columns in the table " _ID INTEGER PRIMARY KEY, " + " WORD TEXT" " FREQUENCY INTEGER " + " LOCALE TEXT )"; ... /** * Helper class that actually creates and manages the provider‘s underlying data repository.这里面真正管理数据仓库的创建和管理。 */ protected static final class MainDatabaseHelper extends SQLiteOpenHelper { /* * Instantiates an open helper for the provider‘s SQLite data repository * Do not do database creation and upgrade here.这里初始化provider sqlite数据仓库的helper,不要在这进行数据库创建和更新。 */ MainDatabaseHelper(Context context) { super(context, DBNAME, null, 1); } /* * Creates the data repository. This is called when the provider attempts to open the * repository and SQLite reports that it doesn‘t exist.这里创建数据仓库,在provider要打开数据仓库而数据仓库还没创的时候调用这个方法。 */ public void onCreate(SQLiteDatabase db) { // Creates the main table db.execSQL(SQL_CREATE_MAIN); } }
构建ContentProvider MIME类型
Content Provider有两种可以返回MIME方法的类型。
getType()
对于任何Provider你都需要实现的方法。
getStreamTypes()
如果你的provider提供文件访问那么要实现这个方法。
表的MIME类型
getType()方法返回一个MIME格式的字符串,其中描述了contentURI参数的类型。Uri参是可以是一个特定的类型,而不一定是一个特定的URI,你返回的数据类型应该跟content URI中的模式相对应。
对于通常的数据类型,比如文本,HTML,或者JPEG,getType()方法应该返回那种数据的标准MIME类型。在 IANA MIME Media Types网站上有所有的可用的标准类型。
对于指向表中的一行或者多行的content URI,getType()可以返回一个Android供应商特定形式的MIME类型:
- 类型部分; vnd
- 子类型部分
* 如果URI模式对应的是一个单独的行:androd.cursor.item/
* 如果URI模式对应的多行:android.cursor.dir/
- Provider指定的部分:vnd.<name.<type>
你类提供<name>和<type>。<name>需要保证是全局惟一的,并且<type>值应该是对于特定的URI模式应该是惟一的。把公司名或者你的你的应用的android 包名的一部分作为<name>部分是一个不错的选择。<type>的值最好是可以指定和URI关联的表名。
例如一个provider的authority是com.example.app.provider,它暴露的一个表的名叫做table1,对于table1中多行MIME类型是:
vnd.android.cursor.dir/vnd.com.example.provider.table1
对于table1中的一行,MIME类型是:
vnd.android.cursor.item/vnd.com.example.provider.table1
文件的MIME类型
如果你的provider中包含文件,那么需要实现getStreamTypes()方法。这个方法返回一个String 数据,来指明你的provider接到content URI可以返回的MIME类型。你应该用MIME类型过滤器参数来过滤你提供的MIME类型,这样你就只会返回客户端想要处理的MIME类型了。
例如,如果你的provider可以提供图片格式的访问,以.jpg,png,gif三种格式。如果你一个应用调用Content Resolver的getStreamTypes方法,使用一个过滤器参数image/*,那么这个方法应该返回的数组就是:
{ "image/jpeg", "image/png", "image/gif"}
如果这个请求app只对.jpg文件感兴趣,那么它可以在调用方法的时候传入过滤器字符串 *\/jpeg,接着ContentProvider.getStreamType方法会返回:
{"image/jpeg"}
如果您的provider里面没有任何过滤器参数中的类型,那么应该返回null。
构建一个协议类Contract Class
协议类是一个public final类型的类,其中包含了provide中的 URI,栏目名,MIME类型,以及其他的可变数据的常量定义。这个类在provider和其他应用之间建立起了一个协议,保证了即使URI的值,或者栏目名以及其他项目名有变化,可以正常地访问provider。
协议类对于开发者同样有帮助,因为其中吧常量赋予了一些容易记忆的名字,所以开发者不容易弄错栏目名或者URI。因为他是一个类,所以其中包含了java doc文档。在集成开发环境如Android Studio当中,协议类中的常量可以自动补全,并且可以自动显示文档。
开发者不能直接方粉你的常量类的类文件,但是可以使用你提供的jar文件把它们静态编译到他们的应用当中。
ContactsContract和它的内部类就是协议类的例子。
构建ContentProvider的权限
android系统各个方面的许可和访问请参见 Security and Permissions。Data Storage一节中同样表述了安全和许可在多种类型存储上面的的影响。简而言之,重点就是:
- 默认情况下,在你设备的内部存储空间的数据对于你的应用和provider都是私有的。
- SQLiteDatabse你创建的数据库对已的应用和provider而言是私有的 。
- 默认而言,你存储到外存储设备的的文件都是共有的,全球访问的。你无法使用你的content provider来限制对于外部存储中的问价的访问,因为其他应用可以使用它们api来对其进行读写。
- 调用打开或者创建你内部存储中的文件或者或者数据,也潜在地给了其他应用读写他们的权限。如果你使用了内部的文件或者数据作为你的provider数据仓库,那么你要是使用了word-readable或者word-writable的访问,那么你通过清单文件添加的权限就无法保护你的数据。默认的内部存储的文件和数据库是私有,为了你的你的数据仓库,你不应该修改它。
如果你想使用content provider来控制数据的访问权限,那么你应该在内部存储中存储文件或者数据库,或者是云端,比如远程服务器,你应该保证文件和数据库对于你的应用是私有的。
构建许可权限
因为默认情况下你的provider没有设置访问权限,尽管你的数据是私有的,但是所有的应用都可以读写你的provider。你应该在你的清单文件中使用<provider>元素的属性或者子元素来添加provider权限。你可以设置适用于你整个provider的权限,也可以只设定一个表格或者一条记录的权限,或者三者都有。
使用<permission>一个或者几个,来定义你的provider的权限。为了保证权限对于你provider是独有的,使用androd:name属性,用java风格的作用域。例如,命名读权限为com.example.app.provider.permission.READ_PROVIDER。
下面列举一些权限作用域,从整体provider作用域到更小的粒度的作用域。小粒度的作用域有更高的优先权。
单独的读写provider级别的权限:
这是一个同时赋予provider级别的读写权限的许可权限。用<provider>元素的android:permisison属性进行声明。
读写分离的provider级别权限:
一个读和写的provider级别的权限。使用 <provider>的android:readPermission 或者android:writePermission属性进行声明。他们比android:permission的属性优先级高。
Path级别的权限:
读、写,或者读写同时的,作用域provider的contentURI的一个权限。声明一个你想要限制的URI的<provider>元素下的<path-permission>子元素。对已每一个你的声明的URI权限,可以死读,写,或者读写,都有。读或写权限比读写权限的 优先级更高。同时path级别的优先级比provider级别的优先级高。
临时权限;
一类赋予要访问应用临时权限的授权,即使这个应用没有provider声明的权限。这种权限减少了一个应用必须要在清单里声明的权限的数量。当你使用临时权限的时候,拥有provider永久访问权应用就成为了你获取全部数据的方式。
考虑你要做一个邮件的provider和app,你想让外部的图片显示器可以显示provider中的图片附件。想赋予图片浏览器临时的访问权限而不是授权,就连理临时权限的图片的contentURI。设计你的app,使得你的应用在用户想要显示图片的时候,就返回一个普片的content URI和图片标志位给图片浏览器。图片查看器就可以从你的provider检索图片了,尽管它没有访问你provider的权限。
要开启临时访问权限可以使用<provider>标签的android:grantUriPermisson属性或者使用<grant-uri-permission>的子标签。当你想要移除provider中的content Provider的权限的时候,你必须调用Context.revokeUriPermission()方法,这样这个content URI就只被赋予了一个临时的权限。
这个属性的值决定了你的provider多大程度上 可访问。如果属性被设置为真,那么系统就会赋予整个provider临时权限,覆盖其他所有生命的provider级别或者path级别的权限。
如果属性被设置为false,那么你需要添加<grant-uri-permission>子元素在你的<provider>元素中。子元素生命了对于content URI的临时访问权。
想要把临时访问权托管给一个应用,一个intent中一定要包含FLAG_GRANT_READ_URI_PERMISSION,或者FLAG_GRANT_WRITE_URI_PERMISSION标识,或者两者都有。使用setFlags方法添加。
如果没有android:grantUriPermission的属性那么默认就是false。
<provider>元素
如同activity和service一样,content provider的子类也必须要在清单中使用<provider>元素声明。Android系统从标签中可以获取如下的信息:
职权Authority(android:authorities):
指定系统中整个provider的符号名。在Desining Content URI中有更多的该属性的说明。
Provider的类名(android:name):
实现了ContentProvider的类名。在Implementing the ContentProvider类中有更多的说明。
Permissions权限:
指定了其他要访问provider的应用需要具有的权限。
- android:grantUriPermissionis:临时选线标识
- android:permisson:provider全体的独体读写权限。
- android: readPermisson:provider全体的读权限。
- android:writePermission:provider全体的写权限。
权限和对应的属性参见 Implemeting Content Provider Permssions.
建立和控制属性:
这些属性决定了启动provider的方式和时间,provider的进程特点以及其他运行时的设置。
- android:enabled: 允许操作同启动provider
- android: expert:允许他的应用访问provider
- android: initOrder:和同一个进程中的其他provider相关,provider的启动顺序。
- android: multiProcess:允许系统在调用客户端的进程中启动该进程。
- android:process:provider运行的进程名。
-android:syncable:标识了provider当中的数据要和服务器进行同步。
这些属性在<provider>元素的文档中有详细说明。
报告性属性:Information attribute
一个provider的可选的图标和标签。
- android:icon:一个provider图标的drawable资源。在Setting-》 App-》 All中挨着provider标签的那个。
- android:label:描述provider的数据或者它本身或者二者都有的信息标签。label出现在Setting-Apps-All列表中。
在<provider>标签文档中有详细说明。
Intent 和数据访问
应用可以间接地使用intent来访问content provider。应用不会调用任何Content Provider或者Content Resolver方法。取而代之的是,它使用intent启动了一个provider所在应用的activity。这个目标activity负责检索和现实provider当中的数据。依靠intent的action,目标activity可以提示用户来进行provider中的数据修改。intent中同时可以包含一下extra,供目标activity在UI上显示。用户可以选择在这些数据修改provider之前,添加自己的对数据的修改。
你可能希望使用intent访问来保证数据完整性。你的provider可能依据业务逻辑进行数据的插入,更新和删除。这样的话如果其他应用直接修改你的数据可能会导致不合法的数据。如果你想让开发者使用intent访问,确保有彻底地文档注释。解释给他们为什么使用你的应用的UI进行数据的修改要好于试图用他们的代码直接修改数据。
处理要修改你provider的intent和处理其他的intent类似。可以参见 Intent and Intent Filters。