创建 content provider

内容提供程序管理对中央数据存储区的访问。您将实现 作为 Android 应用中的一个或多个类,以及 清单文件您的其中一个类实现了 ContentProvider,这是提供程序与 其他应用程序

尽管内容提供程序旨在让 您可以在应用中添加一些 activity 来查询和修改由您的提供程序管理的数据。

本页面包含构建 content provider 和列表的基本流程 多种 API 供你选择

着手构建前的准备工作

在开始构建提供程序之前,请考虑以下事项:

  • 决定是否需要内容提供方。您需要构建内容 provider 提供以下一项或多项功能: <ph type="x-smartling-placeholder">
      </ph>
    • 您想为其他应用提供复杂的数据或文件
    • 您希望让用户能够将复杂的数据从您的应用复制到其他应用中。
    • 您想使用搜索框架提供自定义搜索建议。
    • 您希望向微件公开应用数据。
    • 您想要实现 AbstractThreadedSyncAdapterCursorAdapterCursorLoader 类。

    使用数据库或其他类型的数据库,需要提供程序 永久性存储(如果完全在您自己的应用中使用) 并且不需要上述任何功能。相反,您可以 请使用下列任一存储系统: 数据和文件存储概览

  • 如果您还没有这样做,请阅读 <ph type="x-smartling-placeholder"></ph> 内容提供程序基础知识,详细了解提供程序及其工作原理。

接下来,请按照以下步骤构建您的提供程序:

  1. 设计数据的原始存储。content provider 通过以下两种方式提供数据: <ph type="x-smartling-placeholder">
    </ph>
    文件数据
    通常会存储在文件中的数据,例如 照片、音频或视频将文件存储在应用的私有中 空间。为了响应其他应用对文件的请求,您的 提供程序可以提供文件句柄。
    “结构化”数据
    通常存储在数据库、数组或类似结构中的数据。 以与行和列表格兼容的形式存储数据。A 行 表示一个实体,例如人员或库存商品。一列代表 实体的一些数据,如人名或商品价格。 将此类数据存储在 SQLite 数据库中,但可以使用任何类型的 永久性存储如需详细了解可用的存储类型,请参阅 Android 系统,请参阅 设计数据存储部分。
  2. 定义 ContentProvider 类的具体实现,并 所需的方法此类是您的数据与 Android 系统。有关此类的详细信息,请参阅 实现 ContentProvider 类部分。
  3. 定义提供程序的授权字符串、内容 URI 和列名称。如果您想 提供程序的应用来处理 intent,还要定义 intent 操作、extra 数据、 和标记。此外,您还需要为 访问您的数据考虑将所有这些值定义为 单独的协定类。稍后,您可以将此类公开给其他开发者。有关 有关内容 URI 的更多信息,请参阅 设计内容 URI 部分。 如需详细了解 intent,请参阅 intent 和数据访问部分。
  4. 添加其他可选部分,例如示例数据或实现 (共 AbstractThreadedSyncAdapter) 和云端数据

设计数据存储

content provider 是以结构化格式保存的数据的接口。创建前的准备工作 以确定如何存储数据。你能够以任何形式 然后根据需要设计读写数据的接口。

以下是 Android 上提供的一些数据存储技术:

  • 如果您使用的是结构化数据,可以考虑使用关系型数据库,例如 作为 SQLite 或者非关系型键值对数据存储区,例如 LevelDB)。如果您在工作 音频、图片或视频媒体等非结构化数据,然后考虑将 以文件形式保存数据您可以混合搭配多种不同类型的存储方案,并加以公开 使用单个内容提供程序。
  • Android 系统可与 Room 持久性库交互,后者 提供对 Android 自有提供程序的 SQLite 数据库 API 的访问权限 用于存储面向表的数据。要使用此 可以实例化 <ph type="x-smartling-placeholder"></ph> RoomDatabase,如 使用 Room 将数据保存到本地数据库

    您不必使用数据库来实现存储库。提供商 在外部显示为一组表,类似于关系型数据库, 并非对提供程序内部实现的要求。

  • 为了存储文件数据,Android 提供了各种面向文件的 API。 如需详细了解文件存储,请阅读 数据和文件存储概览。如果您 设计一个提供音乐或视频等媒体相关数据的提供程序, 具有用于合并表数据和文件的提供程序。
  • 在极少数情况下, 一个应用例如,你可能想使用 提供不同的数据集合,以便与其他 应用。
  • 如需处理基于网络的数据,请使用 java.netandroid.net。您还可以将基于网络的数据与本地数据同步 例如数据库,然后以表或文件的形式提供数据。

