Android:在 RecyclerView 中拖放项目时如何有效更新 SQLite 数据库?

SS-*_*alt 5 java sqlite android background android-asynctask

我有这个待办事项列表应用程序,其中“任务”可以包含自己的“子任务”列表。问题是,每当这些“任务”通过拖放重新排列时,有时会给我带来奇怪的行为,例如单击它们以显示子列表时崩溃。

这是拖放监听器的代码:

private void attachItemTouchHelperToAdapter() {
    ItemTouchHelper.SimpleCallback touchCallback =
            new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
                    ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
                @Override
                public boolean onMove(RecyclerView recyclerView,
                                      RecyclerView.ViewHolder viewHolder,
                                      RecyclerView.ViewHolder target) {
                    final int fromPosition = viewHolder.getAdapterPosition();
                    final int toPosition = target.getAdapterPosition();
                    rearrangeTasks(fromPosition, toPosition);

                    // Call background thread to update database.
                    new UpdateDatabaseTask(mTaskList).execute();
                    return true;
                }

                @Override
                public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                    int position = viewHolder.getAdapterPosition();
                    Task taskToDelete = mTaskAdapter.mTasks.get(position);
                    TaskLab.get(getActivity()).deleteTask(taskToDelete);
                    updateUI(position);
                }
            };

    ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchCallback);
    itemTouchHelper.attachToRecyclerView(mTaskRecyclerView);
}
Run Code Online (Sandbox Code Playgroud)

重新排列 onMove 中调用的列表的代码:

private void rearrangeTasks(int fromPosition, int toPosition) {
    if (fromPosition < toPosition) {
        for (int i = fromPosition; i < toPosition; i++) {
            Collections.swap(mTaskList, i, i + 1);
        }
    } else {
        for (int i = fromPosition; i > toPosition; i--) {
            Collections.swap(mTaskList, i, i - 1);
        }
    }
    mTaskAdapter.notifyItemMoved(fromPosition, toPosition);
}
Run Code Online (Sandbox Code Playgroud)

AsyncTask调用它来更新 sqlite 数据库:

 private class UpdateDatabaseTask extends AsyncTask<Void, Void, Void> {
    private List<Task> mTaskList;

    public UpdateDatabaseTask(List<Task> taskList) {
        mTaskList = taskList;
    }

    @Override
    protected Void doInBackground(Void... voids) {
        TaskLab.get(getActivity()).updateDatabase(mTaskList);
        return null;
    }

    @Override
    protected void onPostExecute(Void aVoid) {

    }
}
Run Code Online (Sandbox Code Playgroud)

调用数据库辅助函数以使用重新排列的列表更新整个表:

public void updateDatabase(List<Task> tasks) {
    mDatabase.delete(TaskTable.NAME, null, null);
    for (Task task : tasks) {
        addTask(task);
    }
}
Run Code Online (Sandbox Code Playgroud)

这里的问题是,有时,当快速拖放“任务”时,AsyncTask 更新数据库的速度不够快,因此当我尝试在更新完成之前对其执行功能时,会出现奇怪的行为。

RecyclerView在拖放时更新 SQLite 数据库的更好方法是什么?

Rea*_*hed 2

这个问题太宽泛,无法回答,因为您可以考虑多种解决方案来解决您的问题。我现在能想到两个,在这里我将对它们进行简要描述。

解决方案#1

您可能会考虑直接调用更新表的数据库方法,而不通过AsyncTask. 向数据库表注册一个内容观察器,以便每次更新表时,内容观察器都会RecyclerView随之更新,而无需notifyDataSetChanged()调用。示例实现应如下所示。

像这样声明数据库表的 URI。

public static final Uri DB_TABLE_TASKS_URI = Uri
            .parse("sqlite://" + Constants.ApplicationPackage + "/" + DB_TABLE_TASKS);
Run Code Online (Sandbox Code Playgroud)

现在,在您的updateDatabase函数中,您需要调用此通知函数,以在您的任务表发生更改或更新时通知监听此内容的观察者。

public static void updateDatabase(List<Task> taskList) {
    // .. Your other implementation goes here 
    context.getContentResolver().notifyChange(DBConstants.DB_TABLE_TASKS_URI, null);
}
Run Code Online (Sandbox Code Playgroud)

现在使用 来LoaderCallbacks显示 .sql 文件中 sqlite 表中的任务RecyclerView

