Android 数据存储之 Content Providers

这里写图片描述

为什么要使用 Content Provider

在上篇文章中,我们看到 Activity 都是直接调用 PetDbHelper 来直接访问数据库的。所以只要我们知道插入的信息是正确的,这样的操作就没有问题。但不小心在 Activity 中插入了一个不良的数据(例如宠物的值为 -10kg),这种直接交互方法的缺陷就在于 它会将无效的数据直接插入到我们的数据库中,所以这里就有了 Content Provider-集中化数据的访问和编辑

在这个模式中,我们的 UI 代码会直接与 Content Provider 交互,而非直接与数据库交互。Content Provider 作为一个数据验证层(data validation),会在我们错误输入无效的数据时进行验证,如果数据存在错误,就会在这一步被捕捉到。它作为数据源和 UI 代码之间的附加层。它通常被称为抽象层(abstraction layer),因为 Content Provider 会抽象化数据存储的方式或隐藏数据存储的详情,所以 UI 代码在进行任何数据访问时,只需和 Content Provider 进行通信,Content Provider 会以 UI 看不到的方式对底层数据进行暗箱处理。因为 UI 不关心数据是存储在数据库中还是存储在单个文本文件中,所以如果在应用的更新版本中我们想将数据库换做不同的存储类型 UI 代码将保持不变,并继续与现有的 Content Provider 交互。

Content Provider 三大优势:

  • 作为数据源与 UI 代码之间的抽象层,可以帮助我们很好地进行数据验证;
  • 帮助我们与其它框架类完美协助;
  • 帮助我们轻松与其它应用分享数据

ContentProvider 工作原理

这里写图片描述

CatalogActivity 调用 CRUD (查询/插入/修改/删除)方法的任意一个,并传入一个 URI 指定其想与之交互的特定数据集,这个特定数据可以是数据库里某行中的特定名称,然后给定 URI 中的信息,ContentResolver 会将该消息发送到适当的 ContentProvider,而 ContentProvider 会和 PetDbHelper 进行交互,database 从而获取适当的数据并将它返回至调用者,返回的数据会回传至 ContentResolver,并最终返回至 CatalogActivity 以在 UI 中显示。

添加 ContentProvider 代码

在 data 包名下,创建 PetProvider 类继承自 ContentProvider,重写所有的方法。

public class PetProvider extends ContentProvider {

    /** Tag for the log messages */
    public static final String LOG_TAG = PetProvider.class.getSimpleName();

    /**
     * Initialize the provider and the database helper object.
     */
    @Override
    public boolean onCreate() {
        // TODO: Create and initialize a PetDbHelper object to gain access to the pets database.
        // Make sure the variable is a global variable, so it can be referenced from other
        // ContentProvider methods.
        return true;
    }

    /**
     * Perform the query for the given URI. Use the given projection, selection, selection arguments, and sort order.
     */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                        String sortOrder) {
        return null;
    }

    /**
     * Insert new data into the provider with the given ContentValues.
     */
    @Override
    public Uri insert(Uri uri, ContentValues contentValues) {
        return null;
    }

    /**
     * Updates the data at the given selection and selection arguments, with the new ContentValues.
     */
    @Override
    public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
        return 0;
    }

    /**
     * Delete the data at the given selection and selection arguments.
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    /**
     * Returns the MIME type of data for the content URI.
     */
    @Override
    public String getType(Uri uri) {
        return null;
    }
}

在 AndroidManifest 文件中,添加代码 说明我们使用的是带这个 provider 标签的 ContentProvider。

<!--authorities:唯一指定设备上的 Content Provider,通常直接使用应用的包名即可-->
<!--exported:决定我们的 Content Provider 是否对其它应用可见-->
<provider
  android:name=".data.PetProvider"
  android:authorities="com.example.android.pets"
  android:exported="false" />

ContentProvider 方法调用

下面将介绍每种方法的调用。

query() 方法
这里写图片描述
CatalogActivity 使用 query() 方法调用 ContentResolver,为确定 ContentResolver 最终使用哪个 Provider, 我们在 query() 方法中传递了一个内容 URI。这里的 URI 帮助 指定数据库中我们感兴趣的资源。接下来 ContentResolver 从此 query() 方法中获得信息后,它会使用相同的 query() 方法调用合适的 Provider,这里我们知道了要调用的是 PetProvider。接下来为了获取数据,query() 方法会将传入的参数转换为 SQL 语句,然后用此对数据库执行操作。查询到信息后将返回一个 Cursor,它包含我们感兴趣的行,它会最终返回至最初调用此信息的 CatalogActivity。

