Ders 09 / 15
Görevler ve Geri Yığını
Görevin ve geri yığınının yapısı, başlatma kiplerinin yeni örnek mi var olan örnek mi kullanacağı, aynı kipin yığıtın durumuna göre iki ayrı karşılama alması ve CLEAR_TOP bayrağının kenar durumu.
Yayın Güncelleme
Bu derste
- Görevi, geri yığınını ve başlatma kipini birbirinden ayırır
- standard kipinin her istekte yeni örnek açtığını, singleTop ve singleTask kiplerinin var olan örneği kullanabildiğini ölçer
- singleTask kipinde yığıttaki örneğin üstündekilerin temizlendiğini gösterir
- Aynı kipin yığıtın durumuna göre tam ya da ikame karşılandığını doğrular
İçindekiler
Intent ile bir activity başlatılınca platform onu bir yere yerleştirir. Önceki derste bir intent’in hangi uygulamaya ulaştığını ölçtünüz; bu derste ulaştıktan sonra ne olduğuna bakılır: yeni bir ekran mı açılır, var olan ekran mı öne gelir, ve geri tuşu sonra nereye götürür.
Platformda activity’ler görev (task) içinde toplanır. Görev, kullanıcının bir işi yaparken sırayla gezdiği activity’lerin topluluğudur: bir e-posta yazarken gönderen seçme ekranı, ek dosya ekranı ve yazı ekranı aynı görevdedir. Görevin içindeki activity’ler bir geri yığınında (back stack) sıralanır. Yeni açılan activity yığının tepesine konur; geri tuşu tepedeki activity’yi yığından çıkarır ve bir alttakini öne getirir. Yığının en altındaki activity çıkarılınca görev biter.
Yığın bir yığıt (stack) gibi çalışır: son giren ilk çıkar. Bu derste yığıtı bir liste olarak göstereceksiniz ve sırası basılacak. Sol uç en alttaki activity’dir, sağ uç tepedir.
Dersin sorusu: aynı activity’yi yeniden açma isteği geldiğinde platform isteği nasıl karşılıyor? İstek her zaman “bu activity’nin yeni bir örneğini aç”tır. Platform bu isteği başlatma kipine göre ya yeni örnekle, ya da var olan örneği kullanarak yerine getirir. Birincisi tam, ikincisi ikame karşılamadı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 }}")
}
}
Başlatma Kipi
Başlatma kipi (launchMode), bir activity’nin başlatılma isteğinde yeni örnek mi açılacağını yoksa var olan örneğin mi kullanılacağını belirler. Kip bildirim dosyasında activity için bildirilir.
<activity android:name=".DetailActivity" android:launchMode="singleTop" />
Dört kip vardır.
- standard: varsayılan kiptir. Her istek, activity’nin yeni bir örneğini görevin tepesine koyar. Aynı activity yığında birden çok kez bulunabilir.
- singleTop: hedef activity zaten yığının tepesindeyse yeni örnek açılmaz, var olan örneğe
onNewIntentgeri çağrısıyla yeni intent iletilir. Tepede değilse standard gibi yeni örnek açılır. - singleTask: hedef activity görevde bir yerde varsa yeni örnek açılmaz. Var olan örnek öne getirilir ve onun üstündeki bütün activity’ler yığından temizlenir. Yoksa yeni örnek açılır.
- singleInstance: singleTask gibidir, ama activity kendi görevinde tek başına çalışır ve o görevde başka activity barındırmaz. Bu ders bu kipi ölçmez.
Kip yalnız bildirim dosyasında değil, intent bayraklarıyla da belirlenir. FLAG_ACTIVITY_CLEAR_TOP bayrağı hedef activity yığındaysa onun üstündekileri temizler. FLAG_ACTIVITY_NEW_TASK ise activity’yi yeni bir göreve koyar.
// Platform kodu: bayrakla baslatma import android.content.Intent val intent = Intent(this, DetailActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) startActivity(intent)
Yığıt Modeli
Model yığıtı bir liste olarak tutar. Yardımcı dosya başlatma kipini tek bir fonksiyonda kurar: yığıt, hedef ve kip verilir; yeni yığıt ve ne olduğu döner. Ne olduğu iki değerden biridir: yeni-ornek ya da var-olan-ornek. İstenen her zaman yeni-ornektir; çünkü çağıran kod activity’yi açmak istemektedir.
// Tasks.kt — baslatma kiplerini ve ardisik istekleri kurar
class Launch(val stack: List<String>, val how: String)
fun launchActivity(stack: List<String>, target: String, mode: String): Launch {
val at = stack.lastIndexOf(target)
return when (mode) {
"standard" -> Launch(stack + target, "yeni-ornek")
"singleTop" ->
if (stack.lastOrNull() == target) Launch(stack, "var-olan-ornek")
else Launch(stack + target, "yeni-ornek")
"singleTask" ->
if (at >= 0) Launch(stack.take(at + 1), "var-olan-ornek")
else Launch(stack + target, "yeni-ornek")
"standard-clearTop" ->
if (at >= 0) Launch(stack.take(at) + target, "yeni-ornek")
else Launch(stack + target, "yeni-ornek")
"singleTop-clearTop" ->
if (at >= 0) Launch(stack.take(at + 1), "var-olan-ornek")
else Launch(stack + target, "yeni-ornek")
else -> Launch(stack, "tanimsiz-kip")
}
}
fun runLaunches(start: List<String>, targets: List<String>, mode: String): Pair<List<Ask>, List<String>> {
var stack = start
val asks = mutableListOf<Ask>()
for ((index, target) in targets.withIndex()) {
val launch = launchActivity(stack, target, mode)
asks.add(Ask("${index + 1}-$mode-$target", "yeni-ornek", Outcome.Given(launch.how)))
stack = launch.stack
}
return Pair(asks, stack)
}
runLaunches ardışık istekleri sırayla uygular ve her isteği bir Ask olarak toplar. İstek etiketi adım numarasını, kipi ve hedefi taşır. Yığıt her adımdan sonra güncellenir.
UB1. Model görev başına tek yığın varsayar ve NEW_TASK bayrağını, taskAffinity özniteliğini ve çok görevli durumları ölçmez. Yığındaki activity’ler yalnız birer ad olarak tutulur; durumları, yaşam döngüsü geri çağrıları ve yok edilmeleri modellenmez.
standard Kipi
standard kipinde her istek yeni örnek açar. A açıktır; B, sonra A, sonra yine A başlatılır. A’nın yığında üç kez bulunması geçerlidir.
// Main.kt — standard kip
fun main() {
val (asks, stack) = runLaunches(listOf("A"), listOf("B", "A", "A"), "standard")
for (ask in asks) {
ask.report()
}
tally(asks)
println(stack.joinToString(" > "))
}
1-standard-B: istenen=yeni-ornek | verilen=yeni-ornek -> tam 2-standard-A: istenen=yeni-ornek | verilen=yeni-ornek -> tam 3-standard-A: istenen=yeni-ornek | verilen=yeni-ornek -> tam tam: 3 ikame: 0 bekleyen: 0 karsiliksiz: 0 A > B > A > A
Üç istek de tam karşılandı: her biri yeni örnek açtı. Yığın dört elemana çıktı ve A üç kez bulunuyor. Kullanıcı bu yığında ilk A’ya geri tuşuna üç kez basarak ulaşır ve dört elemanın her biri ayrı bir örnektir. Bu, standard kipin getirdiği bedeldir: aynı ekranı tekrar tekrar açan kod yığını sessizce büyütür.
singleTop Kipi
singleTop hedef zaten tepedeyse yeni örnek açmaz. Detay ekranından kendine bağlantı veren bir akış ya da bir kullanıcı bildirimine arka arkaya iki kez dokunmak bu duruma örnektir. Yığın A, B ile başlar; B’ye iki, C’ye iki istek gelir.
// Main.kt — singleTop kip
fun main() {
val (asks, stack) = runLaunches(listOf("A", "B"), listOf("B", "C", "C"), "singleTop")
for (ask in asks) {
ask.report()
}
tally(asks)
println(stack.joinToString(" > "))
}
1-singleTop-B: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame 2-singleTop-C: istenen=yeni-ornek | verilen=yeni-ornek -> tam 3-singleTop-C: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame tam: 1 ikame: 2 bekleyen: 0 karsiliksiz: 0 A > B > C
B zaten tepedeydi; ilk istek var olan örneği kullandı ve karşılama ikame oldu. C tepede değildi, yeni örnek açıldı ve tam karşılandı. Üçüncü istekte C artık tepededir, bu kez ikame. Yığın A, B, C olarak kaldı; ikame edilen iki istek yığını büyütmedi. Çağıran taraf yeni intent’ini onNewIntent geri çağrısında alır ve ekranını güncellemelidir. Yeni örnek açıldığını varsayan bir kod burada yanlış çalışır.
UB2. Modelde onNewIntent‘in çağrılıp çağrılmadığı ölçülmez; yalnız yığına ne olduğu ölçülür. Var olan örneğe intent’in nasıl iletildiği ve ekranın güncellenip güncellenmediği ölçülmedi.
singleTask Kipi
singleTask hedef yığında herhangi bir yerdeyse onu öne getirir ve üstündekileri temizler. A, B, C yığınında B’ye bir istek gelirse C yığından çıkar; B tepe olur. Sonra D açılır, sonra yine B istenir: D de temizlenir.
// Main.kt — singleTask kip
fun main() {
val (asks, stack) = runLaunches(listOf("A", "B", "C"), listOf("B", "D", "B"), "singleTask")
for (ask in asks) {
ask.report()
}
tally(asks)
println(stack.joinToString(" > "))
}
1-singleTask-B: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame 2-singleTask-D: istenen=yeni-ornek | verilen=yeni-ornek -> tam 3-singleTask-B: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame tam: 1 ikame: 2 bekleyen: 0 karsiliksiz: 0 A > B
İlk ve üçüncü istek var olan B’yi kullandı ve ikame oldu; ikincisi D yığında olmadığı için yeni örnekle tam karşılandı. Yığın sonunda A, B. İstekler yığına C ve D’yi eklemiş, sonra silmiştir: singleTask yalnız yeni örnek açmamakla kalmaz, kullanıcının B’ye dönmeden önce gezdiği ekranları da atar. Bu, ana ekran ya da giriş noktası gibi görevin kökünde durması gereken ekranlar için tasarlanmıştır.
CLEAR_TOP Bayrağının Kenar Durumu
FLAG_ACTIVITY_CLEAR_TOP bayrağı singleTask’ın yığın temizleme davranışını bir bayrağa taşır, ama var olan örneğe ne olacağı kipe bağlıdır. Hedef standard kipindeyse platform var olan örneği de yok edip yeni bir örnek oluşturur. Hedef singleTop kipindeyse ya da bayrakla birlikte FLAG_ACTIVITY_SINGLE_TOP verilmişse var olan örnek korunur ve onNewIntent çağrılır.
İki istek aynı yığına aynı hedefi gönderir: A, B, C yığınında B.
// Main.kt — CLEAR_TOP ve kip
fun main() {
val standardClear = runLaunches(listOf("A", "B", "C"), listOf("B"), "standard-clearTop")
val singleTopClear = runLaunches(listOf("A", "B", "C"), listOf("B"), "singleTop-clearTop")
val asks = standardClear.first + singleTopClear.first
for (ask in asks) {
ask.report()
}
tally(asks)
println(standardClear.second.joinToString(" > "))
println(singleTopClear.second.joinToString(" > "))
}
1-standard-clearTop-B: istenen=yeni-ornek | verilen=yeni-ornek -> tam 1-singleTop-clearTop-B: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame tam: 1 ikame: 1 bekleyen: 0 karsiliksiz: 0 A > B A > B
İki yığın aynı: A, B. Karşılamalar farklı: standard ile yeni örnek açıldı ve tam, singleTop ile var olan örnek korundu ve ikame. Yığıtın dışarıdan görünen hâli iki durumda da aynıdır; fark örneğin kimliğinde ve onun durumunda yatar. Standard kipte ekranın içindeki kaydedilmemiş durum, örnek yenilendiği için kaybolur.
Bu ince farkın nedeni yığıtın değil, örneğin ölçülmesidir. Karşılama kategorisi “yığın nasıl görünüyor” sorusuna değil, “istek nasıl karşılandı” sorusuna bakar ve ikisi birbirinden bağımsız çıkar.
Sınırlayıcı Ölçüm
Aynı kip, aynı hedef; yalnız yığının durumu değişir. singleTop kipinde B hedeftir. Birinde B tepededir, ötekinde C’nin altında kalmıştır. singleTask ile karşılaştırma için üçüncü bir durum eklenir: kip singleTask, B yine C’nin altındadır.
// Main.kt — ayni kip, iki yigit durumu
fun main() {
val cases = listOf(
Triple("singleTop-tepede", listOf("A", "B"), "singleTop"),
Triple("singleTop-tepede-degil", listOf("A", "B", "C"), "singleTop"),
Triple("singleTask-tepede-degil", listOf("A", "B", "C"), "singleTask"),
)
val asks = mutableListOf<Ask>()
for ((label, stack, mode) in cases) {
val launch = launchActivity(stack, "B", mode)
asks.add(Ask(label, "yeni-ornek", Outcome.Given(launch.how)))
println("$label yigit: ${launch.stack.joinToString(" > ")}")
}
for (ask in asks) {
ask.report()
}
tally(asks)
println(asks.map { it.fulfillment }.distinct().sorted())
}
singleTop-tepede yigit: A > B singleTop-tepede-degil yigit: A > B > C > B singleTask-tepede-degil yigit: A > B singleTop-tepede: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame singleTop-tepede-degil: istenen=yeni-ornek | verilen=yeni-ornek -> tam singleTask-tepede-degil: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame tam: 1 ikame: 2 bekleyen: 0 karsiliksiz: 0 [ikame, tam]
singleTop kipi iki yığıtta iki ayrı karşılama aldı. B tepedeyse ikame: yığın değişmedi. B tepede değilse tam: yığın A, B, C, B oldu ve B iki örnekle yığındadır. singleTask ise aynı yığında B’yi öne getirdi ve C’yi attı: ikame.
Bu ölçümün söylediği şudur: kip tek başına karşılamayı belirlemez. Karşılama, kip ile yığıtın o anki durumunun birlikte işleyen kuralından çıkar. Aynı kodu iki farklı anda çalıştıran bir uygulama, kullanıcının nereden geldiğine göre iki farklı davranış görür. Bu dersin karşılaması ikamedir: aynı activity’yi açma isteği, platformca çoğu zaman var olan örnekle yerine getirilir.
UB3. Model hedef örneğin yığıtta yalnız bir kez bulunduğu durumları ölçer. Hedefin iki örneği yığındaysa singleTop ve CLEAR_TOP kurallarının hangi örneği seçtiği, ve görevler arası geçişler (örneğin singleInstance) ölçülmedi.
Özet
- Bu dersin ölçütü: karşılama. Activity’ler görevde, görev de geri yığınında toplanır. Başlatma kipi, aynı activity’yi açma isteğinin yeni örnekle mi var olan örnekle mi karşılanacağını belirler. Bu dersin karşılaması: ikame; aynı activity’yi açma isteği çoğu zaman var olan örnekle yerine getirilir.
standardher istekte yeni örnek açar: 3 tam.singleTophedef tepedeyse var olan örneği kullanır: ikame.singleTaskhedef yığındaysa onu öne getirir ve üstündekileri temizler: ikame; yığında yoksa yeni örnek, tam.CLEAR_TOPilestandardkipinde var olan örnek de yenilenir (tam),singleTopkipinde korunur (ikame). Yığıtın dışarıdan görünüşü aynıdır.- Aynı kip yığıtın durumuna göre iki karşılama alır:
singleToptepede ikame, tepede değilse tam.
Sonraki Adım
Bir ekranın hangi göreve ve hangi sıraya yerleştiğini ölçtünüz. O ekran, bir uygulama bileşeni olarak platforma önceden tanıtılmadıkça hiç başlatılamaz. Bu tanıtmanın yeri bildirim dosyasıdır; sıradaki ders, Bildirim Dosyası, orada ne yazdığını ve birden çok kaynaktan gelen dosyaların nasıl birleştiğini ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.