public class TasksActivity extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {
    private final TAKS_QUERY_LOADER = 0;

    protected void onCreate(Bundle savedInstanceState) {
        // Other code. 

        // Get your cursor loader initialized here. 
        getLoaderManager().initLoader(TASKS_QUERY_LOADER, null, this).forceLoad();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在您需要LoaderCallbacks在您的TasksActivity. 一个示例实现应该如下所示。

@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {

    return new SQLiteCursorLoader(getActivity()) {
        @Override
        public Cursor loadInBackground() {

            Cursor cursor;
            cursor = TasksLab.getTasks();

            // Register the cursor with the content observer
            this.registerContentObserver(cursor, DBConstants.DB_TABLE_TASKS_URI);
            return cursor;
        }
    };
}

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // Set the cursor in the adapter of your RecyclerView here. 
}

@Override
public void onLoaderReset(Loader<Cursor> loader) {

}

// Do not forget to destroy the loader when you are destroying the view.
@Override
public void onDestroy() {
    getLoaderManager().destroyLoader(TASKS_QUERY_LOADER);
    super.onDestroyView();
}
Run Code Online (Sandbox Code Playgroud)

就是这个。现在,您可以立即在 sqlite 表中更新您的任务,并立即获得反映RecyclerView。我强烈建议您一次更新一个任务。updateDatabase正如我所看到的,您正在使用在此处传递整个任务列表的函数来更新数据库表。我建议仅传递被拖动或更新的任务 ID,以便表立即更新。

解决方案#2

此解决方案比前一个解决方案更简单。这个想法是在刷新列表或销毁视图时更新 SQLite 数据库。

当任务被拖动到某个位置(即更新其位置)或删除时,您需要将轨道保留ArrayListTasksActivity. 不要立即更新数据库中的 sqlite 表,或使用AsyncTask. 保存此任务以供稍后在您的onDestroyonPause方法中销毁视图时使用。RecyclerView在此过程中,每次Activity恢复(即在功能中)时,您都需要刷新onResume。这是伪实现。

@Override
public boolean onMove(RecyclerView recyclerView,
                      RecyclerView.ViewHolder viewHolder,
                      RecyclerView.ViewHolder target) {
    final int fromPosition = viewHolder.getAdapterPosition();
    final int toPosition = target.getAdapterPosition();
    rearrangeTasks(fromPosition, toPosition);

    // Do not call background thread to update database.
    // new UpdateDatabaseTask(mTaskList).execute();

    // Keep a local ArrayList and keep the track of dragging the item. 
    updateLocalArrayListWhichIsBindToRecyclerView(mTaskList);
    return true;
}

@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    int position = viewHolder.getAdapterPosition();
    Task taskToDelete = mTaskAdapter.mTasks.get(position);
    // TaskLab.get(getActivity()).deleteTask(taskToDelete);
    updateLocalArrayListWhichIsBindedToRecyclerView(taskToDelete);
    updateUI(position);
}
Run Code Online (Sandbox Code Playgroud)

现在,当您离开时,TaskActivity您需要最终将更改保存在数据库中。

@Override
public void onDestroy() {
    updateDatabaseFromTheList();
    super.onDestroyView();
}

@Override
public void onPause() {
    updateDatabaseFromTheList();
    super.onDestroyView();
}
Run Code Online (Sandbox Code Playgroud)

并每次从函数内的数据库加载任务onResume

@Override
public void onResume() {
    super.onResume();
    updateRecyclerViewFromDatabase();
}
Run Code Online (Sandbox Code Playgroud)

更新

基于上述评论中解决方案 #1 所需的澄清。

“我建议仅传递被拖动或更新的任务 ID,以便表立即更新。”。我到底该怎么做?

我不确定您现在如何更新任务表。不过,我可以想到一些我自己的实现。只需传递您想要更新其在任务表中的位置的任务即可。因此伪实现可能如下所示。

public class Task {
    public int taskId;
    public int taskPosition; 
}

public void updateDatabase(Task taskToBeUpdated, int newPosition) {

     // You need two update queries. 
     // Update task_table set task_position += task_position + 1 where task_position >= newPosition;
     // Update task_table set task_position = newPosition where taskId = taskTobeUpdated.taskId
}
Run Code Online (Sandbox Code Playgroud)

希望有帮助。