Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

수명 주기 인식 구성요소로 수명 주기 처리   Android Jetpack의 구성요소

수명 주기 인식 구성요소는 활동 및 프래그먼트와 같은 다른 구성요소의 수명 주기 상태 변경에 따라 작업을 실행합니다. 이러한 구성요소를 사용하면 잘 구성된 경량의 코드를 만들어 더욱 쉽게 유지할 수 있습니다.

일반적인 패턴은 활동과 프래그먼트의 수명 주기 메서드에 종속 구성요소의 작업을 구현하는 것입니다. 하지만 이 패턴으로 인해 코드 구성이 나빠지고 오류가 증가하게 됩니다. 수명 주기 인식 구성요소를 사용하면 수명 주기 메서드에서 구성요소 자체로 종속 구성요소의 코드를 옮길 수 있습니다.

androidx.lifecycle 패키지는 수명 주기 인식 구성요소(활동이나 프래그먼트의 현재 수명 주기 상태를 기반으로 동작을 자동 조정할 수 있는 구성요소)를 빌드할 수 있는 클래스 및 인터페이스를 제공합니다.

Android 프레임워크에 정의된 대부분의 앱 구성요소에는 수명 주기가 연결되어 있습니다. 수명 주기는 운영체제 또는 프로세스에서 실행 중인 프레임워크 코드에서 관리합니다. 수명 주기는 Android 작동 방식의 핵심으로, 애플리케이션은 수명 주기를 준수해야 합니다. 수명 주기를 준수하지 않으면 메모리 누수 또는 애플리케이션의 비정상 종료가 발생할 수 있습니다.

화면에 기기 위치를 표시하는 활동이 있다고 가정해 보겠습니다. 일반적인 구현은 다음과 같을 수 있습니다.

Kotlin

    internal class MyLocationListener(
            private val context: Context,
            private val callback: (Location) -> Unit
    ) {

        fun start() {
            // connect to system location service
        }

        fun stop() {
            // disconnect from system location service
        }
    }

    class MyActivity : AppCompatActivity() {
        private lateinit var myLocationListener: MyLocationListener

        override fun onCreate(...) {
            myLocationListener = MyLocationListener(this) { location ->
                // update UI
            }
        }

        public override fun onStart() {
            super.onStart()
            myLocationListener.start()
            // manage other components that need to respond
            // to the activity lifecycle
        }

        public override fun onStop() {
            super.onStop()
            myLocationListener.stop()
            // manage other components that need to respond
            // to the activity lifecycle
        }
    }
    

자바

    class MyLocationListener {
        public MyLocationListener(Context context, Callback callback) {
            // ...
        }

        void start() {
            // connect to system location service
        }

        void stop() {
            // disconnect from system location service
        }
    }

    class MyActivity extends AppCompatActivity {
        private MyLocationListener myLocationListener;

        @Override
        public void onCreate(...) {
            myLocationListener = new MyLocationListener(this, (location) -> {
                // update UI
            });
        }

        @Override
        public void onStart() {
            super.onStart();
            myLocationListener.start();
            // manage other components that need to respond
            // to the activity lifecycle
        }

        @Override
        public void onStop() {
            super.onStop();
            myLocationListener.stop();
            // manage other components that need to respond
            // to the activity lifecycle
        }
    }
    

이 샘플은 괜찮아 보이지만, 실제 앱에서는 수명 주기의 현재 상태에 따라 UI 및 다른 구성요소를 관리하는 호출이 너무 많이 발생하게 됩니다. 여러 구성요소를 관리하면 onStart()onStop()과 같은 수명 주기 메서드에 상당한 양의 코드를 배치하게 되어 유지하기 어려워집니다.

게다가 활동이나 프래그먼트가 중지되기 전에 구성요소가 시작된다는 보장도 없습니다. onStart()의 일부 구성 확인과 같은 장기 실행 작업을 진행해야 하는 경우 특히 그렇습니다. 이로 인해 onStop() 메서드가 onStart() 전에 종료되어 구성요소가 필요 이상으로 오래 유지되는 경합 상태가 발생할 수 있습니다.

