Код-ревью Senior Производительность и утечки памяти обновлено
Код-ревью: найди утечки памяти
Вопрос
Проведи ревью Activity экрана с геолокацией. Найди утечки памяти и другие проблемы.
class MapActivity : AppCompatActivity(), LocationListener {
companion object {
var lastActivity: MapActivity? = null
}
private val handler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map)
lastActivity = this
val lm = getSystemService(LOCATION_SERVICE) as LocationManager
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000L, 0f, this)
handler.postDelayed({ showHint() }, 30_000)
GlobalScope.launch {
val places = api.loadPlaces()
withContext(Dispatchers.Main) { renderPlaces(places) }
}
Analytics.init(this)
}
override fun onLocationChanged(location: Location) {
findViewById<TextView>(R.id.coords).text = "${location.latitude}"
}
}
Коротко
Пять источников утечек в одном классе: статическое поле, незарегистрированный листенер системного сервиса, отложенное сообщение Handler, GlobalScope и передача контекста Activity в синглтон.
Что должен найти ревьюер
- Ошибкастрока 4Статическое поле lastActivity держит Activity после уничтожения: утечка; убрать или обнулять, лучше не хранить Activity статически
- Ошибкастрока 14requestLocationUpdates без removeUpdates: LocationManager держит Activity как листенер; отписываться в onStop/onDestroy
- Ошибкастрока 15postDelayed с лямбдой, захватившей Activity: живёт 30 секунд после закрытия; removeCallbacks в onDestroy или корутина в lifecycleScope
- Ошибкастрока 16GlobalScope держит Activity до конца запроса и обновит уничтоженный экран; нужен lifecycleScope или загрузка во ViewModel
- Ошибкастрока 20Analytics.init(this) передаёт контекст Activity в синглтон; нужен applicationContext
- Производительностьстрока 14Обновления GPS раз в секунду с минимальным смещением 0 даже в фоне тратят батарею; запрашивать только пока экран виден
Разбор
Пять источников утечек в одном классе: статическое поле, незарегистрированный листенер системного сервиса, отложенное сообщение Handler, GlobalScope и передача контекста Activity в синглтон. Исправления: никаких статических ссылок на Activity, подписка на обновления в onStart и отписка в onStop (или lifecycle-aware компонент), корутины в lifecycleScope или загрузка во ViewModel, applicationContext для синглтонов. LeakCanary показал бы каждую из этих цепочек.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.