Android Cursor自动更新的实现和原理

在Android日常开发中,时常会请求数据到Cursor,然后再通过Cursor获取数据。像SQLiteDatabase和ContentProvider都使用了Cursor。在这些应用中,往往希望当数据发生改变时,Cursor也会自动的更新数据。这篇文章,我就会向你阐述如何通过Android自身的API实现Cursor的自动更新。另外我还将向你阐述这背后的原理。通过这些原理你可以举一反三的实现更为广泛的自动跟新。

文章中的代码

可以在https://github.com/KOHOH1992/CursorSyncDemo中找到文章中出现的代码

该项目共有4个分支。use_provider分支介绍了使用ContentProvider实现Cursor同步更新的方法。use_database分支介绍了不使用ContentProvider实现Cursor同步更新的方法。use_adapter分支介绍了不使用Loader实现Cursor同步更新的方法。

Cursor自动更新的实现

前提

首先假设项目使用了如下的前提

  1. 数据存储在SqliteDataBase当中
  2. 通对ContentProvider的请求,获取封装了数据的Cursor
  3. 使用CursorLoader加载数据
  4. 使用AdapterView和CursorAdapter显示数据

定义同步标志

static final Uri SYNC_SIGNAL_URI = Uri.parse(    "content://com.kohoh.cursorsyncdemo/SYNC_SIGNAL");

在ContentProvider的query中设置NotificationUri

