[Android] Deep dive into LiveData #2 -Observer.onChanged()

이전글

choi jeong heon
슬기로운 개발생활
14 min readNov 3, 2021

--

Android Jetpack

Observer의 onChanged()는 정확히 언제 호출되는 것일까?

Android docs 에는 다음과 같은 설명이 있습니다.

Generally, LiveData delivers updates only when data changes, and only to active observers. An exception to this behavior is that observers also receive an update when they change from an inactive to an active state. Furthermore, if the observer changes from inactive to active a second time, it only receives an update if the value has changed since the last time it became active.

이를 정리하자면,

  1. 일반적으로 LiveData는 데이터가 변경될 때 엑티브 상태의 관찰자에게만 업데이트를 전달합니다.
  2. 이 동작의 예외로, 관찰자가 비활성에서 활성 으로 변경될 때에도 관찰자가 업데이트를 받습니다.
  3. 또한 관찰자가 비활성에서 활성 상태로 두 번째로 변경되면, 마지막으로 활성 상태가 된 이후 값이 변경된 경우에만 업데이트를 받습니다.

여기서의 액티브 상태는 Lifecycle의 STARTED, RESUMED 상태를 말합니다.

또한 다음과 같은 설명이 추가로 있습니다.

observe()를 호출하면 onChanged()가 즉시 호출되고 만약 LiveData 에 값을 설정하지 않았다면 onChanged()는 호출되지 않습니다.

즉, onChanged() 는 다음 조건들에 의해 호출 됩니다.

  1. LiveData에 값이 없다면 (LiveData가 hold 하는 data가 없다면 ) onChanged는 어떤 경우라도 호출되지 않는다.
  2. observe()를 호출하면 onChanged()가 즉시 호출된다.
  3. LifecycleOwner 가 active 상태일때 값이 변경되면, onChanged()가 호출된다.
  4. LifecycleOwner 가 inactive 상태일때는 값이 변경되어도 onChanged()가 호출되지 않는다.
  5. LifecycleOwner 가 처음 inactive -> active 상태로 변경되면, onChanged()가 호출된다. 두번째 부터는 값이 변경된 경우에만 onChanged()가 호출된다.

위의 예시를 분석해보겠습니다.
onCreate 에서 Activity의 Lifecycle 상태는 INITIALIZED , 즉 inactive 상태입니다. inactive 상태이지만 LiveData에 값이 있기 때문에, observe() 로 LifecycleOwner와 Observer를 등록하는 순간 Observer의 onChanged()가 즉시 호출 됩니다.
만약 LiveData 에 값이 없다면 위의 예시에서 onChanged()는 호출되지 않습니다.

LiveData에 값이 있을 때 observe()를 호출하면 onChanged()가 즉시 호출되는데,
그렇다면 observe() 함수 내부에서 observer.onChanged()를 직접 호출해주고 있는 걸까요?

deep dive into observe() !!

observe() 의 실제 구현은 어떻게 되어 있을까요?

위 코드를 하나씩 분석해 보겠습니다.

먼저, @MainThread 와 assertMainThread() 가 보입니다. 메인 스레드에서 동작하도록 강제하고 있는데요, 그 이유가 무엇일까요?
이전글에서 살펴보았듯이, LiveData는 UI에 표현되는 데이터를 관찰할 수 있도록 설계가 되었습니다. 그래서 UI의 상태를 알아야 하기 때문에 LifecycleOwner 를 인자로 받고 있죠. UI 변경은 오직 UI Thread, 즉 Main Thread 에서만 가능하기 때문에 Main Thread 에서 동작하도록 강제하고 있는 것으로 보입니다.

그리고 또 하나 중요한 점이 owner 와 observer 를 감싸고 있는 wrapper 클래스가 있다는 것인데요,

LifecycleBoundObserver, ObserverWrapper

LifecycleBoundObserver
ObserverWrapper

LifecycleBoundObserver 가 ObserverWrapper 를 확장하고 있습니다. 그리고LifecycleBoundObserver 가 LifecycleOwner를 wrapping 하고 있고 ObserverWrapper 가 Observer 를 wrapping 하고 있습니다.

