当使用批处理操作插入许多行时,Android ContentProvider会调用setNotificationUri()的突发到CursorAdapter

Lor*_*ori 11 sqlite android cursor android-contentprovider android-cursoradapter

我有一个自定义ContentProvider管理SQLite数据库的访问.要在a中加载数据库表的内容ListFragment,我使用LoaderManagerwith a CursorLoader和a CursorAdapter:

public class MyListFragment extends ListFragment implements LoaderCallbacks<Cursor> {
    // ...
    CursorAdapter mAdapter;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mAdapter = new CursorAdapter(getActivity(), null, 0);
        setListAdapter(mAdapter);
        getLoaderManager().initLoader(LOADER_ID, null, this);
    }

    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new CursorLoader(getActivity(), CONTENT_URI, PROJECTION, null, null, null);
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
        mAdapter.swapCursor(c);
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        mAdapter.swapCursor(null);
    }
}
Run Code Online (Sandbox Code Playgroud)

SQLite数据库由后台任务更新,后台任务从Web服务获取大量项目,并通过ContentProvider批处理操作将这些项目插入数据库(ContentResolver#applyBatch()).

即使这是批处理操作,ContentProvider#insert()也会将每个调用的每一行调用插入到数据库中,并在当前实现中ContentProvider调用setNotificationUri()每个插入命令.

结果是CursorAdapter接收到通知突发,导致UI过于频繁地更新,从而产生恼人的闪烁效果.

理想情况下,当批处理操作正在进行时,应该有一种方法ContentObserver只在任何批处理操作结束时通知,而不是每个插入命令.

有人知道这是否可行?请注意我可以更改ContentProvider实现并覆盖其任何方法.

Dia*_*rat 13

我发现了一个比Google的I/O应用程序发现的更简单的解决方案.您只需覆盖ContentProvider中的applyBatch方法并执行事务中的所有操作.在事务提交之前不会发送通知,从而最大限度地减少发送的ContentProvider更改通知的数量:

@Override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
        throws OperationApplicationException {

    final SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
    db.beginTransaction();
    try {
        final int numOperations = operations.size();
        final ContentProviderResult[] results = new ContentProviderResult[numOperations];
        for (int i = 0; i < numOperations; i++) {
            results[i] = operations.get(i).apply(this, results, i);
        }
        db.setTransactionSuccessful();
        return results;
    } finally {
        db.endTransaction();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 您可以使用`ContentProviderResult [] results = super.applyBatch(operations);`替换`for`循环. (2认同)

Phi*_*ons 7

为了解决这个确切的问题,我重写了applyBatch并设置了一个阻止其他方法发送通知的标志.

    volatile boolean applyingBatch=false;
    public ContentProviderResult[] applyBatch(
        ArrayList<ContentProviderOperation> operations)
        throws OperationApplicationException {
    applyingBatch=true;
    ContentProviderResult[] result;
    try {
        result = super.applyBatch(operations);
    } catch (OperationApplicationException e) {
        throw e;
    }
    applyingBatch=false;
    synchronized (delayedNotifications) {
        for (Uri uri : delayedNotifications) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
    }
    return result;
}
Run Code Online (Sandbox Code Playgroud)

我公开了一种方法来"存储"批处理完成时要发送的通知:

protected void sendNotification(Uri uri) {
    if (applyingBatch) {
        if (delayedNotifications==null) {
            delayedNotifications=new ArrayList<Uri>();
        }
        synchronized (delayedNotifications) {
            if (!delayedNotifications.contains(uri)) {
                delayedNotifications.add(uri);
            }
        }
    } else {
        getContext().getContentResolver().notifyChange(uri, null);
    }
}
Run Code Online (Sandbox Code Playgroud)

任何发送通知的方法都使用sendNotification,而不是直接触发通知.

可能有更好的方法来做到这一点 - 它看起来似乎应该是 - 但这就是我所做的.