为什么这个分页库示例的性能比回收器视图差得多?
Why does this example of paging library perform much worse than just a recycler view?
所以我一直在阅读 Android 的新分页库。具体来说,我正在尝试使用带有适配器、视图模型和数据源(由改造网络调用支持)的 recyclerview 加载一个永无止境的图像列表(使用 Glide)。
这是基本代码:
// inside my activity
PhotosListAdapter adapter = new PhotosListAdapter(getApplicationContext());
PhotosViewModel viewModel = ViewModelProviders.of(this).get(PhotosViewModel.class);
viewModel.getPhotosList().observe(this, adapter::submitList);
RecyclerView recyclerView = findViewById(R.id.main_recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(adapter);
...
// My adapter code:
@Override
public void onBindViewHolder(@NonNull PhotoViewHolder photoViewHolder, int i) {
Photo photo = getItem(i);
if (photo != null) {
mGlide.load(photo.previewURL).into(photoViewHolder.mPhotoImageView);
}
}
private static final DiffUtil.ItemCallback<Photo> DIFF_CALLBACK = new DiffUtil.ItemCallback<Photo>() {
@Override
public boolean areItemsTheSame(@NonNull Photo photo, @NonNull Photo other) {
return photo.id == other.id; // these are just ints
}
@Override
public boolean areContentsTheSame(@NonNull Photo photo, @NonNull Photo other) {
return photo.previewURL.equals(other.previewURL); // this is just a string
}
};
...
// My View Model code:
private LiveData<PagedList<Photo>> mData;
public PhotosViewModel() {
PhotosDataSource photosDataSource = new PhotosDataSource();
mData = new LivePagedListBuilder<>(new DataSource.Factory<Integer, Photo>() {
@Override
public DataSource<Integer, Photo> create() {
return photosDataSource;
}
}, 25).build();
}
public LiveData<PagedList<Photo>> getPhotosList() {
return mData;
}
// My Data Source:
Call<SearchResult> search = mPixabayService.search(PixabayApi.api_key, "photo", 1, params.requestedLoadSize, true);
search.enqueue(new Callback<SearchResult>() {
@Override
public void onResponse(Call<SearchResult> call, Response<SearchResult> response) {
if (response.isSuccessful()) {
SearchResult body = response.body();
callback.onResult(body.hits, null, 2);
}
// TODO add error cases
}
@Override
public void onFailure(Call<SearchResult> call, Throwable t) {
}
});
@Override
public void loadAfter(@NonNull LoadParams params, @NonNull LoadCallback callback) {
Call<SearchResult> result = mPixabayService.search(PixabayApi.api_key, "photo", (Integer)params.key, params.requestedLoadSize, true);
result.enqueue(new Callback<SearchResult>() {
@Override
public void onResponse(Call<SearchResult> call, Response<SearchResult> response) {
if (response.isSuccessful()) {
List<Photo> hits = response.body().hits;
callback.onResult(hits, (Integer)params.key + 1);
}
}
@Override
public void onFailure(Call<SearchResult> call, Throwable t) {
}
});
}
但是,我仅使用静态项目列表的适配器构建了相同的示例,并且它似乎在我的模拟器上滚动得更顺畅。我的代码中缺少什么吗?
以下是我对可能使情况变得更糟的因素的怀疑:
- 在这种情况下
recyclerview.setHasFixedSize(true)
有意义吗? (当我向下滚动然后返回原始图像时,我确实也注意到图像大小不同 - 有没有办法防止这种情况?)
- 这是因为
DIFF_CALLBACK
中有什么奇怪的东西吗?我其实不太清楚这个class的目的
viewModel.getPhotosList().observe(this, adapter::submitList);
预计会更慢,因为它会使列表无效(与静态列表相比);但是,这会导致 5 倍的延迟吗??!
- 我注意到我在模拟器上飞得很快,这在静态 recyclerview 上似乎不是问题;然而,LivePage 是否为他们的投掷检测做了一些不同的事情?当我一扔时,屏幕似乎变得疯狂。换句话说,我认为 "jagged performance from paging library" 只是因为我在上下摆动(而不是缓慢地上下滚动)吗?
- 我是不是漏掉了某处的关键配置设置?
当然,静态列表会更平滑 - 开销要少得多。如果这是一个实际的可能性,那就去吧。当将整个列表保存在内存中不可行,或者从数据源(尤其是远程数据源)请求整个列表太浪费时间时,您可以使用分页列表 consuming/costly。
1) 取决于您的 UI。固定大小是它在屏幕上的大小,而不是列表的大小。通常,对于完整大小的列表或不 grow/shrink 包含内容的列表,这是正确的。
2)DiffUtil及其回调与分页无关,它们是预先存在的分页库。它们围绕更改区分列表,以尽可能最有效地调用适配器,而不是执行整个 notifyDataSetChanged。但他们这样做确实需要一些时间。重要的是这是正确的,否则你会错过通知,或者使用效率较低的通知。
3)预计比什么慢?但是在任何你使用 observable 的地方你都会看到速度变慢,你可能会在线程之间发布消息。这就是他们经常被严重滥用的原因之一,人们不明白他们实际上做了什么。是的,减速 5 倍似乎不太可能,特别是如果您最终使用效率较低的 notifyDataSetChanged 而不是更精确的通知。
4) 投掷时,可以更快地加载更多项目。这可能会导致更多的远程负载。因此,如果您要遇到问题,那将是您逃避的时候。通常,您希望提前足够长的时间在单独的线程上进行预加载,这不是问题。您是否尝试过调整页面大小和预取距离?
所以我一直在阅读 Android 的新分页库。具体来说,我正在尝试使用带有适配器、视图模型和数据源(由改造网络调用支持)的 recyclerview 加载一个永无止境的图像列表(使用 Glide)。
这是基本代码:
// inside my activity
PhotosListAdapter adapter = new PhotosListAdapter(getApplicationContext());
PhotosViewModel viewModel = ViewModelProviders.of(this).get(PhotosViewModel.class);
viewModel.getPhotosList().observe(this, adapter::submitList);
RecyclerView recyclerView = findViewById(R.id.main_recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(adapter);
...
// My adapter code:
@Override
public void onBindViewHolder(@NonNull PhotoViewHolder photoViewHolder, int i) {
Photo photo = getItem(i);
if (photo != null) {
mGlide.load(photo.previewURL).into(photoViewHolder.mPhotoImageView);
}
}
private static final DiffUtil.ItemCallback<Photo> DIFF_CALLBACK = new DiffUtil.ItemCallback<Photo>() {
@Override
public boolean areItemsTheSame(@NonNull Photo photo, @NonNull Photo other) {
return photo.id == other.id; // these are just ints
}
@Override
public boolean areContentsTheSame(@NonNull Photo photo, @NonNull Photo other) {
return photo.previewURL.equals(other.previewURL); // this is just a string
}
};
...
// My View Model code:
private LiveData<PagedList<Photo>> mData;
public PhotosViewModel() {
PhotosDataSource photosDataSource = new PhotosDataSource();
mData = new LivePagedListBuilder<>(new DataSource.Factory<Integer, Photo>() {
@Override
public DataSource<Integer, Photo> create() {
return photosDataSource;
}
}, 25).build();
}
public LiveData<PagedList<Photo>> getPhotosList() {
return mData;
}
// My Data Source:
Call<SearchResult> search = mPixabayService.search(PixabayApi.api_key, "photo", 1, params.requestedLoadSize, true);
search.enqueue(new Callback<SearchResult>() {
@Override
public void onResponse(Call<SearchResult> call, Response<SearchResult> response) {
if (response.isSuccessful()) {
SearchResult body = response.body();
callback.onResult(body.hits, null, 2);
}
// TODO add error cases
}
@Override
public void onFailure(Call<SearchResult> call, Throwable t) {
}
});
@Override
public void loadAfter(@NonNull LoadParams params, @NonNull LoadCallback callback) {
Call<SearchResult> result = mPixabayService.search(PixabayApi.api_key, "photo", (Integer)params.key, params.requestedLoadSize, true);
result.enqueue(new Callback<SearchResult>() {
@Override
public void onResponse(Call<SearchResult> call, Response<SearchResult> response) {
if (response.isSuccessful()) {
List<Photo> hits = response.body().hits;
callback.onResult(hits, (Integer)params.key + 1);
}
}
@Override
public void onFailure(Call<SearchResult> call, Throwable t) {
}
});
}
但是,我仅使用静态项目列表的适配器构建了相同的示例,并且它似乎在我的模拟器上滚动得更顺畅。我的代码中缺少什么吗?
以下是我对可能使情况变得更糟的因素的怀疑:
- 在这种情况下
recyclerview.setHasFixedSize(true)
有意义吗? (当我向下滚动然后返回原始图像时,我确实也注意到图像大小不同 - 有没有办法防止这种情况?) - 这是因为
DIFF_CALLBACK
中有什么奇怪的东西吗?我其实不太清楚这个class的目的 viewModel.getPhotosList().observe(this, adapter::submitList);
预计会更慢,因为它会使列表无效(与静态列表相比);但是,这会导致 5 倍的延迟吗??!- 我注意到我在模拟器上飞得很快,这在静态 recyclerview 上似乎不是问题;然而,LivePage 是否为他们的投掷检测做了一些不同的事情?当我一扔时,屏幕似乎变得疯狂。换句话说,我认为 "jagged performance from paging library" 只是因为我在上下摆动(而不是缓慢地上下滚动)吗?
- 我是不是漏掉了某处的关键配置设置?
当然,静态列表会更平滑 - 开销要少得多。如果这是一个实际的可能性,那就去吧。当将整个列表保存在内存中不可行,或者从数据源(尤其是远程数据源)请求整个列表太浪费时间时,您可以使用分页列表 consuming/costly。
1) 取决于您的 UI。固定大小是它在屏幕上的大小,而不是列表的大小。通常,对于完整大小的列表或不 grow/shrink 包含内容的列表,这是正确的。
2)DiffUtil及其回调与分页无关,它们是预先存在的分页库。它们围绕更改区分列表,以尽可能最有效地调用适配器,而不是执行整个 notifyDataSetChanged。但他们这样做确实需要一些时间。重要的是这是正确的,否则你会错过通知,或者使用效率较低的通知。
3)预计比什么慢?但是在任何你使用 observable 的地方你都会看到速度变慢,你可能会在线程之间发布消息。这就是他们经常被严重滥用的原因之一,人们不明白他们实际上做了什么。是的,减速 5 倍似乎不太可能,特别是如果您最终使用效率较低的 notifyDataSetChanged 而不是更精确的通知。
4) 投掷时,可以更快地加载更多项目。这可能会导致更多的远程负载。因此,如果您要遇到问题,那将是您逃避的时候。通常,您希望提前足够长的时间在单独的线程上进行预加载,这不是问题。您是否尝试过调整页面大小和预取距离?