Android ContentProvider全面解析

概述

ContentProvider为存储和获取数据提供统一的接口,它可以在不同的应用程序之间共享数据,适合IPC通信。ContentProvider底层实现也是Binder,但是使用起来比AIDL要容易许多。系统也预制了很多的ContentProvider,例如通讯录,音视频等。
下面从ContentProvider设计者的角度来讲讲两个不同应用间数据库共享问题。

两应用间如何通信

最先想到的方法应该是下面这张图这样的,自身应用写好各个数据库接口,供其它应用调用:
这里写图片描述

ContentProvider与对应的URI

ContentProvider的URI就是下面这个形式的:
这里写图片描述

主要分三个部分:scheme, authority and path。scheme表示上图中的content://,authority表示B部分,path表示C和D部分。

  • A部分:表示是一个Android内容URI,说明由ContentProvider控制数据,该部分是固定形式,不可更改的。
  • B部分:是URI的授权部分,是唯一标识符,用来定位ContentProvider。格式一般是自定义ContentProvider类的完全限定名称,注册时需要用到,如:com.example.transportationprovider
  • C部分和D部分:是每个ContentProvider内部的路径部分,C和D部分称为路径片段,C部分指向一个对象集合,一般用表的名字,如:/trains表示一个笔记集合;D部分指向特定的记录,如:/trains/122表示id为122的单条记录,如果没有指定D部分,则返回全部记录。

这个URI就是用来进行全局匹配的,那AndroidManifest.xml里又要怎么写呢?

<provider  
    android:name=".NoteContentProvider"  
    android:authorities="com.example.transportationprovider"  
    android:exported="true"/>

这里的android:authorities必须与上面URI中的B部分一样,因为这个就是用来全局匹配的authority,只有URI的authority与provider的android:authorities匹配上了,才会进入后面的操作。
ContentURI的全局匹配流程图:
这里写图片描述

通过第一步和第二步,当匹配成功ContentProvider以后,就开始进入我们指定的ContentProvider进行处理;大家估计也注意到了,我们的ContentURI的完整部分为:content://com.example.transportionprovider/trains/122
到第二步,我们已经匹配到了content://com.example.transportionprovider,那后面的/trains/122的匹配工作就只有交由ContentProvider来处理了。
在ContentProvder中的匹配是利用UriMatcher来完成的!

UriMatcher

UriMatcher的匹配工作的第一步就是先将所需要的匹配的URI使用addURI()添加到UriMatcher中

public void addURI(String authority, String path, int code)
  • authority:就是URI对应的authority
  • path:就是我们在URI中 authority后的那一串
  • code:表示匹配成功以后的返回值;

下面以我们的URI=content://com.example.transportionprovider/trains/122来演示一下

public static final String AUTHORITY = "com.alexzhou.provider.NoteProvider";  
private static final UriMatcher sUriMatcher;  
static {  
    sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); //如果匹配不成功,返回UriMatcher.NO_MATCH无匹配
    sUriMatcher.addURI(AUTHORITY, "trains", 1);  
    sUriMatcher.addURI(AUTHORITY, "trains/#", 2);  
} 
  • #表示匹配任意数字ID
  • *表示匹配任意文本

所以这句的意思就是匹配content://com.example.transportionprovider/trains/任意数字ID,比如我们的content://com.example.transportionprovider/trains/122

ContentProvider和ContentResolver实例分析

ContentProvider提供数据库查询接口

1、利用SQLiteOpenHelper创建数据库、数据表

在这里,我们在一个数据库(“watson.db”)中创建两个数据表”first”与”second”;每个表都有多出一个字段“table_name”,来保存当前数据表的名称。
代码如下:

public class DatabaseHelper extends SQLiteOpenHelper {

    public static final String DATABASE_NAME = "watson.db";
    public static final int DATABASE_VERSION = 1;
    public static final String TABLE_FIRST_NAME = "first";
    public static final String TABLE_SECOND_NAME = "second";
    public static final String SQL_CREATE_TABLE_FIRST = "CREATE TABLE " +TABLE_FIRST_NAME +"("
            + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
            + "table_name" +" VARCHAR(50) default 'first',"
            + "name" + " VARCHAR(50),"
            + "detail" + " TEXT"
            + ");" ;
    public static final String SQL_CREATE_TABLE_SECOND = "CREATE TABLE "+TABLE_SECOND_NAME+" ("
            + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
            + "table_name" +" VARCHAR(50) default 'second',"
            + "name" + " VARCHAR(50),"
            + "detail" + " TEXT"
            + ");" ;

