본문 바로가기

안드로이드 Android

안드로이드 개발 (32) LiveData와 MutableLiveData


LiveData는 이미 내부적으로 setValue와 postValue가 존재하지만 이를 외부로 공개하지 않고 상속을 받은 서브클래스를 통해 외부로 공개합니다.
그렇기 때문에 이미 안드로이드에서 지원하는 LiveData는 MutableLive라는 서브클래스를 만들어두고 MutableLive에서 set을 허용하도록 개발자에게 환경을 제공합니다.

그렇게 View는 LiveData 에서 읽기만 할 수있고, ViewModel 에서 MutableLiveData를 통해 변경할 수 있는 권한이 생깁니다.

불변과 가변의 차이를 가진 LiveData와 MutableLiveData에 대해 각각 정리해봤습니다.

1.MutableLiveData

package androidx.lifecycle; /** * {@link LiveData} which publicly exposes {@link #setValue(T)} and {@link #postValue(T)} method. * * @param <T> The type of data hold by this instance */ @SuppressWarnings("WeakerAccess") public class MutableLiveData<T> extends LiveData<T> { /** * Creates a MutableLiveData initialized with the given {@code value}. * * @param value initial value */ public MutableLiveData(T value) { super(value); } /** * Creates a MutableLiveData with no value assigned to it. */ public MutableLiveData() { super(); } @Override public void postValue(T value) { super.postValue(value); } @Override public void setValue(T value) { super.setValue(value); } }


위는 MutableLiveData의 내부 코드 모습입니다. 주석을 제외하면 라인수가 17~20줄 밖에 안됩니다. 정말 17~20줄 빼고는 아무것도 존재하지않고 그저 LiveData를 상속받아서 LiveData 내부에서만 사용할 수 있는 postValue와 setValue를
override 함으로써 외부에서 사용할 수 있게끔만 하고 그외 LiveData의 생성자를 사용한다는것 빼고 아무것도 없습니다.

MutableLiveData는 그저 불변인 LiveData를 상속받아서 postValue와 setValue를 public함수에 호출함으로써 가변성을 부여하기 위함인 것을 추측할 수 있습니다.

2. LiveData

/** * LiveData is a data holder class that can be observed within a given lifecycle. * ... */ public abstract class LiveData<T> { ... // 대략 400~500줄 }


LiveData의 내부를 살피면 위와 같은 주석을 통해서 자세한 내용을 살펴볼 수 있기도 합니다.
LiveData는 일반 observe과 달리 Android 개발 전용으로 효율이 좋습니다. 왜냐하면 LiveData를 사용할 때 액티비티, 프레그먼트, 서비스 등등의 Lifecycle을 체크해서 해당 Lifecycle에 따라 livedata 객체가 유지가 되거나 해제가 됩니다.

그래서 개발자가 Lifecycle에 따라 직접 메모리를 해제하지 않아도 알아서 liveData에서 Lifecycle 상황에 맞게 해제하기 때문에 메모리누수를 사전에 방지해줍니다.

 @MainThread public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) { assertMainThread("observe"); if (owner.getLifecycle().getCurrentState() == DESTROYED) { // ignore return; } LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); if (existing != null && !existing.isAttachedTo(owner)) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } if (existing != null) { return; } owner.getLifecycle().addObserver(wrapper); }


LiveData에서 값이 변경하면 호출되는 observe 메서드 입니다. 시작부터 if문에서 매개로 받은 owner의 Lifecycle의 State이 DESTORYED가 되는 상황이면 바로 return 을 하는것을 확인할 수 있습니다. 위 코드에서 lifecycle에 따라 조건문이 걸려있다는 것을 확인이 가능합니다.

3. LiveData 는 최신데이터를 유지한다.

그런 LiveData는 과연 어떻게 동작할까? 한번 살펴보도록 했습니다.

mutableLiveData.value = value //or mutableLiveData.postValue(value)

기본적으로 LiveData는 value를 set을 하면 수신을 대기하는 observe 가 호출됩니다. 왜냐하면 LiveData는 언제나 최신 데이터만 유지하는것을 목적으로 합니다. 그래서 set 이 되는 순간 가장 최신 데이터 임으로 별도의 호출 구문 없이 setValue를 하면 수신 대기하는 observe가 호출 됩니다.

 @MainThread protected void setValue(T value) { assertMainThread("setValue"); mVersion++; mData = value; dispatchingValue(null); }

의외로 단순한 LiveData의 setValue 메서드 입니다.
mData가 LiveData가 가진 data입니다. 컬렉션 종류가 아니라 순수 객체 하나임으로 그저 최신 데이터만 항상 유지 합니다.

예를 들어서 setValue를 연속적으로 하는 상황에서,

_livedata.value = "1" _livedata.value = "2"

두번의 set이 일어났으니, 두번의 호출이 있을탠데 두번의 콜백 호출 모두 "2" 라는 데이터로 받게 됩니다.

_livedata.value = "1" _livedata.postValue("2")

(setValue와 postValue 같이 연속적으로 사용 경우 최신 데이터가 간혹 보장이 안되는 경우가 있다고 합니다.
setValue는 mData 객체에 값을 치환해서 사용하고, postValue는 mPendingData에 임시로 값을 할당해서 사용하기 때문에 과정속에서 충돌이 일어나지 않았나 싶습니다. )

4. LiveData 는 최종적으로 UI 스레드에서 동작한다.


LiveData의 value를 set하는 함수가 2가지가 존재합니다.
setValue와 postValue가 존재합니다.

1) setValue()

 @MainThread protected void setValue(T value) { assertMainThread("setValue"); mVersion++; mData = value; dispatchingValue(null); }


setValue에 assertMainThread("setValue") 라는 함수가 존재합니다. 이 함수 내용은 아래와 같습니다.

 static void assertMainThread(String methodName) { if (!ArchTaskExecutor.getInstance().isMainThread()) { throw new IllegalStateException("Cannot invoke " + methodName + " on a background" + " thread"); } }


그 내부에는 메인 쓰레드가 아니라면 throw를 통해 예외 발생시키는 구문이 숨겨져있었습니다.
그렇기 때문에 setValue는 반드시 MainThread 환경에서 호출해야합니다.

2) postValue()

