JAVA에 대한 기초 지식이 없는 상태에서 안드로이드 앱 개발자가 되고자 무작정 코틀린을 공부하고 있는데 아무리 코드를 작성하고 구글링 해 봐도 Context와 this의 차이를 명확하게 구분짓기가 애매해서 정리라도 해 보고자 글을 작성한다.
먼저 결론부터 말하자면.. context를 얻는 방법으로는 getApplicationContext( ), getContext( ), getBaseContext( )가 있고, Context를 확장한 class의 경우 this로도 얻을 수 있다는 것이다.....(?)
* Context란 ?
Android Developer 공식 문서에 의하면 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.
* 해석하자면..
1. Abstract class (추상 클래스)이며, 실제 구현은 Android System이 담당하고 어플리케이션의 자원이나 클래스에 접근할 수 있게 한다.
2. Context를 통해 Application에 특화된 리소스나 클래스에 접근할 수 있을 뿐 아니라, 추가적으로 Application Level의 작업 (Activity 실행, Intent BroadCasting, Intent Receiver(수신) 등)을 하기 위한 API를 호출 할 수도 있다.
그러니까 한번 더 요약하면 아래와 같다는거다.
1. Application에 관하여 시스템이 관리하고 있는 정보에 접근하기
- getPackageName( ), getResource( ) 등이 대표적 (대부분 get이라는 접두어로 시작..)
2. Android System 서비스에서 제공하는 API를 호출할 수 있는 기능
- startActivity( ), bindService( ) 등
언제나 그렇듯 공식 문서는 마치 수학의 정석과도 같은 존재여서 개발자가 한번에 이해하기는 조금 어려운 편인 것 같다..
대부분 코딩을 할 때 정의어들은 가독성을 위해 사전적 의미를 그대로 가져오는 경우가 많기 때문에
단어 그대로 해석 해 보면 아래와 같다.
Con-Text
1. (어떤 일의) 맥락, 전후 사정
2. (글의) 맥락, 문맥
안드로이드의 context 역시 위의 뜻과 같다. Application이나 객체의 현재 상태를 나타 내 주는 역할을 한다고 한다.
방금 막 생성된 객체는 현재 자신이 위치한 환경 (ex : 액티비티 / 어플리케이션)이 어떤 곳인지 대략적으로 알 필요가 있다.
=> "나는 현재 어디에 위치 해 있고, 다른 객체들과 어떻게 소통해야 하는거지?"
즉, 이름에서부터 알 수 있듯이 어플리케이션 / 객체의 현재 상태에 대해 나타낸 것이 Context이다.
새로 생성된 객체가 무슨일이 일어나고 있는지 이해할 수 있도록 하고, 일반적으로 프로그램의 다른 부분(활동, 패키지, 응용프로그램 등)에 대한 정보를 얻기 위해 호출한다.
이럴 때 Context를 사용하는 것이다.
* Context가 굳이 꼭 필요할까 ?
Context의 기능은 대충 알았다 치고, 그래서 굳이 꼭 필요한가를 짚고 넘어가고 싶다.
실무 경험이나 실제 서비스 앱을 운영해 본 경험이 전무했기 때문에.. 구글링에 의해 찾아 본 이유는 아래와 같다고 한다..
전역적인 어플리케이션 정보에 접근하거나 어플리케이션 연관된 시스템 기능을 수행하기 위해, 시스템 함수를 호출하는 일은 안드로이드가 아닌 다른 플랫폼에서도 늘상 일어나는 일이다. 또, 그런 일들은 대게의 경우 어떠한 매개체를 거칠 필요 없이, 직접적으로 시스템 API를 호출하면 된다.
반면, 안드로이드에서는 Context라는 Instance화 된 매개체를 통해야만 유사한 일들을 수행할 수 있다.
안드로이드가 아닌 플랫폼에서는 일반적으로 어플리케이션에서 프로세스가 아주 긴밀하게 연결되어있고, OS 커널이 프로세스를 관리한다. 특정 프로세스가 특정 어플리케이션과 맵핑 된다면, 별 다른 매개체 없이 시스템에 직접 프로세스의 정보에 관해 물어볼 수 있고 프로세스와 연관된 시스템 함수를 호출할 수 있는 것이다. (이미 관리되고 있는 정보에 대해 물어보는 것이기 때문에)
그런데 Android 에서는 Application <-> Process 간의 관계가 특이하게도 서로 독립적으로 존재한다.
이것에 관련된 구체적인 내용은 구글 개발자 블로그 포스트를 최대한 참고하였다.
약간만 요약하자면 안드로이드 플랫폼에서는 프로세스가 없는 상황에도 어플리케이션은 살아있는 것 처럼 사용자에게 표시되기도 하고, 메모리가 부족한 상황이 될 경우 작동중이던 프로세스가 강제로 종료되며 대신에 해당 프로세스에서 작동중이던 어플리케이션에 관한 일부 정보만 별도로 관리한다. 이후에 메모리 공간이 확보되면 저장되어 있던 어플리케이션 정보를 바탕으로 새로운 프로세스를 시작하는 등의 동작이 벌어진다고 한다.
안드로이드에서도 역시 프로세스는 OS커널(리눅스) 에서 관리된다. 어플리케이션과 프로세스가 별도로 관리되고 있다면 어플리케이션 정보는 어디에서 관리하고 있는걸까?
=> 바로 Android System Service 중 하나인 ActivityManagerService 에서 이것에 대한 책임을 진다.
그렇다면 또 ActivityManagerService는 어떤식으로 어플리케이션을 관리하고 있는걸까?
=> 의외로 단순한 것이, 특정 Token을 키 값으로 하여 'Key - Value' 쌍으로이루어진 배열을 이용해 현재 작동중인 어플리케이션 정보를 관리한다.
즉 위 내용들을 다시 한데 모아 정리하면, Context는 어플리케이션과 관련된 정보에 접근하고자 하거나 어플리케이션과 연관된 시스템 레벨의 함수를 호출하고자 할 때 사용된다. 그런데 안드로이드 시스템에서 어플리케이션 정보를 관리하고 있는 것은 시스템이 아닌 ActivityManagerService라는 일종의 또 다른 어플리케이션이다. 따라서 다른 일반적인 플랫폼과는 달리 안드로이드에서는 어플리케이션과 관련된 정보에 접근하고자 할 때는 ActivityManagerService를 통해야만 한다.
당연히 정보를 얻고자 하는 어플리케이션이 어떤 어플리케이션인지에 관한 키 값도 필요해 진다.
그래서 위 정리를 토대로 Android Platform 상에서의 관점으로 살펴 본 Context의 역할은 아래와 같다.
- 자신이 어떤 Application을 나타내고 있는지 알려주는 ID 역할
- ActivityManagerService에 접근할 수 있도록 하는 통로 역할
위와 같은 역할을 수행하기 때문에 안드로이드 개발 및 운영 등의 측면에서는 꼭 필요한 존재이다.
* 일반 OS 플랫폼에서의 관점
일반 OS 플랫폼에서는 사실 Application = Process 이나 마찬가지이다. 특정 어플리케이션은 내가 어떤 Process인지 OS에게 알려만 주면 어플리케이션 관련 정보를 얼마든지 획득할 수 있다. 이른바 자신의 존재 자체가 자신임을 증명 해 주는 '지문인식' 또는 '홍채인식' 등의 '생체인식'과 비슷한 개념이기 때문에 Context와 같은 애매한 중간 매개체가 존재 할 이유가 없는 것이다.
* 안드로이드 플랫폼에서의 관점
하지만 Android Platform은 조금 다르다. 비유하자면 '생체인식' 보다는 'ID 카드'를 통한 보안 시스템과 유사한 구조이다.
특정 어플리케이션이 OS플랫폼 처럼 자신은 곧 자신임을 확인할 수 있도록 작동중인 Process를 보여주는 방식이 아니라 자신이 건네받은 ID카드를 제시하는 것 이다. 이 과정에서의 ID 카드 역할을 수행하는 것이 바로 Context 이고, 당연히 이 카드는 위조 변조가 가능하기 때문에 자신의 권한을 제 3자의 어플리케이션에게 넘겨주는 Pending Intent 같은 기능도 가능 해 진다.
* Context가 생성되는 시점
결론부터 말하면 Context가 생성되는 시점은 곧 Application이 생성되는 시점이다.
그렇다면 Android 플랫폼의 관점에서 생각 해 봤을 때 4대 컴포넌트인 액티비티, 서비스, 브로드캐스트 리시버, 프로바이더 등은 모두 동일한 Context를 공유하여 사용하는가?
그것은 또 아니다..
액티비티와 서비스가 생성될 때 만들어지는 Context와 브로드캐스트 리시버가 호출될 때 (onReceive( )) 전달되는 Context는 모두 서로 다른 Instance이다.
즉, Context는 어플리케이션이 시작될 때는 물론이고 어플리케이션 컴포넌트들이 생성될 때 마다 각각 생성되는 셈이라고 생각하면 된다.
물론 새롭게 생성되는 Context들이 부모와 완전 독립인 상태는 아니고, 거의 비슷한 내용을 담고있다는 것은 알아두어야 한다.
* 파생된 Context 인스턴스들은 언제든지 부모 Context에 접근할 수 있다.어플리케이션 컴포넌트들은 도대체 왜 Context 인스턴스를 공유하지 않고, 모두 서로 다른 인스턴스를 사용하고 있는걸까?(알고보면 알맹이는 같긴 하지만..)
정확하지는 않지만 분명한 원인 한가지는 아래와 같다.Context의 기능 중, 시스템 API를 호출하는 기능과 관련되어 한 가지 문제점이 있다.어떤 어플리케이션 컴포넌트가 시스템 API를 호출하느냐에 따라 서로 다른 결과가 나타나야 한다는 점이다.
예로, 동일한 형태로 startActivity Method를 호출하더라도 일반적인 Activity에서는 정상적으로 새로운 Activity를 시작하게 되지만, Service에서 호출 할 경우에는 예외가 발생한다. 만일 어플리케이션을 구성하는 Service와 Activity가 서로 동일한 Context를 공유하고 있다면 동일한 Method 호출에 대하여 서로 다른 결과를 나타내도록 구현하지 못할 것이다.
따라서 현재 안드로이드 시스템은 어플리케이션 Context를 기반으로 컴포넌트를 위한 Context를 생성할 때 해당 Context가 어떤 종류의 컴포넌트인지 알 수 있도록 약간의 표시를 해 두곤 한다.
* 마무리
결론적으로 Android Context 위와 같은 다양한 이유들로 인하여 기존의 프로그램(Process) 플랫폼과는 다른 방식으로 Application을 관리하고 있고, 단순하게 시스템 API를 통해 할 수 있었던 일들을 안드로이드에서는 굳이 한번 더 Context 인스턴스라는 귀찮지만, 그럼에도 불구하고 강력한 녀석을 통해 대행 처리하고 있다고 할 수 있는 것이다.
* 추가
this 키워드는 해당 키워드를 사용한 클래스 자신을 지칭할 때 사용하며, 코틀린에서도 동일한 용도로 사용된다.
// Activity에 버튼 클릭 리스너를 구현하였다.
class MainActivity : AppCompatActivity(), View.OnClickListener {
lateinit var btnHello : Button
override fun onCreate(saveInstanceState: Bundle?) {
super.onCreate(saveInstanceState)
btnHello = findViewById(R.id.btn_hello) as Button
// Button Click Listener => MainActivity를 설정한다.
btnHello.setOnClickListener(this) // this 사용
}
override fun onClick(view: View) {
// Button 클릭 리스너 구현
}
}
this 키워드를 단독으로 사용 한 것은, 해당 위치에서 가장 가까운 범위의 클래스를 의미한다.
이 말인 즉슨, 클래스 내에서 다른 클래스나 인터페이스의 인스턴스를 동적으로 생성하여 사용하는 경우에 this 키워드를 사용하는 경우, 사용 한 위치에 따라 this 키워드가 의미하고 있는 클래스가 달라질 수 있다는 말이다.
이러한 문제를 해결하기 위해 코틀린에서는 아래 코드와 같은 방법으로 표기한다.
class MainActivity: AppCompatActivity() {
lateinit var btnHello: Button
override fun onCreate(saveInstanceState: Bundle?) {
super.onCreate(saveInstanceState)
btnHello = findViewById(R.id.btn_hello) as Button
//버튼 클릭 리스너를 동적으로 생성한다.
btnHello.setOnClickListener(object: View.OnClickListener{
override fun onClick(view: View){
//this = View.OnClickListener
//액티비티의 인스턴스를 참고하기 위해 this@MyActivity를 사용한다.
Toast.makeText(this@MainActivity,
"Hello",
Toast.LENGTH_SHORT).show()
// JAVA에서의 MainActivity.this와 동일함.
}
})
}
}
위 내용들에 대해 조금 더 짬뽕하여 설명하자면 아래와 같다.
Context와 this는 Intent를 통해 Activity 간 이동을 한다거나, Toast나 Dialog 같은 기본적인 뷰 또는 다양한 곳에 사용되는 요소이다. 위 경우에는 getApplicationContext( ) 혹은 this@{ActivityName} (=Kotlin)로 사용하곤 한다.
( Java의 경우 {ActivityName}.this로 사용하던것을 Kotlin에서는 위와 같이 사용함. )
( 즉, {ActivityName}.this == this@{ActivityName} 인데, 문법이 다른 것. )
내부 클래스에서 this를 적으면 MainActivity의 this가 아닌, 내부 클래스의 this가 되는 것이고,MainActivity의 this를 원한다면 this@MainActivity의 형식으로 적는다.
class A {
inner class B {
fun Int.foo() {
val a = this@A // class A에 대응하는 this a
val b = this@B // class B에 대응하는 this b
val c = this // foo()에 대응하는 foo()의 수신자, Int 형식
val c1 = this@foo // (동일)foo()에 대응하는 foo()의 수신자, Int 형식
val funLit = lambda@ fun String.() {
val d = this // funLit의 수신자.
}
val funLit2 = { s: String ->
// foo()'s receiver, since enclosing lambda expression
// doesn't have any receiver
val d1 = this
}
}
}
* 참고
https://blog.naver.com/huewu/110085457720
https://stackoverflow.com/questions/3572463/what-is-context-on-android#comment18833859_3572553
https://velog.io/@allocproc/android-Context%EB%9E%80
https://kotlinlang.org/docs/this-expressions.html
#Android Studio #Kotlin #App #Develop #Context #this #Developer
#안드로이드 스튜디오 #코틀린 #앱 #개발 #콘텍스트 #디스
#앱 개발 #개발자 #취준 #코딩 #프로그래밍
'Daily IT & 성장 일기' 카테고리의 다른 글
[2022.04.14] 앱 개발 기획하기 (2) | 2022.04.14 |
---|---|
[Github] 깃허브 메인 페이지 Overview 꾸미기 (0) | 2022.04.01 |
[2022.02] KB 국민은행 IT's Your Life 최종결과 발표 (0) | 2022.03.04 |
[2022.02] KB국민은행 IT아카데미 IT's Your Life에 참가하다. (0) | 2022.02.25 |