Android 의 Context 알아보기

wooyoung-tom
10 min readJan 3, 2024

Android Context 에 관해서 공부한 것을 정리한 글입니다.

Context 란?

사전적 정의로는 ‘문맥’, ‘맥락’ 의 뜻을 가지고 있는데, 안드로이드에 입각해서 생각해보면 애플리케이션 현재 상태에 대한 ‘문맥’ 이라고 볼 수 있을 것입니다.

안드로이드 공식 레퍼런스 에서는 Context 는 애플리케이션에 대한 글로벌 정보를 가지는 인터페이스이며, 안드로이드 시스템에서 구현체가 제공되는 추상 클래스라고 소개하고 있습니다.

Context 가 할 수 있는 것

  • application 각각의 리소스나 클래스에 대한 접근을 허용합니다.
  • activity 를 시작하거나, broadcasting, intent 수신 등 애플리케이션 레벨의 작업을 위한 up-call 을 허용합니다.

Context 의 종류

Application Context

Application Context 는 application 의 생명주기와 연결되어 application 이 실행중일 때 항상 같은 Context 를 유지하는 싱글톤 인스턴스입니다.

이는 application 내에서 현재 Context 와 분리된 생명주기를 가진 Context 가 필요하거나, activity 의 범위를 넘어 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 >
  • application 이 종료되면 해당 application 의 하위 activity 가 모두 종료된다.
  • 특정 activity 가 종료된다고 해서 application 이 종료되는 것은 아니다.

만약, activity 내에서 application 전역으로 사용되는 객체 (예를 들어 SharedPreferences) 를 생성할 때 Context 를 넘겨주어야 하는 상황이 생기면 어떤 Context 를 넘겨주어야 하는지에 대해 고민할 수 있습니다.

여기서 수명주기를 고려하지 않고 Activity 의 context 를 넘겨주게 된다면, 해당 Activity 가 종료되어도 application 은 종료되지 않으므로 application 전역으로 사용되는 객체는 소멸되지 않기 때문에 activity context 에 대한 참조가 사라지지 않습니다.

즉, 해당 Activity 는 종료되었으나 이에 대한 참조는 남아있기에 프로세스의 GC는 해당 activity 를 collect 하지 않게 됩니다.

이는 memory leak 이 발생하는 비극적인 결말을 낳게됩니다. 😢

그럼 context 가 필요할 때 항상 application context 를 사용하면 되겠네요?

그렇게 생각할 수 있지만, application context 는 만능이 아닙니다.

분명히 수행하지 못하는 작업이 있습니다.

application 은 사용자가 앱과 상호작용할 수 있도록 해주는 UI(View) 를 가지지 않습니다.

다시 말해, GUI 와 연관된 작업들에 필요한 context 들은 application context 가 도와줄 수 없습니다.

Context 클래스

😎 “안드로이드 프로그래밍 Next Step” 의 4장 Context 부분을 공부 후 정리하였습니다.

마지막으로 짧게나마 Context 클래스의 조금 더 상세한 부분을 정리해보았습니다.

Context 는 추상 클래스이므로 이를 상속한 자식 클래스가 구현되어야 합니다.

안드로이드에서 주요한 Context 의 하위 클래스로는 ContextWrapper 클래스가 있고, ContextWrapper 클래스를 한번 더 상속한 우리가 잘 아는 Activity, Service, Application 이 있습니다.

ContextWrapper

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

ContextImpl 클래스는 Context 의 하위 클래스가 아닌, Context 의 여러 메서드를 직접 구현한 인스턴스입니다.

앞서 attachBaseContext(Context base) 메소드에서 base parameter 가 바로 각각의 컴포넌트에서 생성된 ContextImpl 인스턴스입니다.

ContextImpl 의 주요 메소드

ContextImpl 의 메소드는 기능별로 helper, permission, access system service 이 세 가지 그룹으로 나눌 수 있습니다.

  • 앱 패키지 정보를 제공하거나, 내/외부 파일, SharedPreferences, 데이터베이스 등을 사용하기 위한 helper 메소드가 있다.
  • Activity, BroadcastReceiver, Service 와 같은 컴포넌트를 시작하는 메소드와 앱 내부 권한을 체크하는 메소드가 있다.
  • 시스템 서비스에 접근하기 위한 getSystemService() 메소드가 있다.

하위 클래스

지금까지 공부해 온 내용을 토대로 다음과 같은 diagram 을 그릴수 있습니다.

객체 지향 원칙에서 상속보다는 구성을 사용하라고 하는데, 위 다이어그램을 보면 원칙에 맞다는 것을 알 수 있습니다.

Activity, Service, Application 을 보면 ContextImpl 을 직접 상속하는 것이 아니라, ContextImpl 의 메소드를 호출하는 형태임을 알 수 있습니다.

이렇게 되면 ContextImpl 의 변수가 외부로 노출될 일도 없고, ContextWrapper 에서는 ContextImpl 의 퍼블릭 메소드만 호출하게 됩니다. 또한, 각 컴포넌트 별 사용하는 기능을 제어하기도 편해집니다.

그럼 사용할 수 있는 Context 가 많네요?

Activity, Service, Application 모두 내부에서 Context 를 사용할 수 있습니다.

Activity 를 예로 들면 Context 인스턴스를 3가지 사용할 수 있습니다.

  • Activity 자신이 가지는 Context
  • ContextWrapper 의 getBaseContext() 메소드를 통해 가져오는 ContextImpl 인스턴스
  • ContextWrapper 의 getApplicationContext() 를 통해 가져오는 Application 인스턴스
  • Activity 내부의 getApplication() 메소드로 가져오는 인스턴스와 같습니다.
  • 당연히 Activity 가 자신이 Context 를 가지는 것처럼 Application 도 가지니깐요.

주의!

당연히 세 가지의 인스턴스는 다릅니다.

자칫 세 가지의 Context 가 모두 동일하다 생각하고 getBaseContext() 로 가져온 ContextImpl 인스턴스를 Activity 로 캐스팅했다가는 ClassCastException 이 발생합니다.

View 클래스에도 Context 있어요!

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 를 가지는 지에 대해서 까지 공부하면 더 기억에 오래 남지 않을까 생각해봅니다. 👍

참고

Understanding Context In Android Application

[Android] 안드로이드 Context란?

[Android] Context, 너 대체 뭐야?

안드로이드 프로그래밍 Next Step — YES24

[Android/안드로이드] Context, 뭐하는 녀석인지 알고 사용하자!

--

--