全面总结之 ContentProvider 篇

有些时候,我们的程序需要去读取其他程序的数据,或者将自己的数据共享给其他程序,我们可以使用一个共享文件,但这个方法并不推荐大家使用,Android 系统有更为完善的内容共享机制:ContentProvider,它可以保证自己的数据安全的被允许的其他程序所访问。

如果您想要访问其他程序提供的数据,可以将应用的 Context 中的 ContentResolver 对象作为客户端与提供者进行通信。ContentResolver 对象会与提供者对象(即实现 ContentProvider 的类的实例)通信。提供程序对象从客户端接收请求,并返回结果。

如果我们的程序没有向外接提供数据的需求,那么就不需要开发自己的 ContentProvider。但是,如果您需要通过自己的 ContentProvider 在您自己的程序内提供自定义搜索建议、如果您想将复杂的数据或文件从您的应用复制并粘贴到其他应用中,也需要创建您的 ContentProvider。

Android 本身包含的 ContentProvider 可以管理音频、视频、图像和电话短信等数据。android.provider 软件包参考文档中列出了部分提供程序。任何 Android 程序都可以访问这些 ContentProvider。

如何使用系统提供给我们的 ContentProvider?

ContentProvider 以一个或多个表的形式将数据展现给外部,我们通过 ContentResolver 对象对提供给我们的 ContentProvider 进行查询并获取结果。

使用 ContentResolver 访问 ContentProvider

我们通过 ContentResover 去访问别的程序提供的 ContentProvider,通过调用 getContentResolver() 方法来获取 ContentResolver 对象,该对象提供一系列 “CRUD” 方法:

  • insert,用于添加数据。

  • delete,用于删除数据。

  • query,用于查询数据。

  • update,用于修改数据。

可以看到,ContentResolver 的操作方法和 SQLite 有些类似,不同的是,ContentResolver 不接收表名参数,需要通过 URI 来确定我们要操作的数据。

我们以通讯录为例来演示 ContentResolver 的相关操作:

准备工作

我们要操作通讯录,需要先明白通讯录的数据库结构:

Android 通讯录的数据库都放在 contacts2.db 这个数据库中,其路径是:/data/data/com.Android.providers.contacts/databases/contacts2.db

我们的联系人数据并不是一条一条完整的存在某个表中的,而是一条联系人数据分别存储在若干个表中,然后这些表相互关联,所以在进行一些联系人操作的时候,需要进行多个表关联操作,我们先看一下这些表:

我们先新建一个联系人:

姓名:JACK
电话:13111111111
住宅电话:13222222222
邮箱:123@456.com

mimetypes

这个表就相当于字典,每个 _id 对应一个 mimetype,这些在其他表中会用到。

data

Data 表存储了联系人的详细信息,表中的每一行存储一个特定类型的信息,比如 Email、Address 或 Phone。每一行通过一个mimetype_id 的字段来表示该行存储的是什么类型的数据,该字段引用了 mimetyps 表。其中 raw_contact_id 与下面的 raw_contact 表中的 _id 相对应。

字段含义如下:

mimetype_id:表示该行存储的信息的类型(和 mimetype 对应)

raw_contact_id:表示该行所属的 RawContact

is_primary:多个data数据组成一个 raw contact,该字段表示此data是否是其所属的 raw contact 的主 data,即其 display name 会作为 raw contact 的 display name

is_super_primary:该 data 是否是其所属的 contact 的主 data,如果 is_super_primary 为 1 则 is_primary 一定为 1

data1~data15:15 个数据字段,对于不同类型的信息,表示不同的含义,ContactsContract.CommomDataKinds 类中定义了与常用的数据类型相对应的一些类,这些类中分别定义了相应数据类型中这些字段表示的含义。一般 data1 表是主信息(如电话,Email 地址等),data2 表示副信息,data15 表示 Blob 数据。

data_sync1~data_sync4:sync_adapter 要用的字段(sync_adapter 用于数据的同步,比如你手机中的 Gmail 帐户与 Google 服务器的同步)。

data_version:数据的版本,用于数据的同步。

raw_contacts

