"Do not place Android context classes in static fields: this is a momory leak"
혹시 위와 같은 경고 문구를 보신 적이 있나요?
context를 전역 변수로 할당했을 때 볼 수 있는 경고 문구인데요,
필자는 어디서든 쉽게 context를 가져오고 싶은 마음에 전역 변수로 할당했다가
경고 문구를 보고 그냥 인자로 넘겼던 기억이 있습니다...
이번 기회에 다시 한번 Memory Leak 관련해서 Context에 대해 알아보고 정리해보려고 합니다.
1. Context 정리
Context는 어플리케이션 환경에 대한 인터페이스입니다.
주요 내용으로 아래와 같이 정리할 수 있습니다.
- Application 및 Activity는 모두 Context를 상속받은 클래스입니다.
- Application 및 Activity에 대한 정보를 얻는데 사용할 수 있습니다.
- 데이터베이스, 리소스, 공유 환경설정(Shared preferences), 시스템 서비스(SystemServices) 등등에 접근하는데 사용할 수 있습니다.
Context는 크게 Application Context와 Activity Context 두 가지로 나눠 볼 수 있습니다.
하나의 어플리케이션에서 여러 개의 Activity가 존재할 수 있듯이 Activity Context도 여러 개를 가지며,
반대로 하나의 어플리케이션에서 Application Context는 하나만 존재할 수 있습니다.
[Application Context]
Application 생명주기를 가지고 앱시작부터 끝날때 까지 단 하나의 Context가 보장된다.
[Activity Context]
Activity 생명주기를 가지고 Activity가 파괴될때 같이 파괴되기 때문에 UI와 관련된 처리에 사용된다.
2. Context Memory Leak
Context 사용에 있어서 메모리 누수는 왜 발생하는 것일까요?
Activity Context가 전역변수로 할당되었고 해당 Activity가 onDestory 됐다고 가정해 보면
전역 변수에 할당된 Activity Context로 인해 메모리가 해제되지 않고 메모리 누수가 발생하게 됩니다.
위와 같은 상황은 Context가 가지는 생명주기가 고려되지 않았기 때문입니다.
Context가 가지는 특징(생명주기, UI)을 고려하여 Context를 사용해야 합니다.
[Context 잘못된 사용 예시]
class MainActivity : AppCompatActivity() {
companion object {
var context: Context? = null // it can cause memory leak
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
context = this
}
}
object Singleton {
var context: Context? = null // it can cause memory leak
}
3. Context 사용
Context 사용에 있어서 Application Context, Activity Context로 나누어 보고,
각각의 특성(생명주기, UI)에 맞게 가장 밀접한 Context를 사용하는게 가장 바람직합니다.
Application Context 사용 예시
abstract class AppDatabase : RoomDatabase() {
abstract fun Dao(): Dao
companion object {
private var appDatabase: AppDatabase? = null
fun getInstance(context: Context): AppDatabase? {
if (appDatabase == null) {
appDatabase = databaseBuilder(
context.applicationContext, // Application Context
AppDatabase::class.java,
"dataBase"
).build()
}
return appDatabase
}
}
}
object Singleton {
//var context: Context? = null // it can cause memory leak
fun requestVibrate() {
val context = MainApplication.getContext() // Application Context
val vibrator = context.getSystemService(Vibrator::class.java) as Vibrator
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createOneShot(1000L, VibrationEffect.DEFAULT_AMPLITUDE))
} else {
@Suppress("DEPRECATION")
vibrator.vibrate(500L)
}
}
}
Activity Context 사용 예시
val layoutInflater = LayoutInflater.from(context/*Activity Context*/)
fun showDialog(context: Context/*Activity Context*/) {
val alertDialogBuilder = AlertDialog.Builder(context)
alertDialogBuilder.create().show()
}
fun showToast(context: Context/*Activity Context*/) {
Toast.makeText(context, "Hello world", Toast.LENGTH_SHORT).show()
}
fun moveGpsSetting(context: Context/*Activity Context*/) {
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
4. Application Context 쉽게 가져오기
(조금 번외의 얘기일 수 있는데요..)
Application Context는 getApplicationContext() 호출을 통해 가져올 수 있지만
개인적으로 MainApplication에서 비교적 쉽게 접근할 수 있도록 사용하는 것도 추천 드립니다.
언제 어디서든 쉽게 가져올 수 있기 때문에 코딩에 있어서도 좋은 영향이 있을 거라고 생각 합니다.
아래 MainApplication 클래스를 통해 Application Context를 가져오는 예시입니다.
[MainApplication 작성하기]
class MainApplication: Application() {
init {
instance = this
}
companion object {
lateinit var instance: MainApplication
fun getContext(): Context {
return instance.applicationContext
}
}
}
[AndroidManifest에 MainApplication 등록하기]
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".MainApplication"
...
</application>
</manifest>
[Application Context 가져오기]
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MainApplication.getContext() // use me anytime, anywhere.
}
}
[Reference]
Context In Android Application
Android의 Context와 ApplicationContext
0 comments: