본문 바로가기

Android(Kotlin) Study A부터 Z까지 등반

Activitiy란? 4대 컴포넌트 중 하나에 대해서

Activity란?

  • 사용자 관점에서는 보이는 화면
  • 개발하는 관점에서는 .kt + .xml 파일의합물론 객체 지향이랑은 다른 이야기이다!
  • 쉽게 생각한다면 화면이라는 객체를 만들기 위해 붕어빵 틀(.xml)과 붕어빵이라는 것을 만들기 위한 재료(.kt)가 합쳐져 붕어빵이 만들어진다고(화면) 생각하면 될 것 같다.
  • 안드로이드 4대 컴포넌트 중 하나로 안드로이드를 시작하면 가장 먼저 접하기도 하고 결국에 Client가 보는 화면이라 가장 중요한 파일이기 때문에 4대 컴포넌트라고 ‘일단은’ 생각하자.

어떻게 만드는가?

알거라고 생각한다. Empty 프로젝트로 “직접 타이핑해서 뷰의 컴포넌트를 만들겠다!” 도 좋은 생각이지만 개인적으로 제공해주는 다른 Activity로 생성을 해보는 것을 추천한다(ex: Login Activity 등등). 안드로이드는 무엇으로 만들어야하는가? 안드로이드 스튜디오이다. 필수인가? 거의 확실히 필수이다. 안드로이드는 누가 만들었는가? 구글이 만들었다. 제공해주는 Activity의 코드들의 퀄리티가 간단하지만 강력하다고 생각한다. 실제로 하나씩 만들어본 다음에 코드를 분석해보는 것도 좋은 경험이 될 것이라고 생각한다. 물론 스튜디오 제공 파일 생성 말고도 다른 방법으로도 만들 수는 있다. 근데 굳이? 라는 생각이 들어서 추가하지 않겠다.

  • 생성 시 자동적으로 Manifest에 추가가 되지만, 초기 실행 액티비티를 처음 만든 Activity가 아닌 것으로 해주고 싶다면 intent-filter를 교체하면 된다.
  • 안드로이드 뿐만 아니라 웬만한 파일 이름, 사용자 이름등을 영어로 해주자. 한글로 하면 낭패를 보는 경우가 많을 것이다. 나는 특히 사용자 이름 때문에 최근에 고생했다...

Activty 생명주기

생명주기가 무엇인지는 알 것이다. 앱 실행부터 종료까지의 시퀀스이다. 이것은 외부 자료를 첨부하겠다.

◾ 1. onCreate() - 필수 구현 항목

  • Activity가 생성될 때 호출
  • 사용자 인터페이스 초기화에 사용됨, 생성자 같은 것이라고 생각하면 편하다.
  • 실행 완료 시 onStart() / onResume()을 호출
  • 다시 불려질 수 없음(비정상으로 종료 후 재시작의 경우 제외)
  • Activity를 만들 때 단 한번만 하면 되는 작업들

◾ 2. onRestart()

  • Activity가 멈췄다 다시 시작되기 전 호출

◾ 3. onStart()

  • Activity가 화면에 보여지기 직전 호출
  • 활동이 '재개됨' 상태가 됨
  • 실행 완료 시 onResume() 호출

◾ 4. onResume()

  • Activity가 사용자와 상호작용을 하게 됐을 때 호출(이때부터서야 사용자와 상호작용이 가능)
  • 전화가 오거나, 다른 Activity로 이동하거나, 기기 화면이 꺼지는 등으로 앱에서 포커스가 끌날 때까지 이 상태에 머무름
  • 다시 App으로 돌아올때 무조건 호출됨
  • Activity가 다시 호출될 떄 하면 되는 작업들

◾ 5. onPause()

  • 화면의 일부분이 보여지지 않는 상태에 호출
  • 데이터 저장, 네트워크 호출 등의 처리를 해서는 안됨: 아주 잠깐 실행되기 때문에 완료되지 못할 수 있어서!
  • Activity가 다시 시작되면 onResume() 호출, 완전히 중지 시 onStop() 호출

◾ 6. onStop()

  • Activity가 더이상 사용되지 않을 때 호출(화면 전부가 보이지 않을 때)
  • 주의할 점은 메모리 부족 시 호출 되지 않을 수 있음
  • 부하가 큰 종료작업은 여기서 실행해야함 (ex. DB저장)
  • Activity 실행 종료 시 onDestroy() 호출

개발자분께서 말씀하시길 onPause()와 onStop()을 확실하게 구분짓기는 어렵기 때문에 나눠서 구현하지 않고 그냥 한곳에다가 구현한다더랑