    public DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

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

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS first");
        db.execSQL("DROP TABLE IF EXISTS second");
        onCreate(db);
    }
}

2、利用ContentProvider提供数据库操作接口

新建一个类MyContentProvider,派生自ContentProvider基类,写好之后,就会自动生成query(),insert(),update(),delete()和getType()方法;这些方法就是根据传过来的URI来操作数据库的接口。此时的MyContentProvider是这样的:

public class MyContentProvider extends ContentProvider {  
    @Override  
    public boolean onCreate() {  
        return false;  
    }  

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

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

    @Override  
    public Uri insert(Uri uri, ContentValues values) {  
        return null;  
    }  

    @Override  
    public int delete(Uri uri, String selection, String[] selectionArgs) {  
        return 0;  
    }  

    @Override  
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {  
        return 0;  
    }  
}  

3、使用UriMatcher匹配URI

public class MyContentProvider extends ContentProvider {  
    private static final UriMatcher sUriMatcher;  
    private static final int MATCH_FIRST = 1;  
    private static final int MATCH_SECOND = 2;  
    public static final String AUTHORITY = "com.hx.contentprovider.MyContentProvider";  
    public static final Uri CONTENT_URI_FIRST = Uri.parse("content://" + AUTHORITY + "/first");  
    public static final Uri CONTENT_URI_SECOND = Uri.parse("content://" + AUTHORITY + "/second");  

    static {  
        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);  
        sUriMatcher.addURI(AUTHORITY, "first", MATCH_FIRST);  
        sUriMatcher.addURI(AUTHORITY, "second", MATCH_SECOND);  
    }  

    private DatabaseHelper mDbHelper;  

    @Override  
    public boolean onCreate() {  
        mDbHelper = new DatabaseHelper(getContext());  
        return false;  
    }  
    ...  
}  

我们要做一个应用:外部可以使用两个URI传来给我们,当传来content://com.hx.contentprovider.MyContentProvider/first时,就操作first数据库;如果传来content://com.hx.contentprovider.MyContentProvider/second时,就操作second数据库。

4、insert方法

@Override  
public Uri insert(Uri uri, ContentValues values) {  
    SQLiteDatabase db = mDbHelper.getWritableDatabase();  
    switch (sUriMatcher.match(uri)){  
        case MATCH_FIRST:{  
            long rowID = db.insert(DatabaseHelper.TABLE_FIRST_NAME, null, values);  
            if(rowID > 0) {  
                Uri retUri = ContentUris.withAppendedId(CONTENT_URI_FIRST, rowID);  
                return retUri;  
            }  
        }  
        break;  
        case MATCH_SECOND:{  
            long rowID = db.insert(DatabaseHelper.TABLE_SECOND_NAME, null, values);  
            if(rowID > 0) {  
                Uri retUri = ContentUris.withAppendedId(CONTENT_URI_SECOND, rowID);  
                return retUri;  
            }  
        }  
        break;  
        default:  
            throw new IllegalArgumentException("Unknown URI " + uri);  
    }  
    return null;  
}  

首先,利用UriMatcher.match(uri)来匹配到来的URI,如果这个URI与com.hx.contentprovider.MyContentProvider/frist匹配,就将数据键值对(values)插入到first表中。插入之后,会得到新插入记录当前所在行号,然后将行号添加到URI末尾,做为结果返回。

5、update()方法

@Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        SQLiteDatabase db = mDbHelper.getWritableDatabase();
        int count = 0;
        switch (sUriMatcher.match(uri)) {
            case MATCH_FIRST:
                count = db.update(DatabaseHelper.TABLE_FIRST_NAME, values, selection, selectionArgs);
                break;
            case MATCH_SECOND:
                count = db.update(DatabaseHelper.TABLE_SECOND_NAME, values, selection, selectionArgs);
                break;

            default:
                throw new IllegalArgumentException("Unknow URI : " + uri);
        }
        this.getContext().getContentResolver().notifyChange(uri, null); //更新数据时,通知其他ContentObserver
        return count;
    }

