Android 앱을 "한 번" 이라도 해본 사람은 Context 를 보지 못했을 수가 없습니다.
저는 Context 를 사용할 때, Context 가 대체 뭐야? 하고 상위클래스를 따라가보다가 수천줄에 기겁하고, 닫아버렸던 기억이 있네요.
그렇게 얕게 Context 가 무엇인지 대충 블로그를 통해 학습하고, 넘겼었습니다.
이번엔 그런식으로 넘기는게 아니라, 다시 한 번, Context 에 대하여 분석을 해볼까 합니다.
Android Reference 에 나온 설명
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.
애플리케이션 환경에 대한 글로벌 정보에 대한 인터페이스입니다. Android 시스템에서 구현을 제공하는 추상 클래스입니다. 이를 통해 애플리케이션 별 리소스 및 클래스에 액세스 할 수있을뿐만 아니라 활동 시작, 인 텐트 브로드 캐스트 및 수신 등과 같은 애플리케이션 수준 작업에 대한 상향 호출도 가능합니다.
응 블로그 열면 거의 인삿말이이였지, 그래서?
1. Context 는 글로벌 정보에 대한 인터페이스입니다. (Context 는 추상클래스입니다.)
2. Context 는 Android 시스템에서 구현을 제공합니다.
일단 이 2가지에 대한 설명을 기억한채로 이어가겠습니다.
일단, 우리에게 익숙한 클래스인 Application 과 Activity, 그리고 Service 를 보였습니다.
Android Component
- Activity : Context 의 하위 클래스
- Service : Context 의 하위 클래스
- Broadcast receiver : public abstract class BroadcastReceiver { ... }
- Content provider : public abstract class ContentProvider implements ContentInterface, ComponentCallbacks2 {...}
위에 작성한 것처럼, Android Component 중에서는 Activity 와 Service 만 Context 의 하위 클래스입니다.
아, 그렇다면 Context 가 최상위에 존재하고, Application , Activity, Service 들은 하위 클래스 이므로, 서로 다른 Context 가 생성되겠구나!
까지만 생각하지 마시고, 우리는 좀더 깊게!!
Application 의 Context 는 Singleton 입니다.
즉, 1개의 앱은 1개의 Application Context 만을 갖고 있습니다.
Activity 와 Service 의 Context 는 생성될 때마다 각자의 새로운 Context 를 갖고 있습니다.
이해를 돕기 위하여 바로 다음단계를 작성하겠습니다.
LifeCycle 에 따른 사용 가능한 Context
위 그림처럼 Context 는 LifeCycle 과 함께 존재합니다.
자, 이제 우리는 getApplicationContext(), getBaseContext(), this 등으 로 아무 생각없이 사용하던 Context 가 어느 곳에 있는 Context 를 사용하고 있는 것인지 생각할 수 있게 되었습니다.
Context 를 사용하는 코드로 이 글의 결론에 한발짝 다가서봅니다.
1. Activity 에 선언된 View 의 Context 는 누구일까?
public class MainActivity extends Activity {
private final String TAG = MainActivity.class.getSimpleName();
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.tvTitle);
if (textView.getContext() == getApplicationContext()) {
Log.d(TAG,"Application Context");
}
if (textView.getContext() == this) {
Log.d(TAG,"Activity Context");
}
}
}
위 코드처럼 작성한 View 는 Activity Context 를 전달받게 됩니다. 따라서 View 의 Context 는 Activity Context 와 동일합니다.
2. CustomView 를 만들어서 xml 에 직접 추가하는 경험을 하신 분들도 계실겁니다. 그렇다면 View 를 직접 만들 땐..?
public class MainActivity extends Activity {
private final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView1 = new TextView(this);
TextView textView2 = new TextView(getApplicationContext());
if (textView1.getContext() == getApplicationContext()) {
Log.d(TAG,"textView1 : Application Context");
}
if (textView1.getContext() == this) {
Log.d(TAG,"textView1 : Activity Context");
}
if (textView2.getContext() == getApplicationContext()) {
Log.d(TAG,"textView2 : Application Context");
}
if (textView2.getContext() == this) {
Log.d(TAG,"textView2 : Activity Context");
}
}
}
이 상황에서 문제가 발생합니다.
현재 코드의 이상향은, MainActivity 가 종료되고 다른 Activity 가 실행된다고 하면, MainActivity 에 있는 객체들은 GC 의 대상이 되어야 합니다.
그러나 textView2 같은 경우는 Application Context 를 사용하여 만들어졌고, Application Context 를 참조하고 있습니다.(실행결과)
그렇다면 textView2 는 GC 대상에서 제외됩니다. 이런 경우 메모리 릭이 발생합니다..
*메모리 릭 : 메모리 누수(memory leak) 현상은 컴퓨터 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 현상이다. (wiki)
유사 케이스로 아래처럼 View 를 static 으로 선언하여 사용하려는 경우에도 메모리 릭이 발생합니다.
(하지만, 이 경우는 경고 메시지를 통해서 '아 static 쓰지 말라는구나' 하고 지워버리기 때문에 그냥 넘어가는 분들도 계실겁니다.)
3. View 만 조심하면 되는데, 그럼 끝난거 아닌가?
public class MyClass {
private static MyClass instance;
private Context mContext;
public static MyClass getInstance(Context context) {
if(instance == null) {
instance = new MyClass(context);
}
return instance;
}
private MyClass(Context context) {
mContext = context;
}
public String toString(Context context) {
StringBuilder sb = new StringBuilder();
sb.append("MyClass: ");
if(context == mContext) {
sb.append("True");
} else {
sb.append("false");
}
return sb.toString();
}
}
위와 같이 context 를 전달받는 싱글톤 객체가 있다고 가정하겠습니다.
public class MainActivity extends Activity {
private final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, MyClass.getInstance(this).toString(this));
startActivity(new Intent(MainActivity.this, NextActivity.class));
finish();
}
}
public class NextActivity extends Activity {
private final String TAG = NextActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, MyClass.getInstance(this).toString(this));
}
}
그리고 이렇게 실행한 결과를 보도록 하겠습니다.
MainActivity 에서는 MainActivity 의 Context 와 싱글토 객체의 Context 가 일치합니다.
MainActivity 의 Context 를 전달해 주었기 때문이죠..
그러나 NextActivity 에서는 false 가 출력됩니다.
NextActivity 의 Context 는 MainActivity 의 Context 와 다르기 때문이죠.
그럼 Application Context 랑 비교하면?
저희는 위에서 공부했듯이 당연히 false , false 가 출력될 것이라는 것을 알 수 있습니다.
그렇다면, 이 코드의 문제는?
역시나, MainActivity 의 Context 가 사라지지 않는다는 것입니다.
싱글톤 객체는 어짜피 유지되어야 하지 않나요?
우리는 싱글톤 객체가 유지되기를 원한것이지, MainActivity의 Context 까지 유지되기를 원한 것이 아닙니다.
만약 싱글톤 객체에서 Context 의 기능이 필요하다면 Application Context 를 사용하는게 메모리릭을 방지 할 수 있겠죠?
Application Context 는 앱에서 단 한개만 존재하고, 그렇다는 것은 어디서 비교해서 true true 가 나오겠죠?
오늘의 결론
1. Application Context 는 실행이후 단 1개만 존재하며, Application 의 LifeCycle 과 함께 해야 한다.
2. Activity Context 는 생성되는 경우에 따라 여러개 존재하며, Activity 의 LifeCycle 과 함께 해야 한다.
3. 만약 Activity LifeCycle 과 함께하는 데이터가 Application Context 를 사용한다면, 그것은 곧 메모리 릭으로 이어진다.
* Context 사용시 LifeCycle 을 고려하여 작성하자!
이 객체는 Activity 에서 사용하니까 Activity Context 를 써야지 라고 생각하면 안된다!!
객체의 LifeCycle 을 고려해야 한다!! Static 변수를 조심하자!!
메모리 릭이 계속 발생한다면, 정상적으로 동작하던 앱을 끄지 않고 이것저것 사용하는 도중에 갑자기 꺼질 수도...
'BackUp (관리중지) > Android 이론' 카테고리의 다른 글
Android Service (0) | 2021.05.14 |
---|---|
Android Coroutine [코루틴] (0) | 2021.05.04 |
[Android Jetpack] LiveData란, (0) | 2021.04.21 |
Fragment 와 Fragment LifeCycle 분석 (0) | 2021.04.19 |
Activity 와 Activity LifeCycle 분석 (0) | 2021.04.15 |