【问题标题】:RecyclerView blocks UI when setting the adapter设置适配器时 RecyclerView 阻塞 UI
【发布时间】:2018-02-17 23:34:00
【问题描述】:

我有一个需求,我有一个超过 300k 行的填充数据库。我已经根据这个问题成功实现了一个 CursorAdapter,其中混合了两个投票最多的答案HERE

我已经为后台服务实现了一个 AsyncTask 来执行对数据库的查询,这非常快,不会超过 2-3 秒。我的 AsyncTask 中的 ProgressDialog 有时很难检测到。

我的问题是,当任务完成并检索光标时,当我将适配器设置为 RecyclerView 时,该过程会冻结我的 UI 几秒钟,直到设置数据。当我执行搜索(新查询,与获取所有行但行数较少的过程相同)并替换光标以更新数据时,也会发生这种情况。

以下是一些相关代码:

异步任务

@Override
protected Void doInBackground(Void... Void) {
    if(type==Constants.GET_ZIP_CODES)
        cursor = db.getAllZipCodes();
    else
        cursor = db.searchZipCodes(text);

    return null;
}

@Override
protected void onPostExecute(Void Void) {
    setAdapter();
    mProgressDialog.dismiss();
    super.onPostExecute(Void);
}

方法

private void setAdapter(){
    if(myAdapter == null){
        myAdapter = new MyAdapter(getActivity(), cursor);
        search_rv.setAdapter(myAdapter);
    } else
        myAdapter.swapCursor(cursor);
}

因为这是一个搜索,所以除了notifyDataSetChanged(),我在这里没有什么可做的,因为所有数据在每次搜索中都会发生很大变化。

这正常吗?既然 RecyclerView 只渲染可见视图,为什么在 AsyncTask 中 Cursor 已经准备好后,它会冻结并需要这么长时间才能更新?

编辑

我已经更改了我的适配器以避免使用 CursorAdapter,因为 @cricket_007 指出在适配器中包含适配器是不好的设计。

这是我的适配器:

public class SearchListAdapter extends RecyclerView.Adapter<SearchListAdapter.ViewHolder> {


private Context mContext;

private Cursor mCursor;

private boolean mDataValid;
private int mRowIdColumn;

private DataSetObserver mDataSetObserver;

public SearchListAdapter(Context context, Cursor c) {

    mContext = context;
    mCursor=c;

    mDataValid = c != null;
    mRowIdColumn = mDataValid ? mCursor.getColumnIndex("_id") : -1;
    mDataSetObserver = new NotifyingDataSetObserver();
    if (mCursor != null) {
        mCursor.registerDataSetObserver(mDataSetObserver);
    }

}

static class ViewHolder extends RecyclerView.ViewHolder {
    TextView itemTV;

    ViewHolder(View itemView) {
        super(itemView);
        itemTV = (TextView) itemView.findViewById(R.id.itemTV);
    }
}


@Override
public void setHasStableIds(boolean hasStableIds) {
    super.setHasStableIds(true);
}

@Override
public int getItemCount() {
    if (mDataValid && mCursor != null) {
        return mCursor.getCount();
    }
    return 0;
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    // Passing the binding operation to cursor loader
    mCursor.moveToPosition(position);
    String town = mCursor.getString(mCursor.getColumnIndex(Constants.COLUMN_TOWN));
    String zipcode = mCursor.getString(mCursor.getColumnIndex(Constants.COLUMN_ZIPCODE));
    String zipcode_etx = mCursor.getString(mCursor.getColumnIndex(Constants.COLUMN_ZIPCODE_EXTENSION));
    holder.itemTV.setText(zipcode+"-"+zipcode_etx+", "+town);

}

@Override
public SearchListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_search_list_item,parent,false);
    // Passing the inflater job to the cursor-adapter
    return new SearchListAdapter.ViewHolder(itemView);
}

public void swapCursor(Cursor cursor) {
    Cursor old = changeCursor(cursor);
    if (old != null) {
        old.close();
    }
}

private Cursor changeCursor(Cursor newCursor) {
    if (newCursor == mCursor) {
        return null;
    }
    final Cursor oldCursor = mCursor;
    if (oldCursor != null && mDataSetObserver != null) {
        oldCursor.unregisterDataSetObserver(mDataSetObserver);
    }
    mCursor = newCursor;
    if (mCursor != null) {
        if (mDataSetObserver != null) {
            mCursor.registerDataSetObserver(mDataSetObserver);
        }
        mRowIdColumn = newCursor.getColumnIndexOrThrow("_id");
        mDataValid = true;
        notifyDataSetChanged();
    } else {
        mRowIdColumn = -1;
        mDataValid = false;
        notifyDataSetChanged();
    }
    return oldCursor;
}

private class NotifyingDataSetObserver extends DataSetObserver {
    @Override
    public void onChanged() {
        super.onChanged();
        mDataValid = true;
        notifyDataSetChanged();
    }

    @Override
    public void onInvalidated() {
        super.onInvalidated();
        mDataValid = false;
        notifyDataSetChanged();
    }
}


}

【问题讨论】:

  • Cursor cursor 是一个类变量。我只在这些方法和适配器中使用它。适配器本身与swapCursor 方法几乎相同
  • 在适配器中包含适配器听起来像是糟糕的设计决策
  • 进度对话框消失得非常快,但列表会在几秒钟后更新,UI 冻结
  • 我已经更新了我的答案,如果你愿意看一下,我将不胜感激

标签: android android-recyclerview


【解决方案1】:

嗯,我发现了为什么会发生这种情况,原因很奇怪。问题与 RecyclerView 无关,而与获取数据的方式有关。

在我获取数据的 AsyncTask 中,我写了一个 Log.d 来打印光标大小,如下所示:

@Override
protected Void doInBackground(Void... Void) {
    if(type==Constants.GET_ZIP_CODES)
        cursor = db.getAllZipCodes();
    else
        cursor = db.searchZipCodes(text);

    Log.d("DATABASE","SIZE   "+cursor.getCount());
    return null;
}

这使得 AsyncTask 需要更长的时间,ProgressDialog 需要更长的时间才能关闭。我的理解是,不知何故,数据库查询被执行,代码不断编译,但数据只是在一段时间后才在游标中准备好。一旦我在查询之后打印了结果,直到光标完全加载它才超过该行。

【讨论】:

    【解决方案2】:

    实际上这不是一个答案(如果我有足够的声誉点,我会在评论中提出)只是我在将数据从数据库加载到 recyclerView 时遇到的建议/案例研究。我没有将光标直接发送到适配器,而是将其作为数组列表发送,但除此之外。

    我像你一样冻结的地方是当我必须将超过 700-800 个字符的文本加载到卡片中时。因此,当我将文本裁剪到小于 600 时,冻结消失了。

    因此,只需检查您是否有任何具有大字符集的数据,如果有,请尝试将其删除并进行测试。

    希望它对你有用,建议为像 whatsapp 这样的大文本添加阅读更多选项!

    【讨论】:

    • 感谢您的建议,但不,我没有任何超过 20 个字符的条目
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多