RecyclerView在设置适配器时会阻塞用户界面。

11 浏览
0 Comments

RecyclerView在设置适配器时会阻塞用户界面。

我有一个需求,我有一个有超过30万行的填充数据库。我已经成功地根据这个问题实现了一个基于CursorAdapter的适配器,并混合了两个最高得票的答案这里。\n我已经实现了一个用于后台服务的AsyncTask来执行对数据库的查询,速度非常快,不超过2-3秒。我的AsyncTask中的ProgressDialog有时很难检测到。\n我的问题是,当任务完成并且我检索到Cursor时,当我将适配器设置给RecyclerView时,该过程会冻结我的UI几秒钟,直到数据设置完成。当我执行搜索(新查询,与获取所有行的过程相同,但行数较少)并替换Cursor以更新数据时,也会发生这种情况。\n下面是一些相关的代码:\nAsyncTask\n

@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);
}

\n方法\n

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

\n由于这是一个搜索,我在这里除了notifyDataSetChanged()之外没有太多要做的,因为每次搜索时数据都会广泛更改。\n这正常吗?由于RecyclerView只渲染可见视图,为什么会冻结并花费很长时间来更新,因为Cursor已经准备好来自AsyncTask的数据?\n编辑\n我已经更改了我的适配器,避免使用CursorAdapter,因为@cricket_007指出在适配器中使用另一个适配器是不好的设计。\n这是我的适配器:\n

public class SearchListAdapter extends RecyclerView.Adapter {
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();
    }
}
}

0
0 Comments

问题的原因是在设置适配器时,RecyclerView会阻塞用户界面。解决方法是检查是否有具有大字符集的数据,并尝试删除这些数据,以解决冻结问题。另外,建议为大文本添加类似于WhatsApp的“阅读更多”选项。

以下是完整的

在加载数据到RecyclerView时,我遇到了一个问题:当设置适配器时,RecyclerView会阻塞用户界面。尽管我不是通过直接将游标传递给适配器来解决这个问题的,但我认为我的经验可能对你有所帮助。

当我尝试将一个包含700-800个字符的文本加载到卡片中时,我遇到了与你类似的冻结问题。但是,当我将文本长度限制在600个字符以下时,冻结问题消失了。

所以,我建议你检查一下是否有具有大字符集的数据,如果有的话,尝试删除这些数据并重新测试一下。另外,你可以像WhatsApp那样为大文本添加一个“阅读更多”选项,以提供更好的用户体验。

谢谢你的建议,但是我没有任何长度超过20个字符的条目。

0
0 Comments

RecyclerView blocks UI when setting the adapter的问题出现的原因是数据获取的方式。在AsyncTask中,数据获取的过程中使用了Log.d来打印Cursor的大小,导致AsyncTask的执行时间变长,ProgressDialog的消失时间也变长。问题出现的原因是数据库查询操作是在后台进行的,而代码继续执行,只有在Cursor完全加载后才能继续执行。

解决方法是将打印结果的代码放在查询操作之后,即在Cursor加载完毕后再打印结果。这样可以确保查询操作完成后再继续执行其他代码,避免了RecyclerView阻塞UI的问题。

以下是修正后的代码示例:

protected Void doInBackground(Void... Void) {
    if(type==Constants.GET_ZIP_CODES)
        cursor = db.getAllZipCodes();
    else
        cursor = db.searchZipCodes(text);
    // 查询操作完成后打印结果
    return null;
}
protected void onPostExecute(Void result) {
    Log.d("DATABASE","SIZE   "+cursor.getCount());
    // 其他操作
}

通过将打印结果的代码放在查询操作之后,并在onPostExecute方法中执行,可以确保查询操作完成后再打印结果,避免了RecyclerView阻塞UI的问题。

0