안드로이드 개발을 하다보면 Context를 종종 접하게 된다.
가장 흔하게 접할 수 있는 사례로는, 새로운 Activity를 실행하기 위해서 Intent 객체를 생성해야하는데 이때 생성자의 매개변수로 context를 넘겨준다.
val intent = Intent(context, MyActivity.class);
context.startActivity(intent);
Toast 메세지를 출력할 때에도 다음과 같은 코드를 작성한다.
Toast.makeText(this, "Toast Message", Toast.LENGTH_LONG).show()
안드로이드 개발을 하면서 context를 사용하는 경우는 많이 있지만 context가 무엇인지에 대해서 진지하게 고민해본 적이 없고, 단지 잘못 사용하면 메모리 누수가 일어난다! 라고만 알고 있었다.
따라서 context가 정확히 어떤 역할을 하고 왜 주의해서 사용해야 하는건지 공부해보았다.
우선 공식 문서에서는 context를 다음과 같이 정의하고 있다.
Context는 어플리케이션 환경에 대한 전역 정보의 Interface이다. 이것은 Android 시스템에서 구현을 제공하는 추상 클래스 (Abstract Class)이다.
Context는 어플리케이션 별 리소스와 클래스 접근을 허용해 줄 뿐만 아니라 Activity 시작, Intent 수신과 브로드캐스팅 등의 Application 수준 작업의 호출이 가능하다.
해당 설명에 따르면 context란 대략적적으로 시스템의 자원에 접근하기 위한 추상클래스 정도로 이해할 수 있다.
안드로이드 이외의 다른 플랫폼에서도 시스템에 접근하는 경우는 많이 있다.
예를 들어, C#에서는 다음과 같이 시스템에 접근이 가능하다.
//Get an Application Name.
String applicationName = System.AppDomain.CurrentDomain.FriendlyName;
//Start a new process(application)
System.Diagnostics.Process.Start("test.exe");
자바에서도 System 클래스에 있는 변수나 메소드를 통해 입출력 처리, 시스템 속성 값 등을 관리 하듯이, 안드로이드에서는 Context
라는 추상클래스를 통해서 시스템이 관리하고 있는 어플리케이션에 대한 정보에 접근하고, 안드로이드 시스템 서비스에서 제공하는 API를 호출할 수 있는 것이다.
실제로 Context가 구현된 코드를 직접 살펴보면 추상 클래스로 만들어진 것을 알 수 있다.
public abstract class Context
그렇다면 추상클래스를 초기화해서 사용하기 위해선, 다른 이를 구현한 클래스가 필요할 것이다.
이는 ContextWrapper라는 클래스가 담당하고 있다.
ContextWrapper는 Context의 내부 구현을 모두 delegate하고 있는 Wrapper 클래스이다.
즉, Context
의 원래 구현은 바꾸지 않고, 특정 동작만 추가하거나 수정하고 싶을 때 사용하는 일종의 "포장지" 같은 역할을 하고 있는 클래스이다.
ContextWrapper의 코드 스니펫을 살펴보면 생성자로 Context의 구현체를 받고 Context의 모든 동작을 Context의 구현체로 위임하는 것을 알 수 있다.
그리고 attachBaseContext(Context base)
함수를 통해 Context 구현체를 바꿀 수 있다.
왜 이러한 구조로 Context를 설계했을까?
이는 attachBaseContext
함수의 존재 이유에 있다. Activity
, Service
, Application
과 같은 안드로이드 컴포넌트들은 ContextWrapper
를 상속받아 Context
의 기능을 사용하며, 동시에 자신만의 고유한 기능을 추가할 수 있도록 설계된 클래스들이다.
따라서 특정 시점에서 기본 Context
객체를 동적으로 교체하거나 커스터마이징할 수 있는 유연성을 제공해야할 필요가 있다.
즉, ContextWrapper를 상속하는 Activity, Service, Application과 같은 컴포넌트에서 ContextWrapper의 함수를 호출하면 어떤 Context 구현체가 사용되느냐에 따라 동작이 유연하게 변경되며, 개발자는 각 컴포넌트마다 새로운 Context 동작을 쉽게 재정의하거나 확장할 수 있다.
이를 Proxy 패턴이라고 하는데, 이 패턴을 사용하면 Context 구현체와 이를 사용하는 쪽의 결합도가 낮아져 쉽게 Context 구현체를 변경할 수 있다는 장점이 있다. 이를 공식 문서에서는 원본 Context의 변경 없이 Context 동작을 수정할 수 있다고 설명하고 있다.
그렇다면 안드로이드에서 제공하는 컴포넌트들은 모두 ContextWrapper를 상속받은 구현 클래스일까?
내부 구현코드를 살펴보면 다음과 같다.
public abstract class Service extends ContextWrapper
public abstract class Application extends ContextWrapper
public abstract class Activity extends ContextThemeWrapper
자세히 살펴보면 Service와 Application은 ContextWrapper를 상속받고 있지만 Activity는 ContextThemeWrapper를 상속받고 있다.
그리고 ContextThemeWrapper는 다시 ContextWrapper를 상속받고 있다.
ContextThemeWrapper는 ContextWrapper의 서브 클래스로 Context의 Theme를 수정하거나 교체하기 위해서 사용되는 Wrapper 클래스이다.
ContextWrapper와 비교하였을 때 ContextThemeWrapper는 Theme에 대한 멤버 변수를 가지고 있다.
정리하자면, Application과 Service UI와 관련이 없는 시스템 레벨의 동작을 처리하므로, ContextWrapper를 상속받고 Activity는 ContextThemeWrapper를 상속받아 테마와 스타일을 적용할 수 있는 UI 중심의 Context 기능을 확장한다.
context란 추상클래스로 Application environment의 global한 정보에 접근하기 위한 인터페이스이다.
context는 다음과 같은 상황에서 사용할 수 있다.
Aplication Context는 싱클톤 인스턴스이며 액티비티에서 getApplicationContext()
를 통해 접근할 수 있다. 이 Context는 애플리케이션의 라이프 사이클과 연결되어 있으며, Application Context는 현재의 Context와 분리된 라이프 사이클을 가진 Context가 필요할 때나 액티비티의 범위를 넘어서 Context를 전달할 때 사용한다.
Activity Context는 액티비티에서 사용 가능하며, 이 Context는 액티비티의 라이프 사이클과 연결되어 있다. 액티비티의 범위 내에서 Context를 전달하거나, 라이프 사이클이 현재의 Context에 attach된 Context가 필요할 때 Activity Context를 사용할 수 있다.
context는 TV를 조종하는 리모컨이라고 생각할 수 있다. TV를 앱이라고 한다면 TV의 다양한 채널들은 앱의 자원들이라고 할 수 있다.
리모컨은 TV의 다양한 채널(자원)들에 접근할 수 있게 해준다.
리모컨은 자원에 접근할 수 있게 해주는 역할을 하기 때문에 리모컨을 가진 사람은 당연히 자원에 접근할 수 있다.
context를 얻는 방법으로는 getApplicationContext(), getContext(), getBaseContext() 혹은 this(Context를 확장한 class의 경우)등이 존재한다.
//this -> 리모콘 (context)
TextView tv = new TextView(this);
View.getContext()
Activity.getApplicationContext()
ContextWrapper.getBaseContext()
this