【问题标题】:Loading large number of items in recycler view在回收站视图中加载大量项目
【发布时间】:2017-06-03 19:38:26
【问题描述】:

我在片段中有一个回收器视图,基本上我试图在回收器视图中加载歌曲列表。回收器视图的每一行都包含一个 imageview(用于专辑封面)和 textview (用于歌曲名称)。当数据集的大小很大时,我会遇到麻烦,即当歌曲太多时,回收器视图滞后并且应用程序最终给出 ANR。我正在使用 Glide 在每一行的图像视图中加载专辑封面。 谷歌音乐播放器如何能够毫无延迟地播放如此大量的歌曲?

编辑: 这是我的SongsFragment

public class SongsFragment extends Fragment {
static {
    AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
ProgressBar progressBar;    // progress bar to show after every 30 items
NestedScrollView nestedScrollView;  //for smooth scrolling of recyclerview as well as to detect the end of recyclerview
RecyclerView recyclerView;
ArrayList<Song> songMainList = new ArrayList<>();  //partial list in which items are added
ArrayList<Song> songAllList = new ArrayList<>(); //Complete List of songs
SongAdapter songsAdapter;
private LinearLayoutManager layoutManager;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment_songs, container, false);

    nestedScrollView = (NestedScrollView) rootView.findViewById(R.id.nestedScrollView);
    progressBar = (ProgressBar) rootView.findViewById(R.id.progressBar);

    String songJson = getActivity().getIntent().getStringExtra("songList");
    songAllList = new Gson().fromJson(songJson, new TypeToken<ArrayList<Song>>() {
    }.getType());
            //Getting list of all songs in songAllList

    if (songAllList.size() > 30) {  
        songMainList = new ArrayList<>(songAllList.subList(0,30));
    } else {
        songMainList = songAllList;
    }

    //if size of fetched songAllList>30 then add only 30 rows to songMainList

    recyclerView = (RecyclerView) rootView.findViewById(R.id.songs);
    int spanCount = 1; // 2 columns
    int spacing = 4; // 50px
    recyclerView.addItemDecoration(new GridItemDecoration(spanCount, spacing, true));
    recyclerView.setHasFixedSize(true);
    recyclerView.setNestedScrollingEnabled(false);
    recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    songsAdapter = new SongAdapter(getActivity(), songMainList, recyclerView);

    nestedScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            View view = (View) nestedScrollView.getChildAt(nestedScrollView.getChildCount() - 1);
            int diff = (view.getBottom() - (nestedScrollView.getHeight() + nestedScrollView
                    .getScrollY()));

            if (diff == 0) {    //NestedScrollView scrolled to bottom
                progressBar.setVisibility(View.VISIBLE);    //show progressbar
                new Handler().postDelayed(new Runnable() { 
                    @Override
                    public void run() {
                        if (songMainList.size() < songAllList.size()) {
                            int x = 0, y = 0;
                            if ((songAllList.size() - songMainList.size()) >= 30) {
                                x = songMainList.size();
                                y = x + 30;
                            } else {
                                x = songMainList.size();
                                y = x + songAllList.size() - songMainList.size();
                            }
                            for (int i = x; i < y; i++) {
                                songMainList.add(songAllList.get(i));   //Adding new items from songAllList to songMainList one by one
                                songsAdapter.notifyDataSetChanged();
                            }
                        }
                        progressBar.setVisibility(View.GONE);
                    }
                }, 1500);


            }
        }
    });
    recyclerView.setAdapter(songsAdapter);


    return rootView;
    }
}

这是我的 RecyclerViewAdapter 以及 viewholder

public class SongAdapter extends RecyclerView.Adapter {

private List<Song> songsList;
private Context c;

private RecyclerView.ViewHolder holder;

public SongAdapter(Context context) {
    mainActivityContext = context;
}

public SongAdapter(Context context, List<Song> songs, RecyclerView recyclerView) {
    songsList = songs;
    LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
    c = context;
}

public SongAdapter getInstance() {
    return SongAdapter.this;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.song_list_row, parent, false);
    return new SongViewHolder(view,c);
}

@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {

    if (holder instanceof SongViewHolder) {
        Song song = songsList.get(position);
        this.holder = holder;

        String name = song.getName();
        String artist = song.getArtist();
        String imagepath = song.getImagepath();

        ((SongViewHolder) holder).name.setText(name);

        ((SongViewHolder) holder).artist.setText(artist);

        if (!imagepath.equalsIgnoreCase("no_image")) //if the album art has valid  imagepath for this song

            Glide.with(c).load(imagepath)
                    .centerCrop()
                    .into(((SongViewHolder) holder).iv);
        else
            ((SongViewHolder) holder).iv.setImageResource(R.drawable.empty);

        ((SongViewHolder) holder).song = song;
    }
}

