转载

Android开发-API指南-创建 Content Provider

Creating a Content Provider

英文原文: http://developer.android.com/guide/topics/providers/content-provider-creating.html

采集日期:2015-01-23

在本文中

  1. 设计数据存储形式
  2. Content URI 设计
  3. 实现 ContentProvider 类
    1. 必需实现的方法
    2. 实现 query() 方法
    3. 实现 insert() 方法
    4. 实现 delete() 方法
    5. 实现 update() 方法
    6. 实现 onCreate() 方法
  4. 实现 Content Provider MIME 类型
    1. 数据表的 MIME 类型
    2. 文件的 MIME 类型
  5. 实现 Contract 类
  6. 实现 Content Provider 权限
  7. <provider> 元素
  8. Intent 和数据访问

关键类

  1. ContentProvider
  2. Cursor
  3. Uri

相关示例

  1. Note Pad 应用

See also

  1. Content Provider 基础
  2. Calendar Provider

Content Provider 管理着数据库的访问工作。 依据 Manifest 中的定义, Provider 实现为 Android 应用中的一个或多个类。 其中一个类实现了 ContentProvider 的子类,作为连接 Provider 与其他应用程序的接口。 虽然 Content Provider 是用于向其他应用程序提供数据的,但在其所在的应用程序中,用户当然也可以通过 Activity 查询并修改 Provider 中的数据。

本文列出了建立一个 Content Provider 的基本步骤,并给出了涉及的 API。

准备工作

在建立 Provider 之前,请完成:

  1. 确定必要性。 创建 Content Provider 是为了实现以下目标:
    • 需要向其他应用程序提供复杂的数据或文件
    • 用户需要把本应用中的数据复制给其他应用程序
    • 需要利用(系统的)搜索机制提供自定义的搜索建议项

    如果只是在应用程序内部使用, Provider 不一定 要用到 SQLite 数据库。

  2. 如果目的还不确定,请阅读文章 Content Provider 基础 以便对 Provider 进行更深入的了解。

接下来,按照以下步骤建立自己的 Provider:

  1. 设计数据的的底层存储形式。Content Provider 可以用两种方式提供数据:
    文件数据
    数据通常存放在文件中,比如图片、音频、视频等。 文件存放在应用程序的私有空间中。 当其他应用程序发起访问请求时,Provider 将给出相应的文件句柄。
    “结构化”(Structured)数据
    数据通常存放在数据库、数组或类似的数据结构中。 存储格式类似于行、列组成的表格。 每行代表一个实体,比如一个人或者商品。 每列代表与实体相关的数据项,比如人的姓名或者商品的价格。 常用的存储方式是放入 SQLite 数据库中,但其实可以使用任何类型的持久性存储方式。 如果要了解 Android 系统支持的存储类型,请参阅设计数据存储形式一节。
  2. 设计自己的 ContentProvider 类,并实现必要的方法。 此类为访问数据的途径,更多信息请参阅实现 ContentProvider 类一节。
  3. 定义 Provider 的 authority 字符串、Content URI 及各数据列的名称。 如果 Provider 应用需要处理 Intent,则还要定义 Intent Action、extra 数据及标志位。 并且,还要为访问数据的应用定义权限。 建议将所有上述定义都设为常量,并定义在一个单独的 Contract 类中,这样只要把这个类公布给其他开发人员即可。 关于 Content URI 的详细信息,请参阅设计 Content URI一节。关于 Intent 的详细信息,请参阅Intent 和 Data 访问一节。
  4. 填加其他的可选部分,比如示例数据、用于将 Provider 和云端数据进行同步的 AbstractThreadedSyncAdapter 类。

设计数据存储形式

Content Provider 是为数据提供结构化访问途径的接口。 在创建这个接口之前,首先必须确定数据的存储形式。 数据可以用任何方式存储,只要设计好必要的的读写接口即可。