注意最后调用getContentResolver().notifyChange(uri, null);来通知ContentObserver当前的数据库有改变,让监听这个数据库的所有应用执行对应的操作,有关共享数据库的变量监听与响应的问题,我们会在后面讲。

6、query() 方法

@Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
        switch (sUriMatcher.match(uri)) {
            case MATCH_FIRST:
                // 设置要查询的表
                queryBuilder.setTables(DatabaseHelper.TABLE_FIRST_NAME);
                break;

            case MATCH_SECOND:
                queryBuilder.setTables(DatabaseHelper.TABLE_SECOND_NAME);
                break;

            default:
                throw new IllegalArgumentException("Unknow URI: " + uri);
        }

        SQLiteDatabase db = mDbHelper.getReadableDatabase();
        Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, null);
        return cursor;
    }

7、delete()方法

@Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = mDbHelper.getWritableDatabase();
        int count = 0;
        switch (sUriMatcher.match(uri)) {
            case MATCH_FIRST:
                count = db.delete(DatabaseHelper.TABLE_FIRST_NAME, selection, selectionArgs);
                break;

            case MATCH_SECOND:
                count = db.delete(DatabaseHelper.TABLE_SECOND_NAME, selection, selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unknow URI :" + uri);

        }
        //更新数据时,通知其他ContentObserver
        this.getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

8、getType()

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

这个函数,我们这里暂时用不到,直接返回NULL,后面我们会专门来讲这个函数的作用与意义。

9、AndroidManifest.xml中声明provider

<provider              
     android:authorities="com.hx.contentprovider.MyContentProvider"
     android:name=".MyContentProvider"
     android:exported="true"/>

第三方应用通过ContentResolver操作共享数据库

Studio新建一个Module叫做ThirdApp,布局如下:
这里写图片描述
最上头有两个按钮,用来切换当前使用哪个URI来增、删、改、查操作,因为不同的URI会操作不同的数据表。

    public static final String AUTHORITY = "com.hx.contentprovider.MyContentProvider";
    public static final Uri CONTENT_URI_FIRST = Uri.parse("content://" + AUTHORITY + "/first");
    public static final Uri CONTENT_URI_SECOND = Uri.parse("content://" + AUTHORITY + "/second");
    public static Uri mCurrentURI = CONTENT_URI_FIRST; //默认first表

1、insert()插入操作

Uri insert(Uri url, ContentValues values) /**参数设置*/
    private void insert() {
        ContentValues values = new ContentValues();
        values.put("name", "jack");
        values.put("detail", "a Java Developer");
        Uri uri = this.getContentResolver().insert(mCurrentURI, values);
        showlog(uri.toString());
    }

2、update()更新操作

int update(Uri uri, ContentValues values, String where, String[] selectionArgs) //参数设置
    private void update() {
        ContentValues values = new ContentValues();
        values.put("detail", "a excelent Java Developer");
        int count = this.getContentResolver().update(mCurrentURI, values, "name = ?", new String[] {"jack"});
        showlog("count=" + count);
        query(); //再次查询看结果
    }

3、query()——查询操作

Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)  //参数设置
    private void query() {
        Cursor cursor = this.getContentResolver().query(mCurrentURI, null, null, null, null);
        showlog("count=" + cursor.getCount());
        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            String table = cursor.getString(cursor.getColumnIndex("table_name"));
            String name = cursor.getString(cursor.getColumnIndex("name"));
            String detail = cursor.getString(cursor.getColumnIndex("detail"));
            showlog("table_name:" + table);
            showlog("name: " + name);
            showlog("detail: " + detail);
            cursor.moveToNext();
        }
        cursor.close();
    }

这里其实只用一句话,就完成的查询操作,由于没有加任何的限定语句,所以是查询出此数据表中的所有记录。

4、delete()删除操作