◾ 7. onDestroy()

  • Activity가 완전히 끝났을 때 호출
  • finish() 호출 또는 Activity 제거 시 호출

그래서 이걸 왜 알아야하는가?

초반에 개발하다보면 거의 onCreate에서만 할 것이다. 왜냐? 우린 앱 실행시에만 프로그램이 돌아갈 것이니깐!

💡 onCreate에서 setContentsView()를 호출 되는 것을 알 수 있다. 뷰에 보이는 것은 처리했지만 내가 만든 앱을 쓰다가 전화가 오거나, 잠시 카톡을 보내고 오고 싶을 때가 있다. 하지만 우리의 앱은 API를 사용하여 외부 서버에서 실시간으로 데이터를 받아오는 리스트 뷰가 있다고 해보자. 그 사이에 데이터가 새로운것들이 추가가 됐다. 하지만 우리는 데이터 받아오는 코드를 onCreate()에 구현을 해놓아버렸다. 전화 받고 온다면 onCreate()는 호출되지 않을 것이다. 따라서 우리는 onStart, onResume에서 변화를 체크해주는 작업을 해주면 좋을 것 같다.

💡 다른 예로 종료시에 현재까지 하던 작업을 기억하고 싶다면, 예를 들어 로그인 기록(사용자 데이터), 사용하던 페이지나 작업을 기억하고 싶다(데이터 베이스나, sharedPreference 등). 그것을 언제 기록할 것인가? onCreate에서? 어려울 것이다. 종료되는 시점의 pause, stop, destroy를 잘 활용한다면 체계적인 어플리케이션을 만들 수 있을 것이다, ###onSaveInstanceState를 잘 활용하면 아주 좋다! Bundle이용, 나는 보통은 sharedPreference를 쓴다. onPause, onResume에 대해서 저장하고 불러오는 방식을 선호한다.

상태 저장 관련해서 뷰 모델을 언급하려 했으나, 이후 디자인 패턴에서 언급해주는게 더 나을듯하다!

Bundle이란?

많은 타입의 값을 저장할 수 있는 맵 클래스이다.

int, double, long, String 부터 FloatArray, StringArrayList, Serializable, Parcelable등등까지 제공을 해준다. 보통 saveInstanceState등에 많이 쓰이며, 인텐트 시에 데이터를 넘기는 용도로도 사용은 가능하다(나는 안 써봤다)

SharedPreference란?

예제 코드는 따로 달진 않겠다. 이해만 시켜주자면, 앱이 설치된 개인 휴대폰에 파일을 따로 저장해둬서 필요할때마다 Load, Save 해줄 수 있는 메모장 파일 같은 존재이다. 나는 빈번하게 사용한다.

언제 액티비티가 종료될까?

명시적 종료

  • Back 버튼 누르기
  • Recents(최근앱) 화면에서 앱을 밀어서 종료시키기
  • 상위 액티비티로 이동하기
  • 설정화면에서 앱을 강제로 종료하기
  • finish() 호출에 의한 Activity 종료하기

명시적이지 않은 종료

  • Configuration변경 - 화면 회전, 멀티 윈도우 등, background로 들어갔을 때 onStop이 호출된다.
  • 시스템에 의한 종료 - 메모리 초과 등으로 갑자기 꺼졌다가 재시작할 때도 있고 아예 꺼지는 경우가 이때임, 메모리 우선순위가 낮으면 보통 종료가 된다.
    액티비티의 상태에 따른 종료될 가능성

Back Stack이란?

화면전환을 여러번 했던 경우에 finishActivity()를 안해주면 어떻게 되는가? 뒤로가기를 누르면 이전 창이 보인다. 가장 먼저 들어온 액티비티부터 pop되므로 우리는 이것을 스택이라고 부를 수 있다. 명칭의 이유는 자세히 안 알아봤지만 뒤로가기(Back)시 스택 pop이므로 백스택이지 싶다.

화면 전환은 intent로 해준다. 그때 flags를 줄 수 있으며 스택에 대한 관리를 할 수 있게 해준다.

  • FLAG_ACTIVITY_NEW_TASK

새로운 Task를 생성하여 그 Task 안에 액티비티를 추가할 때 사용한다. 단, 기존에 존재하는 Task들 중에 생성하려는 액티비티와 동일한 affinity를 가지고 있는 Task가 있다면 그곳으로 액티비티가 들어가게 된다.

  • FLAG_ACTIVITY_SINGLE_TOP

시작되고 있는 액티비티가 백 스택 맨 위에 있는 액티비티인 경우, 해당 액티비티의 새 인스턴스를 생성하는 대신 기존 인스턴스가 onNewIntent()에 대한 호출을 받는다.

