Android Context 에 관해서 공부한 것을 정리한 글입니다.
사전적 정의로는 ‘문맥', ‘맥락' 의 뜻을 가지고 있는데, 안드로이드에 입각해서 생각해보면 애플리케이션 현재 상태에 대한 ‘문맥' 이라고 볼 수 있을 것입니다.
안드로이드 공식 레퍼런스 에서는 Context 는 애플리케이션에 대한 글로벌 정보를 가지는 인터페이스이며, 안드로이드 시스템에서 구현체가 제공되는 추상 클래스라고 소개하고 있습니다.
Application Context 는 application 의 생명주기와 연결되어 application 이 실행중일 때 항상 같은 Context 를 유지하는 싱글톤 인스턴스입니다.
이는 application 내에서 현재 Context 와 분리된 생명주기를 가진 Context 가 필요하거나, activity 의 범위를 넘어 Context 를 전달해야 할 경우에 사용할 수 있다는 의미이기도 합니다.
Activity 내에서만 사용할 수 있는 Context 입니다. 물론 application 과 마찬가지로 activity 와 생명주기가 연결되어 있습니다.
이는 곧 activity 내부에서 사용되거나, 해당 activity 와 같은 생명주기를 공유하는 어떤 다른 객체를 생성할 때 사용될 수 있다는 뜻입니다.
activity 와 생명주기를 공유하기 때문에, 당연히 activity 가 종료되면 context 또한 함께 소멸됩니다.
💡 둘의 생명주기가 다르다.
앞서 두 종류의 Context 를 다루면서 공통적으로 이야기했던 부분이 바로 생명주기입니다.
activity 와 application 의 관계에 대해서 한 번 생각해보면 이해하기 편할 것 같습니다. 아래 코드를 보면, AndroidManifest 에서 우리는 activity 를 application 의 하위 요소로 작성합니다.
<manifest ... >
<application ... >
<activity android:name=".ExampleActivity1" />
<activity android:name=".ExampleActivity2" />
...
</application ... >
...
</manifest >
만약, activity 내에서 application 전역으로 사용되는 객체 (예를 들어 SharedPreferences) 를 생성할 때 Context 를 넘겨주어야 하는 상황이 생기면 어떤 Context 를 넘겨주어야 하는지에 대해 고민할 수 있습니다.
여기서 수명주기를 고려하지 않고 Activity 의 context 를 넘겨주게 된다면, 해당 Activity 가 종료되어도 application 은 종료되지 않으므로 application 전역으로 사용되는 객체는 소멸되지 않기 때문에 activity context 에 대한 참조가 사라지지 않습니다.
즉, 해당 Activity 는 종료되었으나 이에 대한 참조는 남아있기에 프로세스의 GC는 해당 activity 를 collect 하지 않게 됩니다. 이는 memory leak 이 발생하는 비극적인 결말을 낳게됩니다. 😢
그렇게 생각할 수 있지만, application context 는 만능이 아닙니다. 분명히 수행하지 못하는 작업이 있습니다.
application 은 사용자가 앱과 상호작용할 수 있도록 해주는 UI(View) 를 가지지 않습니다. 다시 말해, GUI 와 연관된 작업들에 필요한 context 들은 application context 가 도와줄 수 없습니다.
😎 “안드로이드 프로그래밍 Next Step” 의 4장 Context 부분을 공부 후 정리하였습니다.
마지막으로 짧게나마 Context 클래스의 조금 더 상세한 부분을 정리해보았습니다.
Context 는 추상 클래스이므로 이를 상속한 자식 클래스가 구현되어야 합니다.
안드로이드에서 주요한 Context 의 하위 클래스로는 ContextWrapper 클래스가 있고, ContextWrapper 클래스를 한번 더 상속한 우리가 잘 아는 Activity, Service, Application 이 있습니다.
ContextWrapper 클래스는 이름 그대로 Context 를 한번 더 감싼 형태의 클래스로, 다음과 같은 ContextWrapper(Context base)
생성자를 가지고 있습니다.
ContextWrapper 클래스 내부에서 아래와 같은 메소드를 찾을 수 있습니다.
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
우리가 자주 사용하는 Activity, Service, Application 클래스들은 ContextWrapper 의 생성자를 직접 사용하는 것이 아닌, 각 컴포넌트 내부의 attach()
메소드의 attachBaseContext()
메소드를 호출합니다.
ContextImpl 클래스는 Context 의 하위 클래스가 아닌, Context 의 여러 메서드를 직접 구현한 인스턴스입니다.
앞서 attachBaseContext(Context base)
메소드에서 base
parameter 가 바로 각각의 컴포넌트에서 생성된 ContextImpl 인스턴스입니다.
ContextImpl 의 메소드는 기능별로 helper, permission, access system service 이 세 가지 그룹으로 나눌 수 있습니다.
getSystemService()
메소드가 있다.지금까지 공부해 온 내용을 토대로 다음과 같은 diagram 을 그릴수 있습니다.
객체 지향 원칙에서 상속보다는 구성을 사용하라고 하는데, 위 다이어그램을 보면 원칙에 맞다는 것을 알 수 있습니다.
Activity, Service, Application 을 보면 ContextImpl 을 직접 상속하는 것이 아니라, ContextImpl 의 메소드를 호출하는 형태임을 알 수 있습니다.
이렇게 되면 ContextImpl 의 변수가 외부로 노출될 일도 없고, ContextWrapper 에서는 ContextImpl 의 퍼블릭 메소드만 호출하게 됩니다. 또한, 각 컴포넌트 별 사용하는 기능을 제어하기도 편해집니다.
Activity, Service, Application 모두 내부에서 Context 를 사용할 수 있습니다.
Activity 를 예로 들면 Context 인스턴스를 3가지 사용할 수 있습니다.
getBaseContext()
메소드를 통해 가져오는 ContextImpl 인스턴스getApplicationContext()
를 통해 가져오는 Application 인스턴스getApplication()
메소드로 가져오는 인스턴스와 같습니다.당연히 세 가지의 인스턴스는 다릅니다. 자칫 세 가지의 Context 가 모두 동일하다 생각하고 getBaseContext()
로 가져온 ContextImpl 인스턴스를 Activity 로 캐스팅했다가는 ClassCastException 이 발생합니다.
View 의 생성자에도 Context 가 있는데, 이 Context 가 어디서 왔는지를 알아봅시다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val helloWorld = findViewById<TextView>(R.id.hello_world)
Log.d("MainActivity", "${helloWorld.context == this}")
Log.d("MainActivity", "${helloWorld.context == baseContext}")
Log.d("MainActivity", "${helloWorld.context == applicationContext}")
Log.d("MainActivity", "${helloWorld.context == application}")
}
}
앱을 실행시키고 로그를 확인해보면 위와 같은 결과를 얻을 수 있습니다.
물론 View 의 생성자에 앞서 알아보았던 세 가지 Context 모두 다 전달이 가능합니다. 하지만, View 는 Activity 와 가장 연관이 있기에 Activity 의 Context 가 전달된 것을 확인할 수 있었습니다.
여기까지 Context 에 대해 공부한 것을 정리해 보았습니다.
안드로이드의 Context 는 application context 와 activity context 두 가지 종류를 가지고 있었습니다. 사실 이 두 가지에 대한 차이점을 알고, 적용하는 데에는 시간이 오래 걸리지 않습니다. 하지만 그만큼 쉽게 잊을 수 있다고 생각합니다.
쉽게 잊었을 때에 대한 리스크는 memory leak 이나 exception 에 의한 app crash 일 가능성이 높겠죠, 아주 치명적입니다. 그래서 우리는 항상 Context 를 숙지해야 합니다. 마치 안드로이드 처럼.
또한 Context 의 세부적인 사항까지도 한 번 훑어봤습니다. 단순히 application context 와 activity context 를 비교하고 적절히 사용한다 까지만 하면 좀 아쉽습니다.
안드로이드에서 Context 클래스가 어떻게 되어있는지, 그 하위 클래스들은 어떤지, 우리가 자주 사용하는 컴포넌트들은 어떻게 Context 를 가지는 지에 대해서 까지 공부하면 더 기억에 오래 남지 않을까 생각해봅니다. 👍