int delete(Uri url, String where, String[] selectionArgs) //参数设置
    private void delete() {
        int count = this.getContentResolver().delete(mCurrentURI, "_id = 1", null);
        showlog("count=" + count);
        query(); //再次查询看结果
    }

结果

1、我们先运行ContentProvider对应的APP:ContentProvider,这是提供共享数据库接口的APP。然后再运行ThirdApp,这是第三方通过URI来操作数据库的APP。
2、选择Uri为content://com.hx.contentprovider.MyContentProvider/second 来操作ContentProvider的数据库;
3、点两下insert按钮,看返回的URI,在每个URI后都添加上了当前新插入记录的行号,结果如下:
这里写图片描述
4、然后点击查询按钮——query()
由于我们的URI是针对second记录的,所以看到这里的table_name是“second”,并且插入了两条记录,结果如下:
这里写图片描述
5、点击更新按钮——update()
执行Update()操作,会将name = jack的记录的detail字段更新为“a excelent Java Developer”,两条记录都会被更新,结果如下:
这里写图片描述
6、点击删除按钮——delete()
删除操作只会删除_id = 1的记录,所以操作之后的query()结果如下:
这里写图片描述

注意:如果操作过程中ContentProvider进程被系统杀掉,你再点击点击上述按钮执行增删改查操作会报error:Unknow URL content://com.hx.contentprovider.MyContentProvider/second,说明系统进行全局匹配,并没有匹配到正确的authority。

MIME类型与getType()

还记得前面自定义ContentProvider类时,我们重写了数据库操作的insert()、query()、update()、delete()函数,但对于getType()直接返回了null吗?下面主要讲讲这个getType()函数有什么作用。
先来看下getType()的官方说明:

public abstract String getType (Uri uri)  

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.  

Parameters:  
uri the URI to query.  
Returns:  
a MIME type string, or null if there is no type.

总体来说,就是传进去一个URI,返回一个表示MIME类型的字符串;里面还说,如果是单条记录应该返回以vnd.android.cursor.item/ 为首的字符串,如果是多条记录,应该返回vnd.android.cursor.dir/ 为首的字符串。

MIME类型

MIME:全称Multipurpose Internet Mail Extensions,多功能Internet邮件扩充服务。它是一种多用途网际邮件扩充协议,在1992年最早应用于电子邮件系统,但后来也应用到浏览器。MIME类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
依然不懂!简单来讲,MIME类型就是用来标识当前的Activity所能打开的文件类型!
下面简单列出来系统中自带的几种文件类型和对应的MIME类型:
(前面是文件名,后面是对应的MIME类型字符串)

{".bmp", "image/bmp"}
{".c", "text/plain"}
{".class", "application/octet-stream"}
{".conf", "text/plain"}
{".cpp", "text/plain"}
{".doc", "application/msword"}

在Android中,MIME类型是用来干什么的呢?MIME类型主要是Activity的Intent-filter的data域,比如下面这个Activity:

<activity
    android:name=".SecondActivity">  
    <intent-filter>  
        <action android:name="com.hx.mime.test"/> 
        <data android:mimeType="image/bmp"/>  
    </intent-filter>  
</activity>

这里指定了data域的MimeType值是”image/bmp”,即在利用隐式Intent匹配时,只有指定MimeType是”image/bmp”时,才能启用这个Activity,也就是说,这个Activity只能打开image/bmp类型的文件!这才是MIME类型匹配的重点。所以MIME类型在Activity中是用来指定当前Activity所支持打开的文件类型!

getType()

现在再回过来看看ContentProvider中的getType()函数,这个函数会根据传进来的URI,生成一个代表MimeType的字符串;而此字符串的生成也有规则:

  • 如果是单条记录,应该返回以vnd.android.cursor.item/ 为首的字符串
  • 如果是多条记录,应该返回以vnd.android.cursor.dir/ 为首的字符串

至于自符串“/”后的字符就随便定义了。“/”前面的部分是系统识别的部分,相当于我们定义一个变量时的变量数据类型,通过这个“数据类型”,系统就知道我们所要表示的是个什么东西。“/” 后面的部分就是我们自已来随便定义的“变量名”了。