insert() 方法
这里写图片描述
和 query() 方法比较相似,但是它有一个附加参数 ContentValues,此参数包含我们实际想插入数据库中的信息,而 Uri 告诉我们插入的位置。同样 CatalogActivity 使用此 insert() 方法调用 ContentResolver,然后 ContentResolver 使用其自己的名称相同的 insert() 方法来调用合适的 provider,这里仍然是 PetProvider,以将我们的特定值插入数据库中。insert() 方法传入的信息会被转换为 SQL 用来决定将什么值插入到数据库中的什么位置,但对于 insert() 方法返回的将不是 Cursor,而是一个 Uri 告诉我们指定宠物在数据库中插入的位置。

update() 方法
这里写图片描述

分析过程同上,update() 方法返回的将是数据库中更新的行的编号。
delete() 方法
这里写图片描述
分析过程同上,delete() 方法返回的将是数据库中被删除行的编号。

什么是 Url

在使用 Provider 时,我们基本上需要告诉它两件事:

  1. 我们想要执行什么操作(查询/插入/修改/删除)?
  2. 我们想在这个操作中使用什么数据(整个表格/表格中单独行)?

Uri 会帮助定义我们想要对其执行操作的数据,标识 Provider 中的数据,它可以指向数据库的某个部分如单个行、单个表或一组表,也可以指向文件如文本文件、照片或其它媒体文件。
这里写图片描述
Uri 代表统一资源标识符,它可以标识出我们感兴趣资源的名称、位置。URL 就像我们常见的网站。事实上 URL 是 URI 的子集,而 URL 代表统一资源定位符,它会给出某个文件或数据在网站上的具体位置。现在我们想要使用 URI 来标识一些数据的位置,位置为手机上的类似 SQL 的数据库文件。

这里写图片描述

Scheme:在这个例子中我们用的是 content://,因为就像我们之前提到的这是 Android 应用中 URI 的标准开头,就像我们使用 HTTP://HTTPS:// 作为网页 URL 的前缀一样。
Content Authority:这一部分是 URI 最为重要的部分,因为它指定我们要使用的 ContentProvider。它的主要作用是指定我们要进行通信的 ContentProvider 类。回想一下我们在 AndroidManifest 文件中如何设置我们 ContentProvider 的 android:authorities 参数的。

<!--authorities:唯一指定设备上的 ContentProvider,通常直接使用应用的包名即可-->
<!--exported:决定我们的 ContentProvider 是否对其它应用可见,即共享数据-->
<provider
  android:name=".data.PetProvider"
  android:authorities="com.example.android.pets"
  android:exported="false" />

它必须与 URI 的 Content Authority 匹配。我们添加的 provider 标记,这个标记会将此 Content Authority 与 java 类 PetProvider 关联起来。当我们的 URI 使用该 Content Authority 时,它指定我们要使用这个 Provider 类。

Type of data:它指定了我们要执行操作的数据,这部分为表名(TABLE NAME)。
这里写图片描述

但是如果我们想获取表中单个行的数据,该怎么办?

我们可以在 URI 中表名的后面再跟上数字,指定我们感兴趣的具体行的 ID 编号。
这里写图片描述

由此可知,设计和使用正确的 URI 对我们从表中获取所需的信息非常重要。

向 Contract 添加 URI

如何向 PetContract.java 代码添加 URI ?

上文中说到的 URI 的 3 个部分:SchemeContent AuthorityType of data。其中某些成分是可重复使用的,不会发生变化,我们可以将它们作为常数。那么存储这些常数的最佳地方是哪里?

我们之前将与数据相关的所有常数存储子啊 Contract 类中,所以我们也可以将 URI 常数信息存储其中。

CONTENT_AUTHORITY

存储我们之前在 AndroidManifest 文件中 provider 标签下的 android:authorities 的内容。

 <provider
      android:name=”.data.PetProvider”
      android:authorities=”com.example.android.pets”
      android:exported=”false” />

PetContract.java 中设置

