-
안드로이드 Context안드로이드 학습/Android 기술면접 대비 2023. 6. 16. 10:55
차례: 1. 안드로이드 context란 2. Context가 필요한 이유 3. Application Context & Activity Context 4. Context 얻는 방법
1. Android Context 란
- 어플리케이션의 현재 상태를 갖고 있음.
- Activity와 Application의 정보를 얻기 위해 사용
- Context는 시스템 서비스에서 제공하는 API (Resource, Database, Shared preference) 등의 시스템 자원에 접근할 수 있게 해줌.
- Context는 새로 생성된 객체가 지금 어떤 일이 일어나고 있는지에 대한 정보(어플리케이션 패키지 이름, 리소스 정보) 를 알 수 있도록 합니다. (예 : getPackageName(), getResource())
- Activity, Application class는 Context class를 상속받은 클래스. Context는 여러 컴포넌트의 상위 class다.
2. Context가 필요한 이유
다른 플랫폼에서는 직접적으로 시스템에 접근하는 방법이 있는데 안드로이드는 어플리케이션과 프로세스가 서로 독립적이어서 어플리케이션을 따로 관리된다. 예를 들어, Service나 Broadcast Receiver가 어플리케이션 실행 여부와 무관하게 백그라운드에서 실행 가능하다.
이런 특성으로 인해서 프로세스와 어플리케이션은 따로 관리된다. 프로세스는 운영체제가 관리를 하고, 어플리케이션은 ActivityManagerService가 관리합니다. ActivityManagerService는어플리케이션들을 key-value로 묶어서 관리 중이다
Context는 자신이 어떤 어플리케이션인지 나타내고 있는 ID의 역할을 하고 ActivityManagerService에 접근할 수 있도록 한다. 그래서 Context가 필요하다.
요약하자면, 안드로이드에서는 어플리케이션과 프로세스 독립적이고. 어플리케이션 정보를 가져오려면 context 이용해 어플리케이션을 관리하는 ActivityManagerService 접근해야 되서 context 가 필요함
ContextWrapper와 ContextImpl
Context는 추상 클래스이기 때문에 이를 사용하기 위해서는 구현체가 있어야 한다. 기본 구현체는 ContextImpl이고, 이 구현체는 사용자에게 노출하지 않고 ContextWrapper로 감싸져 있다.
ContextImpl
ContextWrapper 생성자에 전달되는 argument가 ContextImpl.
추상클래스인 Context를 실제 구현한 클래스로 앱에서 직접 사용할 수 있는 클래스는 아니다.
소스코드는 공개되어 있어서 확인할 수 있습니다. ContextImpl은 안드로이드 시스템에서 제공받을 수 있습니다.
ContextWrapper
ContextWrapper는 Context를 직접 상속한 클래스로 이름처럼 Context를 래핑하여 Context와 관련된 호출을 모두 담당하여 처리하게 됩니다.
더보기public class ContextWrapper extends Context { public ContextWrapper(Context base) { throw new RuntimeException("Stub!"); } protected void attachBaseContext(Context base) { throw new RuntimeException("Stub!"); } public Context getBaseContext() { throw new RuntimeException("Stub!"); } public AssetManager getAssets() { throw new RuntimeException("Stub!"); } public Resources getResources() { throw new RuntimeException("Stub!"); } public PackageManager getPackageManager() { throw new RuntimeException("Stub!"); } public ContentResolver getContentResolver() { throw new RuntimeException("Stub!"); } public Looper getMainLooper() { throw new RuntimeException("Stub!"); } public Executor getMainExecutor() { throw new RuntimeException("Stub!"); } public Context getApplicationContext() { throw new RuntimeException("Stub!"); } .... 생략 ... }
ContextWrapper의 클래스는 ContextWrapper(Context base) 생성자를 가지고 있습니다. 생성자와 attachBaseContext 메소드에서는 Context를 파라미터로 받고 있는데 이 때 대입되는 것은 Context의 구현체인 ContextImpl 인스턴스가 전달됩니다.
그렇게 해서 ContextWrapper는 ContextImpl의 메소드를 대신 호출해주는 역할을 하게 됩니다.
안드로이드 Activity를 상속한 부분을 따라가다 보면 ContextWrapper와 Context를 만나게 된다. Context는 추상 class 였고 ContextWrapper는 여러가지 메소드를 가지고 있었다.
7번 정도 타고 들어가야 context를 만났다. 왜 Activity가 Fragment에 비해 무겁다는지 이해가 된다....
더보기class MainActivity : AppCompatActivity()
public class AppCompatActivity extends FragmentActivity
public class FragmentActivity extends ComponentActivity
public class ComponentActivity extends androidx.core.app.ComponentActivity
public class ComponentActivity extends Activity
public class Activity extends ContextThemeWrapper
public class ContextThemeWrapper extends ContextWrapper
public abstract class Context
3. Application Context & Activity Context
Context 는 크게 두 가지로 나눌 수 있다.
- Application Context
- Activity Context
Application Context :
- Context 는 application lifecycle과 묶여있어, 현재 Context 가 종료되고 나서도 Context 가 필요한 작업이나, 액티비티 범위를 벗어난 곳에 Context 가 필요한 작업에 적합하다.
- 예제 1 : Activity 안에서 라이브러리를 초기화해야 하는 경우 => Application Context 를 전달하여 사용
- 예제 2: 어플리케이션 내에 싱글톤 객체를 만드려고 하는데 이 객체가 Context 를 필요로 할 때, Application Context 를 사용하면 된다. 만약 이런 상황에 Activity Context 를 넘겨주게 되면, Activity 에 대한 참조를 메모리에 남겨두며 GC (Garbage Collected) 되지 않은 채 분명 메모리 릭이 발생할 것이다.
- 예제 3: 어플리케이션 전역에서 사용할 어떤 라이브러리를 MainActivity 에서 초기화 할 때 Context 가 필요하다면 Application Context 이다. 만일 Activity Context 를 넘겨주게 되면, MainActivity 에 대한 참조가 메모리 상에서 GC 되지 않아 메모리 릭이 발생하기 때문이다. 오랫동안 지속되거나 앱 전역에서 사용될 녀석의 경우 Application Context 를 넘겨주면 된다.
메모리 릭???
- 어플리케이션은 동작을 위해 메모리가 필요합니다. 어플리케이션이 사용이 끝난 메모리를 반환하지 않는 것을 메모리 릭이라고 합니다. 보통 릭이 발생하면, 사용한 메모리는 반환하지 않은 채 추가로 필요한 메모리를 시스템에 요청하기 때문에 사용하는 메모리 양이 계속 증가합니다.
- 이 과정이 계속되면, (1) 버벅거림 (2) OOM(Out Of Memory)를 발생시키고 어플리케이션이 강제 종료후 App에 할당되었던 메모리가 회수
GC (Garbage Collected) ???
- GC는 더이상 사용되지 않는 쓰레기 객체들을 정리하는 작업을 한다.
- GC는 필요에 따라 자동으로 실행된다.
Room DB 예제 :
더보기Room Database
@Database(entities = [User::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao companion object { private var instance: AppDatabase? = null @Synchronized fun getInstance(context: Context): AppDatabase? { if (instance == null) { synchronized(AppDatabase::class) { instance = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "user-database" ) .build() } } return instance } } }
MainActivity.kt
db = AppDatabase.getInstance(applicationContext)!!
RoomDB를 전역적으로 사용하기 때문에 싱글톤 패턴형식으로 만들고 application context를 넘겨줬다.
Activity Context :
- 해당 Context 는 액티비티 안에서만 사용 가능하다. 특정 Activity 의 라이프 사이클에 종속되어 있다. 이 녀석은 Activity 스코프 내에서 사용될 때 넘겨주거나, Activity 와 라이프사이클이 같은 객체를 생성할 때 넘겨준다. 즉, Activity 가 소멸되면 해당 Context 도 같이 소멸되는 것이다.
- 예제 1: Toast , Dialog 등의 UI 동작에 있어 Context 가 필요하다면 Activity Context 사용. 해당 UI 컴포넌트들은 Activity의 라이프 사이클에 종속되는 것들이기 때문에, Activity Context 를 사용해주면 된다.
Application Context 를 사용하면 안되는 경우
- Application Context 는 Activity Context 가 지원하는 모든 것을 지원하지 않는다.
- 예를들어, Application Context 를 활용하여 AlertDialog 를 show() 하게 되면 오류가 발생한다.
- Activity 는 Garbage Collection 이 가능하지만, Application 은 앱 프로세스가 살아있는 동안 계속하여 남아있다. 메모리 릭의 발생을 막기 위해 상황에 따라 메모리 할당 해제 필요.
AlertDialog 예제 :
더보기아래의 예처럼 Alertdialog를 추가시키면 에러가 발생한다.
val dialog = MyDialog(activity?.applicationContext) dialog.setCanceledOnTouchOutside(false) dialog.show()
Caused by: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
AlertDialog를 생성할 때 getApplicationContext()를 사용하는 대신에 Activity의 인스턴스인 this를 사용하는 것이 권장됩니다. 이는 AlertDialog가 현재 활성화된 Activity의 컨텍스트와 관련된 UI 작업을 수행할 수 있도록 해주기 때문입니다.
AlertDialog를 만들 때 getApplicationContext()를 사용하는 경우, AlertDialog는 어플리케이션의 전역 컨텍스트를 사용하게 됩니다. 하지만 어플리케이션 컨텍스트는 Activity의 생명주기와 관련이 없으며, UI 작업을 수행할 수 있는 환경이 제한되어 있습니다. 따라서 getApplicationContext()를 사용하여 AlertDialog를 생성하면 다음과 같은 문제가 발생할 수 있습니다:
- UI 리소스에 접근 불가: AlertDialog가 UI 리소스에 접근할 필요가 있을 때, 어플리케이션 컨텍스트는 해당 리소스에 접근할 수 없으므로 오류가 발생할 수 있습니다.
- 화면 갱신 문제: AlertDialog가 Activity의 컨텍스트와 연결되어 있지 않기 때문에, Activity의 화면 갱신 주기와 동기화되지 않을 수 있습니다. 이는 화면 갱신이나 상태 변화를 정확하게 반영하지 못하거나, UI와 관련된 다른 문제를 일으킬 수 있습니다.
- 메모리 누수: AlertDialog가 Activity보다 더 오래 지속될 경우, 어플리케이션 컨텍스트에 대한 참조로 인해 메모리 누수가 발생할 수 있습니다. 이는 액티비티가 종료되었음에도 불구하고 메모리에 유지될 수 있는 문제를 초래할 수 있습니다.
이러한 문제들 때문에 AlertDialog에 application context를 넣는 것을 제한하는 것 같다.
4. Context 얻는 방법 :
ActivityName.this
- Activity Context를 반환한다. Activity Scope 안에서 사용할 때는 this만으로도 가져올 수 있다.
getApplicationContext()
- Application Context를 반환한다.getApplication()
- Application 인스턴스를 반환한다.getBaseContext()
- ContextWrapper의 Context 인스턴스를 반환한다.View의 getContext()
- View에도 getContext() 메서드가 있어 Context를 가져올 수 있는데, View를 생성할 때 생성자의 인자로 들어가는 Context가 getContext()의 결과로 반환된다.'안드로이드 학습 > Android 기술면접 대비' 카테고리의 다른 글
Intent (인텐트) 와 Bundle (0) 2023.07.11 AAC - Databinding (0) 2023.06.21 Jetpack - AAC (0) 2023.06.13 Activity와 Fragment 그리고 생명주기 (0) 2023.06.12 ANR이란 (0) 2023.06.12