其实,getType()返回的MIME类型,主要就是用来隐式匹配Intent的MIMETYPE域来启动Activity的。利用Content URI来隐式启用Activity又是怎样一个流程呢?
这里写图片描述

(1) 首先,第三方应用通过content Uri和action隐式匹配Intent来启用Activity。

    private void thirdPart() {
        Intent intent = new Intent();
        intent.setAction("com.hx.mime.test");
        intent.setData(mCurrentURI);
        try {
            startActivity(intent);
        } catch (Exception e) {
            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

(2)系统通过URI中的Authority来匹配ContentProvider,从而找到我们的MyContentProvider。
(3)在找到MyContentProvider后,由于我们是来匹配Intent的,所以这时候会调用getType(uri)来返回URI类型:

    public static final String CONTENT_FIRST_TYPE = "vnd.android.cursor.dir/watson.first";
    public static final String CONTENT_SECOND_TYPE = "vnd.android.cursor.item/watson.second";
   ...

    @Override
    public String getType(Uri uri) {
        switch (sUriMatcher.match(uri)) {
            case MATCH_FIRST: {
                return CONTENT_FIRST_TYPE;
            }
            case MATCH_SECOND: {
                return CONTENT_SECOND_TYPE;
            }
        }
        return null;
    }
  • 当匹配”/first”时,返回自定义的MIME类型:vnd.android.cursor.dir/watson.first
  • 当匹配“/second”时,返回自定义的MIME类型:vnd.android.cursor.item/watson.second

(4)下面就是根据Action和MIME类型来匹配Intent了

        <activity
            android:name=".SecondActivity">
            <intent-filter>
                <action android:name="com.hx.mime.test"/>
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="vnd.android.cursor.dir/watson.first"/>
            </intent-filter>
        </activity>

在AndroidManifest.xml中,为SecondActivity添加上隐式匹配所需要的Intent-filter;注意我们在getType()里根据不同的URI返回了两种MIME类型,而这里的SecondActivity的data域只添加一个mimeType:vnd.android.cursor.dir/watson.first,即当我们使用content://com.hx.contentprovider.MyContentProvider/second来隐式匹配Intent时,是没办法启用SecondActivity的,因为MIME类型不匹配!
看一下运行结果吧:
使用content://com.hx.contentprovider.MyContentProvider/first,点击“thirdPart”按钮,通过URI调起了Activity:
这里写图片描述 这里写图片描述

使用content://com.hx.contentprovider.MyContentProvider/second,由于MIME不匹配,导致无法调起Activity:
这里写图片描述 这里写图片描述

ContentProvider读写权限

描述

在AndroidManifest.xml中provider标签中有三个额外的参数permission、readPermission、writePermission;
先看下面这段代码:

        <provider
            android:authorities="com.hx.contentprovider.MyContentProvider"
            android:name=".MyContentProvider"
            android:exported="true"
            android:permission="com.hx.contentprovider"
            android:readPermission="com.hx.contentprovider.read"
            android:writePermission="com.hx.contentprovider.write"/>
  • exported:这个属性用于指示该服务是否能被其他程序应用组件调用或跟他交互。
  • readPermission:使用Content Provider的查询功能所必需的权限,即使用ContentProvider里的query()函数的权限;
  • writePermission:使用ContentProvider的修改功能所必须的权限,即使用ContentProvider的insert()、update()、delete()函数的权限;
  • permission:客户端读、写 Content Provider 中的数据所必需的权限名称。本属性为一次性设置读和写权限提供了快捷途径。 不过,readPermission和writePermission属性优先于本设置。

实例

有关自定义权限

自定义权限需要先申请,没有申请过的一串String代表的字符串系统根本无法识别,申请permission的代码:

<permission  
    android:name="com.hx.contentprovider.read"  
    android:label="provider read pomission"  
    android:protectionLevel="normal" />  

这样,我们的permission才会在系统中注册,在第三方应用中使用<\uses-permission来声明使用权限时,才有意义;如果不申请,那么系统中是不存在这个权限的,那么虽然你使用了<\uses-permission来申请使用权限,但系统中根本找不到这个权限,所以到provider匹配时,就会出现permission-deny的错误;

<uses-permission android:name="com.hx.contentprovider.read"/>  

带有权限的ContentProvder实例

首先在ContentProvider中向系统申请两个权限:分别对应读数据库权限和写数据库权限

    <permission
        android:name="com.hx.contentprovider.read"
        android:label="provider read pomission"
        android:protectionLevel="normal" />
    <permission
        android:name="com.hx.contentprovider.write"
        android:label="provider write pomission"
        android:protectionLevel="normal" />

然后在provider中,我们这里同时使用读、写权限:

<provider
            android:authorities="com.hx.contentprovider.MyContentProvider"
            android:name=".MyContentProvider"
            android:exported="true"
            android:readPermission="com.hx.contentprovider.read"
            android:writePermission="com.hx.contentprovider.write"/>

在第三方APK中我们只添加读取权限而没有添加写权限,同样需要先申请注册:

<permission
    android:name="com.hx.contentprovider.read"
    android:label="provider read pomission"
    android:protectionLevel="normal" />
...
<uses-permission android:name="com.hx.contentprovider.read"/>  

结果

当我们点击查询(query)按钮,可以正确读取ContentProvider的内容,然而当我们点击插入(insert)按钮时,却抛出permission-denied异常了,符合预期。
这里写图片描述

ContentObserver数据监听实例分析

在第二节中,我们提到过一个函数:getContentResolver().notifyChange(uri, null);
这个函数是在数据库中数据有变更的时候用来通知第三方应用的,下面就带领大家看看有关数据库变更通知与第三方监听有关的内容。

实例

要监听指定URI上的数据库变化,在监听方需要两步:
(1)生成一个类派生自ContentObserver

public class DataBaseObserver extends ContentObserver {

    public DataBaseObserver(Handler handler) {
        super(handler);
    }

    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
        MainActivity.showlog("received first database changed");
    }

}

注意这里会自动生成一个onChange()方法,当有改变到来时就会跑到onChange()里,所以我们在这里面打一个LOG,注意LOG内容:“received first database changed”,很明显这里我只用DataBaseObserver来监听”content://com.hx.contentprovider.MyContentProvider/frist “所对应的数据库进行监听,至于如何指定URI往下看。

(2)注册和反注册监听

  • 注册

在使用URI来操作第三方数据之前,先进行ContentObserver注册,这里我们在MainActivity的OnCreate()函数里注册:

private DataBaseObserver observer;  

@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.activity_main);  
    observer = new DataBaseObserver(new Handler());  
    //注册指定URI 变动监听  
    getContentResolver().registerContentObserver(CONTENT_URI_FIRST,true,observer);  
} 

