Android/Android

[안드로이드] DiffUtil

_hong 2023. 2. 21. 18:28

 

기존 notifyDataChanged()의 단점

리사이클러뷰를 사용할 때 아이템 뷰의 데이터가 변경되어서 업데이트를 해야 하는 경우 notifyDataChanged() 함수를 통해 리사이클러뷰에게 알리게 된다.

notifyDataChanged() 함수는 업데이트를 할 때 모든 아이템 뷰의 데이터들을 업데이트를 하기 때문에 적은 개수의 아이템을 업데이트하는 경우 비효율적이다.

 

Diffutil

Diffutil은 olditems와 newitems의 차이를 계산해서 업데이트해야 하는 아이템들만 변경하게 된다.

추상 클래스인 DiffUtil.Callback을 사용해서 구현을 하게 된다.

4가지의 추상 메서드와 1가지의 비 추상 메서드를 가지고 있다.

 

  • getOldListSize() : 바뀌기 전 리스트의 크기를 반환합니다.
  • getNewListSize() : 바뀐 후 리스트의 크기를 반환합니다.
  • areItemsTheSame(oldPosition:Int, newPosition:Int) : 두 객체가 동일한 항목을 나타내는지 확인합니다. 만일 true라면 다음 비교를 하고, false라면 리스트 갱신 시 화면이 깜빡거리는 현상이 발생할 수 있습니다. 즉, areItemsTheSame을 잘못 정의한다면 다시 새로 만들게 되어서 notifyDataSerChanged()와 다를 바 없이 집니다.
  • areContentsTheSame(oldPosition:Int, newPosition:Int) : 두 항목의 데이터가 같은지 확인한다. 해당 메서드는 areItemsTheSame()에서 true 인 경우에만 호출합니다. 같은 id값을 가졌더라도 내부의 값이 달려졌다면 변경된 것이므로 그것을 확인해야 한다. 최종적으로 false인 item에 대해서만 onBindViewHolder 메서드가 호출됩니다.

 

AsyncListDiffer

 

아이템 수가 많은 경우 olditems와 newitems의 차이를 계산을 해야 하는 시간이 오래 거릴 수 있다. 그래서 AsyncListDiffer가 등장했다. 백그라운드에서 diffutil을 사용할 수 있게 helper 클래스이다.

 

리사이클러뷰를 업데이트하기위해 submitList() 메서드를 호출한다. submitList를 통해 넘어온 리스트가 이전 리스트와 같다면

리스트를 변경하지않고 그대로 종료한다. (아이템이 없었는데 새로 생겼거나, 반대로 아이템이 있었는데 없어진 경우 내부적으로 콜백리스너(ListUpdateListener)를 통해 RecyclerView.Adapter 의 notifyItemRangeInserted(), notifyItemRangeRemoved() 함수가 호출) 다른 리스트를 전달할 경우 백그라운드에서 이전 리스트와 새로운 리스트의 차이를 연산하게 되는데 이때 difftuil.callback() 함수를 사용한다.

연산을 Main Thread에서 dispatchUpdatesTo 넘겨줌으로써 RecyclerView update한다.

 

// AsyncListDiffer.java
final List<T> oldList = mList;

// background thread에서 calculateDiff를 수행
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
    @Override
    public void run() {
        final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { ... });

		// 차이를 계산하여 main thread에서 update
        mMainThreadExecutor.execute(new Runnable() {
            @Override
            public void run() {
                if (mMaxScheduledGeneration == runGeneration) {
                    latchList(newList, result, commitCallback);
                }
            }
        });
    }
});

@SuppressWarnings("WeakerAccess") /* synthetic access */
void latchList(
        @NonNull List<T> newList,
        @NonNull DiffUtil.DiffResult diffResult,
        @Nullable Runnable commitCallback) {
    final List<T> previousList = mReadOnlyList;
    mList = newList;
    // notify last, after list is updated
    mReadOnlyList = Collections.unmodifiableList(newList);
    diffResult.dispatchUpdatesTo(mUpdateCallback);
    onCurrentListChanged(previousList, commitCallback);
}

 

ListAdapter

 

AsyncListDiffer를 좀 더 편하게 사용할 수 있도록 해주는 래퍼클래스이다.

ListAdapter는 DiffUtil을 활용하여 리스트를 업데이트하는 기능이 추가된 Adapter이다.

 

Reference