정상에서 IT를 외치다

[Android, Context] Context 넌 무엇이더냐? 본문

안드로이드

[Android, Context] Context 넌 무엇이더냐?

Black-Jin 2019. 7. 2. 15:40
반응형

안녕하세요. 블랙진입니다.


넌 무엇이더냐? 세번째 시리즈로 이번에는 Context에 대해 알아보겠습니다.


지난 시리즈


Thread 넌 무엇이더냐?

Handler 넌 무엇이더냐?



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.


구글문서 / code


Context는 어플리케이션 환경에 관한 전체 정보를 받을 수 있는 안드로이드 시스템에 의해 제공되어지는 추상 클래스 입니다. 그렇기 때문에 Context가 없으면 엑티비티를 시작할 수도, 브로드캐스트를 발생시킬 수도, 서비스를 시작할 수도 없습니다. 또한 리소스에 대한 접근도 Context를 통해서만 가능합니다.




ContextWraper?



Proxying implementation of Context that simply delegates all of its calls to another Context. Can be subclassed to modify behavior without changing the original Context.


구글문서 / code


Context의 대리인(프록시)으로 원본 Context를 변화시키지 않고 수정할 수 있게 해주는 서브클래스입니다. 보호 프록시 패턴 혹은 어댑터 패턴 으로 구현되어 ContextImpl 의 변수를 노출하지 않고 ContextWrapper 에서는 ContextImpl 의 공개 메서드만 호출하게 합니다.




ContextImpl


ContextWrapper 생성자에 전달되는 아규먼트가 ContextImpl 입니다. 

ContextImpl는 앱에서 직접 사용할 수 있는 클래스가 아니므로 소스 코드로만 보실 수 있습니다.

Context와 컴포넌트 사이에 위치한게 ContextWrapper입니다. 



ContextWrapper에는 Context의 여러 메소드를 구현한 ContextImpl 인스턴스를 전달 받습니다.


다시 말하지만 ContextWrapper 는 ContextImpl 에서 구현한 함수를 중간에서 일부만 공개해 주거나 수정을 합니다. (디자인 패턴 중 보호 프록시, 혹은 어댑터 역활을 합니다.) 그렇게 함으로서 컴포넌트(Acitvity, Service, Application)들이 ContextImpl을 직접 상속해서 Context 메소드를 사용하는게 아니라 ContextWrapper 을 중간에 두어 Context 메소드를 사용하게 합니다. 이는"상속보다는 구성을 사용하라" 는 객체 지향의 원칙을 준수하기 위함이죠.




Context 의 3가지 모습


안드로이드에는 4가지 컴포넌트 Acitivty, Service, BroadCastReceiver, Content Provider 가 있습니다. 이 중 Activity, Service 그리고 Application 만이 ContextWrapper를 상속하고 있습니다.(BroadCastReceiver, ContentProvide는 Context를 상속하고 있지 않습니다.)


Acitvity, Service, Application 컴포넌트는 각각 ContextImpl을 생성하고 ContextWrapper에서 getBaseContext() 와 getApplicationContext() 메서드를 사용해 Context를 가지고 옵니다. 여기서 Application 인스턴스는 앱에서 1개 밖에 없으므로 동일한 인스턴스를 반환합니다.


우리는 Acitivty 에서 3가지 방법으로 Context를 가져옵니다.


1. Activity 인스턴스 자신(this)

2.getBaseContext()를 통해 가져오는 ContextImpl 인스턴스

3.getApplicationContext()를 통해 가져오는 ApplicationContext


이 세 개의 인스턴스는 모드 다른 인스턴스 입니다. 


getBaseContext()로 가져온 것은 Activity로 캐스팅하면 ClassCaseException이 발생합니다. 

물론 AppicaionContext 또한 Activity로 캐스팅하면 에러가 발생합니다.


그럼 View 클래스의 생성자에 있는 Context는 어디서 온 건지 확인해 보겠습니다.


