[Android Fundamentals] Activity - 2. Introduction to Activities
``Activity`` 클래스는 안드로이드에서 매우 중요한 컴포넌트이다. ``main()`` 함수에서 시작하는 다른 언어들과 달리 안드로이드 프레임워크는 ``Activity`` 인스턴스로부터 앱을 시작하며, 생명주기에 따라 적절한 콜백을 실행한다.
Concept
모바일 앱은 상황에 따라 다른 경로로 실행될 수 있다. 앱 목록에서 이메일 앱을 실행하면 아마 메일 리스트가 보일 것이다. 그러나 SNS 앱에서 ``메일 보내기`` 작업을 선택하여 메일 앱으로 넘어왔다면 메일 쓰기 화면이 보일 것이다.
``Activity``는 이런 방식에 맞게 설계되었다. 어떤 앱에서 다른 앱을 시작할 때에는 시작할 앱의 activity를 호출해야 한다. 즉 activity는 앱의 entry point 역할을 한다.
Activity에는 앱이 UI를 그리는 window가 있다. Window는 일반적으로 화면을 가득 채우지만, 그보다 작을 수도 있으며 다른 앱 위에 띄워질 수도 있다(floating).
일반적으로 하나의 activity가 하나의 화면을 구현한다. 대부분의 앱은 2개 이상의 화면을 가지므로 일반적으로 2개 이상의 activity를 가진다. 그 중 하나를 골라 사용자에게 맨 처음 보이는 main activity로 지정할 수 있다. 그 후에는 activity에서 다른 activity를 start할 수 있다.
이메일 앱의 main activity는 메일 목록 화면일 가능성이 높다. 이 화면에서 시작하여 메일 자세히 보기, 메일 쓰기, 설정 화면 등 다양한 화면을 각각 activity로 구현할 수 있다.
물론 compose-only 앱에서는 activity를 하나만 사용하고, 나머지는 composable로 구현한다.
이처럼 하나 이상의 activity를 함께 사용하는 경우가 많지만, 시스템적으로는 activity 간 종속성은 최소한으로만 유지된다. 사실 코드 레벨에서도 activity 간 종속성을 최소한으로 낮춰야 유리하다. 모듈화 등..
위의 SNS 앱 예시처럼 다른 앱의 activity를 실행할 수도 있다.
Activity를 사용하기 위해서는 manifest에 activity의 정보를 등록해야 하며, activity의 생명주기를 잘 관리해야 한다.
Manifest
Activity를 사용하려면 activity의 존재와 속성을 manifest에 등록해야 한다.
Activity 정의
다음과 같이 manifest에 activity를 등록할 수 있다. ``<application>`` 안에서 ``<activity>``를 사용한다.
<manifest ... >
<application ... >
<activity android:name=".ExampleActivity" />
...
</application ... >
...
</manifest >
필수 속성은 ``android:name``뿐이지만, 실제로는 ``android:exported`` 등 다양한 속성을 설정할 수 있다. 속성별 정보는 여기에서 확인할 수 있다.
Intent filter 선언
Intent filter는 안드로이드 플랫폼에서 매우 강력한 역할을 한다. Activity에서는 명시적/암묵적 intent를 통해 activity를 실행할 수 있게 한다.
명시적 intent는 실행할 activity를 명시적으로 지정한다. "Gmail 앱의 메일 쓰기 activity를 실행하라"가 예시이다.
반면 암묵적 intent는 "메일을 쓸 수 있는 앱을 실행하라"와 같이 activity가 수행할 작업을 지정한다. 암묵적 intent가 지정한 작업을 수행할 수 있는 activity가 여러 개 있다면, 어떤 activity를 실행할 지는 사용자가 결정한다. Activity가 실행할 수 있는 작업을 확인할 때 intent filter가 사용된다.
``<activity>`` 태그 안에 ``<intent-filter>``를 선언하여 intent filter를 적용할 수 있다. ``<intent-filter>`` 안에는 필수 태그인 ``<action>``과, 선택 태그인 ``<category>``와 ``<data>``를 선언할 수 있다. 예를 들어 메시지를 보낼 수 있으며 텍스트를 데이터로 받는 activity는 다음과 같이 선언할 수 있다.
<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
- ``<action>``: Activity가 실행하는 작업을 명시한다.
- ``<category>``: Activity가 수신할 intent의 종류를 명시한다. 다른 앱에서 activity를 실행할 수 있게 하고 싶다면 ``DEFAULT``를 사용한다.
- ``<data>``: Activity가 보낼 수 있는 정보의 타입을 의미한다.
위에서 선언한 ``ExampleActivity``는 다음과 같이 실행할 수 있다.
// 암묵적 intent
val sendIntent = Intent().apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, textMessage)
}
startActivity(sendIntent)
다른 앱에서 activity를 실행하는 것을 막고 싶다면 intent filter를 선언하지 않아야 한다. Intent filter 자체가 activity를 외부에 노출하는 역할을 하기 때문. Intent filter를 선언하지 않아도 같은 앱에 속한 actiivty는 명시적 intent를 통해 언제든 실행할 수 있다.
권한
권한을 통해 activity를 start할 수 있는 앱을 제한할 수도 있다. Activity를 실행하려면 해당 activity가 요구하는 권한을 모두 갖고 있어야 한다. 권한이 없는 앱에서 우회적으로 권한에 접근하는 행위를 방지하기 위해서이다.
예를 들어 다음과 같이 ``SHARE_POST``라는 커스텀 권한을 선언한 activity가 있다고 해 보자.
<manifest>
<activity android:name="...."
android:permission=”com.google.socialapp.permission.SHARE_POST”
/>
이 activity를 실행하려는 앱은 반드시 같은 권한을 가지고 있어야 한다.
<manifest>
<uses-permission android:name="com.google.socialapp.permission.SHARE_POST" />
</manifest>
Activity 생명주기
Activity는 시작된 후 끝날 때까지 여러 생명주기를 거쳐간다. 생명주기가 바뀔 때마다 다음과 같은 콜백이 실행된다. 콜백에서 생명주기에 맞게 리소스를 관리하는 등 여러 작업을 수행할 수 있다.
onCreate()
Activity가 만들어질 때 실행되는 콜백이다. UI를 보여주려면 필수로 구현해야 한다. UI 생성, 데이터 바인딩 등 activity에서 사용할 리소스를 초기화하며, ``setContentView()``를 실행하여 앱의 UI를 결정해야 한다.
``onCreate()`` 다음 콜백은 항상 ``onStart()``이다.
onStart()
``onCreate()``가 종료되면 activity는 started 상태가 되어 사용자에게 보이게 된다. 여기에서 사용자와 상호작용할 마지막 준비를 할 수 있다.
근데 나는 지금까지 onStart()를 구현한 적이 거의 없는 듯하다.
onResume()
Activity가 사용자와 상호작용하기 직전에 실행된다. 이 시점에서 activity는 activity stack의 맨 위에 존재하며, 사용자의 모든 입력을 받을 수 있다. 앱의 많은 기능이 이 함수에 구현된다.
근데 나는 onResume()을 구현한 적이 거의 없는데...? 상용 앱에서는 다르려나?
``onResume()`` 다음 콜백은 항상 ``onPause()``이다.
onPause()
Activity가 포커스를 잃어 사용자와 더 이상 상호작용할 수 없을 때 호출된다. Activity를 다른 activity가 가린다던가, 최근 앱 화면으로 나갈 때 실행될 수 있다. 사용자가 어떤 형태로든 activity를 떠나고 있다는 신호인 경우가 많다.
포커스를 잃긴 했지만, 이론적으로는 여전히 사용자에게 보이는 상태이므로 (필요하다면) UI를 계속 업데이트해야 한다.
``onPause()``에서 무거운 작업을 실행해서는 안 된다. Resume과 pause 상태는 매우 자주 반복될 수 있기 때문.
``onPause()`` 다음 콜백은 상황에 따라 ``onStop()`` 또는 ``onResume()``이다.
onStop()
Activity가 더 이상 사용자에게 보이지 않을 때 호출된다. Activity가 destroy되는 과정일 수도 있고, 다른 activity에 의해 완전히 가려진 상황일 수도 있다. 어쨌든 사용자에게 전혀 보이지 않는 상태를 의미한다.
``onStop()`` 다음 콜백은 상황에 따라 ``onRestart()`` 또는 ``onDestroy()``이다.
onRestart()
Activity가 stop 상태에서 start 상태로 갈 때, 즉 사용자에게 다시 보이게 될 때 실행된다. ``onRestart()``에서는 activity가 stop됐을 때의 state를 복원할 수 있다.
``onRestart()`` 다음 콜백은 항상 ``onStart()``이다.
onDestroy()
Activity가 완전히 종료되기 직전에 호출된다. Activity 생명주기의 마지막 콜백으로, ``onCreate()``에서 얻은 리소스를 해제하는 등의 작업을 수행해야 한다.
생명주기에 관한 자세한 내용은 다음 글에서 살펴본다.
참고자료
Introduction to activities | Android Developers