Kotlin

    class MyActivity : AppCompatActivity() {
        private lateinit var myLocationListener: MyLocationListener

        override fun onCreate(...) {
            myLocationListener = MyLocationListener(this) { location ->
                // update UI
            }
        }

        public override fun onStart() {
            super.onStart()
            Util.checkUserStatus { result ->
                // what if this callback is invoked AFTER activity is stopped?
                if (result) {
                    myLocationListener.start()
                }
            }
        }

        public override fun onStop() {
            super.onStop()
            myLocationListener.stop()
        }

    }
    

자바

    class MyActivity extends AppCompatActivity {
        private MyLocationListener myLocationListener;

        public void onCreate(...) {
            myLocationListener = new MyLocationListener(this, location -> {
                // update UI
            });
        }

        @Override
        public void onStart() {
            super.onStart();
            Util.checkUserStatus(result -> {
                // what if this callback is invoked AFTER activity is stopped?
                if (result) {
                    myLocationListener.start();
                }
            });
        }

        @Override
        public void onStop() {
            super.onStop();
            myLocationListener.stop();
        }
    }
    

androidx.lifecycle 패키지는 이러한 문제를 탄력적이고 단독적인 방법으로 처리하는 데 도움이 되는 클래스와 인터페이스를 제공합니다.

Lifecycle

Lifecycle은 활동이나 프래그먼트와 같은 구성요소의 수명 주기 상태 관련 정보를 포함하며 다른 객체가 이 상태를 관찰할 수 있게 하는 클래스입니다.

Lifecycle은 2개의 기본 열거를 사용하여 연결된 구성요소의 수명 주기 상태를 추적합니다.

이벤트
프레임워크 및 Lifecycle 클래스에서 전달되는 수명 주기 이벤트입니다. 이러한 이벤트는 활동과 프래그먼트의 콜백 이벤트에 매핑됩니다.
상태
Lifecycle 객체가 추적한 구성요소의 현재 상태입니다.
수명 주기 상태 다이어그램
그림 1. Android 활동 수명 주기를 구성하는 상태 및 이벤트

상태를 그래프의 노드로, 이벤트를 이러한 노드 간 에지로 생각하세요.

클래스는 메서드에 주석을 추가하여 구성요소의 수명 주기 상태를 모니터링할 수 있습니다. 그런 후, 다음 예에서와 같이 Lifecycle 클래스의 addObserver() 메서드를 호출하고 관찰자의 인스턴스를 전달하여 관찰자를 추가할 수 있습니다.