public static final String CONTENT_AUTHORITY = "com.example.android.pets";

BASE_CONTENT_URI

将 CONTENT_AUTHORITY 常数与 Scheme 的 content:// 连接起来,它将由与 PetsProvider 关联的每个 URI 共用

"content://" + CONTENT_AUTHORITY
---
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

PATH_TableName

该常数存储位置将添加到基本内容 URI 的每个表的路径:

 public static final String PATH_PETS = "pets";

完成 CONTENT_URI

最后,在 contract 中的每个 Entry 类中,我们为类创建一个完整的 URI 作为常数,称为 CONTENT_URI。

 public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, PATH_PETS);

如何实现这个 Provider,即 Provider 具体如何与数据源进行交互?

URI Matcher

这里写图片描述

PetProvider 如何与数据源进行交互 ?

在上图例子中,数据源是数据库。URI 代码在这个例子中就是 CatalogActivity 会执行一个操作,例如使用宠物 URI 向 ContentResolver 发出查询,ContentResolver 会判断出此宠物 URI,然后将该请求发送到正确的 PetProvider 以进行处理。在这里轮到 Provider 决定如何处理此请求,要么获取数据要么按要求修改数据源,为了决定如何处理请求,PetProvider 会使用 URI Matcher, URI Matcher 会确定传递给它的 URI 属于哪种类型,然后我们可以用这个信息决定要进入的分支。

这里写图片描述

URI 的使用者包括 CatalogActivity,ContentResolver 和 PetProvider,现在只需点击 URI Matcher,就可以使用 URI 来决定将进入的分支,做出这个决定后 URI 的使命就完成了。之后我们就确切地知道感兴趣的数据表了,并通过与 SQLite 数据库对象直接交互,来与数据表交互。

为两条分支指定名称,列出 Provider 中的不同行为模式:

URI parttern Code Constant name
content://com.example.android.pets/pets 100 PETS
content://com.example.android.pets/pets/# 101 PET_ID

# 为通配符,可替换为任何长度的整数,这个整数就是表中的特定行。* 可替换为任何长度的字符串。

这就是如何使用 URI,来决定我们需要采取操作的数据源部分。

Contacts URI Matcher

联系人的数据库更加复杂,Provider 提供了跟多不同的内容 URI 来访问,具体数据部分如下:

URI parttern Code Constant name
content://com.android.contacts/contacts 1000 CONTACTS
content://com.android.contacts/contacts/# 1001 CONTACTS_ID
content://com.android.contacts/lookup/* 1002 CONTACTS_LOOKUP
content://com.android.contacts/lookup/*/# 1003 CONTACTS_LOOKUP_ID
content://com.android.contacts/data 3000 DATA
content://com.android.contacts/data/# 3001 DATA_ID

这里写图片描述

上图 URI 由 .../contacts 组成,URI Matcher 会返回代码 1000,代表 contacts 情况,意味着将返回对整个 contacts 表的查询结果。假设另一个例子 content URI 以 .../contacts/5 结尾,那么 URI Matcher 将返回代码 1001,返回的结果就是 contacts 表中第 5 行的数据。

向 ContentProvider 添加 URI Matcher

要使用 URI Matcher ,我们需要在 ContentProvider 中实现两个步骤:

  1. 用 ContentProvider 可接受的 URI 模式设置 UriMatcher,并向每个模式分配一个整数代码;
  2. 调用 UriMatcher.match(Uri) 方法,传入一个 URI,返回对应的整数代码。前提是它与某个有效模式匹配,如果不匹配它也会给我们提示。

下面通过联系人示例看看如何实现

/** URI matcher code for the content URI for the pets table */
private static final int PETS = 100;

    /** URI matcher code for the content URI for a single pet in the pets table */
private static final int PET_ID = 101;

/**
     * UriMatcher object to match a content URI to a corresponding code.
     * The input passed into the constructor represents the code to return for the root URI.
     * It's common to use NO_MATCH as the input for this case.
     */
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);


// 全局变量 UriMatcher 调用 addURI() 方法
// Static initializer. This is run the first time anything is called from this class.
    static {
        // The calls to addURI() go here, for all of the content URI patterns that the provider
        // should recognize. All paths added to the UriMatcher have a corresponding code to return
        // when a match is found.

        // TODO: Add 2 content URIs to URI matcher
        sUriMatcher.addURI(PetContract.CONTENT_AUTHORITY, "pets", PETS);
        sUriMatcher.addURI(PetContract.CONTENT_AUTHORITY, "pets/#", PET_ID);
    }

