Activity란?

이윤설·2024년 10월 22일
0

안드로이드 연구소

목록 보기
6/33

액티비티(Activity)란?

안드로이드 앱에서 사용자와 상호작용할 수 있는 화면(window)을 의미한다.
앱의 한 화면을 나타내는 기본 구성 요소이다.

그렇다면 네이버처럼 화면이 많은 앱은 액티비티가 몇백개일까?
결론은 개발 방식에 따라 달라진다.

단일 액티비티와 다중 액티비티

초기 안드로이드 개발 - 단일 액티비티 (XML 레이아웃 시대)

예전 개발방식으로는, 각 화면 = 1개의 액티비티 였다.
화면을 전환하려면 반드시 새로운 액티비티를 생성해야 했었다.

예: 로그인 화면 → 메인 화면 → 프로필 화면

// 로그인 액티비티
class LoginActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
    }
}

// 메인 액티비티
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

Jetpack Compose 시대 - 다중 액티비티

이제 화면 != 액티비티인 시대가 되었다.
단일 액티비티 내에서 여러 화면(컴포저블) 구현이 가능해진 것이다.

Jetpack Compose를 사용함으로써 내비게이션을 사용하여서 여러 화면(컴포저블)을 구현하면 된다.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // 하나의 액티비티 안에 여러 화면(컴포저블) 존재
            NavHost {
                composable("login") { LoginScreen() }
                composable("main") { MainScreen() }
                composable("profile") { ProfileScreen() }
            }
        }
    }
}

Activity 정의와 구조의 변화

그럼 jetpack compose 등장 이후, Activity의 객체 자체의 구조가 완전히 수정된걸까?
아니면 구조 자체는 동일한데 사용 트렌드가 바뀐걸까? 두 문장은 큰 차이가 있다!

Activity 객체 자체의 본질적인 구조는 동일하다.
다. 그러나 사용하는 방식과 앱 개발의 트렌드는 크게 변화했다.

  • Compose 이전 (다중 액티비티 방식)
    각 화면마다 Activity를 생성하여 UI를 구성하는 방식이 일반적이었다.
    액티비티는 화면 전환을 책임지며, 각각의 생명주기와 관련된 작업을 처리해야 했다.

  • Compose 이후 (단일 액티비티 방식)
    Activity는 애플리케이션의 시작점이자 화면의 컨테이너 역할로 축소되었다.
    실제 화면은 Compose의 Composable 함수와 상태 관리로 구성되며, 하나의 Activity에서 화면을 동적으로 전환하는 방식이 보편화되었다.

Compose 이후 다중 액티비티 방식의 사용 감소 배경

1) 단일 Activity의 유지보수성과 유연성 증가
Compose는 화면 전환 및 상태 관리가 강력하고 간소화된 방식으로 가능하므로, 굳이 여러 액티비티를 사용하지 않아도 되었다.

  • 상태 공유: 하나의 ViewModel이나 Compose 상태로 여러 화면의 데이터를 쉽게 공유 가능.
  • 생명주기 관리 간소화: 하나의 액티비티만 관리하므로 생명주기 관련 복잡성이 줄어듦.
  • Navigation Component와의 통합: Jetpack Navigation과의 사용이 더 자연스럽고 간편함.

2) Composable 함수의 UI 구성 철학
Compose는 화면을 함수형으로 설계한다. 즉, 화면의 UI를 Composable 함수 단위로 나누고 조합할 수 있으므로, 화면 간 전환과 상태 변화도 함수 호출만으로 해결된다.

3) 다중 액티비티의 단점

  • 리소스 사용 증가: 각 액티비티는 별도의 리소스를 소비하며, 액티비티 간 데이터를 주고받는 데 추가적인 작업 필요.
  • 복잡한 전환 처리: 여러 액티비티가 있을 경우 백스택, 전환 애니메이션 등을 관리해야 하는 부담 증가.
  • 테스트 복잡성 증가: 액티비티마다 별도로 UI 및 상태 테스트를 작성해야 함.

Compose 이후에도 다중 액티비티를 쓸 필요가 있는 경우

물론 Compose 시대에도 다중 액티비티 방식이 완전히 사라진 것은 아닙니다.

  1. 다른 앱에서 호출될 가능성이 있는 화면
    특정 액티비티를 외부에서 바로 호출해야 하는 경우(예: 파일 선택 화면).

  2. 명확한 컨텍스트 분리 필요
    앱의 주요 기능이 완전히 다른 컨텍스트를 가지며 서로 독립적일 경우.

  3. 대규모 팀 작업
    화면 단위로 개발을 분리하여 독립적으로 작업해야 하는 경우.