注意:如果您对代码库所做的更改 因此您需要用新版本标记代码库 数字。您还需要为 实现新的 content provider。进行此更改会阻止系统 降级,以防系统在尝试重新安装 应用具有不兼容的内容提供程序。

数据设计注意事项

以下是有关设计提供程序数据结构的一些提示:

  • 表数据必须始终具有“主键”由提供商维护的 为每行的唯一数值。您可以使用此值将该行关联到 其他表中的行(将其用作“外键”)。尽管您可以使用任何名称 对于此列,使用 BaseColumns._ID 是最佳选择 因为将提供程序查询结果链接到 ListView 要求检索到的其中一个列具有名称 _ID
  • 如果您想提供位图图像或其他非常大的面向文件的数据, 文件中的数据,然后间接提供数据,而不是直接将其存储在 表格。如果这样做,您需要告知提供商的用户,他们需要使用 ContentResolver 文件方法访问数据。
  • 使用二进制大型对象 (BLOB) 数据类型存储大小不一或 结构变化。例如,您可以使用 BLOB 列来存储 协议缓冲区JSON 结构

    您还可以使用 BLOB 来实现独立于架构的表。在 对于这种类型的表,您可以定义主键列、MIME 类型列以及 更宽泛的列作为 BLOB。BLOB 列中数据的含义通过 MIME 类型列中的值。这样,您就可以将不同的行类型存储在 同一个表。联系人提供程序的“数据”桌子 ContactsContract.Data 是一个独立于架构的示例 表格。

设计内容 URI

内容 URI 用来在提供程序中标识数据。内容 URI 包括 整个提供程序(其授权)的符号名称和一个 指向表或文件的名称(路径)。可选 ID 部分指向 表中的单个行。Google Cloud 的 ContentProvider 将内容 URI 作为参数。这样,您就可以 确定要访问的表、行或文件

如需了解内容 URI,请参阅 <ph type="x-smartling-placeholder"></ph> content provider 基础知识

设计授权方

提供程序通常具有单一授权,该授权充当其 Android 内部名称。接收者 避免与其他提供商发生冲突,使用互联网域名所有权(反向) 作为提供方授权的基础。因为此建议也适用于 Android 因此可以将提供程序授权定义为 包含该提供程序的软件包的物理组件。

例如,如果您的 Android 软件包名称为 com.example.<appname>,请向您的提供商提供 授权方 com.example.<appname>.provider

设计路径结构

开发者通常通过附加指向 单个表例如,假设您有两个表:table1table2 中,可以将它们与上例中的授权结合,得到 内容 URI com.example.<appname>.provider/table1com.example.<appname>.provider/table2。路径不是 而不必为路径的每一级创建表格。

处理内容 URI ID

按照惯例,提供程序通过接受内容 URI 来提供对表中单个行的访问权限 URI 末尾的行的 ID 值。此外,按照惯例,提供商与 ID 值添加到表格的 _ID 列中,并对 匹配的行。

此惯例为访问提供程序的应用提供了一种常见的设计模式。应用 对提供程序执行查询并显示生成的 CursorListView 中使用 CursorAdapter。 定义 CursorAdapter 需要以下列中的某一列: Cursor将变为_ID

然后,用户从界面显示的行中任选一行,以查看或修改 数据。应用程序从支持 Cursor ListView,获取此行的 _ID 值,并将其附加到 内容 URI,并向提供程序发送访问请求。然后,提供程序便可执行 查询或修改用户选择的确切行。

内容 URI 模式

为了帮助您选择对传入的内容 URI 执行的操作,提供程序 API 提供了以下功能: 便捷类 UriMatcher,它会将内容 URI 模式映射到 整数值。您可以在 switch 语句中使用整数值, 为符合特定格式的一个或多个内容 URI 选择所需的操作。

内容 URI 模式使用以下通配符匹配内容 URI:

  • * 匹配由任意长度的任何有效字符组成的字符串。
  • # 匹配由任意长度的数字字符组成的字符串。

以设计和编码内容 URI 处理为例,假设一个具有 可识别以下内容 URI 的授权 com.example.app.provider 指向表:

  • 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 的表。

如果这些内容 URI 附加了行 ID,则提供程序也能识别这些内容 URI。例如,content://com.example.app.provider/table3/1 表示由 “table3”中的“1”。

