cursoradpter自动更新



03 November 2014
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自动更新的实现


前提


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


数据存储在SqliteDataBase当中
通对ContentProvider的请求,获取封装了数据的Cursor
使用CursorLoader加载数据
使用AdapterView和CursorAdapter显示数据
定义同步标志


static final Uri SYNC_SIGNAL_URI = Uri.parse(
    "content://com.kohoh.cursorsyncdemo/SYNC_SIGNAL");
在ContentProvider的query中设置NotificationUri


@Override
public 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


@Override
public 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));
}
@Override
public 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;
}
@Override
public 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自动更新是通过观察者模式实现的,整个过程如下图所示






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






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


其他的实现方式


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


不使用ContentProvider


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


// 实例化一个全局的ForceLoadContentObserver 
ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
@Override
public 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。


就是这样!!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值