RecyclerView 의 Adapter 를 구현할 , RecyclerView.Adapter 를 상속받아 구현하는 방법보다 ListAdapter 를 상속하는 방식이 더 빠르다고 알려져있다.
ListAdapter 는 AsyncListDiffer 를 사용하고 있고, AsyncListDiffer 가 내부적으로 DiffUtil.ItemCallback 을 사용하고 있다.
ListAdapter-> AsyncListDiffer -> DiffUtil
DiffUtil 클래스는 이전 데이터 상태와 현재 데이터간의 상태 차이를 계산하고, 반드시 업데이트해야 할 최소한의 데이터에 대해서만 갱신하는 클래스이다.
DiffUitl 이 원래 목록과 새로 들어온 목록간의 차이를 계산하고 난 뒤 DiffUtil.Callback 이라는 추상 클래스를 콜백 클래스로 활용하게 된다. DiffUtil.Callback 은 4개의 추상 메소드와 1개의 일반 메소드로 이루어져있는데, 이러한 메소드를 오버라이딩하여 사용한다. (4개 추상 메소드의 경우 당연하게도 오버라이딩 필수)
기존의 RecyclerView 를 사용할 때 동적으로 데이터가 변경되는 경우 RecyclerView Adapter 가 제공하는notifyItem 메소드
를 이용해 아이템의 갱신을 리사이클러뷰에 알려줬었다.
그런데 개발자가 데이터가 변경되는 방식을 직접 판단하고, 그때마다 이렇게 notify를 일일이 해 주는것은 번거롭다. 그리고 알맞지 않은 메소드를 사용한다면 갱신이 필요없는 ViewHolder를 같이 갱신하는 불필요한 작업이 생길수도 있다.
DiffUtil 클래스는 두 데이터셋을 받아서 그 차이를 계산해주는 클래스이다. DiffUtil을 사용하면 두 데이터 셋을 비교한 뒤 그중 변한부분만을 파악하여 Recyclerview에 반영할 수 있다.
DiffUtil 함수의 코드를 까보면 내부적으로 데이터 변경 상황에 맞게 아래와 같은 함수들 중 적절한 하나를 알아서 실행시켜주고 있다.
DiffUtil 이 내부적으로 알아서 처리해주기 때문에 더 이상 위의 번거롭고 실수가 발생할 수 있는 과정을 거치지 않아도 된다. 캡슐화 ~ 👍
사전 지식
Main Thread
일반 Thread
Looper 란
Handeler 란
Handler 의 주요 용도
1. 일반 스레드에서 UI 업데이트
일반 스레드에서 네트워크 통신이나 DB 작업 시 UI 업데이트가 필요할 경우 Main Thread의 Handler를 통해 업데이트 작업을 Runnable이나 Message를 통해 요청할 수 있음
2. 메인 스레드에서 다음 작업 예약
메인 스레드에서 UI 작업을 바로 하지 못하는 경우도 있다. 이 때 API 중 Delayed가 붙은 메소드를 통해 특정 시간 이후에 실행할 수 있도록 Handler를 사용할 수 있음
3. 반복 갱신
4. 시간 제한
더 자세히 알고 싶다면 아래 블로그 방문 ~
https://brunch.co.kr/@mystoryg/84
ListAdapter 는 2가지 생성자를 제공하고 있다.
public abstract class ListAdapter<T, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
final AsyncListDiffer<T> mDiffer;
protected ListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
mDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
new AsyncDifferConfig.Builder<>(diffCallback).build());
mDiffer.addListListener(mListener);
}
protected ListAdapter(@NonNull AsyncDifferConfig<T> config) {
mDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), config);
mDiffer.addListListener(mListener);
}
// ...
}
ListAdapter 는 AsyncListDiffer 를 프로퍼티로 가지며, 실제 ItemList 객체 관리나 DiffUtil 을 통한 Item 변경 사항 확인 및 로직 처리 등은 AsyncListDiffer 클래스 내에서 이루어진다.
ListAdapter 클래스 안에는 submitList 함수가 오버로딩 되어있다.
public void submitList(@Nullable List<T> list)
public void submitList(@Nullable List<T> list, @Nullable final Runnable commitCallback)
public void submitList(@Nullable final List<T> newList,
@Nullable final Runnable commitCallback) {
// incrementing generation means any currently-running diffs are discarded when they finish
final int runGeneration = ++mMaxScheduledGeneration;
if (newList == mList) {
// nothing to do (Note - still had to inc generation, since may have ongoing work)
if (commitCallback != null) {
commitCallback.run();
}
return;
}
final List<T> previousList = mReadOnlyList;
// fast simple remove all
if (newList == null) {
//noinspection ConstantConditions
int countRemoved = mList.size();
mList = null;
mReadOnlyList = Collections.emptyList();
// notify last, after list is updated
mUpdateCallback.onRemoved(0, countRemoved);
onCurrentListChanged(previousList, commitCallback);
return;
}
// fast simple first insert
if (mList == null) {
mList = newList;
mReadOnlyList = Collections.unmodifiableList(newList);
// notify last, after list is updated
mUpdateCallback.onInserted(0, newList.size());
onCurrentListChanged(previousList, commitCallback);
return;
}
final List<T> oldList = mList;
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
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) {
//생략
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
//생략
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
//생략
}
});
mMainThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (mMaxScheduledGeneration == runGeneration) {
latchList(newList, result, commitCallback);
}
}
});
}
});
}
AsyncListDiffer.submitList() 가 호출되면 기존 리스트를 previousList 에 보관하고 새로운 리스트로 currentList 를 업데이트 한다.
이때 아이템에 변동이 생기면 내부적으로 콜백리스너를 통해 RecyclerView.Adapter 의 함수들이 호출된다. (notifyItemRangeInserted(), notifyItemRangeRemoved() 등등)
그리고 background thread 에서 DiffUtil.calculateDiff() 함수가 호출되는데, 이때 이 함수의 파라미터로 우리가 ListAdapter 의 생성자로 넘긴 DiffUtil.ItemCallback 이 활용된다.
내부적으로 아이템들을 비교해나가기 시작하는데 이때 우리가 오버라이딩한 areItemsTheSame(), areContentsTheSame() 함수가 사용된다.
background thread 에서 기존 리스트와 새 리스트의 차이점을 비교하는 작업을 하고 MainThreadExecutor 는 Handler 을 이용해 main thread(ui thread) 에서 callback 함수를 실행한다.