protected void postValue(T value) { boolean postTask; synchronized (mDataLock) { postTask = mPendingData == NOT_SET; mPendingData = value; } if (!postTask) { return; } ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); }


반면 postValue는 mPendingData 객체에 임시로 value를 할당합니다. 이 함수는 setValue와 달리 MainThread 환경에서 호출되지 않아도 예외처리 되는 부분은 존재하지 않습니다. 다만,

ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable) 를 통해서 내부적으로 MainThread 로 바꿔서 실행합니다.

 private final Runnable mPostValueRunnable = new Runnable() { @SuppressWarnings("unchecked") @Override public void run() { Object newValue; synchronized (mDataLock) { newValue = mPendingData; mPendingData = NOT_SET; } setValue((T) newValue); } };


매개변수로 들어갔던 mPostValueRunnable 객체 입니다. mPendingData의 값은 newValue라는 지역변수에 할당 되고 mPendingData는 다시 기초 값으로 초기화 됩니다.

3) setValue , postValue 차이

setValue -> MainThread에서 실행해야함 mData라는 멤버변수의 값을 할당함
postValue -> MainThread에서 실행하지 않아도 됨, 하지만 다시 MainThread환경으로 바꿔줌, mPendingData를 통해 값을 전달하고 mPendingData객체는 디폴트값으로 초기화 됨

5. LiveData의 getValue()

LiveData는 getValue를 통해서 LiveData 내부에 있는 mData를 참조해서 사용할 수 있습니다. 하지만 실제로 mData를 직접적으로 참조하는 것이 아닙니다.

내부를 보기전에는 아래와 같이 되있을것이라 코드를 상상했습니다.

//실제 코드가 아닙니다. void getValue(){ retrun this.mData }


하지만 실제로 getValue()를 살펴보니 아래와 같은 코드로 되어있었습니다.

 @Nullable public T getValue() { Object data = mData; if (data != NOT_SET) { return (T) data; } return null; }


data지역 변수를 만들어서 return 합니다.

하지만 MutableLiveData <MutableList<T>>와 같이 List Type을 가진 LiveData에서 List에 .add()를 하고 싶다면 아래와 같은 코드로 작성할 수가 있습니다.

fun <T> MutableLiveData<T>.notifyObserver() { this.value = this.value }
fun addIssuePost(issuePost: IssuePost) { mIssuePostLiveData.value?.add(issuePost) mIssuePostLiveData.notifyObserver() }



setValue = getValue를 하는 형식입니다. setValue() 통해 observe 호출을 하기 위해 사용되는 구문 같습니다.

List 같은 타입은 call by reference로 값을 참조하기 때문에 getValue에서 만든 data와 mData가 같은 주소값을 참조하고 있어서, 임시로 만든 data의 값이 변하면 똑같이 mData의 값을 변경할 수 있습니다.

그래서 this.value = this.value를 하면 이전에 .add()를 통해 변경된 값이 mData에 적용이 되어있기 때문에 최신 값 그대로 setValue()를 통해서 observe를 호출 할수 있습니다.

이상 LiveData와 MutableLiveData에 대해 정리해봤습니다.