val textView = findViewById<TextView>(R.id.textView)
Dlog.d("${textView.context == this}") - 1
Dlog.d("${textView.context == baseContext}") - 2
Dlog.d("${textView.context == applicationContext}") - 3


위 예제는 setContextView(R.layout.activity_main) 에서 있는 textView 입니다. 1번만 true가 나오는 것을 확인할 수 있습니다. View 클래스는 생성자에 Context가 전달 되어야 하는데 Activity에 쓸 수 있는 3가지 모두 다 전달 가능하다.  그러나 View와 연관이 깊은 것은 Acitvity 이므로 Activity가 전달된 것으로 확인할 수 있습니다..


val textView = TextView(baseContext)
Dlog.d("${textView.context == this}") - 1
Dlog.d("${textView.context == baseContext}") - 2
Dlog.d("${textView.context == applicationContext}") - 3


근데 위와 같은 코드로 Activity에서 직접 생산해 주면 어떻게 될까요? 당연히 위 코드는 2번에서만 true가 나옵니다. 물론 applicationContext로 TextView를 만들면 3번이 true가 나옵니다.


이부분에서 좀더 나아가 재밌는 부분을 확인할 수 있었습니다. setContextView로 등록된 textView의 context는 Acitivty인 것이죠? 그럼 Adapter 에서도 똑같이 적용될까요?


Dlog.d("holder.textView : " + (holder.textView.getContext() instanceof Activity)); - 1
Dlog.d("holder.itemView : " + (holder.itemView.getContext() instanceof Activity)); - 2


이번에는 Adaoter 에서 holder에 있는 view 들의 context 값이 Activity 인지 확인해 보았습니다.  위 예상대로라면 결과는 1,2번 모두 true가 나와야겠죠? 근데 희한하게도 여기서 안드로이드 버전에 따라 결과가 달랐습니다.


4.4(API 19) 에서는 1번은 false가 나오고 2번은 true가 나옵니다. API 19 상위 버전에서는 1,2번 둘다 true 값이 나옵니다. 왜 여기서는 API 버전에 따라 결과가 다르게 나오는 건지는 찾아볼려고 해도 찾을 수가 없더라구요... 혹시 아시는 분은 댓글 부탁드리겠습니다.


holder.textView.setOnClickListener(v -> {
Activity activity = (Activity)
holder.textView.getContext();
activity.startActivity(new Intent(activity, DetailActivity.class));
});


그래서 위 코드와 같이 adapter 에서 holder의 itemView가 아닌 특정 view를 Activity로 캐스팅해서 사용하게 되면 API 19 이하 버전에서는 ClassCaseException 이 발생합니다. 예전에 위와 같이 코드를 작성하다가 킷캣에서 앱을 터트려본적이 있다는.....



Context의 올바른 사용


ApplicationContext는 싱글톤으로 프로세스에서 1개만 존재합니다. 싱글톤과 같은 오프젝트를 초기화 할 때 혹은 전역적으로 사용할 레퍼런스를 초기화 할 때 사용하면 좋다. 반면에 Context는 라이프 사이클을 잘 생각해야 합니다. 만약 싱글톤에 Acitvity의 Context를 사용하면 Activity가 Destory 되어도 메모리에서 제거되지 않습니다. 이는 GC의 대상이 되기 위에서는 모든 객체의 참조가 null이 되어야 하기 때문입니다. Acitivty는 파괴되었지만 싱글톤에 참조되어 있기 때문에 제거되지 않고 이는 메모리릭으로 직결됩니다. 따라서 컴포넌트의 라이프 사이클 안에서만 사용할 경우 Context를 쓰고 전역적으로 사용할 경우에는 ApplicationContext를 사용하자!!.



<참고자료>


Understanding Context In Android

Why use ContextImpl

안드로이드 프로그래밍 Next Step (책)

반응형
Comments