RecyclerView在更新期间阻止ui线程

时间:2017-04-25 10:48:54

标签: android android-recyclerview ui-thread android-runonuithread

我的列表中有200多个项目。 RecyclerView定期更新(每10秒钟)。 RecyclerView在更新期间阻止ui线程几秒钟。我正在使用notifyDataSetChanged方法进行刷新回收查看。还有另一种防止冻结的方法吗?顺便说一下,我不想使用分页。

此方法每10秒运行一次:

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

RecyclerViewAdapter类:

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

}

4 个答案:

答案 0 :(得分:2)

您无需致电notifyDataSetChanged,这是一项昂贵的操作,您的整个RecyclerView将完全重绘,重新绑定等。

正如文件所说:

  

此事件未指定数据集的更改内容,   强迫任何观察者假设所有现有的物品和结构   可能不再有效。 LayoutManagers将被迫完全重新绑定   并重新布局所有可见的视图。

您需要做的就是遍历每个位置,如果需要,请更新所需的项目,否则不执行任何操作或跳过。

你应该做什么:

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

答案 1 :(得分:1)

执行操作以将适配器更新为:

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

答案 2 :(得分:0)

经过一番研究后,我找到了用于更新列表的DiffUtil类。

来自文档:

  

DiffUtil是一个可以计算它们之间差异的实用程序类   两个列表并输出一个转换更新操作的列表   第一个列入第二个。

DiffUtil需要新列表和旧列表。它仅更新列表中的更改项目。我创建了AssetDiffUtil类,如下所示:

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类中添加了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秒。不幸的是,这不是我的问题的最终解决方案。

答案 3 :(得分:0)

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

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