cursor.setNotificationUri() 是做啥用的?

Posted

技术标签:

【中文标题】cursor.setNotificationUri() 是做啥用的?【英文标题】:What is cursor.setNotificationUri() used for?cursor.setNotificationUri() 是做什么用的? 【发布时间】:2014-03-04 15:02:29 【问题描述】:

我研究了如何使用ContentProviders 和来自this tutorial 的加载器

我是怎么看的: 我们有一个ActivityListViewSimpleCursorAdapterCursorLoader。我们还实现了ContentProvider

Activity 中,我们可以通过单击按钮调用getContentResolver().insert(URI, contentValues);

在我们的ContentProvider 实现中,在insert() 方法的末尾,我们调用getContentResolver().notifyChange(URI, null); 并且我们的CursorLoader 将接收到它应该重新加载数据并更新UI 的消息。此外,如果我们在SimpleCursorAdapter 中使用FLAG_REGISTER_CONTENT_OBSERVER,它也会收到消息,并调用其方法onContentChanged()

所以如果我们插入、更新或删除数据,我们的 ListView 将会更新。

Activity.startManagingCursor(cursor); 已弃用,cursor.requery() 已弃用,所以我看不出 cursor.setNotificationUri() 有任何实践意义。

我查看了setNotificationUri() 方法的源代码,发现它在方法内部调用了mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver)CursorLoader 也一样。最后 cursor 将收到消息,并在 Cursor 内部调用以下方法:

protected void onChange(boolean selfChange) 
    synchronized (mSelfObserverLock) 
        mContentObservable.dispatchChange(selfChange, null);
        // ...
    

但我无法理解这一点。

所以我的问题是:我们为什么要在 query() 方法的 ContentProvider 实现中调用 cursor.setNotificationUri()

【问题讨论】:

【参考方案1】:

如果您调用Cursor.setNotificationUri(),Cursor 将知道它是为什么而创建的ContentProvider Uri

CursorLoader 使用ContextContentResolver 为您在调用setNotificationUri 时指定的URI 注册自己的ForceLoadContentObserver(扩展ContentObserver)。

所以一旦 ContentResolver 知道 URI 的内容已更改 [当您在 ContentProviderinsert()update()delete() 方法中调用 getContext().getContentResolver().notifyChange(uri, contentObserver); 时,就会发生这种情况] 它会通知所有的观察者,包括 CursorLoader 的ForceLoadContentObserver

ForceLoadContentObserver 然后将 Loader 的 mContentChanged 标记为 true

【讨论】:

即使我迟到了,我的问题是,即使对于使用此 URI 查询我们的 ContentProvider 的另一个应用程序,这是否可能?【参考方案2】:

CursorLoader 为光标注册观察者,不是到 URI。

查看下面的CursorLoader's source code。请注意,CursorLoadercontentObserver 注册到cursor

/* Runs on a worker thread */
    @Override
    public Cursor loadInBackground() 
        synchronized (this) 
            if (isLoadInBackgroundCanceled()) 
                throw new OperationCanceledException();
            
            mCancellationSignal = new CancellationSignal();
        
        try 
            Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
                    mSelectionArgs, mSortOrder, mCancellationSignal);
            if (cursor != null) 
                try 
                    // Ensure the cursor window is filled.
                    cursor.getCount();
                    cursor.registerContentObserver(mObserver);
                 catch (RuntimeException ex) 
                    cursor.close();
                    throw ex;
                
            
            return cursor;
         finally 
            synchronized (this) 
                mCancellationSignal = null;
            
        

Cursor需要调用setNotificationUri()方法将mSelfObserver注册到uri

//AbstractCursor.java
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); // register observer to the uri
            mSelfObserverRegistered = true;
        
    

contentProviderinsertupdatedelete 方法中,您需要调用getContext().getContentResolver().notifyChange(uri, null); 来通知uri 观察者的更改。

因此,如果您不致电cursor#setNotificationUri(),那么如果uri 的基础数据发生变化,您的CursorLoader 将不会收到通知。

【讨论】:

【参考方案3】:

我为光标适配器使用一个 URI。

@Override
public void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);

    Bundle args = new Bundle();
    Uri uri = TemperatureContract.SensorEntry.buildSensorID0AddressUri(mDeviceAddress);
    args.putParcelable("URI", uri);
    getSupportLoaderManager().initLoader(0, args, this);



@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) 
    if (args != null) 
        Uri mUri = args.getParcelable("URI");
        return new CursorLoader(this,
                mUri,
                null, // projection
                null, // selection
                null, // selectionArgs
                null); // sortOrder
     else 
        return null;
    

在另一个类中,我使用不同的 URI 来更改数据库内容。为了更新我的视图,我必须更改数据提供者 update 方法的 default 实现。默认实现只通知相同的 URI。我必须通知另一个 URI。

我最终在我的数据提供者类上调用了两次notifyChange() update 方法:

@Override
public int update(
        Uri uri, ContentValues values, String selection, String[] selectionArgs) 
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    final int match = sUriMatcher.match(uri);
    int rowsUpdated;
    switch (match) 
        case ...:
            break;
        case SENSOR_BY_ID_AND_ADDRESS:
            String sensorId = TemperatureContract.SensorEntry.getSensorIdFromUri(uri);
            String sensorAddress = TemperatureContract.SensorEntry.getSensorAddressFromUri(uri);
            rowsUpdated = db.update(
                    TemperatureContract.SensorEntry.TABLE_NAME, values, "sensorid = ? AND address = ?", new String[]sensorId, sensorAddress);
            if (rowsUpdated != 0) 
                Uri otheruri = TemperatureContract.SensorEntry.buildSensorID0AddressUri(sensorAddress);
                getContext().getContentResolver().notifyChange(otheruri, null);
            
            break;
        case ...:
            break;
        default:
            throw new UnsupportedOperationException("Unknown uri: " + uri);
    
    if (rowsUpdated != 0) 
        getContext().getContentResolver().notifyChange(uri, null);
    
    return rowsUpdated;

我对 insertdelete 方法做了同样的事情。

【讨论】:

以上是关于cursor.setNotificationUri() 是做啥用的?的主要内容,如果未能解决你的问题,请参考以下文章