singleTop과 동일 동작

  • FLAG_ACTIVITY_CLEAR_TOP

시작되고 있는 액티비티가 이미 현재 Task에서 실행 중인 경우, 해당 액티비티의 새 인스턴스를 시작하는 대신 그 위에 있는 모든 액티비티가 소멸되고 이 인텐트는 해당 액티비티의 재개된 인스턴스로, onNewIntent()를 통해 전달된다.

Task Affinity

Activity 마다 정해진 taskAffinity가 있다. 기본적으로 앱의 패키지 명을 따른다. 이 task affinity는 어느 한 액티비티를 실행할 때 어떤 태스크에 속하게 실행할 것인지 지정하는 것이다. 예를 들어, 내가 만든 앱의 activity의 taskAffinity를 유튜브로 설정하면 유튜브의 task로 내 액티비티가 실행된다.

💡 하지만 특정 액티비티에 대해서 앞서 설명했던 Flag가 항상 같다면 더 간단하게 만들 수 있다.

 

Menifest에 액티비티마다 launch mode 속성을 추가

standard : 평소에 쓰던대로

singleTop : 스택 최상위에 있으면 인스턴스 안 만든다. onNewIntent() 후 onResume()호출

singleTask : 테스크를 새로 만들어서 액티비티를 추가한다. 그 액티비티 다시 호출하면 해당 Task에서만 onNewIntent()호출

singleInstance : singleTask와 같지만, 액티비티를 다시 호출하면 똑같은 액티비티라도 새로운 Task에서 호출

스택에 쌓인다면 안에서 계속 상호작용을 할 수 있는가?

일반적으로 말하자면 스택 아래에 쌓인 액티비티는 일시중지가 된다. 따라서 상호작용을 하려면 다른 방법을 강구해야 할 것이다. 하지만 최상위로 온다면 다시 시작할 것이다! Back Stack이 해당 Activity의 테스크를 보존하고 관리해주기 때문이다.

💡 프로세스에서도 문맥이라는 것이 존재한다. 안드로이드도 프로세스들(액티비티라고 하자)이 있을테니 문맥이 존재하지 않을까?

 

Android Context란?

Context 는 애플리케이션 환경에 대한 글로벌 정보를 갖는 인터페이스이다. Context 는 Android 시스템에서 구현체를 제공하는 추상 클래스로, 애플리케이션 별 리소스 및 클래스 접근에 사용되며, 액티비티 실행, 브로드캐스트, 인탠트 수신 등과 같은 애플리케이션 수준 작업에 사용된다고 한다.

  • 어플리케이션의 현재 상태를 갖고 있음
  • 시스템이 관리하고 있는 액티비티, 어플리케이션의 정보를 얻기 위해 사용
  • 안드로이드 시스템 서비스에서 제공하는 API (리소스, DB, Shared Preferences 등) 에 접근하기 위해 사용→ getResource() 같은 메소드를 써봤다면 이해하기 쉽다. 이 얘기 하는 거다.
  • Activity, Application context 로 나뉘며 그 클래스는 Context 클래스를 상속받은 클래스

각 Context 구분해서 사용해야 메모리릭이 일어나지 않는다고 한다.

Application Context란?

getApplicationContext로 접근하는 context이다. 현재 컨택스트가 종료된 이후에도 컨택스트가 필요한 작업이나 액티비티 스코프를 벗어난 컨택스트가 필요한 작업에 적합하므로 애플리케이션에 싱글턴 오브젝트를 생성시에 컨텍스트를 필요로 할 때 전달하고, 전반적으로 쓰는 라이브러리에 컨텍스트를 달아줄 때에도 Application Context를 달아주자.

프로세스 단위? 느낌으로 생각하자.

Activity Context란?

Context는 가지고 있지만 Activity에만 종속되어 해당 Activity에서만 유효하다. Activity가 종료될 때 같이 종료되고 생성될 때 같이 생성되어야하는 컴포넌트 생성시에 Activity Context를 전달해주면 된다.

val builder = AlertDialog.Builder(this)

val builder = AlertDialog.Builder(applicationContext)

Toast.makeText(this, "message", Toast.LENGTH_LONG).show()

Toast.makeText(applicationContext, "message", Toast.LENGTH_LONG).show()

아마 둘 다 가능 할 것이다. Task 단위? 느낌으로 생각하자.

 

 

정리하며, 생명주기 관련해서 이제는 더 자세히 봐야할 이유가 있는게 멀티 윈도우를 켰을때나, 이전에는 알람이 울려도 pause가 뜬다고 했는데 지금은 아닌것같다. 그래서 더 자세히 알아봐야 할 것으로 보인다.