Ders 03 / 15
Activity Yaşam Döngüsü
Activity'nin yaşam döngüsü geri çağrıları, yapılandırma değişikliğinde ve süreç sonlanmasında hangi durumun korunduğu ve durdurulmuş bir activity'ye gelen arayüz isteğinin nasıl karşılandığının ölçümü.
Yayın Güncelleme
Bu derste
- Activity yaşam döngüsü geri çağrılarını onCreate'ten onDestroy'a sıralar
- Yapılandırma değişikliğinde kaydedilmeyen alanın varsayılan değerle döndüğünü ölçer
- Kullanıcının kapattığı activity'nin durumunun atıldığını gösterir
- Durdurulmuş activity'ye gelen arayüz isteğinin bekleyen kaldığını doğrular
İçindekiler
Bildirilen bir activity açıldı; kullanıcı ekranı döndürdü. Activity yok edilip yeniden kurulur, ve yeni activity’nin alanları ilk kurulumdaki değerleri taşır. Bu ders, yeniden kurulumda hangi değerin geri geldiğini ölçer. Ölçüt yine karşılamadır, ama burada isteğin kendisi bir değeri geri istemektir: döndürmeden önce 5 olan sayaç, sonra platformdan 5 olarak mı istenir ve 5 olarak mı gelir?
Bu dersin ölçüsü, önceki kurslardaki yaşam döngüsü ölçülerinden ayrıdır. M12/K04’te “ertelendi” ile “bekleyen” ayrı tutuldu; M30/K01’in yaşam döngüsü dersinde soru, ifadenin ne zaman değerlendirildiğiydi. Burada soru başkadır: isteğin ne zaman değerlendirildiği değil, activity’nin o anki yaşam döngüsü durumunda nasıl karşılandığı ölçülür.
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
data class Given(val value: String) : Outcome
data class Waiting(val on: String) : Outcome
data class Refused(val reason: String) : Outcome
}
class Ask(val label: String, val wanted: String, val outcome: Outcome) {
val fulfillment: String = when (outcome) {
is Outcome.Waiting -> "bekleyen"
is Outcome.Refused -> "karsiliksiz"
is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
}
fun report() {
val detail = when (outcome) {
is Outcome.Given -> "verilen=${outcome.value}"
is Outcome.Waiting -> "bekledigi=${outcome.on}"
is Outcome.Refused -> "neden=${outcome.reason}"
}
println("$label: istenen=$wanted | $detail -> $fulfillment")
}
}
fun tally(asks: List<Ask>) {
for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
println("$name: ${asks.count { it.fulfillment == name }}")
}
}
Yaşam Döngüsü Geri Çağrıları
Bir activity belli durumlardan geçer ve platform her geçişte bir yaşam döngüsü geri çağrısı çağırır. Kurulum sırası şöyledir.
onCreate: activity kurulur, arayüz hazırlanır. Kaydedilen örnek durumu varsa buraya gelir.onStart: activity ekranda görünür hale gelir.onResume: activity ön plandadır ve kullanıcıyla etkileşir.onPause: activity ön planı bırakır; bir başka activity öne gelmektedir.onStop: activity artık görünmez.onDestroy: activity yok edilir.
Ekran döndürmek gibi bir yapılandırma değişikliğinde platform activity’yi önce onPause, onStop ve onDestroy ile yıkar, sonra yeni yapılandırmayla onCreate, onStart ve onResume ile yeniden kurar. Alanlar bu arada yok olur. Yıkılmadan önce çağrılan onSaveInstanceState küçük bir paket tutar; yeniden kurulan activity bu paketi onCreate’te alır.
// Platform kodu: kaydedilen ornek durumuna yalniz sayac yazilir
import android.os.Bundle
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("sayac", counter)
}
Burada counter kaydedilir; draft gibi başka bir alan yazılmamıştır. Aynı zamanda sistem, bellek azaldığında arka plandaki bir sürecin tamamını sonlandırabilir. Bu süreç sonlanması da aynı pakete dayanır: kayıtlı olmayan alan geri gelmez.
Model, platformun bu kuralını kurar. Yardımcı dosya tek kuralı taşır: anahtar kaydedilen pakette varsa o değer verilir, yoksa alanın varsayılan değeri verilir. Girdi değerleri düz dizgilerdir.
// Restore.kt — kaydedilen ornek durumundan alan geri yukleme
fun restoreField(key: String, saved: Map<String, String>, defaults: Map<String, String>): Outcome =
Outcome.Given(saved[key] ?: defaults.getValue(key))
Döndürmede Üç Alan
Döndürmeden önce üç alan bir değer taşır: sayac 5, ad Ada, taslak merhaba. onSaveInstanceState yalnız ilk ikisini yazar. İstenen her zaman döndürmeden önceki değerdir.
// Main.kt — yapilandirma degisikliginde alanlarin geri gelisi
fun main() {
val before = mapOf("sayac" to "5", "ad" to "Ada", "taslak" to "merhaba")
val saved = mapOf("sayac" to "5", "ad" to "Ada")
val defaults = mapOf("sayac" to "0", "ad" to "bos", "taslak" to "bos")
val asks = before.map { (key, value) -> Ask(key, value, restoreField(key, saved, defaults)) }
for (ask in asks) {
ask.report()
}
tally(asks)
}
sayac: istenen=5 | verilen=5 -> tam ad: istenen=Ada | verilen=Ada -> tam taslak: istenen=merhaba | verilen=bos -> ikame tam: 2 ikame: 1 bekleyen: 0 karsiliksiz: 0
Kaydedilen iki alan istendiği gibi geri geldi: tam. Kaydedilmeyen taslak varsayılan değerle geldi: bir değer verildi, ama merhaba değil bos; ikame. Kullanıcı ekran döndürmeden önce yazdığı metnin gittiğini görür, ve kod hata vermez. Bu yüzden ikame, karşılıksızdan daha sessiz bir sorun üretir. Karşılıksız bir istek kodu durdurur; ikame akar ve yanlış değeri taşır.
UB1. Model tek bir varsayılan değer alır. Gerçek alanın başlangıç değeri onCreate’teki kuruluma bağlıdır ve bir ViewModel gibi durumu yapılandırma değişikliğinden sağ çıkaran düzenekler bu derste ölçülmedi.
Üç Olay, Üç Karşılama
Aynı pakete farklı olaylardan dönülür. Döndürme ve süreç sonlanması paketi korur. Kullanıcı activity’yi kendisi kapatırsa, örneğin geri düğmesiyle, platform durumu atar: kaydedilen paket de bırakılır. Aşağıdaki model olayı da girdi alır.
// Main.kt — olaya gore durumun korunmasi
fun restoreAfter(event: String, key: String, saved: Map<String, String>, defaults: Map<String, String>): Outcome =
if (event == "kullanici-kapatti") Outcome.Refused("durum-atildi") else restoreField(key, saved, defaults)
fun main() {
val saved = mapOf("sayac" to "5")
val defaults = mapOf("sayac" to "0", "taslak" to "bos")
val asks = listOf(
Ask("dondurme-sayac", "5", restoreAfter("dondurme", "sayac", saved, defaults)),
Ask("sonlanma-taslak", "merhaba", restoreAfter("surec-sonlanmasi", "taslak", saved, defaults)),
Ask("kapatma-sayac", "5", restoreAfter("kullanici-kapatti", "sayac", saved, defaults)),
)
for (ask in asks) {
ask.report()
}
tally(asks)
}
dondurme-sayac: istenen=5 | verilen=5 -> tam sonlanma-taslak: istenen=merhaba | verilen=bos -> ikame kapatma-sayac: istenen=5 | neden=durum-atildi -> karsiliksiz tam: 1 ikame: 1 bekleyen: 0 karsiliksiz: 1
Döndürmede kaydedilen alan tam geri geldi. Süreç sonlanmasında kaydedilmeyen alan ikame ile döndü. Kullanıcı activity’yi kapattığında ise durum hiç geri gelmedi: karşılıksız. Kapatma bir hata değildir, kullanıcının isteğidir; platform kapatılmış bir activity’den kalan durumu sonraki açılışa taşımaz.
UB2. Model üç olayı yalnız bu adlarla ayırır. Sistemin activity’yi hangi koşulda sonlandırdığı ve paketin boyut sınırı ölçülmedi.
Geri Çağrı Sırası
Yaşam döngüsü geri çağrılarının sırası, activity’ye hangi olayın geldiğine göre değişir. Sıra beklenenden saptığında en sık görülen hata, bir geri çağrıda yapılması gereken işin hiç çalışmamasıdır. Bu bölüm dört olayı sırasıyla ölçer. İstenen, kurulum sırası bölümünde anlatılan tam zincirdir; verilen ise olaya göre platformun gerçekten çağırdığı geri çağrılardır.
Bir uygulama yapılandırma değişikliğini kendisi karşılamayı seçebilir. Bu durumda activity yıkılmaz; yalnız onConfigurationChanged çağrılır. Seçim bildirim dosyasında configChanges özniteliğiyle yapılır.
<activity android:name=".EditorActivity" android:configChanges="orientation|screenSize" />
Model dört olayı kurar. ac ilk açılıştır. dondur varsayılan yapılandırma değişikliğidir. dondur-kendi-karsilar aynı olayın configChanges bildirilmiş hâlidir. surec-sonlandi ise sistemin arka plandaki süreci kapatmasıdır; bu durumda onDestroy çağrısının gelmesi garanti edilmez.
// Main.kt — olaya gore geri cagri zinciri
fun callbacksFor(event: String): Outcome = when (event) {
"ac" -> Outcome.Given("onCreate>onStart>onResume")
"dondur" -> Outcome.Given("onPause>onStop>onDestroy>onCreate>onStart>onResume")
"dondur-kendi-karsilar" -> Outcome.Given("onConfigurationChanged")
else -> Outcome.Refused("onDestroy-garanti-degil")
}
fun main() {
val asks = listOf(
Ask("ac", "onCreate>onStart>onResume", callbacksFor("ac")),
Ask("dondur", "onPause>onStop>onDestroy>onCreate>onStart>onResume", callbacksFor("dondur")),
Ask("dondur-kendi-karsilar", "onPause>onStop>onDestroy>onCreate>onStart>onResume", callbacksFor("dondur-kendi-karsilar")),
Ask("surec-sonlandi", "onDestroy", callbacksFor("surec-sonlandi")),
)
for (ask in asks) {
ask.report()
}
tally(asks)
}
ac: istenen=onCreate>onStart>onResume | verilen=onCreate>onStart>onResume -> tam dondur: istenen=onPause>onStop>onDestroy>onCreate>onStart>onResume | verilen=onPause>onStop>onDestroy>onCreate>onStart>onResume -> tam dondur-kendi-karsilar: istenen=onPause>onStop>onDestroy>onCreate>onStart>onResume | verilen=onConfigurationChanged -> ikame surec-sonlandi: istenen=onDestroy | neden=onDestroy-garanti-degil -> karsiliksiz tam: 2 ikame: 1 bekleyen: 0 karsiliksiz: 1
İlk iki olay istenen zinciri aynen verdi. dondur-kendi-karsilar satırı ikame çıktı: uygulama yapılandırma değişikliğini üstlendiği için activity yıkılmadı, bu yüzden alanlar yerinde kaldı. Burada ikame zararsızdır, hatta istenen bir sonuçtur; ama onCreate içinde yapılan bir kurulum ya da onSaveInstanceState içinde yapılan bir kayıt, bu yolda hiç çalışmaz. configChanges bildiren her activity, yapılandırmaya duyarlı kaynakları kendisi yeniden yüklemekle yükümlüdür. Dördüncü satır karşılıksızdır: onDestroy temizliğine güvenen bir kod, süreç bir anda sonlandığında bu temizliği yapamaz. Kalıcı olması gereken veriler onStop ya da daha önce kaydedilmelidir.
UB3. Model configChanges değerlerini tek bir mantıksal bayrağa indirger. Gerçek öznitelik her yapılandırma türü için ayrı ayrı belirtilir; bildirilmeyen bir tür yine de activity’yi yıkar. Hangi türün hangi koşulda yıkıma yol açtığı ölçülmedi.
Yarı Saydam Üst Activity
onStop geri çağrısı, activity’nin artık hiç görünmediği anlamına gelir. Üstüne yeni bir activity açıldığında bu her zaman doğru değildir: yeni activity ekranı tamamen kapatmıyorsa, örneğin bir iletişim kutusu biçiminde ya da yarı saydam bir tema ile açılmışsa, alttaki activity görünür kalır. Bu durumda alttaki activity yalnız onPause alır; onStop çağrılmaz.
// Platform kodu: yari saydam temali activity bildirimi // <activity android:name=".ConfirmActivity" // android:theme="@style/ConfirmDialogTheme" />
Aşağıdaki ölçüm üst activity’nin örtme biçimine göre alttaki activity’ye gelen geri çağrıları verir. İstenen onPause>onStop’tur.
// Main.kt — ust activity ve alttaki activity'nin geri cagrilari
fun belowCallbacks(coverage: String): Outcome = when (coverage) {
"tam-ekran" -> Outcome.Given("onPause>onStop")
"iletisim-kutusu" -> Outcome.Given("onPause")
"ayni-gorev-ayni-ekran" -> Outcome.Waiting("ust-activity-kapanmasi")
else -> Outcome.Refused("bilinmeyen-ortme")
}
fun main() {
val asks = listOf("tam-ekran", "iletisim-kutusu", "ayni-gorev-ayni-ekran", "yok").map {
Ask("ust-$it", "onPause>onStop", belowCallbacks(it))
}
for (ask in asks) {
ask.report()
}
tally(asks)
}
ust-tam-ekran: istenen=onPause>onStop | verilen=onPause>onStop -> tam ust-iletisim-kutusu: istenen=onPause>onStop | verilen=onPause -> ikame ust-ayni-gorev-ayni-ekran: istenen=onPause>onStop | bekledigi=ust-activity-kapanmasi -> bekleyen ust-yok: istenen=onPause>onStop | neden=bilinmeyen-ortme -> karsiliksiz tam: 1 ikame: 1 bekleyen: 1 karsiliksiz: 1
Tam ekran bir activity açıldığında alttaki activity durduruldu: tam. İletişim kutusu biçiminde bir activity açıldığında alttaki activity yalnız onPause aldı, durdurulmadı: ikame. Alttaki activity hâlâ ekranda görünür, ve onStop içinde bırakılacak kaynakları bırakmaz. Bu fark, kamera ya da konum güncellemesi gibi kaynakları onStop’ta kapatan kodun neden kimi zaman çalışmadığını açıklar: iletişim kutusu açıkken onStop hiç gelmez. Üçüncü satır bir başka kullanımı gösterir: üst activity kapanana kadar beklenen bir durum bekleyen olarak görünür.
UB4. Model örtme biçimini dört adla ayırır. Çok pencereli kullanımda ve resim içinde resim kipinde alttaki activity’nin görünürlüğü ve hangi geri çağrıların geldiği ölçülmedi.
Sınırlayıcı Ölçüm
Yaşam döngüsünün ikinci etkisi, bir isteğin hangi durumda yapıldığıdır. Arayüzü güncellemek isteyen kod, activity’nin durumuna göre farklı bir karşılama alır. Ön planda olan activity güncellemeyi hemen uygular. Durdurulmuş activity henüz görünür değildir; güncelleme, activity yeniden başlatılana kadar bekler. Yok edilmiş bir activity’ye güncelleme ise hiçbir yere gitmez.
// Main.kt — yasam dongusu durumuna gore arayuz guncelleme
fun updateUi(state: String): Outcome = when (state) {
"resumed" -> Outcome.Given("guncellendi")
"stopped" -> Outcome.Waiting("baslatilmis-durum")
else -> Outcome.Refused("activity-yok")
}
fun main() {
val asks = listOf(
Ask("on-plan", "guncellendi", updateUi("resumed")),
Ask("durdurulmus", "guncellendi", updateUi("stopped")),
Ask("yok-edilmis", "guncellendi", updateUi("destroyed")),
)
for (ask in asks) {
ask.report()
}
tally(asks)
println(asks.filter { it.fulfillment == "bekleyen" }.map { it.label })
}
on-plan: istenen=guncellendi | verilen=guncellendi -> tam durdurulmus: istenen=guncellendi | bekledigi=baslatilmis-durum -> bekleyen yok-edilmis: istenen=guncellendi | neden=activity-yok -> karsiliksiz tam: 1 ikame: 0 bekleyen: 1 karsiliksiz: 1 [durdurulmus]
Üç durumda aynı güncelleme isteği üç ayrı karşılama aldı. Bekleyen satırı önemlidir: durdurulmus reddedilmedi, ertelenmedi de; belirli bir koşulu, baslatilmis-durumu bekliyor ve koşul gelince uygulanacak. Bu, M12/K04’teki ayrımın activity karşılığıdır: bekleyen, koşulu adı belli olan bir istektir.
UB5. Model üç durumu ayırır; started ve paused ayrı bir satır almadı. Bu iki durumda görünürlüğün tam olarak ne olduğu platform sürümüne ve çok pencereli kullanıma göre değişir.
Özet
- Bu dersin ölçütü: karşılama. İstek, activity’nin yaşam döngüsü durumuna göre karşılanır. Bu dersin karşılaması: ikame; kaydedilmeyen alan hata vermeden varsayılan değerle döner.
- Yapılandırma değişikliğinde kaydedilen alanlar tam, kaydedilmeyen alan varsayılan değerle ikame geri geldi: 2 tam ve 1 ikame.
- Kullanıcı activity’yi kapattığında durum atılır; aynı istek karşılıksız kalır.
- Durdurulmuş activity’ye gelen arayüz isteği bekleyen kalır ve koşulunu adıyla taşır:
baslatilmis-durum. - İkame sessizdir, karşılıksız sesli: kod hata vermeden yanlış değerle devam eder.
Sonraki Adım
Activity’nin durumu onSaveInstanceState ile korunur; ekranın bölümleri ise ayrı yaşam döngüleri taşır. Sıradaki ders, Fragment'lar, bir activity’nin içindeki fragment’ların nasıl eklendiğini ve ev sahibi activity durmuşken fragment işleminin nasıl karşılandığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.