MergeCursor引起的CursorAdapter更新无效问题

CursorAdapter中有个onContentChanged方法,是protected,只能被子类重写。

    protected void onContentChanged() {
        if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
            if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
            mDataValid = mCursor.requery();
        }
    }
在这个方法可以看成是数据变化的通知。最近遇到了一个问题是该方法无效了。

onContentChanged回调的流程

构造方法

    public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
        init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
    }
构造方法中auroRequery为false的情况下,会开启数据监听
    void init(Context context, Cursor c, int flags) {
        if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) { //这里其实看出只要flags>0就会启动监听
            flags |= FLAG_REGISTER_CONTENT_OBSERVER;
            mAutoRequery = true;
        } else {
            mAutoRequery = false;
        }
        ...
        if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
            mChangeObserver = new ChangeObserver(); //新建观察者对象
            mDataSetObserver = new MyDataSetObserver();
        } else {
            mChangeObserver = null;
            mDataSetObserver = null;
        }

        if (cursorPresent) {
            if (mChangeObserver != null) c.registerContentObserver(mChangeObserver); //注册监听
            if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
        }
    }

观察者

 private class ChangeObserver extends ContentObserver {
        public ChangeObserver() {
            super(new Handler());
        }

        @Override
        public boolean deliverSelfNotifications() {
            return true;
        }

        @Override
        public void onChange(boolean selfChange) { //就是之前提及的方法
            onContentChanged();
        }
    }

    private class MyDataSetObserver extends DataSetObserver {
        @Override
        public void onChanged() {
            mDataValid = true;
            notifyDataSetChanged();  //通知数据变化,这个方法会引起UI的重绘
        }

        @Override
        public void onInvalidated() {
            mDataValid = false;
            notifyDataSetInvalidated(); //通知数据失效
        }
    }

changeCursor

除了构造方法外,设置cursor的方法也和监听有关:
    public void changeCursor(Cursor cursor) {
        Cursor old = swapCursor(cursor);
        ...
    }
    public Cursor swapCursor(Cursor newCursor) {
        ...
        Cursor oldCursor = mCursor;
        if (oldCursor != null) {
            if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver); //卸载旧cursor的监听
            if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
        }
        mCursor = newCursor;
        if (newCursor != null) {
            if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver); //开启新的监听
            if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
            mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
            mDataValid = true;
            // notify the observers about the new cursor
            notifyDataSetChanged();
        }
        ...
        return oldCursor;
    }

registerContentObserver实现和通知

frameworks/base/core/java/android/database/AbstractCursor.java

AbstractCursor是Cursor的实现类,其中两个注册方法如下:

    @Override
    public void registerContentObserver(ContentObserver observer) {
        mContentObservable.registerObserver(observer);
    }

    @Override
    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }
通知方法:

    protected void onChange(boolean selfChange) {
        synchronized (mSelfObserverLock) {
            mContentObservable.dispatchChange(selfChange, null);
            if (mNotifyUri != null && selfChange) {
                mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
            }
        }
    }
    public boolean requery() {     //也即CursorAdapter的onContentChanged默认实现才会引起DateSetObservable的回调
        if (mSelfObserver != null && mSelfObserverRegistered == false) {
            mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
            mSelfObserverRegistered = true;
        }
        mDataSetObservable.notifyChanged();
        return true;
    }


通知最终的原理

AbstractCursor内部有个静态类:

    protected static class SelfContentObserver extends ContentObserver {
        ...

        @Override
        public void onChange(boolean selfChange) {
            AbstractCursor cursor = mCursor.get();
            if (cursor != null) {
                cursor.onChange(false);
            }
        }
    }


    public void setNotificationUri(ContentResolver cr, Uri notifyUri, int userHandle) {
        synchronized (mSelfObserverLock) {
            mNotifyUri = notifyUri;
            mContentResolver = cr;
            if (mSelfObserver != null) {
                mContentResolver.unregisterContentObserver(mSelfObserver);
            }
            mSelfObserver = new SelfContentObserver(this);
            mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver, userHandle);
            mSelfObserverRegistered = true;
        }
    }
终于找到了监听uri的常规代码,至于ContentResolver怎么监听uri后续的流程不再深究了,网上已有,罗升阳的博客里应该是有分析的。理解为ContactsProvider相关Uri有notify的话,观察者就会收到通知。


bug原因

代码分析可以看出CursorAdapter整个流程要跑通,setNotificationUri是必须要调用的。

参见ContactsProvider2中的一段代码,

	private Cursor doQuery(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
			String selection, String[] selectionArgs, String sortOrder, String groupBy,
			String having, String limit, CancellationSignal cancellationSignal) {
		...
		final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, having,
				sortOrder, limit, cancellationSignal);
		if (c != null) {
			LogUtils.d(TAG, "[query]c.count(): " + c.getCount());
			c.setNotificationUri(getContext().getContentResolver(), ContactsContract.AUTHORITY_URI);
		}
		return c;
	}

因为加入了setNotificationUri,CursorAdapter的回调才会起作用。

可见自定义ContentProvider中是要加入setNotificationUri的代码的,如果忘记就不起效果了。

不过我遇到的bug中,并不是ContentProvider中没有调用setNotificationUri。而是代码中使用了MergeCursor,合并了两个Cursor对象(但是这两个对象没有调用setNotificationUri),并且MergeCursor也调用了setNotificationUri。

注意MergeCursor看做Cursor的一个容器比较恰当,看做cursor的一个子类就不太符合子类的行为。

    public void registerContentObserver(ContentObserver observer) {
        int length = mCursors.length;
        for (int i = 0 ; i < length ; i++) {
            if (mCursors[i] != null) {
                mCursors[i].registerContentObserver(observer);
            }
        }
    }
其中的registerContentObserver实现就是调用mCursors数组中的每个对象的registerContentObserver方法,自身并没有实际注册监听。所以只有 mCursors中的对象有调用setNotificationUri,MergeCursor才会触发回调,只有MergeCursor自己调用setNotificationUri是没有效果的。

如果mCursors数组中的对象同属一个监听uri,会引发回调的多次触发,所以这种情况下只要一个对象调用setNotificationUri即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值