Activity 생명주기

  • onCreate(): 액티비티가 생성될 때 호출된다. UI를 설정하고 초기화 작업을 수행한다.
  • onStart(): 액티비티가 사용자에게 보여지기 시작할 때 호출된다.
  • onResume(): 액티비티가 포그라운드에서 사용자와 상호작용할 준비가 되었을 때 호출된다.
  • onPause(): 다른 액티비티가 시작되기 전에 호출된다. UI 업데이트와 같은 작업을 중지하는 데 사용한다.
  • onStop(): 액티비티가 더 이상 사용자에게 보이지 않을 때 호출된다. 리소스를 해제하거나 데이터를 저장할 수 있다.
  • onDestroy(): 액티비티가 종료되기 전에 호출된다. 메모리 누수를 방지하기 위해 리소스를 정리해야 한다.

그렇다면 생명주기가 개발에 있어서 왜 중요한지 유튜브 앱을 기반으로 알아보자.

  • 백그라운드 재생: 사용자가 유튜브 앱에서 동영상을 시청하는 도중, 다른 앱으로 전환하거나 화면을 회전하면, 생명주기 메서드가 호출되어 현재 상태를 저장하거나 재생 상태를 관리해야 한다.
    예를 들어, onPause()에서 비디오 재생을 일시 정지하고, onResume()에서 사용자가 다시 돌아왔을 때 비디오를 재생하는 로직을 개발자가 작성해야 한다.

  • 메모리 관리: 동영상 스트리밍과 같은 리소스가 많은 기능을 구현할 때, 메모리 누수를 방지하는 것이 중요하다. onStop()이나 onDestroy()에서 리소스를 해제하는 코드를 작성하여 메모리 효율성을 높일 수 있다.

  • 사용자 상태 저장: 사용자가 동영상을 시청 중에 앱이 종료되거나 백그라운드로 이동할 경우, onSaveInstanceState() 메서드를 통해 현재 시청 중인 동영상의 상태를 저장하고, 나중에 복원할 수 있어야 한다. 유튜브에서는 사용자가 어디까지 봤는지 기억하는 기능이 사용자 경험을 크게 향상시킨다.


전통적인 방식의 생명주기 사용방법

옛날 방식으로 생명주기를 관리할 때는 ViewModel을 사용하지 않고,
주로 액티비티의 생명주기 메서드를 직접 오버라이드하여 상태를 관리했다.

전통적인 방식으로 생명주기 관리

class MainActivity : AppCompatActivity() {

    private var currentPlaybackPosition: Long = 0
    private var isPlaying: Boolean = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // UI 초기화 코드
    }

    override fun onStart() {
        super.onStart()
        // 액티비티가 시작될 때 필요한 작업 수행
    }

    override fun onResume() {
        super.onResume()
        // 저장된 재생 위치 복원
        // 예: mediaPlayer.seekTo(currentPlaybackPosition)
        if (isPlaying) {
            // 비디오 재생 시작
            // mediaPlayer.start()
        }
    }

    override fun onPause() {
        super.onPause()
        // 현재 재생 위치 저장
        // 예: currentPlaybackPosition = mediaPlayer.currentPosition
        // 비디오 일시 정지
        // mediaPlayer.pause()
    }

    override fun onStop() {
        super.onStop()
        // 액티비티가 더 이상 보이지 않을 때 필요한 작업 수행
    }

    override fun onDestroy() {
        super.onDestroy()
        // 리소스 해제
        // 예: mediaPlayer.release()
    }
}
  1. 상태 변수: ViewModel을 사용하지 않고, currentPlaybackPositionisPlayingMainActivity 클래스의 프로퍼티로 직접 정의했다.

  2. UI 초기화: Compose를 사용하지 않고 XML 레이아웃을 사용하는 경우, setContentView(R.layout.activity_main)을 통해 레이아웃을 설정한다.

  3. 리소스 관리: onDestroy()에서 메모리 누수를 방지하기 위해 비디오 플레이어와 같은 리소스를 해제하는 코드가 필요하다.

결론

전통적인 방식으로 생명주기를 관리하는 것은 상태를 관리하고 UI를 업데이트하는 데 더 많은 코드를 필요로 하며, 각 생명주기 메서드에서 직접 상태를 관리해야 하기 때문에 코드의 가독성이 떨어질 수 있다.

반면, ViewModel을 사용하는 현대적인 접근 방식은 상태 관리를 쉽게 해주며, 생명주기와 UI 업데이트를 더 깔끔하게 분리할 수 있다.

Jetpack Compose 생명주기 사용방법

요즘에는 최신 라이브러리들을 활용하면 생명주기 관리가 훨씬 간편해졌다.

1.1 ViewModel 사용

ViewModel은 UI 관련 데이터를 저장하고 관리하는 클래스다. 액티비티가 재생성되더라도 ViewModel은 생명주기와 관계없이 유지되므로, 생명주기 관리의 복잡성을 줄일 수 있다.
예를 들어, 동영상의 현재 재생 위치나 재생 상태를 ViewModel에 저장하면, 액티비티가 재생성되더라도 쉽게 복원할 수 있다.