可以使用以下内容 URI 模式:

content://com.example.app.provider/*
匹配提供程序中的任何内容 URI。
content://com.example.app.provider/table2/*
匹配表 dataset1 的内容 URI 和 dataset2,但与 table1table3
content://com.example.app.provider/table3/#
匹配内容 URI 表示 table3 中的单行,例如 content://com.example.app.provider/table3/6,针对由 6

以下代码段展示了 UriMatcher 中的方法的工作原理。 此代码处理整个表的 URI 不同于 使用内容 URI 模式创建单行, content://<authority>/<path> 用于表和 content://<authority>/<path>/<id>(单行)。

addURI() 方法将 整数值的授权和路径。match() 方法会返回 URI 的整数值。switch 语句 选择是查询整个表还是查询单个记录。

Kotlin

private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    /*
     * The calls to addURI() go here for all the content URI patterns that the provider
     * recognizes. For this snippet, only the calls for table 3 are shown.
     */

    /*
     * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
     * in the path.
     */
    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.
     */
    addURI("com.example.app.provider", "table3/#", 2)
}
...
class ExampleProvider : ContentProvider() {
    ...
    // Implements ContentProvider.query()
    override fun query(
            uri: Uri?,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
    ): Cursor? {
        var localSortOrder: String = sortOrder ?: ""
        var localSelection: String = selection ?: ""
        when (sUriMatcher.match(uri)) {
            1 -> { // If the incoming URI was for all of table3
                if (localSortOrder.isEmpty()) {
                    localSortOrder = "_ID ASC"
                }
            }
            2 -> {  // If the incoming URI was for a single row
                /*
                 * 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.
                 */
                localSelection += "_ID ${uri?.lastPathSegment}"
            }
            else -> { // If the URI isn't recognized,
                // do some error handling here
            }
        }

        // Call the code to actually do the query
    }
}

Java

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        /*
         * The calls to addURI() go here for all the content URI patterns that the provider
         * recognizes. For this snippet, only the calls for table 3 are shown.
         */

        /*
         * Sets the integer value for multiple rows in table 3 to one. No wildcard is used
         * in the path.
         */
        uriMatcher.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.
         */
        uriMatcher.addURI("com.example.app.provider", "table3/#", 2);
    }