@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder) {    SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();    Cursor cursor = database.query(ContactContract.CONTACT_TABLE, projection,    selection,selectionArgs, null, null, sortOrder);    //设置NotificationUri    cursor.setNotificationUri(contentResolver, ContactContract.SYNC_SIGNAL_URI);    return cursor;}

在ContentProvider的insert,update,delete中触发NotificationUri

@Overridepublic Uri insert(Uri uri, ContentValues values) {    SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();    long id = database.insert(ContactContract.CONTACT_TABLE, null, values);    if (id >= 0) {        //触发NotificationUri        contentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);    }    return uri.withAppendedPath(ContactContract.CONTACT_URI, String.valueOf(id));}
@Overridepublic int update(Uri uri, ContentValues values, String selection,     String[] selectionArgs) {    SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();    int result = database.update(ContactContract.CONTACT_TABLE, values,         selection, selectionArgs);    if (result > 0) {        //触发NotificationUri        contentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);    }    return result;}
@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {    SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();    int result = database.delete(ContactContract.CONTACT_TABLE, selection,         selectionArgs);    if (result > 0) {    //触发NotificationUri    contentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);    }    return result;}

Cursor的实现原理

Android的Cursor自动更新是通过观察者模式实现的,整个过程如下图所示

  1. 通过ContentPorvider和ContentResolver使得数据发生了改变
  2. ContentProvider通知Cursor的观察者数据发生了改变
  3. Cursor通知CursorLoader的观察者数据发生了改变
  4. CursorLoader通过ContentProvider加载新的数据
  5. ContentPovider向DataBase请求新的数据
  6. CursorLoader调用CursorAdapter#changeCursor,用封装了新数据的Cursor替换旧的Cursor
  7. CursorAdapter告知AdapterView的观察者有新的数据
  8. AdapterView重新加载并显示数据

在Android的android.database包下,有一个ContentObserver。Android正是通过他来实现观察者模式的。当数据改变之后,观察者会将数据改变的消息通知相应的对象,进而做出反馈。在代码中,当数据改变之后,我会调用ContentResolver#notifyChange,发出ContactContract.SYNC_SIGNAL_URI信号,通知数据发生了改变。而在此之前,从ContentProvider#query中获得的Cursor已经通过Cursor#setNotificationUriContactContract.SYNC_SIGNAL_URI信号进行了监视。当该信号出现,Cursor就会将信息改变的消息告诉CursorLoader的观察者(在此之前CursorLoader已经对该Cursor设立了观察者)。CursorLoader会开始重新开始加载数据。当数据加载成功,CursorLoader会通过CursorAdapter#changeCursor设置封装了新数据的Cursor。而后CursorAdapter又会通知AdapterView的观察者数据发生了改变(在此之前AdapterView已经对CursorAdapter设立了观察者)。最后AdapterView就会重新加载并显示新的数据。

在整个过程当中,我要做的就是在改变数据时发出信号,对封装数据的Cursor设置需要监视的信号。具体的说就是在query中调用Cursor#setNotificationUri,在insertupdatedelete中调用ContentResolver#notifyChange。这里需要补充的是Cursor和ContentResolver的信号机制同样是通过观察者模式实现的。

其他的实现方式

这里要介绍的其他的实现方式,依旧是通过观察者模式实现的。区别在于是否使用ContentProvider和CursorLoader

不使用ContentProvider

在开发过程中,如果数据不用于应用之间的共享,使用ContentProvider似乎有一些多余。然而Android提供的CursorLoader的API必须通过ContentProvider才能实现数据加载和数据同步更新。但是你任然可以在不使用ContentProvider的情况下实现Cursor的自动更新。你需要做的只是在你的Loader中加入下面的代码

// 实例化一个全局的ForceLoadContentObserver ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
@Overridepublic Cursor loadInBackground() {    SQLiteDatabase database = mSqLiteOpenHelper.getReadableDatabase();    Cursor cursor = database.query(mTable, mColumns, mSelection, mSelectionArgs,         mGroupBy,mHaving, mOrderBy);    if (cursor != null) {        cursor.getCount();        // 对Cursor设立观察者        cursor.registerContentObserver(mObserver);        // 设置Cursor的观察信号        cursor.setNotificationUri(getContext().getContentResolver(),             mNotificationUri);    }    return cursor;}

ForceLoadContentObserver是Loader的内部类。当观察到数据发生变化之后,该类会调用Loader#forceLoad,进而开始重新加载数据。另外你也可以直接使用我项目中的DatabaseLoader。该类是我参照CursorLoader编写的一个工具,通过它你可以绕过ContentProvider,直接请求Database。

不使用Loader

如果你不想要使用Loader(我非常不赞成你这么做),你可以通过如下的代码实现Cursor的同步更新。

// 使用CursorAdapter.FLAG_AUTO_REQUERY标志adapter = new SimpleCursorAdapter(this, R.layout.contact_item, null, from, to,        CursorAdapter.FLAG_AUTO_REQUERY);
private void loadData() {    SQLiteOpenHelper sqliteOpenHelper = ContactContract.getSqliteOpenHelper(this);    SQLiteDatabase database = sqliteOpenHelper.getReadableDatabase();    String[] columns = {ContactContract._ID, ContactContract.NAME,         ContactContract.PHONE};    Cursor cursor = database.query(ContactContract.CONTACT_TABLE, columns, null,         null, null,null, null);    //设置NotificationUri    cursor.setNotificationUri(this.getContentResolver(),         ContactContract.SYNC_SIGNAL_URI);    adapter.changeCursor(cursor);}

这里的关键在于,在实例化CursorAdapter时使用了CursorAdapter.FLAGAUTOREQUERY标志。当使用该标志后,每当收到数据更新的消息,CursorAdapter就会自己调用CursorAdapter#requery重新加载数据。然而整个加载过程会再UI线程中发生,这很有可能会使得程序运行部流畅。正是因为这个原因该方法以及被Android设置为Deprecated了。因此如果有可能,我还是推荐你使用Loader。

就是这样!!!

更多相关文章

  1. Android(安卓)ViewPager实现循环轮播图
  2. Android(安卓)ContentProvider
  3. Android(安卓)订餐APP(Sqlite数据库,完整的增删改查)
  4. 【安卓面试笔记】Content Provider(五)
  5. android ViewPager TabLayout 动态创建问题
  6. Android数据绑定(DataBinding)
  7. Android(安卓)开发之详解 IPC 进程通信
  8. Android(安卓)HAL层模块的加载过程
  9. Afinal 介绍

随机推荐

  1. Android为ToolBar设置沉浸式状态栏及其相
  2. android开发之Dialog
  3. Android中的Handler总结 转载
  4. 2012年最有价值的Android开发精品文章荟
  5. API23以上的运行时权限
  6. 2016年3月android面试总结(2)
  7. Android悬浮窗操作使用总结
  8. Android(安卓)1.6 支持更多的屏幕大小和
  9. Programming access to Android(安卓)Mar
  10. 广播 (Broadcast)