ContentProvider 的 Query() 方法

这里写图片描述

一开始 CatalogActivity 调用 ContentResolver query() 方法,ContentResolver 将请求传递给 PetProvider,然后 UriMatcher 会帮助我们确定 Uri 匹配的模式,然后我们就可以对 pets 表进行查询,适当情况下我们需要应用传入的 projectionselectionselectionArgssortOrder 参数。查询的结果将用一个 Cursor 显示,它包含来自原表某些行或列的数据,然后此 Cursor 或被 PetProvider query() 方法返回至 ContentResolver,然后返回到 CatalogActivity 以最终显示在 UI 上。

首先我们需要获得数据库对象,然后将 URI 发送到 UriMatcher, 之后选择分支操作。具体操作如下:

在 PetProvider 类中:


@Override
    public boolean onCreate() {
        // TODO: Create and initialize a PetDbHelper object to gain access to the pets database.
        // 初始化 PetDbHelper 变量来访问数据库
        mDbHelper = new PetDbHelper(getContext());
        // Make sure the variable is a global variable, so it can be referenced from other
        // ContentProvider methods.
        return true;
    }

/**
     * Perform the query for the given URI. Use the given projection, selection, selection arguments, and sort order.
     */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                        String sortOrder) {

        // Get readable database
        SQLiteDatabase database = mDbHelper.getReadableDatabase();

        // This cursor will hold the result of the query
        Cursor cursor;

        // Figure out if the URI matcher can match the URI to a specific code
        int match = sUriMatcher.match(uri);
        switch (match) {
            case PETS:
                // For the PETS code, query the pets table directly with the given
                // projection, selection, selection arguments, and sort order. The cursor
                // could contain multiple rows of the pets table.
                // TODO: Perform database query on pets table
                cursor = database.query(PetContract.PetEntry.TABLE_NAME, projection, selection,
                        selectionArgs, null, null ,sortOrder);
                break;
            case PET_ID:
                // For the PET_ID code, extract out the ID from the URI.
                // For an example URI such as "content://com.example.android.pets/pets/3",
                // the selection will be "_id=?" and the selection argument will be a
                // String array containing the actual ID of 3 in this case.
                //
                // For every "?" in the selection, we need to have an element in the selection
                // arguments that will fill in the "?". Since we have 1 question mark in the
                // selection, we have 1 String in the selection arguments' String array.
                selection = PetContract.PetEntry._ID + "=?";
                // ContentUris.parseId(uri):路径最后一段换成数字
                selectionArgs = new String[] {String.valueOf(ContentUris.parseId(uri))};

                // This will perform a query on the pets table where the _id equals 3 to return a
                // Cursor containing that row of the table.
                cursor = database.query(PetContract.PetEntry.TABLE_NAME, projection, selection,
                        selectionArgs, null, null, sortOrder);
                break;
            default:
                throw  new IllegalArgumentException("Cannot query unkonwn URI " + uri);
        }
        return cursor;
    }

这里写图片描述

ContentProvider 的 Insert() 方法

这里写图片描述

首先使用 UriMatcher 确定匹配的模式,根据我们之前在 query() 方法中所做类似。但是在 insert() 情形下,我们无法在 PET_ID case 下插入行,所以 insert() 方法不支持这种模式,因此我们只能在 PETS case 下添加新宠物,

/**
     * Insert new data into the provider with the given ContentValues.
     */
    @Override
    public Uri insert(Uri uri, ContentValues contentValues) {
        final int match = sUriMatcher.match(uri);
        switch (match) {
            case PETS:
                return insertPet(uri, contentValues);
            default:
                throw new IllegalArgumentException("Insertion is not supported for " + uri);
        }
    }

    /**
     * Insert a pet into the database with the given content values. Return the new content URI
     * for that specific row in the database.
     */
    private Uri insertPet(Uri uri, ContentValues contentValues) {
        // TODO: Insert a new pet into the pets database table with the given ContentValues
        // Get readable database
        SQLiteDatabase database = mDbHelper.getWritableDatabase();
        long petId = database.insert(PetEntry.TABLE_NAME, null, contentValues);

        // Once we know the ID of the new row in the table,

        // If the ID is -1, then the insertion failed. Log an error and return null.
        if (petId == -1) {
            Log.e(LOG_TAG, "Failed to insert row for " + uri);
            return null;
        }
        // return the new URI with the ID appended to the end of it
        return ContentUris.withAppendedId(uri, petId);
    }