Kotlin

    class MyObserver : LifecycleObserver {

        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        fun connectListener() {
            ...
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        fun disconnectListener() {
            ...
        }
    }

    myLifecycleOwner.getLifecycle().addObserver(MyObserver())
    

자바

    public class MyObserver implements LifecycleObserver {
        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        public void connectListener() {
            ...
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        public void disconnectListener() {
            ...
        }
    }

    myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
    

위 예에서 myLifecycleOwner 객체는 LifecycleOwner 인터페이스를 구현합니다. 이 내용은 다음 섹션에서 설명합니다.

LifecycleOwner

LifecycleOwner는 클래스에 Lifecycle이 있음을 나타내는 단일 메서드 인터페이스입니다. 이 인터페이스에는 클래스에서 구현해야 하는 getLifecycle() 메서드가 하나 있습니다. 대신 전체 애플리케이션 프로세스의 수명 주기를 관리하려는 경우 ProcessLifecycleOwner를 참조하세요.

이 인터페이스는 FragmentAppCompatActivity와 같은 개별 클래스에서 Lifecycle의 소유권을 추출하고 함께 작동하는 구성요소를 작성할 수 있게 합니다. 모든 맞춤 애플리케이션 클래스는 LifecycleOwner 인터페이스를 구현할 수 있습니다.

관찰자가 관찰을 위해 등록하는 수명 주기를 소유자가 제공할 수 있으므로, LifecycleObserver를 구현하는 구성요소는 LifecycleOwner를 구현하는 구성요소와 원활하게 작동합니다.

위치 추적 예에서는 MyLocationListener 클래스에서 LifecycleObserver를 구현하도록 한 후 onCreate() 메서드에서 활동의 Lifecycle로 클래스를 초기화할 수 있습니다. 이렇게 하면 MyLocationListener 클래스가 자립할 수 있습니다. 즉, 수명 주기 상태의 변경에 반응하는 로직이 활동 대신 MyLocationListener에서 선언됩니다. 개별 구성요소가 자체 로직을 저장하도록 하면 활동 및 프래그먼트 로직을 더 쉽게 관리할 수 있습니다.

Kotlin

    class MyActivity : AppCompatActivity() {
        private lateinit var myLocationListener: MyLocationListener

        override fun onCreate(...) {
            myLocationListener = MyLocationListener(this, lifecycle) { location ->
                // update UI
            }
            Util.checkUserStatus { result ->
                if (result) {
                    myLocationListener.enable()
                }
            }
        }
    }
    

자바

    class MyActivity extends AppCompatActivity {
        private MyLocationListener myLocationListener;

        public void onCreate(...) {
            myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
                // update UI
            });
            Util.checkUserStatus(result -> {
                if (result) {
                    myLocationListener.enable();
                }
            });
      }
    }
    

일반적인 사용 사례에서는 Lifecycle이 현재 정상 상태가 아닌 경우 특정 콜백 호출을 피합니다. 예를 들어 활동 상태가 저장된 후 콜백이 프래그먼트 트랜잭션을 실행하면 비정상 종료가 트리거될 수 있으므로 개발자는 그 콜백을 호출하지 않습니다.

이러한 사용 사례를 쉽게 만들 수 있도록 Lifecycle 클래스는 다른 객체가 현재 상태를 쿼리할 수 있도록 합니다.

Kotlin

    internal class MyLocationListener(
            private val context: Context,
            private val lifecycle: Lifecycle,
            private val callback: (Location) -> Unit
    ) {

        private var enabled = false

        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        fun start() {
            if (enabled) {
                // connect
            }
        }

        fun enable() {
            enabled = true
            if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
                // connect if not connected
            }
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        fun stop() {
            // disconnect if connected
        }
    }
    

자바

    class MyLocationListener implements LifecycleObserver {
        private boolean enabled = false;
        public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
           ...
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        void start() {
            if (enabled) {
               // connect
            }
        }

        public void enable() {
            enabled = true;
            if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
                // connect if not connected
            }
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        void stop() {
            // disconnect if connected
        }
    }
    

이 구현으로 LocationListener 클래스는 수명 주기를 완전히 인식합니다. 다른 활동이나 프래그먼트의 LocationListener를 사용해야 한다면 클래스를 초기화하기만 하면 됩니다. 모든 설정과 해제 작업은 클래스 자체에서 관리합니다.

라이브러리에서 Android 수명 주기와 작업하는 데 필요한 클래스를 제공한다면 수명 주기 인식 구성요소를 사용하는 것이 좋습니다. 클라이언트 측에서 수동으로 수명 주기를 관리하지 않아도 라이브러리 클라이언트가 이러한 구성요소를 쉽게 통합할 수 있습니다.

맞춤 LifecycleOwner 구현

지원 라이브러리 26.1.0 이상의 프래그먼트 및 활동에서는 이미 LifecycleOwner 인터페이스가 구현되어 있습니다.

LifecycleOwner를 만들려는 맞춤 클래스가 있다면 LifecycleRegistry 클래스를 사용할 수 있지만 다음 코드 예에서와 같이 이 클래스에 이벤트를 전달해야 합니다.