ObserverWrapper 는 관찰자가 active 인지 inactive 인지 그 상태를 boolean 값으로 관리하는 역할을 하고 있습니다. (mActive 멤버변수 ; 아주 중요한 역할을 합니다!, 이 값이 false 로 init 된다는 것도 중요한 사실입니다. )

LifecycleBoundObserver는 LifecycleOwner 의 상태변경 알림을 받고 그에 따라 ObserverWrapper가 관리하는 mActive 를 변경시키는 역할을 합니다. ( mActive의 변경은 onStateChanged() 에서 이루어지는데 아래에서 다시 살펴보겠습니다. )

다시 observe() 함수로 돌아와서,
마지막 코드에서 addObserver() 로 wrapper를 lifecycle 의 observer로 등록하고 있는데요, wrapper 가 Lifecycle 의 observer가 될 수 있는 이유는 LifecycleBoundObserver 가 LifecycleEventObserver 를 구현하는 구현체이기 때문이고 LifecycleEventObserver 가 LifecycleObserver 를 상속받는 interface 이기 때문입니다.

Lifecycle 의 addObserver()가 궁금하다면 여기 를 참고하세요.

다시 본론으로 돌아와서, observe() 내부의 메소드들을 따라가면 onChanged() 를 호출하는 부분을 찾을 수 있을 줄 알았지만 그렇지 않았습니다.
그렇다면 어디에서 onChanged()가 호출되는 것일까요?

considerNotify()

onChanged() 는 considerNotify() 라는 메서드에서 호출 됩니다. LiveData의 버전을 확인하고 가장 최신 버전에서 버전의 변경이 있었다면, 버전을 새 버전으로 변경하고 onChanged()를 호출합니다.

여기서 중요한 점이 LiveData 는 mVersion 이라는 Int 로 데이터의 버전을 관리하고 있다는 것과 observer 에 mLastVersion 을 따로 두고 있다는 점 입니다. 그리고 이 둘을 비교하여 LiveData의 업데이트 여부를 판단합니다.

setValue()

LiveData의 값을 변경시키는 setValue() 를 보시면, 값이 변경될 때 버전을 1씩 증가시키고 있다는 것을 알 수 있습니다. 따라서 mVersion > mLastVersion 인 상태가 되어considerNotify() 에서 onChanged() 를 호출하게 되는 것입니다.

setValue() 에서 버전을 무조건 증가시키기 때문에, 실제로는 데이터가 변경이 되지 않아도 onChanged()가 호출되게 됩니다. 예를 들어 LiveData의 값이 1 이 었는데 setValue(1) 을 하면, 실제로는 1에서 1로 set 한 것이므로 값의 변경이 없지만 버전이 증가하므로 onChanged()가 호출되게 됩니다.

onChanged() 호출 과정

자, 이제 considerNotify() 까지는 어느정도 이해가 되었는데, 그렇다면 considerNotify()는 또 누가 호출하게 되는 걸까요?

considerNotify() 부터 계속 함수를 타고 올라가면, 다음과 같은 구조가 됩니다.

  1. setValue() → dispatchingValue() → considerNotify() → onChanged()
  2. LifecycleBoundObserver 클래스의 onStateChanged() → ObserverWrapper 클래스의 activeStateChanged() → dispatchingValue() → considerNotify() → onChanged()

setValue() 로 부터 시작하는 흐름은, 우리가 LiveData 값을 변경시켰기 때문에 onChanged()가 호출 되는 경우입니다.

문제는 두번째 흐름인데요, 우리가 처음 observe 등록할 때, onChanged() 가 호출되는 것이 바로 이 흐름에 해당됩니다. 이 글 가장 첫번째에서 나온 docs에서 설명하는 2, 3번에 해당하는 경우입니다.

위에서 다시 살펴보겠다고 했던 LifecycleBoundObserver 클래스의 onStateChanged()가 여기서 등장합니다.

onStateChanged() → ObserverWrapper 클래스의 activeStateChanged() → dispatchingValue()

onStateChanged() 의 핵심은 while 문 인데, prevState가 null 로 초기화 되므로 처음 while 문을 들어올 땐 무조건 통과됩니다. 그리고 prevState를 현재의 LifecycleOwner 상태로 바꿉니다.