向 ContentProvider 添加数据验证

数据验证的重要性

确定用户输入的数据有效是非常重要的,一旦无效的数据进入我们的数据库,要整理良好和不良数据就麻烦了。它会让我们的数据分析变得困难,而且 UI 代码会变得极其复杂,因为它必须处理所有这些异常值,而无法对数据做出特定假设。

因此,我们在将数据插入数据库前,需要进行一个快速测试,确保数据在我们的合理预期內。

向 ContentProvider insert() 和 update() 方法添加完整性检查

ContentProvider 中有 query()、insert()、update() 和 delete()方法,但是查询/删除数据不需要对数据库进行任何更改,所以无需在这两个方法中添加任何检查。ContentProvider 可以视为警察,它负责允许或拒绝进入数据库的数据

ContentProvider 还有一个优点。如果没有它,我们就得在插入或更新数据的 UI 代码中的所有地方复制/粘贴同样的数据验证逻辑。当复制粘贴操作较多时,难免会引入错误。而且将来开发者可能会调整一个地方的数据验证代码,但意外的忘记了调整其它地方的代码。但是现在,所有的逻辑都可以集中在 ContentProvider 中,如果需要修改,我们只在一处修改即可。

private Uri insertPet(Uri uri, ContentValues contentValues) {
        // Check that the name is not null
        // 确定每种数据要求,例如不想让空名称进入数据库
        String name = contentValues.getAsString(PetEntry.COLUMN_PET_NAME);
        // 然后检查名称是否为空,为空就做出提示,如抛出一个异常。停止进入表格操作。
        if (name == null) {
            throw new IllegalArgumentException("Pet requires a name");
        }
        // Check that the gender is valid
        Integer gender = contentValues.getAsInteger(PetEntry.COLUMN_PET_GENDER);
        if (gender == null || !PetEntry.isValidGender(gender)) {
            throw new IllegalArgumentException("Pet requires valid weight");
        }

        // If the weight is provided, check that it's greater than or equal to 0 kg
        Integer weight = contentValues.getAsInteger(PetEntry.COLUMN_PET_WEIGHT);
        if (weight == null || weight < 0) {
            throw new IllegalArgumentException("Pet requires valid weight");
        }

        // No need to check the breed, any value is valid (including null).
        ...
}        

ContentProvider 的 Update() 方法

这里写图片描述

根据 UriMatcher 的结果,PETS 和 PETS_ID 都支持。在 PETS case 下,调用者想按照 selection 和 selectionArgs 更新 pets 表中的多个行。在 PETS_ID case 下,调用者想要更新特定的行。

虽然数据要求与 insert() 方法一样,但是仍然有一个关键差别。对于 insert() 方法,由于要插入的是全新宠物,所有属性(品种除外)都应提供。但对于 update() 方法,ContentValues 对象中不需要全部四个属性,可能只需更新一个或多个属性。在这种情况下,我们在检查值是否合理之前,使用 ContentValues.containsKey() 方法来检查键/值是否存在。

