Код-ревью 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 в синглтон.

Что должен найти ревьюер

  1. Ошибкастрока 4
    Статическое поле lastActivity держит Activity после уничтожения: утечка; убрать или обнулять, лучше не хранить Activity статически
  2. Ошибкастрока 14
    requestLocationUpdates без removeUpdates: LocationManager держит Activity как листенер; отписываться в onStop/onDestroy
  3. Ошибкастрока 15
    postDelayed с лямбдой, захватившей Activity: живёт 30 секунд после закрытия; removeCallbacks в onDestroy или корутина в lifecycleScope
  4. Ошибкастрока 16
    GlobalScope держит Activity до конца запроса и обновит уничтоженный экран; нужен lifecycleScope или загрузка во ViewModel
  5. Ошибкастрока 20
    Analytics.init(this) передаёт контекст Activity в синглтон; нужен applicationContext
  6. Производительностьстрока 14
    Обновления GPS раз в секунду с минимальным смещением 0 даже в фоне тратят батарею; запрашивать только пока экран виден

Разбор

Пять источников утечек в одном классе: статическое поле, незарегистрированный листенер системного сервиса, отложенное сообщение Handler, GlobalScope и передача контекста Activity в синглтон. Исправления: никаких статических ссылок на Activity, подписка на обновления в onStart и отписка в onStop (или lifecycle-aware компонент), корутины в lifecycleScope или загрузка во ViewModel, applicationContext для синглтонов. LeakCanary показал бы каждую из этих цепочек.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.