以下列出了一些 Android 支持的数据存储形式:

  • Android 系统内置了一套 SQLite 数据库 API,系统自带的 Provider 就用它来存储表格形式的数据。 SQLiteOpenHelper 是用于创建数据库的工具类, SQLiteDatabase

    是访问数据库的基础类。

    请记住,并不是一定要用数据库来存储数据。 Provider 向外部展现数据的形式是与关系型数据库类似的表格,但内部不一定非要这么去实现。

  • 对于存储文件数据, Android 也提供了一系列的文件操作 API。 要了解文件存储的详细信息,请参阅文章数据存储 。如果 Provider 需要支持多媒体数据,比如音频和视频文件,可以混合使用表和文件的形式来存储数据。
  • 如果要使用基于网络的数据,请使用 java.netandroid.net 中的类。 还可以先将网络端数据与本地数据库进行同步,再以数据表或文件的形式提供出来。 Sync Adapter 示例 应用就给出了这种同步方式的例子。

数据设计因素

以下列出了一些设计 Provider 的数据结构时需要考虑的因素:

  • 数据表通常应该包含“主键”, Provider 会自动维护该列,为每行数据标识一个唯一的数字值。 该值可用来与其他表中的数据行关联(即用作“外键”)。 虽然主键的列名可以任意指定,但最好还是使用 BaseColumns._ID ,因为 ListView 与 Provider 关联时要求查询结果中必须包含名为 _ID 的列。
  • 如果要提供位图文件或其他大量的文件数据,请以文件的形式存放并由 Provider 间接地提供出来,而不是直接存放在数据表中。 这时,Provider 有必要让使用方知道,需要通过 ContentResolver 的文件方法来访问数据。
  • 如果数据的大小不一,或者数据结构各异,则可以使用二进制大数据对象(BLOB,Binary Large OBject)类型来存放。 比如,可以用 BLOB 类型的字段来存储 Google 协议缓冲区(protocol buffer) 和 JSON 结构 的数据。

    还可以用 BLOB 来实现 库结构无关 (schema-independent)的表。 在这种表中,可以定义一个主键、一个 MIME 类型字段和多个 BLOB 之类的通用字段。 BLOB 字段中的数据含义由 MIME 类型字段来指明。 这样,就可以在同一张表中存放多种不同类型的数据行了。 Contracts Provider 的 “data”表 ContactsContract.Data 就是库结构无关的。

设计 Content URI

Content URI是用于标识 Provider 数据的 URI。 Content URI 包含了整个 Provider 的名称( authority )和某个表或文件的名称( path )。 可选的 id 部分标明了表中的数据行索引。 ContentProvider 中所有的数据访问方法都包含一个 Content URI 参数,指明了要访问的表、数据行或文件。

文章 Content Provider 基础 中介绍了 Content URI 的基础知识。

设计 authority

Provider 通常都需要指定一个 authority ,用作 Android 内部名称。 为了避免与其他 Provider 冲突,请使用 Internet 域名(反向)作为 Provider 的 authority 基础。 因为这也是 Android 包(package)的命名建议,所以可在 Provider 所在包名的基础上进行扩展来定义其 authority 。 比如,假设 Android 包名为 com.example.<appname> , 则 Provider 的 authority 就可以定义为 com.example.<appname>.provider

定义 path 部分

开发人员创建 Content URi 的方式,通常是在 authority 后面添加指明了表名的 path 部分。 比如,假设已有两张表 table1table2 , 则利用上述 authority 示例生成的 Content URI 即为: com.example.<appname>.provider/table1com.example.<appname>.provider/table2 。 可以有多个 path 部分,每个 path 中并不一定包含表。

处理 Content URI ID

作为约定,通过在 Content URI 的末尾附带该行数据的 ID,Provider 提供了对单行数据的访问能力。 同样是系统约定, Provider 会把 ID 在表的 _ID 字段中进行检索,并在匹配的的数据行上执行所需的访问请求。

这样,应用程序可以按照统一的设计模式访问 Provider 中的数据。 在向 Provider 提交查询请求之后,应用程序就可以通过 CursorAdapterListView 中显示返回的 Cursor 了。 CursorAdapter 要求 Cursor 中必须存在一个名为 _ID 的字段。

然后,用户可以在界面中选择需要查询或修改的一行数据。 应用程序从 ListView 后台的 Cursor 中读取相应的数据行,得到该行的 _ID 值,并将它添加到 Content URI 后面,再把访问请求发送给 Provider 。 Provider 然后在用户选中的记录上执行查询或修改操作。

Content URI 的匹配模式