...
    // Implements ContentProvider.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.
         */
        switch (uriMatcher.match(uri)) {


            // If the incoming URI was for all of table3
            case 1:

                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // If the incoming URI was for a single row
            case 2:

                /*
                 * 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 isn't recognized, do some error handling here
        }
        // Call the code to actually do the query
    }

另一个类 ContentUris 也提供了便捷方法, 内容 URI 的 id 部分。UriUri.Builder 包含一些便捷方法,用于解析现有 Uri 对象和构建新对象。

实现 ContentProvider 类

ContentProvider 实例管理访问权限 通过处理来自其他应用的请求,生成结构化的数据集。所有形式 最终会调用 ContentResolver,后者会调用具体的 ContentProvider 方法获取访问权限。

必需的方法

抽象类 ContentProvider 定义了六个抽象方法, 作为具体子类的一部分来实现。除了 onCreate() 由客户端应用调用 且正在尝试访问您的内容提供方

query()
从提供程序检索数据。使用参数选择要排除的表 要返回的行和列以及结果的排序顺序。 将数据作为 Cursor 对象返回。
insert()
向您的提供程序插入新行。使用参数选择 以及获取要使用的列值。返回 新插入的行。
update()
更新提供程序中的现有行。使用参数选择表和行 更新并获取更新后的列值。返回已更新的行数。
delete()
从提供程序中删除行。使用参数选择表和行 删除。返回已删除的行数。
getType()
返回与内容 URI 对应的 MIME 类型。有关此方法的详细信息,请参见 实现内容提供程序 MIME 类型部分了解详情。
onCreate()
初始化提供程序。Android 系统会在其后立即调用此方法 创建提供程序您的提供方在 ContentResolver 对象尝试访问它。

这些方法的签名与名称相同的 ContentResolver 方法。

您在实现这些方法时需要考虑以下事项:

  • 所有这些方法(onCreate() 除外) 可以由多个线程同时调用,因此它们必须是线程安全的。学习内容 有关多个线程的更多信息,请参阅 <ph type="x-smartling-placeholder"></ph> 进程和线程概览
  • 避免在 onCreate() 中执行冗长的操作。将初始化任务推迟到实际需要时执行。 关于实现 onCreate() 方法的部分 我们将对此进行详细介绍
  • 尽管您必须实现这些方法,但您的代码除了 返回预期的数据类型。例如,您可以禁止其他应用 通过忽略对数据的调用, insert() 和返程 0.

实现 query() 方法

通过 ContentProvider.query() 方法必须返回 Cursor 对象,或者,如果它 失败,系统会抛出 Exception。如果您使用 SQLite 数据库作为数据 您可以返回由上述任一方法返回的 Cursor SQLiteDatabase 类的 query() 方法。

如果查询不匹配任何行,则返回 CursorgetCount() 方法返回 0 的实例。 仅当查询过程中发生内部错误时,才返回 null

如果您不使用 SQLite 数据库作为数据存储,请使用其中一个具体的子类 共 Cursor 页。例如,MatrixCursor 类 会实现一个游标,其中每行都是一个由 Object 实例组成的数组。在此类中 使用 addRow() 添加新行。

Android 系统必须能够传达 Exception 进行跨进程边界工作Android 可以针对以下非常有用的异常执行此操作 处理查询错误的方法:

实现 insert() 方法

insert() 方法可将 并使用 ContentValues 中的值 参数。如果 ContentValues 参数中没有列名称,您将 可能需要在提供程序代码或数据库中为其提供默认值 架构。

此方法返回新行的内容 URI。要构建该对象,请将新的 行的主键(通常是 _ID 值)与表的内容 URI 相关联,使用 withAppendedId()

实现 delete() 方法

delete() 方法 而不必从数据存储空间中删除行。如果您使用的是同步适配器 与您的提供商联系,请考虑将已删除的行 包含“delete”的命令标志,而不是完全移除该行。同步适配器可以 检查已删除的行并将其从服务器中移除,然后再将其从提供程序中删除。

实现 update() 方法

update() 方法采用的 ContentValues 参数与 insert()selectionselectionArgs 参数 delete()ContentProvider.query()。 这样一来,您就可以在这些方法之间重复使用代码。

实现 onCreate() 方法

Android 系统会调用 onCreate() 启动提供程序时仅执行快速运行的初始化 并将数据库创建和数据加载推迟到提供程序 收到数据请求如果您在 onCreate(),请减慢 服务提供商的初创公司。进而减慢提供程序对其他请求的响应速度 应用。

以下两个代码段展示了 ContentProvider.onCreate()和 <ph type="x-smartling-placeholder"></ph> Room.databaseBuilder()。第一个 该代码段显示了 ContentProvider.onCreate(),其中 构建了数据库对象,并创建了数据访问对象的句柄:

Kotlin

// Defines the database name
private const val DBNAME = "mydb"
...
class ExampleProvider : ContentProvider() {

    // Defines a handle to the Room database
    private lateinit var appDatabase: AppDatabase

    // Defines a Data Access Object to perform the database operations
    private var userDao: UserDao? = null

    override fun onCreate(): Boolean {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build()

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.userDao

        return true
    }
    ...
    // Implements the provider's insert method
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Java

public class ExampleProvider extends ContentProvider

    // Defines a handle to the Room database
    private AppDatabase appDatabase;

    // Defines a Data Access Object to perform the database operations
    private UserDao userDao;

    // Defines the database name
    private static final String DBNAME = "mydb";

    public boolean onCreate() {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build();

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.getUserDao();

        return true;
    }
    ...
    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

实现 ContentProvider MIME 类型

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

getType()
您为任何提供程序实现的必需方法之一。
getStreamTypes()
您的提供程序提供文件时需要实现的方法。

表的 MIME 类型

getType() 方法会返回一个 MIME 格式的 String,用于描述内容返回的数据类型 URI 参数。Uri 参数可以是模式,而不是特定 URI。 在这种情况下,返回与匹配 模式。

对于文本、HTML 或 JPEG 等常见数据类型, getType() 会返回标准的 相应数据的 MIME 类型。有关这些标准类型的完整列表,请访问 IANA MIME 媒体类型 网站。

对于指向一个或多个表数据行的内容 URI, 退货费用 getType() Android 供应商专用 MIME 格式中的 MIME 类型:

  • 类型部分:vnd
  • 子类型部分: <ph type="x-smartling-placeholder">
      </ph>
    • 如果 URI 模式适用于单个行:android.cursor.item/
    • 如果 URI 模式用于多行:android.cursor.dir/
  • 特定于提供方的部分:vnd.<name><type>

    您需要提供 <name><type><name> 值具有全局唯一性, 且 <type> 值对相应 URI 而言是唯一的 模式。建议选择公司名称或<name> 应用的 Android 软件包名称的一部分。 <type> 是一个字符串,用于标识与 URI

例如,如果提供者的授权是 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 类型

如果您的提供商提供文件,请实现 getStreamTypes()。 该方法会针对提供程序的文件返回 MIME 类型的 String 数组 返回的值。按 MIME 类型过滤您提供的 MIME 类型 filter 参数,以便仅返回客户端想要处理的 MIME 类型。

例如,假设提供商以 JPG 格式的文件形式提供照片图片, PNG 和 GIF 格式。 如果应用使用过滤器字符串 image/* 调用 ContentResolver.getStreamTypes(), 是一幅“映像” 那么 ContentProvider.getStreamTypes() 方法会返回数组:

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

如果应用只对 JPG 文件感兴趣,则可调用 将 ContentResolver.getStreamTypes() 替换为过滤条件字符串 *\/jpeg;以及 getStreamTypes() 会返回:

{"image/jpeg"}

如果您的提供程序未提供过滤器字符串中请求的任何 MIME 类型, getStreamTypes() 返回 null

实现协定类

协定类是一个 public final 类,其中包含 URI、列名称、MIME 类型以及与提供程序相关的其他元数据。类 通过确保提供程序 即使 URI、列名称 等等。

协定类对开发者也有帮助,因为它的常量通常采用助记名称, 因此开发者不太可能为列名称或 URI 使用不正确的值。由于这是一个 类,它可以包含 Javadoc 文档。集成式开发环境,例如 Android Studio 可以从协定类自动填充常量名称,并为 常量。

开发者无法从您的应用访问协定类的类文件,但他们可以 从您提供的 JAR 文件将其静态编译到其应用程序中。

例如,ContactsContract 类及其嵌套类 协定类。

实现 content provider 权限

如需详细了解 Android 系统各个方面的权限和访问权限,请参阅 安全提示。 还可参阅数据和文件存储概览 介绍了针对各种类型存储的有效安全和权限。 简而言之,要点如下:

  • 默认情况下,存储在设备内部存储上的数据文件是 应用和提供程序
  • 您创建的 SQLiteDatabase 个数据库是您的私有数据库 应用和提供程序
  • 默认情况下,您保存到外部存储空间的数据文件是公开的的, 全局可读。您无法使用内容提供方来限制对以下文件夹中文件的访问权限 因为其他应用可以使用其他 API 调用来读取和写入应用。
  • 该方法调用用于在设备的内部系统中打开或创建文件或 SQLite 数据库。 Storage 可能会同时授予所有其他应用的读写权限。如果您 使用内部文件或数据库作为提供程序的存储库,并将其提供给 “全局可读”或“可全局写入”您在 Google Cloud 中 它的清单不会保护您的数据。中文件和数据库的默认访问权限 内部存储空间是“专用”的;请勿为提供方的代码库更改此项。

如果您想使用 content provider 权限来控制对您数据的访问权限,那么 将您的数据存储在内部文件、SQLite 数据库或云端,例如 并将文件和数据库保持为应用的私有文件。

实现权限

默认情况下,所有应用都可以对提供程序执行读取或写入操作,即使底层数据是 私有,因为默认情况下您的提供方未设置权限。如需更改此设置,请按以下步骤操作: 使用属性或子元素在清单文件中为提供程序设置权限 <provider> 元素的子元素。您可以设置适用于整个提供程序、 特定表和/或特定记录

您可以通过一个或多个 <permission> 元素。要使 权限,请针对 android:name 属性。例如,将读取权限命名为 com.example.app.provider.permission.READ_PROVIDER

以下列表介绍了提供程序权限的范围,从 授予此类权限。 更细粒度的权限优先于范围较大的权限。

单一读写提供程序级权限
一项权限同时控制对整个提供程序的读写权限,指定 属性 android:permission <provider> 元素。
单独的读取和写入提供程序级权限
对整个提供程序的读取权限和写入权限。由您自己指定 以及 android:readPermission 和 的 android:writePermission 属性 <provider> 元素。它们优先于 android:permission
路径级权限
对提供程序中的内容 URI 的读取、写入或读写权限。您指定 你希望使用 <path-permission> 子元素 <provider> 元素。对于您指定的每个内容 URI,您可以指定一个 读取/写入权限、读取权限、写入权限或全部三项权限。而读取和 写入权限优先于读取/写入权限。此外,路径级别 权限的优先级高于提供程序级别的权限。
临时权限
一种权限级别,用于授予对某应用的临时访问权限,即使该应用 不具备通常所需的权限。临时性 访问功能可减少应用必须在 。开启临时权限后,只有 提供程序的永久权限是指 数据。

例如,如果您要实现电子邮件提供程序和应用,并且希望实现 您希望外部图片查看器应用 提供商。为了在不请求权限的情况下为图像查看器提供必要的访问权限, 您可以为照片的内容 URI 设置临时权限。

设计电子邮件应用 当用户想要显示照片时,应用会发送一个包含 图片查看器的内容 URI 和权限标记。图片查看器可以 然后查询您的电子邮件服务提供商以检索照片,即使查看者没有 拥有提供程序的正常读取权限。

要启用临时权限,可将 android:grantUriPermissions 属性 <provider> 元素,或者添加一个或多个 <grant-uri-permission> 个子元素添加到您的 <provider> 元素。致电 Context.revokeUriPermission(),只要您取消对与临时权限关联的内容 URI 的支持, 提供商。

此属性的值决定了可访问的提供程序范围。 如果将该属性设置为 "true",则系统会授予 整个提供程序的权限,覆盖 由提供程序级或路径级权限指定。

如果此标志设置为 "false",则添加 <grant-uri-permission>子元素添加到 <provider> 元素。每个子元素都会指定内容 URI 或 已授予临时访问权限的 URI。

如需向应用授予临时访问权限,intent 必须包含 FLAG_GRANT_READ_URI_PERMISSION 标志后, FLAG_GRANT_WRITE_URI_PERMISSION 标志,或同时设置两者。这些 使用 setFlags() 方法进行设置。

如果 android:grantUriPermissions 属性不存在,则假定为 "false"

<provider>元素

ActivityService 组件一样, ContentProvider 的子类 在其应用的清单文件中使用 <provider> 元素。Android 系统会通过 元素:

职权 (android:authorities 个)
用于标识系统中整个提供程序的符号名称。这个 属性的详细说明 设计内容 URI 部分。
提供方类名称 (android:name)
实现 ContentProvider 的类。这个类是 请参阅 实现 ContentProvider 类部分。
权限
用于指定其他应用访问所需权限的属性 提供程序的数据: <ph type="x-smartling-placeholder">

有关权限及其相应属性的说明,请参见 请参阅 实现 content provider 权限部分。

启动和控制属性
这些属性决定了 Android 系统如何以及何时启动提供程序, 提供程序的进程特征以及其他运行时设置: <ph type="x-smartling-placeholder">

有关这些属性的完整说明,请参阅 <provider> 元素。

信息属性
提供商的可选图标和标签: <ph type="x-smartling-placeholder">
    </ph>
  • android:icon:包含提供程序图标的可绘制资源。 该图标会显示在应用列表中的提供商的标签旁边, 设置 >应用 >全部
  • android:label:描述提供程序及其 数据,或者两者兼有。该标签会显示在以下位置的应用列表中: 设置 >应用 >全部

有关这些属性的完整说明,请参阅 <provider> 元素。

注意:如果您以 Android 11 或更高版本为目标平台,请查看 软件包可见性文档 进一步的配置需求。

Intent 和数据访问

应用可以通过 Intent 间接访问内容提供程序。 应用未调用 ContentResolverContentProvider。而是会发送一个启动 activity 的 intent, 这通常是提供程序自己的应用的一部分目标 activity 负责 检索并显示数据。

根据 intent 中的操作, 目标 activity 还可以提示用户对提供程序的数据进行修改。 intent 可能还包含“extra”目标 activity 显示的数据 。接着,用户可以选择更改这些数据,然后再用其修改 数据。

您可以使用 intent 访问权限来帮助确保数据完整性。您的提供商可能会视情况而定 介绍如何根据严格定义的业务逻辑插入、更新和删除数据。如果 就属于这种情况,让其他应用直接修改您的数据会导致 无效的数据。

如果希望开发者使用 intent 访问权限,请务必为其提供详尽说明。 说明为什么使用应用界面的 intent 访问比尝试 来修改数据

处理想要修改提供程序数据的传入 intent 与 处理其他 intent。您可以参阅以下指南,详细了解如何使用 intent intent 和 intent 过滤器

如需更多相关信息,请参阅 日历提供程序概览