该表保存了所有创建过的手机联系人,每个联系人占一行,表里的 deleted 列表示该联系人是否已经删除,有一个字段是 contact_id,该字段的值与下面 contents 表中的 _id 以及 data 表中的 raw_contact_id 一致。

contacts
该表保存了所有的手机测联系人,每个联系人占一行,该表保存了联系人的 ContactID、联系次数、最后一次联系的时间、是否含有号码、是否被添加到收藏夹等信息。这个表中的 name_raw_contact_id 就与上面的 raw_contacts 表中的 _id 相对应。

添加联系人

了解了各个表结构之后,我们就可以开始我们的通讯录操作了,先开始给通讯录中添加一个联系人,也就是相对应的“增”操作。

一般情况下,我们是先向 raw_contacts 插入一个空值,然后就可以获取一个 contact_id,该值正好是 data 表中 raw_contact_id 字段的值。

data 表中,每一行都是一个信息,有的行存的是姓名,有的行存的是邮箱,有的行存的是电话,这些信息使用 mimetype 加以区分,使用 raw_contact_id 字段加以分组。

了解了这些,我们开始插入一个联系人吧!let's go!

    private void insertContact() {
        Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
        ContentValues values = new ContentValues();
        ContentResolver resolver = getContentResolver();
        Uri rawContactsUri = resolver.insert(uri, values);//向 raw_contacts 表中添加一条空数据,insert 方法返回一个 URI,该 URI 可以用来获取 contact_id

        long contactId = ContentUris.parseId(rawContactsUri);// 获取 contact_id

        //向 data 表中添加信息
        uri = Uri.parse("content://com.android.contacts/data");

        //添加姓名
        values.clear();
        values.put("raw_contact_id", contactId);
        values.put("mimetype", "vnd.android.cursor.item/name");
        values.put("data1", "孙悟空");
        values.put("data2", "孙悟空");
        resolver.insert(uri, values);

        //添加电话号码
        values.clear();
        values.put("raw_contact_id", contactId);
        values.put("mimetype", "vnd.android.cursor.item/phone_v2");
        values.put("data1", "110");
        values.put("data2", "110");
        resolver.insert(uri, values);

        //添加邮箱
        values.clear();
        values.put("raw_contact_id",contactId);
        values.put("mimetype", "vnd.android.cursor.item/email_v2");
        values.put("data1","swk@sina.com");
        values.put("data2","swk@sina.com");
        resolver.insert(uri,values);

    }

篇幅问题,省略了一些代码,只列出了插入操作的代码,当然,还需要在清单文件中添加权限信息:

<uses-permission android:name="android.permission.WRITE_CONTACTS"/>

Bingo,添加成功~

查找联系人

查找使用 query 方法,query 方法有如下两种调用方式:

  • query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal)

  • query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

我们日常使用的一般都是第二种,我们看一下 query 方法和 SQL 语句的对比:

query() 参数SELECT 关键字/参数说明
UriFROM table_nameUri 映射至提供程序中名为 table_name 的表。
projectioncol,col,col,...projection 是应该为检索到的每个行包含的列的数组。
selectionWHERE col = valueselection 会指定选择行的条件。
selectionArgs(没有完全等效项。选择参数会替换选择子句中 ? 占位符。)
sortOrderORDER BY col,col,...sortOrder 指定行在返回的 Cursor 中的显示顺序。

譬如我们手机通讯录里有两个叫 JACK 的人,我们来获取一下他们的电话,我们先从 raw_contacts 表中获取到叫 JACK 的人的 id,然后再在 data 表中获取他们的电话:

    private void selectPhoneNum() {
        ContentResolver resolver = getContentResolver();
        Cursor query = null;
        List<String> list = new ArrayList<String>();

        //先查找出 JACK 的 contact_id
        query = resolver.query(Uri.parse("content://com.android.contacts/raw_contacts"), new String[]{"contact_id","display_name"}, "display_name=?", new String[]{"JACK"}, null);
        query.moveToFirst();

        do{
            String contact_id = query.getString(query.getColumnIndex("contact_id"));
            list.add(contact_id);
        }while(query.moveToNext());

        Log.d("TTT","list size:" + list.size());

        //因为有多个 JACK,所以需要分别通过 contact_id 查找电话号码
        for (String contactID : list) {
            query = resolver.query(Uri.parse("content://com.android.contacts/data"), new String[]{"data1"}, "raw_contact_id=? and mimetype_id=?", new String[]{contactID, "5"}, null);
            query.moveToFirst();
            do {
                String phoneNum = query.getString(query.getColumnIndex("data1"));
                Log.d("TTT","JACK " + contactID +" 的电话号码是:" + phoneNum);
            }while (query.moveToNext());
        }

    }