为了便于根据传入的 Content URI 选择相应的 Action, Provider API 中提供了一个工具类 UriMatcher ,该类将 Content URI 映射为整数值。 这样,就可以在 switch 语句中使用整数值来根据 Content URI 或符合匹配规则的 URI 执行所需的 Action。

Content URI 的匹配规则使用以下通配符来进行匹配:

  • * : 匹配任意长度的任意合法字符。
  • # : 匹配任意长度的数字字符。

举个例子,假定有一个 authority 为 com.example.app.provider 的 Provider, 可以识别出以下 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 的 Content URI,比如 content://com.example.app.provider/table3/1 就表示 table3 中 ID 为 的记录行。

以下都是合法的 Content URI 匹配规则:

content://com.example.app.provider/*
匹配 Provider 中的所有 Content URI。
content://com.example.app.provider/table2/*
匹配表 dataset1dataset2 的 Content URI, 但不会匹配 table1table3
content://com.example.app.provider/table3/#
匹配表 table3 中的某一条记录,比如 content://com.example.app.provider/table3/6 表示 ID 为 的记录行。

以下代码演示了如何使用 UriMatcher 中的方法。 这里分别演示了对全表和单条记录 URI 的处理过程, content://<authority>/<path> 匹配全表 Content URI , content://<authority>/<path>/<id> 则表示单条记录。