/**
     * Updates the data at the given selection and selection arguments, with the new ContentValues.
     */
    @Override
    public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
        final int match  = sUriMatcher.match(uri);
        switch (match) {
            case PETS:
                return updatePet(uri, contentValues, selection, selectionArgs);
            case PET_ID:
                // For the PET_ID code, extract out the ID from the URI,
                // so we know which row to update. Selection will be "_id=?" and selection
                // arguments will be a String array containing the actual ID.
                selection = PetEntry._ID + "=?";
                selectionArgs = new String[] {String.valueOf(ContentUris.parseId(uri))};
                return updatePet(uri, contentValues, selection, selectionArgs);
            default:
                throw new IllegalArgumentException("Update is not supported for " + uri);
        }
    }


    /**
     * Update pets in the database with the given content values. Apply the changes to the rows
     * specified in the selection and selection arguments (which could be 0 or 1 or more pets).
     * Return the number of rows that were successfully updated.
     */
    private int updatePet(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
        // If there are no values to update, then don't try to update the database
        if (contentValues.size() == 0) {
            return 0;
        }

        // If the {@link PetEntry#COLUMN_PET_NAME} key is present,
        // check that the name value is not null.
        if (contentValues.containsKey(PetEntry.COLUMN_PET_NAME)) {
            String name = contentValues.getAsString(PetEntry.COLUMN_PET_NAME);
            if (name == null) {
                throw new IllegalArgumentException("Pet requires a name");
            }
        }

        // If the {@link PetEntry#COLUMN_PET_GENDER} key is present,
        // // check that the gender value is valid.
        if (contentValues.containsKey(PetEntry.COLUMN_PET_GENDER)) {
            Integer gender = contentValues.getAsInteger(PetEntry.COLUMN_PET_GENDER);
            if (gender == null || !PetEntry.isValidGender(gender)) {
                throw  new IllegalArgumentException("Pet requires valid gender");
            }
        }

        // If the {@link PetEntry#COLUMN_PET_WEIGHT} key is present,
        // check that the weight value is valid.
        if (contentValues.containsKey(PetEntry.COLUMN_PET_WEIGHT)) {
            Integer weight = contentValues.getAsInteger(PetEntry.COLUMN_PET_WEIGHT);
            if (weight == null || weight < 0) {
                throw new IllegalArgumentException("Pet requires valid weight");
            }
        }

        // No need to check the breed, any value is valid (including null).

        // Otherwise, get writeable database to update the data
        SQLiteDatabase database = mDbHelper.getWritableDatabase();
        // Returns the number of database rows affected by the update statement
        return database.update(PetEntry.TABLE_NAME, contentValues, selection, selectionArgs);
    }

ContentProvider 的 Delete() 方法

这里写图片描述

UriMatcher 帮助确定执行这两种 case 中的哪一个。在 PETS case 中,调用者想要根据 selection 和 selectionArgs 删除 pets 表中的多个行。在 PET_ID case 中,调用者想要删除特定行。返回值为代表删除行编号的整数。

/**
     * Delete the data at the given selection and selection arguments.
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Get writeable database
        SQLiteDatabase database = mDbHelper.getWritableDatabase();
        final int match = sUriMatcher.match(uri);
        switch (match) {
            case PETS:
                // Delete all rows that match the selection and selection args
                return database.delete(PetEntry.TABLE_NAME, selection, selectionArgs);
            case PET_ID:
                // Delete a single row given by the ID in the URI
                selection = PetEntry._ID + "=?";
                selectionArgs = new String[] {String.valueOf(ContentUris.parseId(uri))};
                return database.delete(PetEntry.TABLE_NAME, selection, selectionArgs);
            default:
                throw new IllegalArgumentException("Deletion is not support for " + uri);
        }
    }

ContentProvider 的 GetType() 方法

实现此 [方法] 来处理给定 URI 的 MIME 数据类型请求。返回的 MIME 类型对于单个记录应以“vnd.android.cursor.item” 开头,多个项应以“vnd.android.cursor.dir/”开头。

添加步骤:

步骤一:在 PetContract 中声明 MIME 类型常数

 /**
         * The MIME type of the {@link #CONTENT_URI} for a list of pets.
         *
         * */
        public static final String CONTENT_LIST_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" +
                CONTENT_AUTHORITY + "/" + PATH_PETS;

        /**
         * The MIME type of the {@link #CONTENT_URI} for a single pet.
         *
         * */
        public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" +
                CONTENT_AUTHORITY + "/" + PATH_PETS;

步骤二:实现 ContentProvider getType() 方法

/**
     * Returns the MIME type of data for the content URI.
     */
    @Override
    public String getType(Uri uri) {
        final int match = sUriMatcher.match(uri);
        switch (match) {
            case PETS:
                return PetEntry.CONTENT_LIST_TYPE;
            case PET_ID:
                return PetEntry.CONTENT_ITEM_TYPE;
            default:
                throw new IllegalArgumentException("Unkonwn URI " + uri + " with match " + match);

        }
    }

猜你喜欢

转载自blog.csdn.net/modurookie/article/details/80867912