当然有时候我们还需要对查询出来的结果做非空判断等等,但我们这里只讲解 query 的用法。

删除联系人

删除使用的是 delete 方法,该方法有三个参数:

  • URI uri

  • String where

  • String[] selectionArgs

很好理解了,uri 来确定我们要操作的表,where 相当于 SQL 语句中的条件,selectionArgs 是条件的值,我们来删除 JACK 的一个电话

    private void delContact() {
        ContentResolver resolver = getContentResolver();
        int display_name = resolver.delete(Uri.parse("content://com.android.contacts/raw_contacts"), "display_name=? and contact_id=?", new String[]{"JACK","1"});
    }

修改联系人

修改使用的是 update 方法,该方法有四个参数:

  • URI uri

  • ContentValues values

  • String where

  • String[] selectionArgs

每个参数的含义也没啥好讲的,看代码,我们将通讯录中的 JACK 改为 杰克:

    private void updateContact() {
        ContentResolver resolver = getContentResolver();
        ContentValues values = new ContentValues();
        values.put("data1","杰克");
        resolver.update(Uri.parse("content://com.android.contacts/data"),values,"data1=? and raw_contact_id=?",new String[]{"JACK","3"});
    }

系统常用 URI

之前我们的 CRUD 操作都是直接使用 Uri.parse(xxx) 来获取 URI,实际上,Android 系统为我们提供了一些常用的 URI 供我们直接使用:

关于联系人的 URI:

  • 管理联系人的 Uri:ContactsContract.Contacts.CONTENT_URI

  • 管理联系人的电话的 Uri:ContactsContract.CommonDataKinds.Phone.CONTENT_URI

  • 管理联系人的Email的 Uri:ContactsContract.CommonDataKinds.Email.CONTENT_URI

注:
Contacts 有两个表,分别是 rawContact 和 Data,rawContact 记录了用户的 id 和 name。
其中id栏名称为:ContactsContract.Contacts._ID。
name名称栏为ContactContract.Contacts.DISPLAY_NAME。
电话信息表的外键id为ContactsContract.CommonDataKinds.Phone.CONTACT_ID。
电话号码栏名称为:ContactsContract.CommonDataKinds.Phone.NUMBER。
data表中Email地址栏名称为:ContactsContract.CommonDataKinds.Email.DATA。
其外键栏为:ContactsContract.CommonDataKinds.Email.CONTACT_ID)。

关于多媒体的 URI:

  • 存储在sd卡上的音频文件:MediaStore.Audio.Media.EXTERNAL_CONTENT_URI

  • 存储在手机内部存储器上的音频文件:MediaStore.Audio.Media.INTERNAL_CONTENT_URI

  • SD卡上的图片文件内容:MediaStore.Audio.Images.EXTERNAL_CONTENT_URI

  • 手机内部存储器上的图片:MediaStore.Audio.Images.INTERNAL_CONTENT_URI

  • SD卡上的视频:MediaStore.Audio.Video.EXTERNAL_CONTENT_URI

  • 手机内部存储器上的视频:MediaStore.Audio.Video.INTERNAL_CONTENT_URI

注:
图片的显示名栏:Media.DISPLAY_NAME
图片的详细描述栏为:Media.DESCRIPTION
图片的保存位置:Media.DATA

关于短信的 URI:

  • 短信URI:Content://sms

  • 发送箱中的短信URI:Content://sms/outbox

  • 收信箱中的短信URI:Content://sms/sent

  • 草稿中的短信URI:Content://sms/draft

创建我们自己的 ContentProvider

假如我们的程序也需要对外提供数据,那么就需要实现自己的 ContentProvider 了,实现过程十分简单,只需要让我们的类去继承 ContentProvider,然后实现几个方法就可以了:

  • onCreate()

  • query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

  • insert(Uri uri, ContentValues values)

  • delete(Uri uri, String selection, String[] selectionArgs)

  • update(Uri uri, ContentValues values, String selection, String[] selectionArgs)

  • getType(Uri uri)