addURI() 方法将 authority 和 path 映射为一个整数值。 match() 方法将返回 URI 对应的整数值。 这里使用了 switch 语句来选择查询全表还是单条记录:

 1 public class ExampleProvider extends ContentProvider {  2 ...  3     // 创建 UriMatcher 对象。  4     private static final UriMatcher sUriMatcher;  5 ...  6     /*  7      * 在这里调用匹配全部 Content URI 的 addURI() 。  8      * 但本段代码只匹配 table3。  9      */ 10 ... 11     /* 12      * 设置 table3 全表对应整数值 1。 13      * 注意这里的 path 没有使用通配符。 14      */ 15     sUriMatcher.addURI("com.example.app.provider", "table3", 1); 16  17     /* 18      * 设置单条记录对应数字 2。 19      * 这里使用了通配符 “#”。 20      * 所以“content://com.example.app.provider/table3/3”将符合规则,而“content://com.example.app.provider/table3”就不会匹配了。 21      */ 22     sUriMatcher.addURI("com.example.app.provider", "table3/#", 2); 23 ... 24     // 实现 ContentProvider.query() 25     public Cursor query( 26         Uri uri, 27         String[] projection, 28         String selection, 29         String[] selectionArgs, 30         String sortOrder) { 31 ... 32         /* 33          * 根据 URI 对应的整数值确定查询表和排序方向。 34          * 这里只是给出了 table3 的有关语句。 35          */ 36         switch (sUriMatcher.match(uri)) { 37  38  39             // 如果 URI 对应的是 table3 全表 40             case 1: 41  42                 if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; 43                 break; 44  45             // 如果 URI 对应了单条记录 46             case 2: 47  48                 /* 49                  * 既然 URI 对应单条记录, 就应该给出 _ID 值。 50                  * 读取 URI 的 path 部分的最后一段,也即 _ID 值。 51                  * 然后把它附加到查询语句的 WHERE 条件中。 52                  */ 53                 selection = selection + "_ID = " uri.getLastPathSegment(); 54                 break; 55  56             default: 57             ... 58                 // 如果无法识别 URI,请在这里进行错误处理。 59         } 60         // 执行查询操作 61     }

ContentUris 中,提供了一些操作 Content URI 的 id 部分的方法。在 UriUri.Builder 类中,还提供了一些解析或新建 Uri 对象的方法。

实现 ContentProvider 类

ContentProvider 的实例对象负责处理其他应用程序的查询请求,由此管理着对结构化数据的访问。 各种形式的数据访问最终都要调用 ContentResolver ,再由它调用 ContentProvider 实例对象的方法来完成。

必需实现的方法

抽象类 ContentProvider 定义了6个抽象方法,在其实例子类中必须实现这些方法。 除了 onCreate() 之外,其他所有方法都会被需要访问 Content Provider 的客户端应用调用。

query()
读取 Provider 中的数据。参数中可指明表、数据行、需返回的字段及排序方向。 返回数据为一个 Cursor 对象。
insert()
在 Provider 中插入一条新记录。 参数中指定了目标表和各字段的值。 返回新插入记录的 Content URI。
update()
更新 Provider 中已有的记录。 参数中指定了表、要更新的记录、各字段值。 返回更新成功的记录数。
delete()
删除 Provider 中的记录。 参数中指定了表和要删除的记录。 返回删除成功的记录数。
getType()
返回 Content URI 对应的 MIME 类型。 该方法将在实现 Content Provider 的 MIME 类型一节中详细介绍。
onCreate()
初始化 Provider 。Android 系统将在创建 Provider 之后立即调用该方法。 请注意,在 ContentResolver 对象发起访问请求之前, Provider 是不会被创建的。

注意, ContentResolver 中存在与上述方法同名的对应方法。

上述方法的实现代码应该满足以下要求:

  • onCreate() 外,其他所有方法都有可能同时被多个线程调用,因此必须是线程安全的。 有关多线程的内容,请参阅文章进程和线程。
  • 请勿在 onCreate() 方法中执行耗时较长的任务。 请把初始化工作推迟到实际需要时执行。 在实现 onCreate() 方法一节中将会详细介绍这部分内容。
  • 虽然上述方法都是要求实现的,但在代码中可以不进行任何操作,只要返回类型合适的结果即可。 比如,为了阻止其他应用在表中插入数据,可以忽略 insert() 调用,直接返回 0 即可。

实现 query() 方法

ContentProvider.query() 方法必须返回一个 Cursor 对象,在查询失败时将会抛出一个 Exception 。如果使用 SQLite 数据库存储数据,只要用 SQLiteDatabase 类的 query() 方法直接返回一个 Cursor 即可。 如果没有找到符合条件的记录,应该返回一个 Cursor 实例,其 getCount() 方法应该返回 0。 只有当查询过程中发生内部错误时,才能返回 null

如果没有用 SQLite 数据库存储数据,请使用 Cursor 的子类作为结果返回。 比如, MatrixCursor 类实现了一个游标,其中的每行数据是由 Object 对象组成的数组。 该类使用 addRow() 来添加新的数据行。

请记住, Android 系统有能力保证 Exception 的跨进程传递。 Android 可以有效传递以下这些与查询出错相关的异常:

实现 insert() 方法

insert() 方法将在目标表中添加一条新记录,字段名称在参数 ContentValues 中给出。如果 ContentValues 中未能给出字段名称,可能需要在 Provider 代码或是数据库定义中给出缺省值。

此方法应该返回新记录的 Content URI。利用 withAppendedId() 方法,将新记录的 _ID (或其他主键)附加在表的 Content URI 之后即可实现。

实现 delete() 方法

delete() 方法不一定要物理删除记录。 如果 Provider 用于 Sync Adapter,则应考虑将删除记录标记为“delete”,而不是真的删除它。 Sync Adapter 可以感知这些删除记录,并先删除服务器端数据,再在 Provider 中进行删除。

实现 update() 方法

update() 方法的 ContentValues 参数与 insert() 相同, selectionselectionArgs 参数与 delete()ContentProvider.query() 的相同。因此这些方法就可以实现代码复用了。

实现 onCreate() 方法

Android 系统会在启动 Provider 时调用 onCreate() 方法。在该方法中,只能执行一些能够迅速完成的初始化工作,创建数据库及加载数据的工作请延至确实收到查询请求时再去执行。 如果在该方法中执行了耗时很长的任务, Provider 的启动速度将会减缓。 这会严重滞缓 Provider 对其他应用的响应速度。

比如,假定使用了 SQLite 数据库,可以在 ContentProvider.onCreate() 方法中创建一个 SQLiteOpenHelper 对象,然后在第一次打开数据库时再创建 SQL 表。 为了方便起见,第一次调用 getWritableDatabase() 时,它会自动调用 SQLiteOpenHelper.onCreate() 方法。

以下两段代码演示了 ContentProvider.onCreate()SQLiteOpenHelper.onCreate() 之间的交互过程。第一段代码实现了 ContentProvider.onCreate()

 1 public class ExampleProvider extends ContentProvider  2   3     /*  4      * 定义数据库工具类句柄。  5      * MainDatabaseHelper 类在后面的代码中定义。  6      */  7     private MainDatabaseHelper mOpenHelper;  8   9     // 定义数据库名称 10     private static final String DBNAME = "mydb"; 11  12     // 数据库对象 13     private SQLiteDatabase db; 14  15     public boolean onCreate() { 16  17         /* 18          * 新建一个工具类对象。 19          * 本方法应该尽快返回。 20          * 请注意数据库本身将在调用 SQLiteOpenHelper.getWritableDatabase 时才会被创建并打开。 21          */ 22         mOpenHelper = new MainDatabaseHelper( 23             getContext(),        // 应用程序上下文(Context) 24             DBNAME,              // 数据库名称 25             null,                // 使用默认 SQLite 游标 26             1                    // 版本号 27         ); 28  29         return true; 30     } 31  32     ... 33  34     // 实现 Provider 的插入方法 35     public Cursor insert(Uri uri, ContentValues values) { 36         // 在这里插入代码,选择要打开的表、进行差错处理等等。 37  38         ... 39  40         /* 41          * 获取一个可写入的数据库。Gets a writeable database. This will trigger its creation if it doesn't already exist. 42          * 如果数据库不存在,则会触发创建过程。 43          */ 44         db = mOpenHelper.getWritableDatabase(); 45     } 46 }

以下代码实现了 SQLiteOpenHelper.onCreate() ,其中包含了一个工具类:

 1 ...  2 // 定义建表的 SQL 语句  3 private static final String SQL_CREATE_MAIN = "CREATE TABLE " +  4     "main " +                       // 表名  5     "(" +                           // 字段名  6     " _ID INTEGER PRIMARY KEY, " +  7     " WORD TEXT"  8     " FREQUENCY INTEGER " +  9     " LOCALE TEXT )"; 10 ... 11 /** 12  * 用于创建和管理底层数据的工具类 13  */ 14 protected static final class MainDatabaseHelper extends SQLiteOpenHelper { 15  16     /* 17      * 实例化工具类,用于操作 Provider 的 SQLite 数据库 18      * 这里不要创建或升级数据库。 19      */ 20     MainDatabaseHelper(Context context) { 21         super(context, DBNAME, null, 1); 22     } 23  24     /* 25      * 创建数据库。 26      * 当 Provider 尝试打开数据库但 SQLite 却报告数据库不存在时,将会调用此方法。 27      */ 28     public void onCreate(SQLiteDatabase db) { 29  30         // 创建主表 31         db.execSQL(SQL_CREATE_MAIN); 32     } 33 }

实现 ContentProvider 的 MIME 类型

ContentProvider 类有两个方法是用于返回 MIME 类型的:

getType()
所有 Provider 都必须实现该方法。
getStreamTypes()
如果 Provider 提供文件访问的话,建议实现该方法。

表的 MIME 类型

getType() 方法 应返回 MIME 格式的 String ,指明了 Content URI 的数据类型。 Uri 参数不一定是准确的 URI 。如果 URI 带有通配符,则应返回符合匹配条件的所有 URI 的数据类型。

对于常见的数据类型而言,比如文本、HTML、JPEG, getType() 应该返回标准的 MIME 类型。 所有的标准类型都在网站 IANA MIME 媒体类型 中列出了。

对于一条以上表记录的 Content URI 而言, getType() 应该返回 Android 的厂商定义(vendor-specific) MIME 类型:

  • Type 部分: vnd
  • Subtype 部分:
    • 如果 URI 匹配单行记录: android.cursor.item/
    • 如果 URI 匹配多行记录: android.cursor.dir/
  • Provider-specific 部分: vnd.<name> . <type>

    需要给出的部分是 <name><type><name> 值应该是全局唯一的, <type> 值应该是在 URI 匹配定义中保持唯一。 较好的建议是把公司名称或应用程序包名称的一部分作为 <name> 值, 并把 URI 关联的表名用作 <type> 值。

比如,假定 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() 。该方法将返回 Provider 可提供文件的 MIME 类型组成的 String 数组,目标文件由 Content URI 给定。 请根据 MIME 类型过滤器参数对 MIME 类型进行过滤,以便只返回客户端需要处理的那些 MIME 类型。

例如,假定某个 Provider 用于提供 .jpg.png.gif 格式的照片文件。 如果另一个应用程序调用 ContentResolver.getStreamTypes() 时带有过滤字符串 image/* (图片文件),那么 ContentProvider.getStreamTypes() 方法就应该返回数组:

{ "image/jpeg", "image/png", "image/gif"}

如果应用程序只会处理 .jpg 文件,那么可以在调用 ContentResolver.getStreamTypes() 时附带过滤字符串 *//jpeg ,这时 ContentProvider.getStreamTypes() 应该返回:

{"image/jpeg"}

如果 Provider 无法提供过滤字符串要求的 MIME 类型, getStreamTypes() 应该返回 null

实现合约(Contract)类

Contract 类是一种 public final 类,其中以常量的方式定义了 URI、字段名称、MIME 类型及 Provider 用到的其他元数据(meta-data)。 该类在 Provider 和其他应用程序间建立起一种约定,这样即使是 URI 、字段名称等发生了变化,也能保证 Provider 可被正确访问。

因为常量名称通常更便于记忆, Contract 类可以帮助开发人员减少差错,防止列名或 URI 被用错。 由于这是一个类,所以可以包含 Javadoc 文档。 类似 Eclipse 之类的集成开发环境可以自动完成 Contract 类中的常量名称的填写,并显示常量的 Javadoc 注释。

其他开发人员无法直接访问 Contract 类的文件,但可以把 .jar 文件将它们静态编译到自己的应用中去。

ContactsContract 及其内部类就是 Contract 类的范例。

实现 Content Provider 权限

关于 Android 系统的权限控制,将在文章 安全和权限 中详细介绍。数据存储 一文中也介绍了有关各种存储形式的安全和权限知识。 简而言之,主要包括以下几点:

  • 默认情况下,保存在内部存储中的文件是应用程序及 Provider 的私有数据。
  • 由应用程序及 Provider 创建的 SQLiteDatabase 数据库是其私有数据。
  • 默认情况下,保存在外部存储中的文件是 公有(public)全局可读(world-readable) 的。 靠 Content Provider 无法对外部存储中的文件进行访问限制,因为其他应用可以通过别的 API 读写这些文件。
  • 打开或创建文件及数据库之类的方法会自动赋权,对内部存储的读写权限都可能会开放给所有应用。 如果使用了文件或数据库作为 Provider 的存储方式,并给它赋予了“world-readable”或“world-writeable”权限, 那么 Manifest 文件中设置的 Provider 权限就不再会保护数据了。 内部存储中的文件或数据库的默认权限是“私有”的,用于 Provider 时也不应去改变它。

如果需要通过 Content Provider 来控制数据的访问,就应该将数据保存在内部文件或 SQLite 数据库中,或是保存到“云端” (比如远程服务器),并保证这些文件和数据库是应用程序的私有数据。

实现权限

Provider 默认是没有设置权限的,因此所有的应用程序都可以读写它,即便其底层数据是私有的也没关系。 如果需要为 Provider 添加权限,只要修改 Manifest 文件中 <provider> 的属性或子元素即可。 可以为整个 Provider 、某一张表、甚至某条记录设置权限,也可以三者组合设置。

Provider 的权限是由 Manifest 文件中的一个或多个 <permission> 元素定义的。 为了保证权限名称的唯一性,请使用 Java 风格的域名来定义 android:name 属性。比如,可以把读权限命名为 com.example.app.provider.permission.READ_PROVIDER

以下列出了 Provider 权限的作用范围,首先是适用于整个 Provider 的权限,然后逐渐缩小。 权限的作用域越小,优先级就越高:

一个权限同时控制 Provider 级别的读写操作
通过 <provider> 元素的 android:permission 属性,可以用一个权限控制对整个 Provider 的读取和写入。
分别控制 Provider 级别的读和写操作
分开控制对整个 Provider 的读取和写入。 这通过指定 <provider> 元素的 android:readPermissionandroid:writePermission 属性即可。 该权限优先于 android:permission 的权限设置。
Path 级别的权限
对 Provider 中的某个 Content URI 的读、写、读与写权限。 这通过为每个 URI 定义 <provider><path-permission> 子元素来实现。 可分别对每个 Content URI 指定读写权限、读取权限、写入权限,或者三者都赋予。 读取、写入权限优先于读写权限。 并且, path 级别的权限优先于 Provider 级别的权限设置。
临时权限

临时赋予某个应用程序的权限,即使该应用程序无权访问也没关系。 这种临时授权机制减少了应用程序必须在 Manifest 中申请的权限数量。 如果启用了临时授权,则只有要持续访问所有 Provider 数据的应用程序才需拥有“永久”性访问权限。

现在假定要实现一个 Email Provider 和 App, 需要用外部图片浏览应用显示保存在 Provider 中的图片附件。 如果不需要图片浏览应用申请限,就要让它拥有必要的访问能力,则可以对图片的 Content URI 建立临时权限。 当用户需要显示图片时, Email App 会向图片浏览应用发送一个包含了 Content URI 和权限标志位的 Intent。 然后图片浏览应用会查询 Email Provider 并读取图片,即使它没有 Provider 的读取权限也没关系。

要启用临时授权机制,请设置 <provider>android:grantUriPermissions 属性,或者在 <provider> 元素下添加几条 <grant-uri-permission> 子元素。如果启用了该机制,则在 Provider 的某个关联了临时权限的 Content URI 失效时,必须调用 code> Context.revokeUriPermission() 。

这里的属性值决定着 Provider 可被访问的程度。 如果设为 true ,则系统会对整个 Provider 赋予临时权限,并覆盖其他所有 Provider 级别和 path 级别的权限设定。

如果设为 false ,则必须在 <provider> 元素下添加 <grant-uri-permission> 子元素。每个子元素都定义了一条临时授权。

为了实现对应用程序的临时赋权, Intent 必须包含 FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION 标志位,两者都带也行。这是通过 setFlags() 方法来实现的。

如果未设置 android:grantUriPermissions 属性,则被视为 false

<provider> 元素

类似于 ActivityService 组件, ContentProvider 的子类必须在 Manifest 文件中用 <provider> 元素进行声明。 Android 系统将从此元素中读取以下信息:

Authority ( android:authorities )
用于在系统中唯一标识整个 Provider 的名称。 该属性的详细内容已在设计 Content URI一节中进行了详细介绍。
Provider 类名 ( android:name
实现了 ContentProvider 的类。该类已在Implementing the ContentProvider Class一节中进行了详细介绍。
权限
该属性定义了其他应用程序访问 Provider 数据时必须拥有的权限:

有关权限及对应属性值的信息已在实现 Content Provider 权限一节中详细介绍。

与启动和控制相关的属性
以下属性决定了 Android 系统启动 Provider 的时机和方式、Provider 的进程参数和其他运行时设置:

该属性在开发指南中的 <provider> 元素文档中给出了完整的介绍。

资讯类属性
代表 Provider 的图标和文本标签(可选项)。
  • android:icon :包含了 Provider 图标的 Drawable 资源。 在 设置 > 应用程序 > 所有应用程序 的列表中, Provider 的图标将显示在文本标签的旁边。
  • android:label :描述 Provider 或其内部数据的说明性文字。 显示在 设置 > 应用程序 > 所有应用程序 的列表中。

该属性在开发指南中的 <provider> 元素文档中给出了完整的介绍。

Intent 和数据访问

应用程序可以通过 Intent 直接访问到 Content Provider, 它不需要调用 ContentResolverContentProvider 的任何方法,而是发送一个 Intent 启动某个 Activity 即可。 这个 Activity 往往是 Provider 所在应用的一部分,它负责完成数据读取工作,并显示在自己的界面中。 根据 Intent 中的 Action,此 Activity 也可以让用户完成修改数据的操作。 在 Intent 中还可以包含“extras”数据,目标 Activity 可在其界面中显示这些附加数据,然后用户就有机会修改这些数据并保存到 Provider 中去。

利用 Intent 有助于保证数据的完整性。 因为 Provider 可以用精确的业务规则来控制数据的插入、修改和删除操作。 如果允许其他应用程序直接修改 Provider 中的数据,则可能会带来非法数据。 为了让开发人员通过 Intent 访问数据,请务必给出完整的文档。 并且要让他们明白,通过 Intent 访问时,使用 Provider 应用的界面,比用自己的代码修改数据更为合适。

对于修改 Provider 数据的 Intent 的处理过程,与其他 Intent 没有什么不同。 关于使用 Intent 的详细信息,请阅读文章 Intent 和 Intent 过滤器 .

正文到此结束
Loading...