shouldBeActive() 는 LifecycleOwner 상태가 active 이면 true를 리턴하고 아니면 false 를 리턴합니다.

activeStateChanged()는 ObserverWrapper 클래스의 메서드 입니다. 아래에서 한 번더 설명하겠지만, 현재의 LifecycleOwner 상태와 LiveData가 알고있는 LifecycleOwner의 가장 최근 상태를 비교하여 , inactive → active 로 바뀐 경우라면 dispatchingValue()를 호출합니다.

while 문의 마지막 명령문은 currentState를 현재의 LifecycleOwner 상태로 한 번더 바꿉니다. 이 이유는, while 문이 실행되는 도중에 LifecycleOwner 상태에 변화가 있었을 수도 있기 때문입니다. 그래서 만약 LifecycleOwner 상태에 변화가 생겼다면 while문을 한 번더 수행하게 됩니다.

activeStateChanged()는 위에서 설명했듯이 현재의 LifecycleOwner 상태와 LiveData가 알고있는 LifecycleOwner의 가장 최근 상태를 비교합니다. mActive가 LiveData가 알고있는 LifecycleOwner의 가장 최근 상태 입니다. 그리고 newActive는 onStateChanged() 에서 shouldBeActive()로 구해진 LifecycleOwner의 현재 상태입니다. 이 둘이 같다면, 즉 상태 변화가 없는 경우면 그냥 리턴하게 됩니다.

그게 아니라면, mActive를 업데이트 해주고 mActive가 true 인 경우, 즉 활성 상태인 경우 dispatchingValue() 를 호출하게 되어 결과적으로 onChanged() 가 호출되는 것입니다!

그렇다면 onStateChanged() 과연 누가 호출하는 것일까요?

observe() → addObserver() → dispatchEvent() → LifecycleBoundObserver 클래스의 onStateChanged()

observe()의 마지막 명령문을 보면, 아래와 같이 되어 있습니다.

owner.getLifecycle().addObserver(wrapper);

lifecycleOwner 는 getLifecycle() 을 구현해야 하는데, AppCompatActivity 의 상속구조를 타고 올라가다 보면 ComponentActivity 가 있고 이 녀석이 LifecycleOwner 인터페이스의 구현체입니다. 즉, getLifecycle() 구현이 ComponentActivity 에 있습니다.

..
private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
..
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}

ComponentActivity 의 일부 코드를 가져왔습니다.
LifecycleRegistry 라는 것을 리턴하도록 되어 있다. 이 클래스는 Lifecycle 을 상속했고 addObserver 를 구현하고 있는 클래스입니다. 그리고 여기에 dispatchEvent()onStateChanged() 를 호출하게 됩니다.

정리

LiveData에 값이 있을 때 observe()를 호출하면 onChanged()가 즉시 호출되는 과정은 다음과 같습니다.

LiveData#observe() →
LifecycleRegistry#addObserver() →
LifecycleRegistry#dispatchEvent() → LifecycleBoundObserver#onStateChanged() → ObserverWrapper#activeStateChanged() →
LiveData#dispatchingValue() →
LiveData#considerNotify() →
Observer#onChanged()

Example

저는 이 예시를 보면서 이런 의문점을 가졌습니다.

위 예시는 onResume() 에서 liveData의 observe()를 호출하고 있습니다.

observer를 등록할 시점에 LifecycleOwner 상태가 STARTED 이므로 mActive 가 true로 설정되어야 맞는 게 아닌가?

하지만 observe를 처음 등록할 때 mActive 는 false로 설정됩니다. ObserverWrapper 클래스를 잘 보면 알 수 있습니다.

즉,
onResume() 에서 observe 를 등록할 때 mActive 가 false로 설정됨. onStateChanged() 가 호출 되면 shouldBeActive() 했을 때, 상태가 STARTED 이므로 activeStateChanged() 에서 newActive ≠ mActive 가 됩니다.
따라서 mActive는 true로 변경되고,
dispatchingValue() → considerNotify() → onChanged() 가 뒤이어 호출되어 로그가 찍히게 되는 것입니다.

--

--