【问题标题】:RecyclerView blocking ui thread during updatesRecyclerView 在更新期间阻塞 ui 线程
【发布时间】:2017-04-25 10:48:54
【问题描述】:

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

此方法每 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);
        }
    }

}

【问题讨论】:

  • 你能把这个方法的代码放在你的问题中吗? AssetListHelper.getAssetListByQuery(查询,上下文);
  • assetList 是您活动中的全局变量,对吗?
  • 是的assetList 是一个全局变量。而 getAssetListByQuery 方法并不重要。我正在更新问题。
  • 使用recyclerViewAdapter.notifyItemChanged(position);
  • @Ram 我想刷新所有项目。不仅仅是特定的项目。

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


【解决方案1】:

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

正如文档所说:

这个事件没有说明数据集发生了什么变化, 强迫任何观察者假设所有现有的项目和结构 可能不再有效。 LayoutManagers 将被强制完全重新绑定 并重新布局所有可见视图。

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

你应该做什么:

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

【讨论】:

  • 感谢您的详细解答。经过一些研究,我发现了用于检测更改项目的 DiffUtil 类。即便如此冻结仍然存在。在 DiffUtil 4 秒冻结之前。现在使用 DiffUtil 类后:冻结时间为 2 秒。少冻结 2 秒。 DiffUtil 示例项目:github.com/mrmike/DiffUtil-sample
  • 很高兴您找到了问题的主要原因,现在获取开始和结束可见项目位置并仅更新这些项目,对其余视图项目不执行任何操作,只需将适配器的 List&lt;Asset&gt; 更新为新的一。在这种情况下,您只会更新可见项目,一旦您向上或向下滚动,您的视图将根据更新的列表进行更新
【解决方案2】:

执行更新适配器的操作:

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

【讨论】:

  • @yusufonder 这对您有帮助吗?
  • 结果如何?
  • 没有任何变化。冻结继续。
  • 如果不设置和更新适配器,问题是否仍然存在?检查一次。
  • 这意味着,这不会导致冻结。检查您正在使用的网络服务是否导致。
【解决方案3】:

经过一番研究,我发现了用于更新列表的 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 秒。不幸的是,这不是我的问题的最终解决方案。

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      猜你喜欢
      • 2010-11-11
      • 2019-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多