Android 效能優化系列 — 16 Application context 與 Activity context

Evan Chen
5 min readOct 5, 2022

--

Photo by Marianna Lutkova on Unsplash

上一篇我們介紹了一個因 Activity 被 Singleton 參考所發生的 Memory leak 。

object Class1 {
lateinit var context: Context
}
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMain2Binding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
//傳入Activity context
Class1.initApp(this)
}
}

會造成 Memory leak 的原因在於用錯了 Context。Singleton 的生命週期跟整個 App 的生命週期是一樣的,所以應使用 Application context 而不是 Activity context。這一篇我們就來看一下 Activity 與 Application context 的差異在哪裡?

Application context 與 Activity context

Context 是一個抽象類別,讓我們取得 Activity 與 Application 的環境資訊。例如開啟一個 Activity需要 Context,要顯示 Dialog、啟動 Service、發送 Broadcast、資料庫存取、Shared preferences 都需要 Context。

取得 Activity context:透過 View.getContext 或是在 Activity 直接用 this 取得。

val context = binding.imageView.context
val context = this

取得Application context:透過 getApplicationContext 取得。

this.applicationContext

Application Context 是跟UI無關的

例如在 Activity 要顯示 Dialog,AlertDialog.Builder(context) 裡要傳入的 Context 就必須是Activity content。

val alertDialog = AlertDialog.Builder(this)
alertDialog.setMessage("Message")
alertDialog.create().show()

如果傳入 applicationContext 是會閃退的

//傳入applicationContext會閃退
val alertDialog = AlertDialog.Builder(applicationContext)
alertDialog.setMessage("Message")
alertDialog.create().show()

生命週期不一樣

這兩個最大的差別是 Activity Context 的生命週期是跟著 Activity,Application context 的生命週期是跟著Application。在應該傳入 Application context 的地方如果傳入了 Activity context 就會造成Memory leak。

回來修正剛剛的 Singleton 範例,我們試著來修正這個 Memory leak。

object Class1 {
lateinit var context: Context
}

Class1 既然生命週期與 App 是一樣的,所以需要的是 Application Context,我們就想辦法讓context 只能被設定為 Application context。

步驟:

  1. context 改為 priviate,使其無法直接被設定。
  2. 新增一個 initApp function 傳入 context
  3. 在這個 function 裡將 context 透過 context.applicationContext 一律轉為 Application context。即使傳入了 Activity,也會轉成 Application Context。
object Class1 {
private lateinit var context: Context
//如果傳入了Activity context,也不會直接放在staic,而是取得ApplicationContext fun initApp(context:Context){
context = context.applicationContext
}
}
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMain2Binding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
//傳入Activity context
Class1.initApp(this)
}
}

這樣就修改完成了,確保拿到的一定會是 Application context。最後,誤用 Context 是最常發生的 Memory leak,一定要小心使用 Context。

下一篇:WeakReference 弱引用

--

--