带有游标的 DiffUtil 回调提前关闭

DiffUtil Callback with Cursor closed to early

我尝试使用新的 DiffUtil 来获得 RecyclerView.Adapter 中的差异。但是在计算差异之前,重新加载上的旧光标已关闭,我不知道为什么。这个 CursorCallback is the Callback base, this Adapter 是我的基础,这是我的 activity 代码:

public class RecyclerActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>{

    private RecyclerView recyclerView;
    private ItemAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler);

        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        recyclerView.setHasFixedSize( true );
        recyclerView.setLayoutManager(new LinearLayoutManager(this) );
        recyclerView.setAdapter(adapter = new ItemAdapter(this));
        recyclerView.setItemAnimator(new ItemAnimator());

        ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {

            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                return false;
            }

            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                long id = recyclerView.getAdapter().getItemId( viewHolder.getAdapterPosition() );
                viewHolder.itemView.getContext().getContentResolver().delete(ContentUris.withAppendedId(CategoryContract.CONTENT_URI, id), null, null);
            }
        };
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
        itemTouchHelper.attachToRecyclerView(recyclerView);

        getSupportLoaderManager().initLoader(0, null, this);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new CursorLoader(this, CategoryContract.CONTENT_URI, CategoryContract.PROJECTION, null, null, CategoryContract.COLUMN_ID + " DESC");
    }

    public void addItem( View button ) {
        int count = recyclerView != null ? recyclerView.getChildCount() : 0;
        ContentValues v = new ContentValues(1);
        v.put(CategoryContract.COLUMN_NAME, "Foo Nr. " + count);
        getContentResolver().insert(CategoryContract.CONTENT_URI, v);
    }

    private Task setter;

    @Override
    public void onLoadFinished( final Loader<Cursor> loader, final Cursor data) {
        if( setter != null) {
            setter.cancel(true);
        }

        setter = new Task( adapter );
        AsyncTaskCompat.executeParallel(setter, adapter.getCursor(), data );
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        adapter.changeCursor(null);
    }

    public static class Task extends AsyncTask<Cursor, Void, Pair<Cursor, DiffUtil.DiffResult>> {
        private final CursorRecyclerViewAdapter adapter;

        Task(CursorRecyclerViewAdapter adapter) {
            this.adapter = adapter;
        }

        @Override
        protected Pair<Cursor, DiffUtil.DiffResult> doInBackground(Cursor... params) {
            return Pair.create( params[1], DiffUtil.calculateDiff( new ItemCallback( params[0], params[1]) ) );
        }

        @Override
        protected void onPostExecute(Pair<Cursor, DiffUtil.DiffResult> diffResult) {
            if( isCancelled() )
                return;
            adapter.swapCursor(diffResult.first);
            diffResult.second.dispatchUpdatesTo(adapter);
        }
    }

    public static class ItemAdapter extends CursorRecyclerViewAdapter<ItemHolder>
    {
        ItemAdapter( Context context ) {
            super(context, null);
        }

        @Override
        public void onBindViewHolder(ItemHolder viewHolder, Cursor cursor) {
            CategoryModel model = CategoryModel.FACTORY.createFromCursor( cursor );
            viewHolder.textView.setText( model.getId() + " - " + model.getName() );
        }

        @Override
        public ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new ItemHolder(LayoutInflater.from( parent.getContext() ).inflate( R.layout.item, parent, false ));
        }
    }

    public static class ItemHolder extends RecyclerView.ViewHolder {
        TextView textView;

        ItemHolder(View itemView) {
            super(itemView);
            textView = ( TextView ) itemView.findViewById(R.id.textView);
        }
    }

    public static class ItemCallback extends CursorCallback<Cursor> {
        public ItemCallback(Cursor newCursor, Cursor oldCursor) {
            super(newCursor, oldCursor);
        }

        @Override
        public boolean areRowContentsTheSame(Cursor oldCursor, Cursor newCursor) {
            CategoryModel oldCategory = CategoryModel.FACTORY.createFromCursor(oldCursor);
            CategoryModel newCategory = CategoryModel.FACTORY.createFromCursor(newCursor);
            return oldCategory.getName().equals( newCategory.getName() );
        }

        @Override
        public boolean areCursorRowsTheSame(Cursor oldCursor, Cursor newCursor) {
            return oldCursor.getLong(0) == newCursor.getLong(0);
        }
    }
}

欢迎任何帮助。当返回具有相同查询的新游标时,旧游标可能已关闭。在 onLoadFinished() 中调用 getCursor() 时光标处于打开状态,但在第一次使用时在 CursorCallback 中关闭。

您遇到了 CursorLoader 的预期行为 — 他们会在另一个 Cursor 到达后关闭旧 Cursor,无论您当前是否正在使用它。

你的事件顺序是这样的:

  1. 您获得(仍然打开的)游标并在 AsyncTask 线程池的某个后台线程 X 中开始差异计算
  2. 某处有东西叫ContentResolver.notifyChanged
  3. CursorLoader 正在另一个后台线程 Y 中加载一个新的 Cursor。这个新的 Cursor 被发布到 onLoadFinished,并且可能已经交换到列表适配器中。
  4. CursorLoader 关闭旧 Cursor。
  5. 您的后台线程 X 不知道点 2,3 并且一直使用旧的 Cursor 直到它发现它被 CursorLoader 关闭。抛出异常。

为了在后台线程中继续使用 Cursor,您必须手动管理 Cursor(无需 CursorLoader 的帮助):如果配置更改或 onDestroy 发生,请自行关闭它。

或者,只需拦截异常并将其视为您的背景差异计算正在取消的标志(它很快就会为另一个 Cursor 执行)。