Context

Posted by Eun JongHyeok on February 12, 2021
  1. Context란
    1. Application Context와 Activity Context
    2. 리소스 정보
    3. Acitivity Context를 사용해야 하는 경우
    4. 메모리 누수와 생명 주기
    5. Reference

아마 대부분의 사람들이 안드로이드를 처음 배우면서 토스트메시지를 생성할 때 매개변수로 컨텍스트를 넘기는 것을 알게되었을 것입니다.

이번에는 이 컨텍스트에 대한 내용들을 정리하고자 합니다.

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.

컨텍스트는 안드로이드 시스템이 제공하는 어플리케이션 환경에 대한 전역 정보 인터페이스이고 어플리케이션별 리소스, 클래스에 대한 접근뿐만 아니라 액티비티 런칭, 브로드 캐스팅 인텐트 리시브와 같은 어플리케이션 레벨의 동작 호출을 허용해줍니다.

솔직히 정의만 들어보면 감이 잘 잡히지 않습니다.

Application Context와 Activity Context

Application과 Acitivty는 둘 다 Context를 부모 클래스로 두고 있습니다. 추가로 Service도 Context를 부모 클래스로 두고 있지만 Application과 Activity 위주로 설명하겠습니다.

리소스 정보

이름에서도 알 수 있듯이 각각 Application에 대한 내용과 Activity에 대한 내용을 담고 있습니다. 예시로 간단하게 각 컨텍스트가 가지고 있는 리소스들을 비교해보겠습니다. Application에서 text의 색은 파란색, Activity에서 text의 색은 빨간색으로 설정합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    <!-- AndroidMenifest.xml에서 -->
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.ApplicationContext">
        <activity
            android:name=".MainActivity"
            android:theme="@style/Theme.ActivityContext">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <activity android:name=".TestActivity"></activity>
    </application>

    <!-- themes.xml에서 -->
    <!-- Base application theme. -->
    <style name="Theme.KotlinTest" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/white</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_700</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
    </style>

    <style name="Theme.ApplicationContext" parent="Theme.KotlinTest">
        <item name="android:textColor">#0000FF</item>
    </style>

    <style name="Theme.ActivityContext" parent="Theme.KotlinTest">
        <item name="android:textColor">#FF0000</item>
    </style>

이를 액티비티에서 호출해주면

1
2
3
4
5
6
7
8
9
10
11
    var applicationCtxTV = TextView(applicationContext)
    applicationCtxTV.textSize = 40F
    applicationCtxTV.text = "Application Context"

    var activityCtxTV = TextView(this)
    activityCtxTV.textSize = 40f
    activityCtxTV.text = "Activity Context"

    var layout : LinearLayout = findViewById(R.id.layout)
    layout.addView(applicationCtxTV)
    layout.addView(activityCtxTV)

a capture

각 리소스의 색으로 그려지는 것을 확인 할 수 있습니다.

Acitivity Context를 사용해야 하는 경우

Dialog를 띄우거나 액티비티를 실행시키는 경우, Layout을 inflate하는 경우 Application Context를 사용할 수 없습니다.

주로 Dialog를 띄울 때 Application Context를 사용할 경우

Caused by: android.view.WindowManager$BadTokenException: Unable to add window – token null is not valid; is your activity running?

다음과 같은 에러가 뜨게 되는데 이는 Application Context에는 WindowManager에 대한 정보가 없기 때문입니다.

추가로 참고하면 좋아보이는 글입니다.
https://nhancv.medium.com/android-show-dialog-without-activity-context-94661d48400f

액티비티를 실행하거나 Layout을 inflate 시에는 해결책이 존재합니다.

1
2
    var intent = Intent(applicationContext, TestActivity::class.java)
    applicationContext.startActivity(intent)

어플리케이션 컨텍스트를 이용하여 액티비티를 실행하는 경우에도 다음과 같은 에러 메시지가 출력됩니다.

Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

1
2
    var intent = Intent(applicationContext, TestActivity::class.java)
    applicationContext.startActivity(intent.addFlags(FLAG_ACTIVITY_NEW_TASK))

액티비티 컨텍스트 밖에서 startActivity를 호출할 때는 해당 플래그가 필요하다는 의미이며 이는 서비스에서 액티비티를 실행시킬 때도 동일하게 필요합니다.

Layout을 inflate하는 경우에도 문제가 발생하므로 추가 작업이 필요합니다.

1
    applicationContext.getSystemService(Service.LAYOUT_INFLATER_SERVICE);

메모리 누수와 생명 주기

이렇게 보면 항상 Activity Context를 사용하는 것이 좋아보이지만 개발자 사이트에서는 Application Context 사용을 권장하고 있습니다. 이는 생명주기와 관련이 있습니다. Activity는 결국 Context의 자식 클래스이며 자신도 Context이므로 Activity가 종료되면 Context도 사라지게 됩니다. 하지만 Application Context는 앱이 종료될 때까지 사라지지 않으므로 메모리 누수 등을 방지할 수 있습니다.

싱글턴 패턴이나 static 변수를 사용하는 경우가 대표적입니다. 만일 액티비티가 종료되고도 Activity Context가 참조되고 있을 경우 메모리 누수가 일어나게 됩니다. 이 경우에는 반드시 onDestory()에서 해제를 해주어야 합니다.

static 뿐만 아니라 백그라운드 작업, 쓰레드 작업, Event Handling에서도 도중에 종료될시 Activity Context를 참조하고 있을 경우 메모리 누수가 발생할 수 있으므로 액티비티 종료시 해제를 직접 해주어야 합니다.

Reference

https://developer.android.com/reference/android/content/Context
https://www.youtube.com/watch?v=-e6lyXIkIl0
https://www.charlezz.com/?p=1080
https://yonoo88.tistory.com/1057
https://aroundck.tistory.com/263
https://stackoverflow.com/questions/7043240/get-a-layout-inflater-from-the-applicationcontext/7045031
https://medium.com/swlh/context-and-memory-leaks-in-android-82a39ed33002


Context
Application
Activity

← Previous Post Next Post