DiffUtil을 쓰는 이유
notifyDataSetChanged()의 성능문제
RecyclerView에서 업데이트 된 상태를 확인하기 위해 notifyDataSetChanged()를 사용하여 모든 리스트에 있는 데이터들을 확인한다. 이때 리스트에 1억개의 데이터들이 있고 1개의 데이터만 업데이트 됐다면 모든 리스트들 다시 만들어 렌더링하는 과정을 거쳐야한다 !
→ DiffUtil 클래스를 통해, 이전 데이터 상태와 현재 데이터 상태간의 차이 계산하여 변경된 데이터만 업데이트 한다.
DiffUtil 이란 ?
리스트에 변경된 부분만을 감지하여 갱신시켜주는 것이다.
💡 중첩클래스란 다른 클래스 내부, 인터페이스 본문 내에서 선언이 발생하는 클래스이다.Nested classes
DiffUtill.caluculateDiff(Callback, boolean)
의 결과를 갖고 있는 클래스Public methods
calculateDiff(DiffUtil.Callback cb) : DiffUtil.Callback으로 받은 리스트 간의 차이를 통해 리스트의 업데이트 목록을 계산한다.
DiffUtil.callback
을 받아주고, 반환값으로 DiffUtil.DiffResult
를 반환해준다.DiffUtilCallback 관련한 클래스 생성한 전체 코드
class MyDIffUtilCallback(val oldList: List<UserData>, private val newList: List<UserData>) : DiffUtil.Callback() {
override fun getOldListSize(): Int = oldList.size
override fun getNewListSize(): Int = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
return oldItem.name == newItem.name
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = oldList[oldItemPosition] == newList[newItemPosition]
}
두 목록 간의 차이를 계산하는 동안 DiffUtil에서 사용하는 콜백 클래스
추상 메소드
- areItemsTheSame(int oldItemPosition, int newItemPosition) : 두 아이템이 같은 객체를 가지고 있는지 여부를 반환한다.
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
return oldItem.name == newItem.name
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition] == newList[newItemPosition]
위에는 객체가 같아야하고, 이 함수는 해당 위치에 대한 데이터가 같아야한다.
override fun getNewListSize(): Int = newList.size
override fun getOldListSize(): Int = oldList.size
💡 이제 어뎁터에 DiffUtil을 적용시켜야함일반 메소드
- getChangePayload(int oldItemPosition, int newItemPosition) :
areItemsTheSame(int, int)
는true
를 반환하고,areContentsTheSame(int, int)
는false
를 반환하면 업데이트 내역이 있다고 판단한다. 그리고 DiffUtil은 이 메서드를 호출하여 변경 사항에 대한 페이로드를 가져옴
Public methods
- convertNewPositionToOld
public int convertNewPositionToOld (int newListPosition)
새로운 리스트에 대한 위치가 주어지면, 예전 리스트 위치를 리턴하거나 제거된 경우는 NO_POSITION
을 반환한다.
public int convertOldPositionToNew (int oldListPosition)
아전 리스트에 대한 위치가 주어지면, 새로운 리스트 위치를 리턴하거나 제거된 경우는 NO_POSITION
을 반환한다.
public void dispatchUpdatesTo (ListUpdateCallback updateCallback)
업데이트 내역을 전달하는것으로 첫 번째 업데이트 호출은 이후에 오는 모든 업데이트 호출에 영향을 준다.
updateCallback
업데이트 작업을 보내기 위한 콜백을 인자값으로 전달
public void dispatchUpdatesTo (Adapter adapter)
지정된 어뎁터에게 업데이트 이벤트를 전달한다.
ex) 리스트에 의해 밀리는 어뎁터가 있다면, 새로운 어뎁터로 바꾼 후 이 메서드 호출하여 업데이트 내용을 RecyclerView로 보낸다.
fun updateList(){
userList?.let{
val diffCallback = MyDIffUtilCallback(this.userList, userList)
val diffResult = DiffUtil.calculateDiff(diffCallback)
this.userList.run {
clear()
addAll(userList)
diffResult.dispatchUpdatesTo(this@UserAdapter)
}
}
}
updateList()
호출하여 DiffUtil.calculateDiff
에 데이터 넣어서 업데이트 사항 확인diffResult.dispatchUpdatesTo(this@UserAdapter)
를 통해 리사이티클러뷰 업데이트 시켜줌Public methods
- areContentsTheSame(T oldItem, T newItem) : 두 아이템이 같은 데이터를 가지고 있는지(인자 : 객체 아이템을 전달)
- areItemsTheSame(T oldItem, T newItem) : 두 객체가 같은 객체를 표현하고 있는지
- getChangePayload(T oldItem, T newItem) :
areItemsTheSame(T, T)
는true
를 반환하고,areContentsTheSame(T, T)는
false`를 반환하면 업데이트 내역이 있다고 판단한다. 그리고 DiffUtil은 이 메서드를 호출하여 변경 사항에 대한 페이로드를 가져옴
→ 리스트가 크면 시간이 많이 걸리기 때문에, 백그라운드 스레드에서 실행해야함
백그라운드 스레드
가 무엇인가 ?
안드로이드 앱은 기본 스레드를 통해 UI 작업을 한다. 근데 이 기본 스레드에서 장기 실행 작업을 호출하면 작업이 정지될 수 있다. 그래서 UI 업데이트를 처리하는 동안 추가적으로 백그라운드 스레드를 만들어 장기 실행 작업을 처리할 수 있게 한다. (이름처럼 기본 스레드를 잠시 나둔채 뒤에서 작업하는 것)
getCurrentList()
를 사용하여 현재 리스트에 접근하고 데이터 객체를 보여준다.