是不是有的看起来很眼熟?还是增删查改那四个方法,再说一下 onCreate 和 getType

onCreate 方法中我们一般进行数据库的创建或者升级,返回 true 则表示我们的 ContentProvider 对象创建成功,返回 false 则表示创建失败。

另外,只有当别的 ContentResolver 访问我们的 ContentProvider 的时候,该方法才会执行去创建 ContentProvider 对象。

getType 方法会根据传入的内容 URI 来返回相应的 MIME 类型。

先抛开 getType 不讲,我们先写一个简单的 ContentProvider 并且用客户端去读取它:

ContentProvider :

DBHelper:

public class DBHelper extends SQLiteOpenHelper {

    private String sql = "CREATE TABLE t_shopping ( _id  INTEGER primary key autoincrement, product_name TEXT)";

    public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}

MyProvider:

public class MyProvider extends ContentProvider {

    private DBHelper helper = null;
    private SQLiteDatabase database = null;

    @Override
    public boolean onCreate() {
        helper = new DBHelper(getContext(), "t_shopping", null, 1);
        database = helper.getWritableDatabase();
        return true;
    }


    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

        String tableName = uri.getPath().replace("/", "");
        Cursor query = database.query(tableName, null, null, null, null, null, null);
        return query;
    }


    @Override
    public String getType(Uri uri) {
        return null;
    }


    @Override
    public Uri insert(Uri uri, ContentValues values) {
        long row = database.insert(uri.getPath().replace("/",""), "_id", values);

        if(row>0){
            Uri noteUri = ContentUris.withAppendedId(Uri.parse("content://com.stone.shopping/item"),row);
            return noteUri;
        }

        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int delete = database.delete(uri.getPath().replace("/", ""), selection, selectionArgs);
        return delete;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        int update = database.update(uri.getPath().replace("/", ""), values, selection, selectionArgs);
        return update;
    }
}

AndroidManifest

        <provider
            android:authorities="com.stone.shopping"
            android:name=".MyProvider"
            android:exported="true"></provider>

