RecyclerView 在更新期间阻塞 ui 个线程

RecyclerView blocking ui thread during updates

我的列表中有 200 多个项目。 RecyclerView 定期更新(每 10 秒)。 RecyclerView 在更新期间阻塞 ui 线程几秒钟。我正在使用 notifyDataSetChanged 方法来刷新 recyclerview。还有其他方法可以防止冻结吗?顺便说一句,我不想​​使用分页。

此方法运行 每 10 秒:

public void refreshAssetList(List<Asset> newAssetList){
     recyclerViewAdapter.setAssetList(newAssetList);
     recyclerViewAdapter.notifyDataSetChanged();
}

RecyclerViewAdapter class :

public class AssetListRecyclerViewAdapter extends RecyclerView.Adapter<AssetListRecyclerViewAdapter.BaseViewHolder> {

    private List<Asset> assetList;
    Context context;

    public AssetListRecyclerViewAdapter(List<Asset> assetList, Context context) {
        this.assetList = assetList;
        this.context = context;
    }

    public void setAssetList(List<Asset> assetList) {
        this.assetList = assetList;
    }

    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_asset, null);
            return new DetailViewHolder(itemLayoutView);
    }

    @Override
    public void onBindViewHolder(BaseViewHolder holder, int position) {
        Asset asset = assetList.get(position);
        Last last = asset.getLast();
        if (holder.getItemViewType() == TYPE_DETAIL) {
            DetailViewHolder mHolder = (DetailViewHolder) holder;
            mHolder.dateTextView.setText(last.getDate());
            mHolder.brandTextView.setText(asset.getMc());
        }
    }

     class DetailViewHolder extends BaseViewHolder {

        @Bind(R.id.brandTextV)
        TextView brandTextView;
        @Bind(R.id.dateTextV)
        TextView dateTextView;

         DetailViewHolder(View itemLayoutView) {
            super(itemLayoutView);
            ButterKnife.bind(this, itemLayoutView);
        }
    }

}

执行更新适配器的操作为:

public void refreshAssetList(List<Asset> newAssetList){
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                recyclerViewAdapter.setAssetList(newAssetList);
                recyclerViewAdapter.notifyDataSetChanged();
            }
        }); 
}

您不需要调用 notifyDataSetChanged,这是一项昂贵的操作,您的整个 RecyclerView 将完全重绘、重新绑定等

正如文档所说:

This event does not specify what about the data set has changed, forcing any observers to assume that all existing items and structure may no longer be valid. LayoutManagers will be forced to fully rebind and relayout all visible views.

您需要做的就是遍历每个位置,如果需要更新所需的项目,否则什么也不做或跳过。

你应该做什么:

当您首先更新整个视图时,您需要将您的(可见)适配器的 List<Asset> 与新的 List<Asset> 进行比较,并仅检索那些您需要更新的项目,一旦您拥有列表循环更新列表并使用 viewAdapter.notifyItemChanged(position).

更新适配器的视图

经过一些研究,我发现 DiffUtil class 用于更新列表。

来自文档:

DiffUtil is a utility class that can calculate the difference between two lists and output a list of update operations that converts the first list into the second one.

DiffUtil 需要新列表和旧列表。它只更新列表中的更改项目。我创建了 AssetDiffUtil class 如下:

public class AssetDiffUtil extends DiffUtil.Callback {

    private final List<Asset> oldList;
    private final List<Asset> newList;

    public AssetDiffUtil(List<Asset> oldList, List<Asset> newList) {
        this.oldList = oldList;
        this.newList = newList;
    }

    @Override
    public int getOldListSize() {
        return oldList.size();
    }

    @Override
    public int getNewListSize() {
        return newList.size();
    }

    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return oldList.get(oldItemPosition).getId() == newList.get(newItemPosition).getId();
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        final Last oldItem = oldList.get(oldItemPosition).getLast();
        final Last newItem = newList.get(newItemPosition).getLast();
        return oldItem.getDate().equals(newItem.getDate());
    }

    @Nullable
    @Override
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        // Implement method if you're going to use ItemAnimator
        return super.getChangePayload(oldItemPosition, newItemPosition);
    }
}

然后我在 AssetListRecyclerViewAdapter class 中添加了用于刷新列表的 swapItems 方法。

  public void swapItems(List<Asset> newAssetList) {
        final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new AssetDiffUtil(this.assetList, newAssetList));
        this.assetList.clear();
        this.assetList.addAll(newAssetList);
        diffResult.dispatchUpdatesTo(this);
    }

就是这样。但是我的问题仍然存在。在使用 DiffUtil 之前冻结时间为 4 秒。现在,使用 DiffUtil 后冻结时间为 2 秒。不幸的是,这不是我的问题的最终解决方案。

使用 notifyItemRangeChanged 而不是 notifyDataSetChanged ,有时在使用 SpinKitView 等进度条库时也会增加冻结时间

loadMoreProgress.visibility = View.VISIBLE
            homeViewModel.latestBars.observe(viewLifecycleOwner, Observer {
                latestBarTests.addAll(it)
                latestBarsAdapter.notifyItemRangeChanged(
                    latestBarTests.size - it.size,
                    latestBarTests.size
                )
            })