Android 使用自定义添加的视图时出现 DiffUtil 不一致异常,例如空列表
Android DiffUtil inconsistency exceptions when using custom added views like i.e. for empty lists
我需要 RecyclerView 适配器的 DiffUtil 方面的帮助。我制作了一个自定义适配器,可以添加自定义视图,如加载视图或空视图等。不使用 DiffUtil 一切正常,但当我使用它时,有时会检测到不一致异常。我认为问题出在 getItemCount() 方法中,但我不确定。如果有人可以给我建议,那将非常有帮助。这是我正在使用的代码。
这是我的 DiffUtil 适配器 Class:
public abstract class DiffRecyclerViewAdapter<T extends DiffComparable<T>> extends BaseRecyclerViewAdapter<T> {
private DifferenceCalculator<T> mDiffCalculator = new DifferenceCalculator<>(
new DifferenceCalculator.DiffCallback<>(getPayloadCallback()),
new UpdateCallback(this),
TaskExecutor.getInstance().getExecutor());
public DiffRecyclerViewAdapter(Context mContext, List<T> itemList) {
super(mContext, itemList);
setDataIsLoading(true);
}
public DiffRecyclerViewAdapter(Context mContext, List<T> itemList, int itemsLeftToLoadMore, LoadMoreDataCallback listener) {
super(mContext, itemList, itemsLeftToLoadMore, listener);
setDataIsLoading(true);
}
public void setItemList(List<T> list) {
if (isLoading()) {
setDataIsLoading(false, true);
}
mDiffCalculator.calculateDifference(list);
}
@Override
public int getItemCount() {
int superCount = super.getItemCount();
log("getItemCount() called with count=" + mDiffCalculator.getCurrentList().size());
return superCount > mDiffCalculator.getCurrentList().size() ? superCount : mDiffCalculator.getCurrentList().size();
}
@Override
public List<T> getList() {
return mDiffCalculator.getCurrentList();
}
protected abstract PayloadCallback<T> getPayloadCallback();
protected void onNewList() {
}
protected void onItemsInserted(int position, int count) {
log("onItemsInserted(position=" + position + ", count=" + count + ")");
notifyItemRangeInserted(position, count);
}
@SuppressWarnings("WeakerAccess")
protected void onItemsRemoved(int position, int count) {
log("onItemsRemoved(position=" + position + ", count=" + count + ")");
notifyItemRangeRemoved(position, count);
}
@SuppressWarnings("WeakerAccess")
protected void onItemMoved(int fromPosition, int toPosition) {
log("onItemMoved(fromPosition=" + fromPosition + ", toPosition=" + toPosition + ")");
notifyItemMoved(fromPosition, toPosition);
}
@SuppressWarnings("WeakerAccess")
protected void onItemsChanged(int position, int count, @Nullable Object payload) {
log("onItemsChanged(position=" + position + ", count=" + count + ", payload=" + payload + ")");
notifyItemRangeChanged(position, count, payload);
}
public void log(String msg) {
if (IN_DEBUG_MODE) {
Log.i(getClass().getSimpleName(), msg);
}
}
public static class UpdateCallback implements ListUpdateCallback {
private DiffRecyclerViewAdapter adapter;
private UpdateCallback(DiffRecyclerViewAdapter adapter) {
this.adapter = adapter;
}
@SuppressWarnings("WeakerAccess")
public void onUpdateStart() {
Log.w(getClass().getSimpleName(), "onUpdateStart()");
adapter.setListUpdateInProgress(true);
}
@SuppressWarnings("WeakerAccess")
public void onUpdateFinish() {
Log.w(getClass().getSimpleName(), "onUpdateFinish()");
adapter.setListUpdateInProgress(false);
}
@SuppressWarnings("WeakerAccess")
public void onNewList() {
adapter.onNewList();
}
@Override
public void onInserted(int position, int count) {
adapter.onItemsInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
adapter.onItemsRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
adapter.onItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count, @Nullable Object payload) {
adapter.onItemsChanged(position, count, payload);
}
}
public interface PayloadCallback<T> {
@Nullable
Object getChangePayload(T oldItem, T newItem);
}
}
这是我的差分计算器Class:
public class DifferenceCalculator<T extends DiffComparable<T>> {
private MainThreadExecutor mMainThreadExecutor;
private Executor mBackgroundThreadExecutor;
private DiffCallback<T> mDiffUtilCallback;
private DiffRecyclerViewAdapter.UpdateCallback mUpdateCallback;
private List<T> mList;
private List<T> mReadOnlyList = Collections.emptyList();
private int mMaxScheduledGeneration;
@SuppressWarnings("unused")
public DifferenceCalculator(DiffCallback<T> diffCallback, DiffRecyclerViewAdapter.UpdateCallback callback) {
this(diffCallback, callback, null);
}
@SuppressWarnings("WeakerAccess")
public DifferenceCalculator(DiffCallback<T> diffCallback,
DiffRecyclerViewAdapter.UpdateCallback callback,
Executor backgroundThreadExecutor) {
mDiffUtilCallback = diffCallback;
mUpdateCallback = callback;
mMainThreadExecutor = new MainThreadExecutor();
mBackgroundThreadExecutor = backgroundThreadExecutor != null ? backgroundThreadExecutor :
new ThreadPoolExecutor(
2,
2,
30,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>()
);
}
@SuppressWarnings("WeakerAccess")
@NonNull
public List<T> getCurrentList() {
return mReadOnlyList;
}
@SuppressWarnings("WeakerAccess")
public void calculateDifference(@Nullable final List<T> newList) {
final int runGeneration = ++mMaxScheduledGeneration;
log("calculating difference for process=" + runGeneration);
mUpdateCallback.onUpdateStart();
if (newList == mList) {
mUpdateCallback.onUpdateFinish();
log("abandoned because no change!");
return;
}
if (newList == null) {
int countRemoved = mList.size();
mList = null;
mReadOnlyList = Collections.emptyList();
mUpdateCallback.onRemoved(0, countRemoved);
mUpdateCallback.onUpdateFinish();
log("New List is null!");
return;
}
if (mList == null) {
mList = newList;
mReadOnlyList = Collections.unmodifiableList(newList);
mUpdateCallback.onInserted(0, newList.size());
mUpdateCallback.onNewList();
mUpdateCallback.onUpdateFinish();
log("Complete new List arrived.");
return;
}
final List<T> oldList = mList;
mBackgroundThreadExecutor.execute(() -> {
final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return oldList.size();
}
@Override
public int getNewListSize() {
return newList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
T oldItem = oldList.get(oldItemPosition);
T newItem = newList.get(newItemPosition);
if (oldItem != null && newItem != null) {
return mDiffUtilCallback.areItemsTheSame(oldItem, newItem);
}
return oldItem == null && newItem == null;
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
T oldItem = oldList.get(oldItemPosition);
T newItem = newList.get(newItemPosition);
if (oldItem != null && newItem != null) {
return mDiffUtilCallback.areContentsTheSame(oldItem, newItem);
}
if (oldItem == null && newItem == null) {
return true;
}
throw new AssertionError();
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
T oldItem = oldList.get(oldItemPosition);
T newItem = newList.get(newItemPosition);
if (oldItem != null && newItem != null) {
return mDiffUtilCallback.getChangePayload(oldItem, newItem);
}
throw new AssertionError();
}
});
mMainThreadExecutor.execute(() -> {
if (mMaxScheduledGeneration == runGeneration) {
dispatchResult(newList, result);
} else {
mUpdateCallback.onUpdateFinish();
log("result not dispatched because other pending calculations in queue!");
}
});
});
}
private void dispatchResult(@NonNull List<T> newList, @NonNull DiffUtil.DiffResult diffResult) {
log("dispatching result");
mList = newList;
mReadOnlyList = Collections.unmodifiableList(newList);
diffResult.dispatchUpdatesTo(mUpdateCallback);
mUpdateCallback.onUpdateFinish();
}
private static class MainThreadExecutor implements Executor {
private Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable command) {
handler.post(command);
}
}
public static class DiffCallback<T extends DiffComparable<T>> {
private DiffRecyclerViewAdapter.PayloadCallback<T> callback;
public DiffCallback(DiffRecyclerViewAdapter.PayloadCallback<T> callback){
this.callback = callback;
}
boolean areItemsTheSame(T oldItem, T newItem) {
return oldItem.isItemTheSame(newItem);
}
boolean areContentsTheSame(T oldItem, T newItem) {
return oldItem.isContentTheSame(newItem);
}
@Nullable
public Object getChangePayload(T oldItem, T newItem) {
if(callback != null){
return callback.getChangePayload(oldItem, newItem);
}
return null;
}
}
private void log(String msg){
if(IN_DEBUG_MODE){
Log.w(getClass().getSimpleName(), msg);
}
}
}
从我从堆栈跟踪中读取的信息来看,适配器或 RecyclerView 似乎收到了最后一项的错误位置。它总是试图为位置 == itemCount 的最后一个项目获取一个 Viewholder,当然这不起作用,因为位置从 0 开始。那么为什么它不接收最后一个项目的位置 == (itemCount - 1) ?
解决了!当我的空视图可见并且我插入了一个新的非空列表时,问题就出现了。在插入新列表之前,我忘记调用 notifyItemChanged(0) 了。
我更改了更新回调 class 的 onUpdateStart() 方法,一切正常。这是变化
@SuppressWarnings("WeakerAccess")
public void onUpdateStart() {
Log.w(getClass().getSimpleName(), "onUpdateStart()");
adapter.setListUpdateInProgress(true);
if(adapter.isEmptyViewVisible()){
adapter.notifyItemChanged(0);
}
}
我需要 RecyclerView 适配器的 DiffUtil 方面的帮助。我制作了一个自定义适配器,可以添加自定义视图,如加载视图或空视图等。不使用 DiffUtil 一切正常,但当我使用它时,有时会检测到不一致异常。我认为问题出在 getItemCount() 方法中,但我不确定。如果有人可以给我建议,那将非常有帮助。这是我正在使用的代码。
这是我的 DiffUtil 适配器 Class:
public abstract class DiffRecyclerViewAdapter<T extends DiffComparable<T>> extends BaseRecyclerViewAdapter<T> {
private DifferenceCalculator<T> mDiffCalculator = new DifferenceCalculator<>(
new DifferenceCalculator.DiffCallback<>(getPayloadCallback()),
new UpdateCallback(this),
TaskExecutor.getInstance().getExecutor());
public DiffRecyclerViewAdapter(Context mContext, List<T> itemList) {
super(mContext, itemList);
setDataIsLoading(true);
}
public DiffRecyclerViewAdapter(Context mContext, List<T> itemList, int itemsLeftToLoadMore, LoadMoreDataCallback listener) {
super(mContext, itemList, itemsLeftToLoadMore, listener);
setDataIsLoading(true);
}
public void setItemList(List<T> list) {
if (isLoading()) {
setDataIsLoading(false, true);
}
mDiffCalculator.calculateDifference(list);
}
@Override
public int getItemCount() {
int superCount = super.getItemCount();
log("getItemCount() called with count=" + mDiffCalculator.getCurrentList().size());
return superCount > mDiffCalculator.getCurrentList().size() ? superCount : mDiffCalculator.getCurrentList().size();
}
@Override
public List<T> getList() {
return mDiffCalculator.getCurrentList();
}
protected abstract PayloadCallback<T> getPayloadCallback();
protected void onNewList() {
}
protected void onItemsInserted(int position, int count) {
log("onItemsInserted(position=" + position + ", count=" + count + ")");
notifyItemRangeInserted(position, count);
}
@SuppressWarnings("WeakerAccess")
protected void onItemsRemoved(int position, int count) {
log("onItemsRemoved(position=" + position + ", count=" + count + ")");
notifyItemRangeRemoved(position, count);
}
@SuppressWarnings("WeakerAccess")
protected void onItemMoved(int fromPosition, int toPosition) {
log("onItemMoved(fromPosition=" + fromPosition + ", toPosition=" + toPosition + ")");
notifyItemMoved(fromPosition, toPosition);
}
@SuppressWarnings("WeakerAccess")
protected void onItemsChanged(int position, int count, @Nullable Object payload) {
log("onItemsChanged(position=" + position + ", count=" + count + ", payload=" + payload + ")");
notifyItemRangeChanged(position, count, payload);
}
public void log(String msg) {
if (IN_DEBUG_MODE) {
Log.i(getClass().getSimpleName(), msg);
}
}
public static class UpdateCallback implements ListUpdateCallback {
private DiffRecyclerViewAdapter adapter;
private UpdateCallback(DiffRecyclerViewAdapter adapter) {
this.adapter = adapter;
}
@SuppressWarnings("WeakerAccess")
public void onUpdateStart() {
Log.w(getClass().getSimpleName(), "onUpdateStart()");
adapter.setListUpdateInProgress(true);
}
@SuppressWarnings("WeakerAccess")
public void onUpdateFinish() {
Log.w(getClass().getSimpleName(), "onUpdateFinish()");
adapter.setListUpdateInProgress(false);
}
@SuppressWarnings("WeakerAccess")
public void onNewList() {
adapter.onNewList();
}
@Override
public void onInserted(int position, int count) {
adapter.onItemsInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
adapter.onItemsRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
adapter.onItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count, @Nullable Object payload) {
adapter.onItemsChanged(position, count, payload);
}
}
public interface PayloadCallback<T> {
@Nullable
Object getChangePayload(T oldItem, T newItem);
}
}
这是我的差分计算器Class:
public class DifferenceCalculator<T extends DiffComparable<T>> {
private MainThreadExecutor mMainThreadExecutor;
private Executor mBackgroundThreadExecutor;
private DiffCallback<T> mDiffUtilCallback;
private DiffRecyclerViewAdapter.UpdateCallback mUpdateCallback;
private List<T> mList;
private List<T> mReadOnlyList = Collections.emptyList();
private int mMaxScheduledGeneration;
@SuppressWarnings("unused")
public DifferenceCalculator(DiffCallback<T> diffCallback, DiffRecyclerViewAdapter.UpdateCallback callback) {
this(diffCallback, callback, null);
}
@SuppressWarnings("WeakerAccess")
public DifferenceCalculator(DiffCallback<T> diffCallback,
DiffRecyclerViewAdapter.UpdateCallback callback,
Executor backgroundThreadExecutor) {
mDiffUtilCallback = diffCallback;
mUpdateCallback = callback;
mMainThreadExecutor = new MainThreadExecutor();
mBackgroundThreadExecutor = backgroundThreadExecutor != null ? backgroundThreadExecutor :
new ThreadPoolExecutor(
2,
2,
30,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>()
);
}
@SuppressWarnings("WeakerAccess")
@NonNull
public List<T> getCurrentList() {
return mReadOnlyList;
}
@SuppressWarnings("WeakerAccess")
public void calculateDifference(@Nullable final List<T> newList) {
final int runGeneration = ++mMaxScheduledGeneration;
log("calculating difference for process=" + runGeneration);
mUpdateCallback.onUpdateStart();
if (newList == mList) {
mUpdateCallback.onUpdateFinish();
log("abandoned because no change!");
return;
}
if (newList == null) {
int countRemoved = mList.size();
mList = null;
mReadOnlyList = Collections.emptyList();
mUpdateCallback.onRemoved(0, countRemoved);
mUpdateCallback.onUpdateFinish();
log("New List is null!");
return;
}
if (mList == null) {
mList = newList;
mReadOnlyList = Collections.unmodifiableList(newList);
mUpdateCallback.onInserted(0, newList.size());
mUpdateCallback.onNewList();
mUpdateCallback.onUpdateFinish();
log("Complete new List arrived.");
return;
}
final List<T> oldList = mList;
mBackgroundThreadExecutor.execute(() -> {
final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return oldList.size();
}
@Override
public int getNewListSize() {
return newList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
T oldItem = oldList.get(oldItemPosition);
T newItem = newList.get(newItemPosition);
if (oldItem != null && newItem != null) {
return mDiffUtilCallback.areItemsTheSame(oldItem, newItem);
}
return oldItem == null && newItem == null;
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
T oldItem = oldList.get(oldItemPosition);
T newItem = newList.get(newItemPosition);
if (oldItem != null && newItem != null) {
return mDiffUtilCallback.areContentsTheSame(oldItem, newItem);
}
if (oldItem == null && newItem == null) {
return true;
}
throw new AssertionError();
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
T oldItem = oldList.get(oldItemPosition);
T newItem = newList.get(newItemPosition);
if (oldItem != null && newItem != null) {
return mDiffUtilCallback.getChangePayload(oldItem, newItem);
}
throw new AssertionError();
}
});
mMainThreadExecutor.execute(() -> {
if (mMaxScheduledGeneration == runGeneration) {
dispatchResult(newList, result);
} else {
mUpdateCallback.onUpdateFinish();
log("result not dispatched because other pending calculations in queue!");
}
});
});
}
private void dispatchResult(@NonNull List<T> newList, @NonNull DiffUtil.DiffResult diffResult) {
log("dispatching result");
mList = newList;
mReadOnlyList = Collections.unmodifiableList(newList);
diffResult.dispatchUpdatesTo(mUpdateCallback);
mUpdateCallback.onUpdateFinish();
}
private static class MainThreadExecutor implements Executor {
private Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable command) {
handler.post(command);
}
}
public static class DiffCallback<T extends DiffComparable<T>> {
private DiffRecyclerViewAdapter.PayloadCallback<T> callback;
public DiffCallback(DiffRecyclerViewAdapter.PayloadCallback<T> callback){
this.callback = callback;
}
boolean areItemsTheSame(T oldItem, T newItem) {
return oldItem.isItemTheSame(newItem);
}
boolean areContentsTheSame(T oldItem, T newItem) {
return oldItem.isContentTheSame(newItem);
}
@Nullable
public Object getChangePayload(T oldItem, T newItem) {
if(callback != null){
return callback.getChangePayload(oldItem, newItem);
}
return null;
}
}
private void log(String msg){
if(IN_DEBUG_MODE){
Log.w(getClass().getSimpleName(), msg);
}
}
}
从我从堆栈跟踪中读取的信息来看,适配器或 RecyclerView 似乎收到了最后一项的错误位置。它总是试图为位置 == itemCount 的最后一个项目获取一个 Viewholder,当然这不起作用,因为位置从 0 开始。那么为什么它不接收最后一个项目的位置 == (itemCount - 1) ?
解决了!当我的空视图可见并且我插入了一个新的非空列表时,问题就出现了。在插入新列表之前,我忘记调用 notifyItemChanged(0) 了。 我更改了更新回调 class 的 onUpdateStart() 方法,一切正常。这是变化
@SuppressWarnings("WeakerAccess")
public void onUpdateStart() {
Log.w(getClass().getSimpleName(), "onUpdateStart()");
adapter.setListUpdateInProgress(true);
if(adapter.isEmptyViewVisible()){
adapter.notifyItemChanged(0);
}
}