ContentResolver:
MainActivity:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText productName,oldProductName,newProductName;
    private Button insert_bt, delete_bt, query_bt, update_bt;

    private ContentResolver resolver;

    private ContentValues values = new ContentValues();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        resolver = getContentResolver();

        productName = (EditText) findViewById(R.id.et);
        oldProductName = (EditText) findViewById(R.id.old_product_name);
        newProductName = (EditText) findViewById(R.id.new_product_name);
        insert_bt = (Button) findViewById(R.id.insert_bt);
        delete_bt = (Button) findViewById(R.id.delete_bt);
        query_bt = (Button) findViewById(R.id.query_bt);
        update_bt = (Button) findViewById(R.id.update_bt);

        insert_bt.setOnClickListener(this);
        delete_bt.setOnClickListener(this);
        query_bt.setOnClickListener(this);
        update_bt.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.insert_bt:
                values.clear();
                values.put("product_name", productName.getText().toString());
                Uri insert = resolver.insert(Uri.parse("content://com.stone.shopping/t_shopping"), values);
                if (insert != null) {
                    Toast.makeText(this, "插入成功", Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.delete_bt:
                int delete = resolver.delete(Uri.parse("content://com.stone.shopping/t_shopping"), "product_name=?", new String[]{productName.getText().toString()});

                if (delete > 0) {
                    Toast.makeText(this, "删除成功", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(this, "删除失败", Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.query_bt:
                Cursor query = resolver.query(Uri.parse("content://com.stone.shopping/t_shopping"), null, null, null, null);
                query.moveToFirst();
                do {
                    Log.d("TTT","id:" + query.getInt(query.getColumnIndex("_id")) + "   ProductName:" + query.getString(query.getColumnIndex("product_name")));
                }while(query.moveToNext());
                break;
            case R.id.update_bt:
                values.clear();
                values.put("product_name",newProductName.getText().toString());
                int update = resolver.update(Uri.parse("content://com.stone.shopping/t_shopping"), values, "product_name=?", new String[]{oldProductName.getText().toString()});
                if(update>0){
                    Toast.makeText(this,"修改成功",Toast.LENGTH_SHORT).show();
                }else{
                    Toast.makeText(this,"修改失败",Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }
}

布局文件:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/insert_bt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="插入" />

    <Button
        android:id="@+id/delete_bt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="删除" />

    <Button
        android:id="@+id/query_bt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="查找" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="将" />

    <EditText
        android:id="@+id/old_product_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="修改为:" />

    <EditText
        android:id="@+id/new_product_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/update_bt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="修改" />

</LinearLayout>

然后我们就可以在客户端中进行增删查改操作了。


在上面的例子中,我们都是通过一个 URI 去确定我们要操作的资源,我们简单粗暴的指定了一个 URI,系统也没有对 URI 做任何的处理。实际上,从数据的安全性以及操作的便利性来讲,我们还需要对 URI 进行一些处理。

我们知道 URI 大致可以分为 协议authorities资源三个部分组成,例如:

  • content://aaa.bbb.ccc/ddd 表示要访问 ddd 中的全部数据

  • content://aaa.bbb.ccc/ddd/3 表示要访问 ddd 中 id 为 3 的数据

  • content://aaa.bbb.ccc/ddd/3/eee 表示要访问 ddd 中 id 为 3 的 eee 字段

既然 URI 可以这样写,那么我们之前那么简单粗暴的写一个 URI 就显得有些不太合适了。

Android 提供了 UriMather 工具类来帮助我们进行 URI 处理,他主要有以下两个方法:

  • addUri(String authority, String path, int code)
    该方法用于想 UriMatcher 对象注入 Uri,其中 authoritypath 组成一个 Uri,而 code 则代表该 Uri 对应的标识码。

  • match(Uri uri)
    根据前面注册的 Uri 来判断指定 Uri 对应的标识码。如果找不到匹配的标识码,该方法将会返回 -1

我们先来验证一下这两个方法:

                UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
                matcher.addURI("com.stone.shopping", "t_shopping", 1);
                matcher.addURI("com.stone.shopping", "t_shopping/#", 2);
                matcher.addURI("com.stone.shopping", "t_shopping/#/*", 3);

                Uri uri1 = Uri.parse("content://com.stone.shopping/t_shopping");
                Uri uri2 = Uri.parse("content://com.stone.shopping/t_shopping/3");
                Uri uri3 = Uri.parse("content://com.stone.shopping/t_shopping/4/product_name");

                Log.d("TTT", "matchCode = " + matcher.match(uri1)); //输出1
                Log.d("TTT", "matchCode = " + matcher.match(uri2));//输出2
                Log.d("TTT", "matchCode = " + matcher.match(uri3));//输出3

除了 UriMatcher 之外,Android 还提供了一个 ContentUris,它是一个操作 Uri 字符串的工具类,提供了如下两个工具方法:

  • withAppendedld(uri, id)
    用于为路径加上 ID 部分
    例如:
Uri uri = Uri.parse("content://aa.bb.cc.dd/ee");
Uri resultUri = ContentUris.withAppendedId(uri, 2);
// 生成后的 Uri 为:"content://aa.bb.cc.dd/ee/2"
  • parseld(uri)
    用于从指定 URI 中解析出包含的 ID 值
    例如:
Uri uri = Uri.parse("content://aa.bb.cc.dd/ee/2");
long id = ContentUris.parseId(uri);
// 获取到的 id 为 2

在有了这两个工具类之后,我们是不是就可以优化一下我们的 ContentProvider 呢?单独把 query 方法拿出来演示一下:

    static{
        matcher = new UriMatcher(UriMatcher.NO_MATCH);
        matcher.addURI("com.stone.shopping","t_shopping",1);
        matcher.addURI("com.stone.shopping","t_shopping/#",2);
        matcher.addURI("com.stone.shopping","t_shopping/#/*",3);
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

        Cursor query = null;
        switch (matcher.match(uri)){
            case 1:
                query = database.query("t_shopping", null, null, null, null, null, null);
                break;
            case 2:
                long id = ContentUris.parseId(uri);
                query = database.query("t_shopping",null,"_id=?",new String[]{id+""},null, null, null);
                break;
            case 3:
                List<String> pathSegments = uri.getPathSegments();
                query = database.query("t_shopping", new String[]{pathSegments.get(2)},"_id=?",new String[]{pathSegments.get(1)},null, null, null);
                break;
            default:
                //可以在这里
        }
        return query;
    }

说了半天也没有说道 getType 方法,那么该方法有什么用呢?

官方的定义是:

Implement this to handle requests for the MIME type of the data at the given URI. The returned MIME type should start with vnd.android.cursor.item for a single record, or vnd.android.cursor.dir/ for multiple items. This method can be called from multiple threads, as described in Processes and Threads.

Note that there are no permissions needed for an application to access this information; if your content provider requires read and/or write permissions, or is not exported, all applications can still call this method regardless of their access permissions. This allows them to retrieve the MIME type for a URI when dispatching intents.

大致翻译就是,该方法是用来返回给定 URI 的 MIME 类型的,谷歌要求返回的 MIME 格式为:

  • 如果是单个记录,是:vnd.android.cursor.item/xxx(xxx为自定义字符串)

  • 如果是多条记录,是:vnd.android.cursor.dir/xxx(xxx为自定义字符串)

/ 前面的部分就相当于一个“数据类型”,/ 后面的部分就是我们自定义的变量名了。

之前我们的增删查改四个方法中都没有用到 getType 方法,那么这个方法在什么时候被调用呢?事实上,这个方法只有在 ContentResolver 调用 getType 方法的时候,才会执行,那么这个方法的具体作用是什么呢?

查遍了资料,解释不一,我也有点儿懵逼,就谈一下我的理解:

我的理解就是,这个方法没啥卵用!

这个方法只是返回我们传入的 URI 的 MIME 类型,并且官方规定了格式,我们可以在 query 方法中这样写:

public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        String type = getType(uri);
        if (type.equals("vnd.android.cursor.dir/xxx")) {
            //多行数据类型
        }else if (type.equals("vnd.android.cursor.item/xxxx")) {
            //单行数据类型
        }
        return nnnn;
    }

但其实没多大作用,我们的 URI 可以很明确的指定我们要查询的是多条还是多条,所以说没多大意义!

不过在搜索的时候,有一个帖子角度挺独特的,大家可以参考一下:

是否需要覆盖ContentProvider的getType方法?

ContentObserver 监控 ContentProvider 的变化

为了监听 ContentProvider 的数据变化,Android 提供了 ContentObserver 来帮助我们去捕捉数据库的变化,继而做出一些相应的处理。


ContentObserver 的使用方法

ContentResolver 提供了 regsterContentObserver 方法来帮助我们注册监听器,该方法有三个参数:

  • Uri uri:该监听器所监听的 ContentProvider 的 Uri。

  • boolean notifyDescendents:

    • 如果该参数为 true,假如注册监听的 URI 为 content://abc,那么 URI 为 content://abc/defcontent://abc/def/ghi 的数据发生改变时该监听器都会被触发。

    • 如果该参数为 false,只有 content://abc 的数据发生改变时候该监听器才会被触发。

  • ContentObserver observer:我们的监听器实例。

剩下的我们只需要实现 ContentObserver 就可以了,让我们的类继承自 ContentObserver 并且实现其 onChange 方法就可以了,我们在 onChange 方法中执行当我们监听到变化之后要执行的东西。

例子,监听手机发件箱:

public class MainActivity extends Activity { 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 

        getContentResolver().registerContentObserver(Uri.parse("content://sms"), true, new MyOberver(new Handler())); 
    } 

    // 自定义ContentObserver监听器 
    class MyOberver extends ContentObserver { 

        public MyOberver(Handler handler) { 
            super(handler); 
            // TODO Auto-generated constructor stub 
        } 

        @Override 
        public void onChange(boolean selfChange) { 
            // TODO Auto-generated method stub 
            super.onChange(selfChange); 
            Cursor cursor = getContentResolver().query(Uri.parse("content://sms/outbox"), null, null, null, null); 
            while (cursor.moveToNext()) { 
                Log.d("TTTT", "发送内容:" + cursor.getString(cursor.getColumnIndex("body"))); 
            } 
        } 
    } 
} 

参考资料

Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1