@Override
public int getItemCount() {
    return songsList.size();
}

static class SongViewHolder extends RecyclerView.ViewHolder{

    ImageView iv;
    TextView name, artist;
    CardView songListCard;
    private Context ctx;

    private OnLongPressListener mListener;

    SongViewHolder(View v, Context context) {
        super(v);
        this.ctx = context;
        iv= (ImageView) v.findViewById(R.id.album_art);
        name= (TextView) v.findViewById(R.id.name);
        artist= (TextView) v.findViewById(R.id.artist_mini);
        songListCard = (CardView) v.findViewById(R.id.song_list_card);
    }
}

当只有 150-200 个项目时,recyclerview 工作正常,但当达到 600-700 个项目时,整个应用程序会变慢。这可能是因为我在 onBindViewHolder 中使用了 glide 的方式吗?

【问题讨论】:

  • 你能分享一些代码吗??
  • 在编辑中添加代码

标签: android android-recyclerview android-glide mediastore


【解决方案1】:

排序答案:

LinearLayoutManager(context).apply { isAutoMeasureEnabled = false }
// or in Java
layoutManager.setAutoMeasureEnabled(false)

更新 2020.08.14

已弃用 RecyclerView.LayoutManager#setAutoMeasureEnabled

此方法在 API 级别 27.1.0 中已弃用。 LayoutManager 的实现者应该通过重写 isAutoMeasureEnabled()

来定义它是否使用 AutoMeasure

RecyclerView.LayoutManager#setAutoMeasureEnabled() 的文档我们知道:

如果想要支持 WRAP_CONTENT,通常由 LayoutManager 调用此方法,其值为 {@code true}

它的工作原理是在 {@link RecyclerView#onMeasure(int, int)} 调用期间调用 {@link LayoutManager#onLayoutChildren(Recycler, State)},然后根据孩子的位置计算所需的尺寸。

如果我们设置mAutoMeasure = true,它将在RecyclerView#onMeasure(int, int) 调用期间调用LayoutManager#onLayoutChildren(Recycler, State)。每个子视图的onMeasure()方法都会被调用,这太费时间了。


我们看看LinearLayoutManager的构造函数

public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
    setOrientation(orientation);
    setReverseLayout(reverseLayout);
    setAutoMeasureEnabled(true);
}

所以,我们设置mAutoMeasure = false之后,一切都会好起来的。

【讨论】:

  • RecyclerView.LayoutManager#setAutoMeasureEnabled() 现已弃用,需要改写 #isAutoMeasureEnabled()
【解决方案2】:

通过删除回收器视图上的 NestedScrollView 解决了问题。 nestedscrollview 不允许调用 recyclerview.addOnScrollListener() 因为我在加载更多项目时遇到了延迟。 这是我如何为 RecyclerView 实现 loadOnScroll-

 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if (!recyclerView.canScrollVertically(1))
                onScrolledToBottom();
        }
    });

private void onScrolledToBottom() {
    if (songMainList.size() < songAllList.size()) {
        int x, y;
        if ((songAllList.size() - songMainList.size()) >= 50) {
            x = songMainList.size();
            y = x + 50;
        } else {
            x = songMainList.size();
            y = x + songAllList.size() - songMainList.size();
        }
        for (int i = x; i < y; i++) {
            songMainList.add(songAllList.get(i));
        }
        songsAdapter.notifyDataSetChanged();
    }

}

【讨论】:

  • 似乎是一个更合乎逻辑的解决方案。数据集大小不应该与回收器视图滞后有任何关系,因为 - 无论数据源大小如何,视图都会回收少数视图。当然,除非为视图持有者关闭回收功能,但实际上几乎永远不会这样做。
  • 我遇到了同样的问题,删除 NestedScrollView 对我的情况也有帮助。谢谢!
【解决方案3】:

您是否一次加载所有数据? RecycleView 应该没有问题,我认为如果您有太多数据,处理本身可能会花费太多时间。您应该分块加载数据并检查用户的滚动状态并加载下一批等。有点像 Instagram 或 Facebook 的做法。

【讨论】:

  • 我已经添加了代码,并且我已经以类似的方式为另一个运行良好的 recyclerview 实现了 recyclerview...但我认为问题在于滑翔在 notifyDatasetChanged() 上重新加载图像
  • @breakline 我也希望这样做,但我不知道如何加载数据块。有没有相同的文章?
  • 获取数据应该永远导致回收器视图滞后,否则会出现架构问题,并且 UI 任务与数据任务没有正确分离。只能在后台线程上获取数据。
【解决方案4】:

您可以通过使用 AndroidX Room & Paging 实现此目的

  1. 将您的数据从网络缓存到本地 Room 数据库
  2. 使用 Paging 从 Room 数据库加载数据

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-14
    • 1970-01-01
    • 2016-10-03
    相关资源
    最近更新 更多