Kotlin

    class MyActivity : Activity(), LifecycleOwner {

        private lateinit var lifecycleRegistry: LifecycleRegistry

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)

            lifecycleRegistry = LifecycleRegistry(this)
            lifecycleRegistry.markState(Lifecycle.State.CREATED)
        }

        public override fun onStart() {
            super.onStart()
            lifecycleRegistry.markState(Lifecycle.State.STARTED)
        }

        override fun getLifecycle(): Lifecycle {
            return lifecycleRegistry
        }
    }
    

자바

    public class MyActivity extends Activity implements LifecycleOwner {
        private LifecycleRegistry lifecycleRegistry;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            lifecycleRegistry = new LifecycleRegistry(this);
            lifecycleRegistry.markState(Lifecycle.State.CREATED);
        }

        @Override
        public void onStart() {
            super.onStart();
            lifecycleRegistry.markState(Lifecycle.State.STARTED);
        }

        @NonNull
        @Override
        public Lifecycle getLifecycle() {
            return lifecycleRegistry;
        }
    }
    

수명 주기 인식 구성요소의 권장사항

  • UI 컨트롤러(활동과 프래그먼트)를 가능한 한 가볍게 유지하세요. 이러한 컨트롤러는 자체 데이터를 확보하려고 해서는 안 됩니다. 대신 ViewModel을 사용하여 데이터를 확보하고 LiveData 객체를 관찰하여 변경사항을 다시 뷰에 반영해야 합니다.
  • 데이터 기반 UI를 작성해 보세요. 여기서 데이터 변경에 따라 뷰를 업데이트하거나 사용자 작업을 다시 ViewModel에 알리는 것은 UI 컨트롤러의 책임입니다.
  • ViewModel 클래스에 데이터 로직을 넣으세요. ViewModel은 UI 컨트롤러와 나머지 앱 간의 커넥터 역할을 해야 합니다. 하지만 데이터를 가져오는 것(예: 네트워크에서 데이터 가져오기)은 ViewModel의 책임이 아닙니다. 대신 ViewModel은 적절한 구성요소를 호출하여 데이터를 가져온 후 결과를 다시 UI 컨트롤러에 제공해야 합니다.
  • 데이터 결합을 사용하여 뷰와 UI 컨트롤러 간의 인터페이스를 깔끔하게 유지하세요. 이렇게 하면 뷰를 더욱 선언적으로 만들고, 활동과 프래그먼트에서 작성해야 하는 업데이트 코드를 최소화할 수 있습니다. 이 작업을 자바 프로그래밍 언어로 진행하려면 Butter Knife와 같은 라이브러리를 사용하여 상용구 코드를 피하고 더 나은 추상화를 구현하세요.
  • UI가 복잡하다면 UI 수정을 처리할 presenter 클래스를 만드는 것이 좋습니다. 힘든 작업이지만, 이렇게 하면 UI 구성요소를 더 쉽게 테스트할 수 있습니다.
  • ViewModel에서 View 또는 Activity 컨텍스트를 참조하지 마세요. ViewModel이 활동보다 오래 지속되면(구성이 변경되는 경우) 활동이 누출되고 가비지 컬렉터가 활동을 제대로 처리하지 못합니다.
  • Kotlin 코루틴을 사용하여 장기 실행 작업 및 비동기적으로 실행될 수 있는 기타 작업을 관리하세요.

수명 주기 인식 구성요소의 사용 사례

