LiveData는 Android Jetpack에 포함되어 있는 라이브러리 이며,
관찰 가능한 데이터 홀더 클래스입니다.
관찰 가능한 일반 클래스와 달리 LiveData는 수명주기를 인식하는 특징을 가지고 있습니다.
지금부터 어떻게 LiveData가 값의 변경을 반영하고 수명주기를 인식할 수 있는지 확인해보려고합니다.
일단 LiveData의 사용법부터 시작하도록 하겠습니다.
LiveData 사용법
1. 처음으로 특정 타입의 데이터를 보유할 LiveData의 인스턴스를 생성합니다.
class NameViewModel : ViewModel(){
private val _currentName : MutableLiveData<String> = MutableLiveData("")
val currentName : LiveData<String> = _currentName
}
2. onChange() 메서드를 정의하는 Observer 객체를 만듭니다. 이 메서드는 LiveData 객체가 보유한 데이터 변경 시 발생하는 작업을 정의합니다.
public interface Observer<T>{
void onChanged(T t);
}
/*
A simple callback that can receive from LiveData
*/
In Activity or Fragment
val nameObserver = Observer<String> { newName ->
nameTextView.text = newName
}
3. observe() 메서드를 사용하여 LiveData 객체에 Observer 객체를 연결합니다.
observe() 메서드는 LifecycleOwner를 매개변수로 함께 넘깁니다.
위의 연결을 통해서 LifecycleOwner의 라이크 사이클에 따라서 observer를 제어합니다.
viewModel.currentName.observe(this, nameObserver) // this = LifecycleOwner
위의 3단계로 우리는 관찰가능하고, 수명주기를 인식할 수 있는 LiveData를 사용할 수 있습니다.
이제 어떻게 LiveData의 값을 관찰할 수 있는 지 살펴보겠습니다.
LiveData의 값 변경
LiveData가 정의되어있는 추상클래스를 확인해보겠습니다.
public abstract class LiveData<T>{
private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
new SafeIterableMap<>()
private volatile Object mData;
private int mVersion;
/.../
}
public LiveData(T value){
mData= value;
mVersion = START_VERISON + 1;
}
public class MutableLiveData<T> extends LiveData<T> {
public MutableLiveData(T value) {
super(value); // -> public LiveData(T value)
}
}
라이브 데이터는 내부에 옵저버들이 map으로 관리되고 있고, 데이터와 버전을 관리하는 변수들이 있는 것을 볼 수 있습니다.
라이브 데이터를 처음 생성할 때, 우리는 MutableLiveData로 생성을 하는데요, 그 이유는 LiveData가 추상 클래스이기 떄문에 인스턴스를 생성할 수 없기 때문입니다.
MutableLiveData는 LiveData를 상속하고 있어서, MutableLiveData를 생성해줄 때,
public LiveData()를 호출하는 것을 볼 수 있습니다. -> super(value);
라이브 데이터의 생성을 보았으니, 값을 설정하거나 업데이트 하는 과정을 보겠습니다.
우리는 postValue()나 setValue()를 통해서 라이브 데이터가 가지고 있는 데이터를 변경해 줄 수 있습니다.
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
// mPostValueRunnable도 내부적으로 setValue를 호출하고 있습니다.
}
setValue를 보니까, 매개변수 value를 mData안에 저장과 버전을 올려주고 있고
마지막으로 dispatchingValue()를 호출하고 있습니다.
void dispatchingValue(@Nullable ObserverWrapper initiator){
if(mDispatchingValue){
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if(initiator != null){
considerNotifiy(initiator);
initiator = null;
}
else{
for(Iterator<Map.Entry(Observer<? super T>, ObserverWrapper>>
iterator=
mObservers.iteratorWithAdditions(); iterator.hasNext();){
considerNotify(iterator.next().getValue());
if(mDispatchInvalidated){
break;
}
}
}
}while( mDispatchInvalidated);
mDispatchingValue =false;
}
코드가 벌써 길어지는데요, 여기서 중요한 것은 mObservser에 등록되어 있는 옵저버들에 대해서
considerNotify를 호출해주는 것입니다.
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
considerNotifiy에서는 옵저버별로 활성상태 인지, 아닌지 판단하고 있습니다. 활성 상태 이면서 mVersion이 옵저버의 마지막 버전보다 최신값이라면 옵저버에 등록된 버전을 올려주고 onChange를 호출하는 것을 볼 수 있습니다.
여기서 onChange는 우리가 Fragment나 Activity에서 정의한 Observer입니다.
위에서 우리는 다음과 같이 정의를 했었습니다.
//In Activity or Fragment
val nameObsever = Observer<String>{ newName->
nameTextView.text = newName
}
결국에 새로운 값이 들어오게 된다면, nameTextView.text = newName이 실행되고 새로운 값으로 텍스트가 업데이트 될 것입니다.
이렇게 간략하게 LiveData가 값을 업데이트 해주는 방법을 보았습니다.
이제 수명주기를 인식하는 방법을 보겠습니다.
LiveData 수명주기 인식
우리는 LiveData사용법 3단계에서 Activity나 Fragment에서 observe()를 호출하는 것을 볼 수 있었습니다.
public void observe(Lifecycle owner, Observer<? super T> observer){
if(owner.getLifecycle().getCurrentState() == DESTORY){
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer ,wrapper);
if(existing != null & !existing.isAttahcedTo(owner)){
exception
}
if(existing != null){
return ;
}
owner.getLifecycle().addObserver(wrapper);
}
여기서 중요한 부분은 가장 마지막 줄에 있는 addObserver입니다
owner는 생명주기를 가지는 Activity나 Fragment가 될 수 있는데,
이 둘이 상속하는 ComponentActivity는 LifecycleRegistry를 가지고 있습니다. 이는 Lifecycle를 상속하며, Lifecycle과 관련된 처리를 맡고 있습니다.
@NonNull
@Override
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
} // In ComponentActivity
owner.getLifecycle()은 LifecycleRegistry를 반환합니다.
owner.getLifecylce().addObserver(wrapper)는 LifecycleRegistry에서 구현된 addObserver를 호출하는 것입니다.
@Override
public void addObserver(LifecycleObservser obs){
/.../
State initialState= mState == DESTORYED? DESTORYED : INITIALIZED;
ObserverWithState statefulObserver = new ObserverWithState(obs,initailState);
ObserverWithState previous = mObserverMap.putIfAbsent(obs, statefulObserver);
//LifecycleRegistry 내부에서 map으로 observer가 관리된다
while (...){
final Event event = Event.upFrom(statefulObserver.mState);
statefulObserver.dispatchEvent(lifecycleOwner ,event);
}
} // In LifecycleRegistry
addObservser로 LifecycleRegistry내부에 옵저버를 등록하기도 하고, observe()를 통해서 LiveData 객체 내부에 map에서도 등록이 됩니다.
LifecycleRegistry의 map는 생성된 모든 observer를 관리하고, LiveData의 map에서 관리되는 observer는 생성한 LiveData에 대한 observer들을 관리합니다.
예를들어 하나의 LiveData에 대한 여러가지 observer를 등록한다면 생성한 LiveData의 map에는 여러가지 observer들이 등록되겠죠.
LiveData 내부의 map에 저장이 되어있다면 exisiting이 null이 아니기 때문에 LifecycleRegistry에 중복되어 저장되는 것을 막아줍니다.
LifecycleRegistry는 내부에 라이프 사이클에 대한 이벤트를 수신하고 있습니다.
public void handleLifecycleEvent(Lifecycle.Event event){
moveToState(event.getTargetState());
}
private void moveToState(State next){
/.../
sync();
/.../
if(mState== DESTORYED){
mObserverMap = new FastSafeIterableMap<>();
}
}
// Returns:
// the state that will result from this event
생명주기가 변경이 될 때 handleLifecycleEvent가 호출이 되는데, moveToState를 호출하고 있습니다.
현재의 생명주기가 DESTORYED일 때 내부적으로 관리되는 맵을 초기화해주는 모습을 볼 수 있습니다.
그 이전에 sync()를 호출하는데,
private void sync(){
LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
if(lifecycleOwner == null){
Exception
}
while(!isSynced()){
mNewEventOccurred = false;
if(mState.compareTo(mObserverMap.eldest().getValue().mState) < 0 ){
backwardPass(lifecycleOwner);
}
Map.Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
if(!mNewEventOccurred && newest! = null && mState.compareTo(newest.getValue().mState) > 0 ){
forwardPass(lifecycleOwner);
}
mNewEventOccurred = false;
}
각각의 forwardPass와 backwardPass는 LifecycleRegistry 안에 저장되어 있는 observer들의 dispatchEvent를 호출하고 있습니다.
static class ObserverWithState{
State mState;
LifeycleEventObserver mLifecycleObserver;
ObserverWithState(LifecycleObserver observer ,State initialState){
mLifecycleObserver = Lifecycling.lifecycleEventObserver(observer);
mState = initialState;
}
void dispatchEvent(LifecycleOwner owner, Event event){
State newState = event.getTargetState();
mState = min(mState, newState);
mLifecycleObserver.onStateChanged(owner, event);
mState = newState;
}
}
LifecycleRegistry에서 observer들은 위와 같은 형태로 저장되는데 dispatchEvent에서 onStateChanged를 호출하고 있습니다.
In LiveData
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event){
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
if(currentState == DESTORYED){
removeObserver(mObserver);
return;
}
Lifecycle.State prevState = null;
while(prevState != currentState({
prevState = currentState;
activeStateChanged(shouldBeActive());
currentState = mOwner.getLifecycle().getCurrentState();
}
}
@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
이는 라이브 데이터에 있는 onStateChanged를 호출하는데 현재 생명주기가 DESTORYED일 때 LiveData 내부에서 map으로 관리되는 observer를 제거하고, activeStateChanged를 호출합니다.
activeStateChange에는 내부에서 setValue에서 호출했던, dispatchingValue를 호출하고 있어서, 활성상태일 경우(최소 STARTED)일때만 옵저버의 onChange를 호출함으로써 생명주기에 따라서 값을 관찰할 수 있습니다.
마무리
LiveData를 공부하면서 내부적으로 어떻게 관리 될 수 있었는지 확인할 수 있는 기회였습니다.
간단히 정리하자면 LiveData는 LifecycleRegistry로 인하여 관리될 수 있고, 생명주기에 따라 관리 된다는 것을 볼 수 있었습니다.
현재는 프로젝트를 진행하면서 LiveData를 사용하지 않고 StateFlow를 사용하고 있는데요
다음 글에서는 StateFlow가 어떻게 동작하는지에 대해서 확인해보도록 하겠습니다.
'Android' 카테고리의 다른 글
Activity (0) | 2023.02.22 |
---|---|
Android에서 잠금화면 만들어보자 (0) | 2023.01.19 |
Notification을 수신해보자! (0) | 2022.12.08 |
Coroutine이 무엇일까요? (0) | 2022.11.10 |
Adapter Memory Leak (0) | 2022.05.31 |