어떤 Context를 써야 하나요?



reference:

https://medium.com/@ali.muzaffar/which-context-should-i-use-in-android-e3133d00772c 





 Context

안드로이드 앱 개발을 하다보면  Context에 접근할 일이 많습니다. 그런데, Activity가 아닌 다른 클래스에서 Context를 접근해야 한다면, (물론 인자, 필드 주입 등으로 해결 가능하지만) getContext() 같은 메소드를 호출해야만 합니다. 

Context가 무엇일까요?

Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It
allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.


위는 Context 클래스의 주석입니다. 어플리케이션의 환경, Intent, Activity 이동 등의 행위를 할 수 있는 클래스라고 합니다. 개발하면서 여러분들도 생각한 그런 역할과 크게 다르지 않은 것을 알 수 있습니다.

그런데 문제는 getContext() 뿐 아니라 getBaseContext(), getApplicationContext() 등 다양한 Context가 있다는 것이죠. 잘 못된 Context는 메모리 누수를 발생시킬 수 있습니다.


Context의 종류

Context
| — ContextWrapper
|— — Application
| — — ContextThemeWrapper
|— — — — Activity
| — — Service
|— — — IntentService

Context는 위와 같은 구조를 가집니다. 사실 우리가 부르는 Context는 부르기에 따라서 Application일 수도 있고, Activity일 수도 있고 Service일 수도 있는 것입니다. Context 자체는 사실상 제공해주는 것이 없고, 그것을 상속받은 자식이 모든 행위를 하게 됩니다.

한 예로, ContextThemeWrapper를 보면 다음과 같은데,  전부 theme이 적용된 리소스를 제공하는 것을 알 수 있습니다. 즉, theme과 관련된 작업을 하기 위해서는 이 클래스를 사용해야 한다는 의미입니다.

@Override
public Resources getResources() {
if (mResources != null) {
return mResources;
}
if (mOverrideConfiguration == null) {
mResources = super.getResources();
return mResources;
} else {
Context resc = createConfigurationContext(mOverrideConfiguration);
mResources = resc.getResources();
return mResources;
}
}

@Override public Resources.Theme getTheme() {
if (mTheme != null) {
return mTheme;
}
mThemeResource = Resources.selectDefaultTheme(
mThemeResource,
getApplicationInfo().targetSdkVersion);
initializeTheme();
return mTheme;
}
@Override public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(
getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}

반대로 getApplicationContext() 등의 메소드로 화면의 theme 작업을 하면 적용되지 않습니다. 왜냐면 앞서 언급된 바와 같이 theme은 그 화면에 종속된 특성이며, application context는 말그대로 application-wide한 context이기 때문입니다.


 Memory Leak

잘못된 Context 호출이 어떻게 메모리 누수를 발생시킬까요. 

Context는 40여종의 직/간접적 children을 소유하고 있습니다. 잘못된 Context는 필요치 않은 children의 reference 까지 같이 딸려오게 되므로 메모리가 누수되는 것은 어찌보면 당연합니다. 그래서 우리는, 화면에 종속된 작업을 하는 경우에 getBaseContext() 같은 메소드 호출을 피해야 하는 것입니다. 당연히 해당 Activity or Fragment 등을 호출하는 것이 가장 좋겠죠.


 getApplicationContext()

어떤 경우에 getApplicationContext() 를 호출해야 하는지 생각해 봅시다. 말그대로 application 수준의 context 입니다. 스스로 체크해봅시다. (두 경우 모두 사실 같은 이야기 같네요)


1. 화면과 관계없는 long living object에서 접근하고자 하는가

예를 들어 Activity가 바뀌거나 죽은 뒤에도 계속 수행되는 Thread가 만약 ContextThemeWrapper(Activity or Fragment etc.)를 가지고 있는다면 당연히 메모리 누수.


2. Theme이 적용되어야 하는 작업을 하려고 하는가? 

예를 들어 View를 inflate하고 drawable에 tint를 먹인다던가 하는 따위의 작업은 application context가 사용되어선 안됨. 화면에 종속된 View 작업은 왠만하면 피하자.


getThemedContext()

이 메소드는 ActionBar와 연관이 있습니다. ActionBar에 연관된 theme을 적용한 View를 적용하고 싶을 때 사용합니다. ActionBar에 새로운 text나 icon등을 적용하고 싶을 때 사용된다고 생각할 수 있겠습니다. 


getContext()

ActionBar와 상관 없이 View를 조작하고자 할 때 사용됩니다. 심지어 long living object에서도 사용될 수 있지만, WeakRefernce로 사용되는 것을 권장합니다. 혹은 AlertDialog를 만들 때 쓰이기도 합니다. AlertDialog는 서로 다른 theme이 적용되기도 하는데, 이 경우에 사용됩니다. 위 언급된 바와 같이 theme과 연관이 깊습니다. 

Dark theme을 사용하다가 light theme을 적용하고 싶은 경우를 생각해 봅시다. 물론 매 생성마다 setColor 류의 메소드를 호출하여 일일히 구현해도 되지만, 아래 처럼 사용할 수도 있습니다.

ContextThemeWrapper contextThemeWrapper = 
new ContextThemeWrapper(getActivity(),
android.R.style.Theme_DeviceDefault_Light_Dialog);
LayoutInflater inflater = getActivity()
.getLayoutInflater()
.cloneInContext(contextThemeWrapper);

Context에 원하는 theme을 부여하여 inflate하는 예제입니다. 아래처럼 AlertDialog에 적용할수도 있겠지요.
AlertDialog.Builder builder = 
new AlertDialog.Builder(contextThemeWrapper);


Conclusion

이상으로 올바른 Context를 부르는 방법에 대해서 간략하게 알아봤습니다. 우리의 궁극적인 목표는 적절한 Context를 사용함으로써 불필요한 reference를 줄여 메모리 누수를 방지하는 것입니다. 

'programming > android' 카테고리의 다른 글

View - Widget  (0) 2018.08.23
Companion Objects in Kotlin  (0) 2018.04.08
기본적인 메모리 관리  (0) 2018.03.06
Splash 화면 구성하기  (3) 2018.02.20
Kotlin Coroutines #2  (0) 2018.01.28

+ Recent posts