수명 주기 인식 구성요소를 사용하면 다양한 사례의 수명 주기를 훨씬 쉽게 관리할 수 있습니다. 다음은 몇 가지 예입니다.

  • 대략적인 위치와 세분화된 위치 업데이트 간 전환. 수명 주기 인식 구성요소를 사용하면 위치 앱이 공개 상태인 동안 세분화된 위치 업데이트를 사용하고 앱이 백그라운드에 있을 때 대략적인 위치 업데이트로 전환할 수 있습니다. 수명 주기 인식 구성요소인 LiveData를 사용하면 사용자가 위치를 변경할 때 앱에서 자동으로 UI를 업데이트할 수 있습니다.
  • 동영상 버퍼링 중지와 시작. 수명 주기 인식 구성요소를 사용하면 동영상 버퍼링을 최대한 빨리 시작하지만, 앱이 완전히 시작될 때까지 재생을 연기합니다. 또한 수명 주기 인식 구성요소를 사용하여 앱이 제거될 때 버퍼링을 종료할 수 있습니다.
  • 네트워크 연결 시작과 중지. 수명 주기 인식 구성요소를 사용하면 앱이 포그라운드에 있는 동안 네트워크 데이터를 실시간으로 업데이트(스트리밍)할 수 있으며 앱이 백그라운드로 전환될 때 실시간 업데이트를 자동으로 일시중지할 수도 있습니다.
  • 애니메이션 드로어블 일시중지와 재개. 수명 주기 인식 구성요소를 사용하면 앱이 백그라운드에 있는 동안 애니메이션 드로어블 일시중지를 처리하고 앱이 포그라운드로 전환한 후 드로어블을 재개할 수 있습니다.

중지 이벤트 처리

LifecycleAppCompatActivity 또는 Fragment에 속하면 Lifecycle의 상태가 CREATED로 변경되고 AppCompatActivity 또는 FragmentonSaveInstanceState()가 호출되면 ON_STOP 이벤트가 전달됩니다.

onSaveInstanceState()를 통해 Fragment 또는 AppCompatActivity의 상태를 저장하면 ON_START가 호출될 때까지 UI는 변경할 수 없는 것으로 간주됩니다. 상태를 저장한 후 UI를 수정하려고 하면 애플리케이션의 탐색 상태에 불일치가 나타날 수 있습니다. 따라서 상태가 저장된 후 앱에서 FragmentTransaction을 실행하면 FragmentManager가 예외를 발생시킵니다. 자세한 내용은 commit()을 참조하세요.

LiveData는 관찰자의 관련 Lifecycle이 적어도 STARTED 상태가 아니라면 관찰자를 호출하지 않게 하여 이러한 에지 케이스(edge case)를 처음부터 방지합니다. 하지만 이면에서는 관찰자 호출을 결정하기 전에 isAtLeast()를 호출합니다.

아쉽게도 AppCompatActivityonStop() 메서드는 onSaveInstanceState() 이후에 호출되어 UI 상태 변경이 허용되지 않지만 Lifecycle이 아직 CREATED 상태로 전환되지 않는다는 차이가 생깁니다.

이 문제를 방지하기 위해 beta2 버전 이하의 Lifecycle 클래스는 이벤트를 전달하지 않아도 상태를 CREATED로 표시하여, 시스템에서 onStop()을 호출할 때까지 이벤트가 전달되지 않았더라도 현재 상태를 확인하는 모든 코드가 실제 값을 받도록 합니다.

하지만 이 솔루션에는 다음 두 가지 중대한 문제가 있습니다.

  • API 수준 23 이하에서 Android 시스템은 다른 활동으로 인해 활동이 일부 가려진 경우에도 활동의 상태를 실제로 저장합니다. 즉, Android 시스템은 onSaveInstanceState()를 호출하지만 반드시 onStop()을 호출할 필요는 없습니다. 이로 인해 UI 상태를 수정할 수 없더라도 관찰자는 수명 주기가 계속 활성 상태라고 생각하는 긴 간격이 발생할 수 있습니다.
  • LiveData 클래스에 유사한 동작을 노출하려는 모든 클래스는 Lifecycle 버전 beta 2 이하에서 제공되는 해결 방법을 구현해야 합니다.

참고 자료

수명 주기 인식 구성요소로 수명 주기 처리에 관해 자세히 알아보려면 다음 참고 자료를 참조하세요.

샘플

Codelab

블로그