우리는 종종 코드를 짜다 'context'라는 키워드를 접하게 된다.
흔히, intent를 쓸 때 많이 봐왔을 것이고,
상단의 import 부분을 주의 깊게 봤다면, context라는 글자를 보게 된다.
이뿐만 아니라, Toasat, Adatpers, Inflaters, SharedPreferenes, SystemServices 등을 다룰 때, Context를 인자로 넣어야 한다.
그런데, 지금까지는 그냥 채워넣으라고 해서 채워넣었지만, 한번도 깊게 알아볼 생각은 못했다.
그래서, 이번 기회에 context에 대해 총정리 해보고자 한다.
"Context는 어플리케이션 환경에 관한 전체 정보를 받을 수 있는 추상 클래스(인터페이스)입니다. 이를 통해 애플리케이션별 리소스 및 클래스에 대한 액세스는 물론 액티비티 호출, 브로드캐스팅 및 인텐트 수신 등과 같은 애플리케이션 수준 작업에 대한 상향 호출이 가능합니다."
무슨 말인가 싶다. 하나하나 뜯어보자.
Context를 사전적 정의로 "맥락"이라는 뜻을 지닌다.
즉, 'app의 현재 상태' 내지는 '앱이 흘러가는 맥락'정도로 이해하면 충분하다.
보다시피 Context는 Object를 상속받는 abstract class이다.
말그대로, '추상' 클래스이기 때문에 이를 사용하기 위해서는 구현체가 있어야 한다. 그 구현체가 바로 아래의 도식에 나와있다.
도식에서 볼 수 있듯, Object아래에 ContentProvider, Context, BroadReceiver가 있다. 우리가 안드로이드 4대 컴포넌트를 얘기할 때 쓰는 그것들이 맞다. 여기서 알 수 있듯, contentProvider와 BroadcastReciever는 Context의 구현체가 아니다. 즉, 자기 자신만의 context 맥락을 가지고 있지 않다는 것이다. 그러나, 실제적으로 사용할 때는 onReceive() 함수를 통해 다른 context를 인자로 전달 받아서 사용하며, 전달받은 Context의 생명주기를 따르게 된다.
이제 본격적으로 Context에 대해 알아보자.
Context는 추상 클래스이기 때문에 기본적인 구현체가 있어야 한다고 말했는데, 여기서 기본 구현체는 'ContextImpl'이다.
그러나 이 구현체는 사용자에게 직접적으로 노출되지는 않고,
'ContextWrapper'로 감싸져 있다.
보다시피, Context를 상속받는 ContestWrapper가 있다.
아래에 mBase라는 맴버변수가 선언되어 있는데, mBase는 원본 Context의 타입의 변수로, 원본 Context 객체를 참조하는 역할을 한다.
아래의 attachBaseContext함수는 이 mBase를 설정한 후, Context 구현체를 재정의하거나 확장하는데 사용하며,
Activity, Service, Applicaition 컴포넌트는 각각 ContextImpl을 생성하고, getBaseContext(), getApplicationContext()메서드를 통해 mBase를 return하여 Context를 가져온다.
(이는 아래에서 다시 설명할 것이다.)
참고로, ContextImpl은 앱에서 직접 사용할 수 있는 클래스가 아니어서 소스코드로만 볼 수 있다.
아래는, 이 내용들을 도식화한 것이다.
Activirt, Service, Application은 ContextWrapper을 상속하고, Context의 기능을 사용할 수 있게 되는 것이다.
그러면 이제 이들 각각이 어떻게, context를 구현하고 있는지 알아보자.
✋잠깐! 바로 ContextImpl로 구현하지, 왜 번거롭게 Wrapper로 감싸주나?
👉 보호 PROXY패턴
- proxy는 '대리(인)','대신'이라는 뜻으로, 무언가를 대신한다는 의미를 지닌다.(proxy server할 때, 그 proxy다.)
- '상속보다는 구성을 사용하라'는 객체 지향의 원칙을 준수하기 위함이다. 즉, 컴포넌트(Activity,Service,Application)들이 ContextImpl을 직접 상속해서 Context 메소드를 사용하는게 아니라, 중간에 '대리인'으로 ContextWrapper을 두어 메소드를 사용하게 하는 것이다.
- 이렇게 함으로써, ContextImpl은 Context 클래스가 구현한 API메서드만을 구현하는 데 집중하고, ContextWrapper는 원본 Context를 변화시키지 않으면서도 이를 다양한 용도로 사용함으로써 역할을 분리할 수 있다.
🚫주의
application이 activity보다 더 큰 범위인 것은 맞으나, context의 관점에서 이야기할 때는, 포함관계라기 보다는 서로 다른 역할을 하는 여집합 관계라고 보는 것이 옳다.
Application Context는 Activity Context가 지원하는 모든 것을 지원하지 않는다. 따라서, GUI 관련 동작들에 있어서는 Application Context에서 관리하면 안된다.
context를 가져온다는 게 무슨 의미일까?앞서 얘기했듯, 해당 흐름을 가져오는 것이다.
즉, Activity Context를 가져오면, 해당 activity의 현재 상황에 관련된 모든 맥락과 정보들을 가져오는 것이고, Application Context을 가져오면 해당 app에 관한 모든 것들을 가져온다는 것이다.
🚫주의!
Context를 가져와 직접 접근할 수 있으려면, 해당 코드가 activity 혹은 fragment와 연관되어 있어야 한다.
예를들어, adapter class를 하나만들었다고 했을 때, 이 adapter는 activity 혹은 fragment의 생명주기와 직접 연결되어 있지 않기 때문에 참조할 수 없다.(실제로 참조 코드를 쳐도 에러가 난다.)
굳이 context를 참조하고 싶다면 context를 adapter의 생성자에 전달하여 activity 혹은 fragment의 context와 연결시키는 방법이 있다.
그러나 이경우, activity가 destroy되어도 context를 참조하고 있을 수 있으므로, 아래와 같이 참조를 해제해주어야 한다.override fun onDestroy() { super.onDestroy() // Fragment가 소멸될 때 Adapter에서 Context 참조 해제 adapter.clearContextReference() } } fun clearContextReference() { context = null }
① this@ActivityName(Java의 경우, AcitivityName.this)
② getActivity() / requireActivity()
③ getContext() / requireContext()
① getApplicationContext()
② getApplication()
⇒ getBaseContext()
✅참고: Context 유스케이스
- 핵심: 보다시피, dialog, activity, inflation 등, view UI적인 요소들언 전부 Acitivity Context에서 관리하고 있다.
만약 다른 context에서 관리한다면? 아래에서 살펴보도록 하자.
위의 주의사항에서 얘기했듯, 이러한 Context 참조는 메모리 누수와 매우 밀접하게 관련되어 있기 때문에, 함부로 context를 참조하면 안되고, 역할에 따라 잘 구분해서 사용해야 한다.
메모리 누수란, 더이상 필요하지 않은 리소스가 RAM에 해제되지 않고 계속 남아있는 것을 말한다. 메모리가 누수되면, 에플리케이션에 할당된 메모리가 초과되어 크래쉬가 발생하게 된다.
ⓐ Activity와 분리된 작업에 Acitivity Context를 사용해서는 안된다. (activity context의 옳지 못한 참조의 예)
ⓑ Activity에 종속된 작업에 Application Context를 사용해서는 안된다. (appliciation context의 옳지 못한 참조의 예)
이 외에도, 메모리 누수의 원인은 다음과 같다.
ⓐ static 변수
ⓑ Singleton
ⓒ inner class
TIP. 메모리 누수 탐지도구
Memory Profiler: 안드로이드 스튜디오에 있으며 가비지 컬렉션과 메모리 소비에 대한 정보를 표시하는 가장 간편한 도구이다.
Leak Canary: 이 라이브러리를 앱에 설치하면, 메모리 누수를 발생시키는 것들을 추적할 수 있다.
❤️이 글의 결론❤️
액티비티에서 사용된다면 Activity Context
에플리케이션 전역에서 사용된다면 Application Context를 사용하자.
(백그라운드 작업 또는 데이터 엑세스와 같은, Activity cycle에 종속되지 않고 유지되어야 하는 작업)
📜Reference
Context, 너 대체 뭐야?
Context 넌 대체 무엇이더냐
Context 제대로 알고 사용하자
안드로이드의 Context와 메모리누수
Why use ContextImpl to implement Context rather than ContextWrapper in Android?