注:深入看一下这个注册函数吧,函数原型

public final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)  

功能:为指定的Uri注册一个ContentObserver派生类实例,当给定的Uri发生改变时,回调该实例对象去处理。
参数:
uri : 需要观察的Uri
observer: ContentObserver的派生类实例
notifyForDescendents: 为false 表示精确匹配,即只匹配该Uri;为true 表示可以同时匹配其派生的Uri
举例如下:假设UriMatcher 里注册的Uri共有一下类型:
1、content://com.qin.cb/student (学生)
2、content://com.qin.cb/student/#
3、content://com.qin.cb/student/schoolchild(小学生,派生的Uri)
假设我们当前需要观察的Uri为content://com.qin.cb/student,如果发生数据变化的 Uri 为 content://com.qin.cb/student/schoolchild;那么当notifyForDescendents为 false,那么该ContentObserver会监听不到, 但是当notifyForDescendents 为ture,能捕捉该Uri的数据库变化。

  • 反注册

在OnDestory()时,反注册:

@Override  
protected void onDestroy() {  
    super.onDestroy();  
    //取消监听  
    getContentResolver().unregisterContentObserver(observer);  
} 

监听方的代码完成了,再来看看在ContentProvider中数据库变动通知,在ContentProvider中指定URI上有数据库数据变动时,利用下面的函数来通知

getContext().getContentResolver().notifyChange(uri, null); 

结果

在数据库改变时,收到消息如图所示:
这里写图片描述

好了,有关ContentProvider的用法到此结束。

Demo下载地址

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值