class VideoViewModel : ViewModel() {
    var currentPlaybackPosition: Long = 0
    var isPlaying: Boolean = false
}

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    private val viewModel: VideoViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // Composable UI
        }
    }

    // 생명주기 메서드에서 ViewModel 사용
    override fun onPause() {
        super.onPause()
        viewModel.currentPlaybackPosition = // 현재 재생 위치 저장
    }

    override fun onResume() {
        super.onResume()
        // 저장된 재생 위치 복원
        // viewModel.currentPlaybackPosition 사용
    }
}

1.2. LifecycleObserver 사용

LifecycleObserver를 활용하면 액티비티의 생명주기 이벤트를 좀 더 세부적으로 관리할 수 있다. 특정 작업이 발생하는 생명주기 이벤트를 감지할 수 있다.

class VideoPlaybackObserver : LifecycleObserver {
    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onStart() {
        // 동영상 재생 시작
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onStop() {
        // 동영상 일시 정지
    }
}

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    private val playbackObserver = VideoPlaybackObserver()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycle.addObserver(playbackObserver)
        setContent {
            // Composable UI
        }
    }
}

유튜브 앱의 생명주기 관리 예시

그러면 유튜브 앱 개발자는 생명주기를 어떻게 활용하고 관리할까?

  • 백그라운드 재생: 사용자가 다른 앱으로 전환하거나 화면을 잠글 때, onPause()에서 비디오를 일시 정지하고, 백그라운드에서 오디오를 재생할 수 있도록 설정한다.

  • 데이터 저장 및 복원: 사용자가 다시 돌아왔을 때 현재 재생 중인 동영상의 상태를 복원한다. ViewModel 또는 데이터베이스를 사용하여 사용자가 마지막으로 본 위치를 저장하고, onResume()에서 복원한다.

  • 사용자 경험 최적화: 사용자가 앱을 다시 열었을 때 마지막 시청 위치에서 비디오가 자동으로 재생되도록 하여 연속성을 유지한다. 이를 위해 생명주기 메서드를 통해 상태를 관리하고, 적절한 UI 업데이트를 수행한다.

  • 코드 간결성: 최신 Android 라이브러리와 Compose의 도입으로 인해, 액티비티 생명주기 메서드를 일일이 오버라이드하는 대신, 필요할 때만 메서드를 구현하고, 나머지는 ViewModel이나 LifecycleObserver를 통해 처리하여 코드의 간결성을 높일 수 있다.

Activity 생애

액티비티는 전체(entire), 가시적(visible), 포그라운드(foreground)의 세 가지 생애를 오간다.

전체 생애(Entire Lifetime) - 액티비티가 생성될 때 최초 호출되는 onCreate( ) 메서드 호출과 종결되기 전에 호출되는 onDestroy( ) 호출 사이에 액티비티에서 발생하는 모든 것을 나타내는 데 '전체 생애'라는 용어가 사용된다.

가시적 생애(Visible Lifetime) - onStart( )와 onStop( ) 호출 사이의 액티비티 실행 시기다. 이 시기 동안 액티비티는 자신을 사용자에게 화면으로 보여줄 수 있다.

포그라운드 생애(Foreground Lifetime) - onResume( ) 메서드 호출과 onPause( ) 호출 사이의 액티비티 실행 시기를 의미한다. 액티비티는 자신의 전체 생애 동안 포그라운드와 가시적 생애를 여러 번 거칠 수 있다. 아래의 그림은 액티비티의 생애와 생명주기 메서드들의 개념을 보여준다.

개발 시 고려사항

  • 상태 저장 및 복원: Activity가 종료되거나 재시작될 때 상태를 저장하고 복원하는 것이 중요하다. onSaveInstanceState() 메서드를 사용해 사용자가 보고 있던 화면 상태를 저장하고, 복원할 때는 onRestoreInstanceState() 또는 onCreate()에서 복원해야 한다.
  • 메모리 관리: Activity가 많이 열리거나 장시간 백그라운드에 있을 경우 메모리 누수가 발생할 수 있다. 특히 View나 Context를 잘못 참조하거나, 비동기 작업을 적절히 종료하지 않으면 문제가 생길 수 있다.
  • 다중 Activity 전환 관리: 여러 Activity를 전환할 때는 Intent로 데이터를 전달하거나 onActivityResult()를 사용하여 데이터를 주고받을 수 있다. 최근에는 ActivityResultLauncher로 더 간단하게 관리할 수 있다.
  • Compose와의 통합: Jetpack Compose를 사용하여 UI를 구성할 때도 여전히 Activity는 화면을 관리하는 컨테이너 역할을 한다. Compose에서는 setContent를 통해 Composable을 Activity에 연결할 수 있다.
  • 구성 변경(Configuration Changes): 화면 회전이나 언어 변경 같은 구성 변경에 대비하여 Activity가 적절히 동작하도록 고려해야 한다. ViewModel이나 Compose의 상태 관리를 통해 이러한 변화를 처리할 수 있다.

Ref:
https://mattlee.tistory.com/73

profile
화려한 외면이 아닌 단단한 내면

0개의 댓글

관련 채용 정보