---
title: 'Platform Servisleri ve Tümleştirmeler'
source: 'https://academia.sh/tr/kurslar/android-servisler'
language: tr
updated: '2026-10-21T05:02:41+00:00'
license: 'CC BY-SA 4.0'
lessons: 14
outcomes:
    - 'Sistem servislerini izin modeline uygun kullanır'
    - 'Bildirim ve derin bağlantı akışlarını kurar'
    - 'Uygulama verisini platform güvenlik araçlarıyla korur'
    - 'Gönderilen bir olayın alıcıya birebir mi, yinelenerek mi ulaştığını, sonraki bir olayla mı ezildiğini yoksa kaybolduğunu mu ayırt eder'
---

# Platform Servisleri ve Tümleştirmeler

Bir uygulamanın gönderdiği ya da aldığı olay alıcıya her seferinde bir kez ulaşmaz: aynı kimlikli bildirim öncekinin yerini alır, kapalı bir kanala giden bildirim hiç görünmez, yanıtı gecikmiş bir istek yeniden denenince sunucuya iki kez varır. Kurs her düzeneği tek bir soruyla ölçer — gönderilen bu olay alıcıya kaç kopya olarak ulaştı? Bildirimler, bulut mesajlaşma, konum, kamera, sensörler, derin bağlantılar, pencere öğeleri, şifreli depolama, ağ güvenliği, kimlik doğrulama, uygulama imzalama, analitik, uzaktan yapılandırma ve uygulama içi satın alma aynı çerçeveden geçirilir.

## Sistem Yetenekleri

Kanal ve öncelikli bildirimler, sunucudan aygıta bulut mesajlaşma, konum sağlayıcıları, kamera ve medya erişimi, sensör verisi, derin bağlantılar ve kısayollar, ana ekran pencere öğeleri.

### Bildirimler

Source: https://academia.sh/tr/kurslar/android-servisler/bildirimler

Kullanıcı bildirimi kanal, önem düzeyi ve eylemle kurulur, çalışma zamanı izniyle gösterilir. Ders sekiz olayın alıcıya kaç kopya olarak ulaştığını ölçer; kapalı kanalın, verilmemiş iznin ve aynı kimliğin teslimi neye çevirdiğini gösterir.

Önceki kursta veri uygulamanın içindeydi ve ekran onu çekiyordu. Bu kursta yön tersine döner: veri uygulamaya gönderilir. Sunucu bir olay yollar, platform onu süzer, birleştirir, bazen yeniden dener ve olay alıcıya ulaşır ya da ulaşmaz. İlk durak kullanıcı bildirimidir. Kursun sorusu bu derste doğar: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?**

Soruyu dört yanıtla ölçeceğiz ve bu yanıtlara teslim diyeceğiz. Olay alıcıya tam bir kopya olarak ulaştıysa **birebir**, birden çok kopya olarak ulaştıysa **yinelenen**dir. Kopyası ulaşmadı ama aynı anahtarı taşıyan daha sonraki bir olay ulaşıp onun yerini aldıysa **ezilen**, hiçbir şey ulaşmadıysa **kayıp**tır. Olayın kimliğine anahtar, gönderim sırasına sıra diyoruz. Sınıflandırmayı etiket değil bir kural yapar.

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

Kural alıcının listesinde gönderilenle aynı sırayı taşıyan kopyaları sayar. Bir kopya birebir, birden çok kopya yinelenen demektir. Hiç kopya yoksa aynı anahtarla daha büyük sıralı bir olayın ulaşıp ulaşmadığına bakar: ulaştıysa ezilen, ulaşmadıysa kayıptır. Kural kopya sayısını görür; içeriği ve nedeni görmez.

#### Kullanıcı Bildiriminin Parçaları

Platformda bir olay ekrana kullanıcı bildirimi olarak düşmeden önce dört kapıdan geçer ve her kapı olayı alıcıdan uzaklaştırabilir. Kapıları sırayla tanıyalım, çünkü dersin geri kalanı bu sırayı ölçer.

Kullanıcı bildirimi (notification) dört parçayla kurulur. Bildirim kanalı (notification channel), kullanıcı bildirimlerini konuya göre gruplar ve kullanıcının tek tek açıp kapatabildiği ayarı taşır. Önem düzeyi (importance), kullanıcı bildiriminin ne kadar dikkat çekeceğini belirler: sesli ve ekranın üstüne düşen bir kullanıcı bildirimi ile sessizce listeye eklenen biri aynı kanalın iki farklı düzeyidir. Eylem (action), kullanıcı bildiriminin üzerindeki düğmedir ve basılınca bir `PendingIntent` çalıştırır. Kanalın önem düzeyi kanal oluşturulurken verilir; uygulama kanalı kurduktan sonra düzeyi yükseltemez, değişikliği kullanıcı kanal ayarlarından yapar. Bu yüzden kanalı doğru kurmak, sonradan düzeltmekten ucuzdur. Dördüncü parça izindir: Android'in bildirim göndermeyi çalışma zamanı izni (runtime permission) yaptığı sürümlerde uygulama izin verilmeden kullanıcı bildirimi gösteremez.

```kotlin
// Platform kodu: OrderNotifier
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import androidx.core.app.NotificationCompat

fun createChannel(manager: NotificationManager) {
    val channel = NotificationChannel("siparis", "Siparisler", NotificationManager.IMPORTANCE_HIGH)
    manager.createNotificationChannel(channel)
}

fun postOrder(context: Context, manager: NotificationManager, mapAction: PendingIntent) {
    val notification = NotificationCompat.Builder(context, "siparis")
        .setContentTitle("Siparisin yolda")
        .setSmallIcon(android.R.drawable.ic_dialog_info)
        .addAction(0, "Haritada gor", mapAction)
        .build()
    manager.notify(42, notification)
}
```

`createChannel` kanalı bir kimlik ve bir önem düzeyiyle kurar, `postOrder` aynı kanal kimliğiyle bir kullanıcı bildirimi hazırlar ve `notify` ile gönderir. `addAction` ile eklenen eylem, kullanıcı düğmeye bastığında `mapAction` olarak verilen `PendingIntent`'i çalıştırır; eylem kullanıcı bildiriminin gösterilmesine değil, gösterildikten sonra ne yapılabileceğine aittir. Buradaki `42` kullanıcı bildiriminin kimliğidir; sınırlayıcı ölçüm onu kullanacak.

```xml
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
```

Bildirim dosyasındaki bu satır izni bildirir, ama izin ancak kullanıcı çalışma zamanında onaylarsa geçerli olur. Bu iki parça platform kodudur ve burada çıktı, süre ya da gösterim sayısı iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

#### Sekiz Olayın Dört Teslimi

Modelde gönderilen olay bir `Signal`'dır: anahtarın noktadan önceki kısmı kanalı, tamamı olayı verir. Sekiz olay gönderiyoruz. Üç işlev yolu kurar: `gate` kapatılmış kanalın olaylarını eler, `collapse` aynı anahtarlı olaylardan yalnız sonuncusunu bırakır, `resend` seçilen sıradaki olayı iki kez iletir. Alıcıya ulaşan liste bu üç işlevin dönüşüdür.

```kotlin
// Main.kt
fun gate(sent: List<Signal>, closed: Set<String>): List<Signal> =
    sent.filter { it.key.substringBefore('.') !in closed }

fun collapse(sent: List<Signal>): List<Signal> =
    sent.groupBy { it.key }.values.map { it.last() }.sortedBy { it.seq }

fun resend(sent: List<Signal>, again: Set<Int>): List<Signal> =
    sent.flatMap { if (it.seq in again) listOf(it, it) else listOf(it) }

fun main() {
    val sent = listOf(
        Signal("mesaj.deniz", 1),
        Signal("siparis.42", 2),
        Signal("mesaj.deniz", 3),
        Signal("kampanya.indirim", 4),
        Signal("odeme.onay", 5),
        Signal("siparis.42", 6),
        Signal("kampanya.yeni", 7),
        Signal("hatirlatma.ilac", 8),
    )
    val received = resend(collapse(gate(sent, setOf("kampanya"))), setOf(5))
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [3, 5, 5, 6, 8]
olay-1: anahtar=mesaj.deniz | kopya=0 -> ezilen
olay-2: anahtar=siparis.42 | kopya=0 -> ezilen
olay-3: anahtar=mesaj.deniz | kopya=1 -> birebir
olay-4: anahtar=kampanya.indirim | kopya=0 -> kayip
olay-5: anahtar=odeme.onay | kopya=2 -> yinelenen
olay-6: anahtar=siparis.42 | kopya=1 -> birebir
olay-7: anahtar=kampanya.yeni | kopya=0 -> kayip
olay-8: anahtar=hatirlatma.ilac | kopya=1 -> birebir
birebir: 3
yinelenen: 1
ezilen: 2
kayip: 2
```

Sekiz olayın üçü birebir, biri yinelenen, ikisi ezilen, ikisi kayıp ulaştı: 3/1/2/2. Bu dağılımı kurs boyunca referans alacağız. Dört teslimin her biri bir işlevden doğdu. `gate` kampanya kanalındaki 4 ve 7 numaralı olayları aldı ve ikisi de kayıp oldu. `collapse` 1 ve 2 numaralı olayları eledi, çünkü aynı anahtarla 3 ve 6 gelmişti; bu ikisi ezilendir. `resend` 5 numaralı olayı iki kez ilettiği için o yinelenendir. Dokunulmayan 3, 6 ve 8 birebir kaldı. Beş olayın yolu bir işlevin eliyle değişti; kural işlevi görmez, yalnız sonucu sayar. Aynı dağılım başka işlevlerle de çıkabilirdi ve kural bunları ayıramazdı.

**SY1.** Model, bir olayın ulaşmasını alıcının listesinde görünmesi diye kurar. Platformda olayın gösterilmesi ekranın açık olmasına, uygulamanın ön planda olmasına ve sistemin güç kısıtlarına da bağlıdır; bunlar modelde yoktur.

#### Kapalı Kanal ve Verilmemiş İzin

Bu dersin teslimi **kayıp**tır. Kullanıcı bir kanalı kapattıysa o kanala gönderilen kullanıcı bildirimi gösterilmez. İzin hiç verilmediyse hiçbir kanal kullanıcı bildirimi gösteremez. Çekirdekte kampanya kanalı kapalıydı ve iki olay kayıp oldu. Şimdi izni verilmemiş bir uygulama için aynı süzmeyi bir işleve alıyoruz.

```kotlin
// Channel.kt — kanal ve izin durumuna gore gosterilebilecek kullanici bildirimlerini suzer
fun deliverable(sent: List<Signal>, closed: Set<String>, permitted: Boolean): List<Signal> =
    if (!permitted) emptyList()
    else sent.filter { it.key.substringBefore('.') !in closed }
```

Dört olay gönderiyoruz. Hiçbir kanal kapalı değil, ama izin verilmedi.

```kotlin
// Main.kt — izin verilmemisse hicbir kanal kullanici bildirimi gostermez
fun main() {
    val sent = listOf(
        Signal("mesaj.ayse", 9),
        Signal("odeme.iade", 10),
        Signal("hatirlatma.toplanti", 11),
        Signal("kampanya.hafta", 12),
    )
    val received = deliverable(sent, emptySet(), permitted = false)
    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-9: anahtar=mesaj.ayse | kopya=0 -> kayip
olay-10: anahtar=odeme.iade | kopya=0 -> kayip
olay-11: anahtar=hatirlatma.toplanti | kopya=0 -> kayip
olay-12: anahtar=kampanya.hafta | kopya=0 -> kayip
birebir: 0
yinelenen: 0
ezilen: 0
kayip: 4
```

Kanal kapalı olduğunda da izin verilmediğinde de sonuç aynı kategoridir. Kural bir olayın neden ulaşmadığını görmez; yalnız ulaşmadığını ve yerini alan bir olay olmadığını görür.

K02'de `karsiliksiz`, uygulamanın bir isteğine platformun verdiği yanıttı: istenen izin verilmemişti. Burada kayıp başka bir şeydir: platformun yanıtı değil, gönderilen olayın alıcıya varmamasıdır. İzin reddi bu ikisini birbirine bağlar, ama ölçtükleri şey aynı değildir. Biri isteğin sonucunu, öteki olayın kopya sayısını sayar.

**SY2.** Model, izni tek bir doğru ya da yanlış değer olarak kurar. Platformda kullanıcı izni sonradan ayarlardan kapatabilir, kanalın önem düzeyini düşürebilir ya da yalnız belirli bir kanalı susturabilir; bunların hiçbiri modelde ayrı bir durum değildir.

#### Önem Düzeyi Sessizdir, Kayıp Değildir

Önem düzeyi kanalın ne kadar dikkat çekeceğini söyler, ulaşıp ulaşmayacağını söylemez. Yüksek düzeyli kanal sesle ve ekranın üstüne düşerek gelir. Düşük düzeyli kanal ses çıkarmadan listeye eklenir. Yalnız en alttaki düzey, yani kanalı kapatan düzey, kullanıcı bildirimini hiç göstermez. Modelde düzeyleri `Map` ile kuruyoruz: 4 sesli, 2 sessiz, 0 kapalı. Bu sayılar platformdaki düzey sabitlerinin değerleriyle aynıdır; model yalnız bu üç düzeyi alır.

```kotlin
// Importance.kt — kanalin onem duzeyine gore yalniz kapali kanali eler, sessiz kanali eletmez
fun byImportance(sent: List<Signal>, levels: Map<String, Int>): List<Signal> =
    sent.filter { (levels[it.key.substringBefore('.')] ?: 0) > 0 }
```

Üç kanal, dört olay: mesaj kanalı sesli, hatırlatma kanalı sessiz, kampanya kanalı kapalı.

```kotlin
// Main.kt — sessiz kanaldaki kullanici bildirimi ulasir, kapali kanaldaki ulasmaz
fun main() {
    val sent = listOf(
        Signal("mesaj.nur", 13),
        Signal("hatirlatma.su", 14),
        Signal("kampanya.gun", 15),
        Signal("hatirlatma.uyku", 16),
    )
    val levels = mapOf("mesaj" to 4, "hatirlatma" to 2, "kampanya" to 0)
    val received = byImportance(sent, levels)
    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-13: anahtar=mesaj.nur | kopya=1 -> birebir
olay-14: anahtar=hatirlatma.su | kopya=1 -> birebir
olay-15: anahtar=kampanya.gun | kopya=0 -> kayip
olay-16: anahtar=hatirlatma.uyku | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 0
kayip: 1
```

İki sessiz hatırlatma da birebir ulaştı. Sessiz olmak kopya sayısını değiştirmez; kural sessiz kullanıcı bildiriminin fark edilip edilmediğini görmez, yalnız alıcıya bir kopya ulaştığını görür. Kayıp olan tek olay kapalı kanaldaki kampanyadır.

**SY3.** Model, önem düzeyini kanal başına tek bir sayı olarak kurar. Platformda kullanıcı düzeyi sonradan ayarlardan değiştirebilir ve düzeyin sese, titreşime ve ekranın üstüne düşmeye etkisi sürüme göre değişir; bunlar modelde yoktur.

#### Kapıları Kim Kapatır

Dört kapının her birinin ayrı bir sahibi vardır ve sahibin kim olduğu, kaybın nasıl önleneceğini belirler. İzin kapısını kullanıcı açar ve uygulama yalnızca isteyebilir; kullanıcı iki kez reddettiğinde sonraki istekler pencere göstermeden reddedilir, yani uygulama bu kapıyı kendi başına yeniden açamaz. Kanal kapısını da kullanıcı kapatır: uygulama kanalı oluşturur, ama kapatılmış bir kanalı kod yoluyla açamaz. Önem düzeyi kapısında uygulama ilk kararı verir, kullanıcı sonradan değiştirir. Eylem kapısı yalnızca uygulamanındır.

Bu dağılım, bir uygulamanın kayıp kullanıcı bildirimleriyle ne yapabileceğini sınırlar. İzin ya da kanal yüzünden kaybolan bir kullanıcı bildirimini uygulama geri getiremez. Yapabileceği şey, kapının kapalı olduğunu bilmek ve kullanıcıya ayarları göstermektir. Kanalın ya da iznin durumu sorgulanabilir; kullanıcı bildirimi göndermeden önce bu durumu kontrol eden bir uygulama, boşa gönderim yapmaz ve kullanıcıya neden sessiz kaldığını söyleyebilir. Ölçümdeki kayıp olaylar, uygulamanın bu kontrolü yapmadığı durumun sayısal karşılığıdır.

Kanalı ayrıntılı kurmanın nedeni de buradadır. Tek bir kanalda her şeyi gönderen uygulama, kullanıcıya iki seçenek bırakır: hepsini al ya da hiçbirini alma. Kullanıcı yalnızca kampanya kullanıcı bildirimlerinden rahatsızsa ve kampanyalar sipariş durumlarıyla aynı kanaldaysa, kanalı kapatınca sipariş durumlarını da kaybeder. Konuya göre ayrılmış kanallar, kullanıcının kaybettiği şeyi seçebilmesini sağlar.

#### Yaygın Yanılgılar

**"İzin verildiyse bütün kullanıcı bildirimleri gösterilir."** İzin yalnızca birinci kapıdır. Kanal kapalıysa, ya da kullanıcı uygulamanın kullanıcı bildirimlerini sistem ayarlarından sessize aldıysa, izne rağmen kullanıcı bildirimi ya hiç gösterilmez ya da dikkat çekmez.

**"Sessiz kullanıcı bildirimi kaybolmuş demektir."** Sessiz kanala düşen kullanıcı bildirimi ulaşır ve birebirdir. Yalnızca ses ve ekran uyarısı yoktur. Ölçümde düşük önem düzeyindeki olaylar bu yüzden kayıp sayılmadı.

**"Uygulama kanalın önem düzeyini sonradan yükseltebilir."** Kanal oluşturulduktan sonra önem düzeyini yalnızca kullanıcı değiştirebilir. Uygulama aynı kimlikle kanalı yeniden oluşturmayı denerse platform kullanıcının değiştirdiği ayarları korur ve önem düzeyini yükseltmez; ad ve açıklama güncellenir. Düzey değişecekse yeni bir kanal kimliği gerekir, ama bu, eski kanalı kullanan kullanıcının ayarını sıfırlar.

**"Kayıp, gönderim kodundaki bir hatadır."** Gönderim kodu kusursuz çalışabilir ve olay yine kayıp olabilir; çünkü kayıp alıcı tarafındaki bir kapıda olur. Kural gönderimin başarısını değil, ulaşmayı sayar.

#### Sınırlayıcı Ölçüm

Ulaşmayan olay, yerine geçen bir olay yoksa kayıp diye adlandırılır. Yerine geçen bir olay varsa ad değişir. Platformda `notify` aynı kimlikle ikinci kez çağrılırsa ikinci kullanıcı bildirimi birincinin yerini alır; liste iki satır değil bir satır gösterir. Modelde kullanıcı bildiriminin kimliği anahtardır.

```kotlin
// Main.kt — ayni kimlikle gonderilen ikinci kullanici bildirimi birincinin yerini alir
fun replaceById(sent: List<Signal>): List<Signal> =
    sent.filter { s -> sent.none { it.key == s.key && it.seq > s.seq } }

fun main() {
    val sent = listOf(
        Signal("siparis.42", 17),
        Signal("mesaj.deniz", 18),
        Signal("siparis.42", 19),
        Signal("hatirlatma.ilac", 20),
    )
    val received = replaceById(sent)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [18, 19, 20]
olay-17: anahtar=siparis.42 | kopya=0 -> ezilen
olay-18: anahtar=mesaj.deniz | kopya=1 -> birebir
olay-19: anahtar=siparis.42 | kopya=1 -> birebir
olay-20: anahtar=hatirlatma.ilac | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 1
kayip: 0
```

17 numaralı olay ezilen oldu: aynı anahtarla 19 numaralı olay ulaştı ve onun yerini aldı. Kuralın görmediği şey, 17 numaralı kullanıcı bildiriminin kullanıcıya bir an için gösterilip gösterilmediğidir. Alıcının listesinde yalnız 19 vardır ve kural yalnız bunu okur. Bu, kayıpla ezilen arasındaki farkı da verir: ilk olay hiç gösterilmemiş olsa da sonuç aynıdır, çünkü fark olayın ulaşıp ulaşmadığında değil, sonra gelen bir olayın ulaşıp ulaşmadığındadır.

**SY4.** Model, aynı kimlikli ikinci kullanıcı bildirimini birincinin anında silinmesi diye kurar. Platformda ikinci kullanıcı bildirimi birincinin yerine geçer, ama değiştirmenin görünür olması ve sesin yeniden çalıp çalmaması kanalın ve kullanıcı bildiriminin ayarına bağlıdır.

#### Özet

- Bu dersin teslimi: kayıp. Kapatılmış bir kanala ya da izni verilmemiş bir uygulamaya gönderilen kullanıcı bildirimi alıcıya hiç ulaşmaz.
- Kullanıcı bildirimi kanal, önem düzeyi, eylem ve izinle kurulur; model bu kuralı `Signal` listeleriyle kurar, platformu kurmaz.
- Sekiz olay 3 birebir, 1 yinelenen, 2 ezilen ve 2 kayıp ulaştı; bu dağılım kurs boyunca referanstır.
- İzin verilmediğinde dört olayın dördü kayıp oldu: kayip 4. Kapalı kanalla aynı kategoridir.
- Sessiz kanaldaki kullanıcı bildirimi ulaşır: üç birebir, bir kayıp. Önem düzeyi kullanıcı bildiriminin ne kadar dikkat çektiğini değiştirir, teslimini değiştirmez.
- Aynı kimlikli ikinci kullanıcı bildirimi birincinin yerini aldı: ezilen 1. Kural yerini alanı görür, ilkinin gösterilip gösterilmediğini görmez.

#### Sonraki Adım

Bu derste olay uygulamanın kendi içinden kullanıcı bildirimi olarak doğuyordu. Çoğu zaman olay sunucudan çıkar ve aygıta bir servis üzerinden gelir. Sıradaki ders, `Bulut Mesajlaşma`, aygıt çevrimdışıyken aynı daraltma anahtarıyla biriken iletilerden hangisinin ulaştığını ve ulaşmayanın neden kayıp değil ezilen olduğunu ölçer.

### Bulut Mesajlaşma

Source: https://academia.sh/tr/kurslar/android-servisler/bulut-mesajlasma

Bulut mesajlaşma servisi sunucudan aygıta iletiyi aygıt belirteciyle taşır; daraltma anahtarı ve yaşam süresi iletinin akıbetini belirler. Ders çevrimdışı aygıtta biriken iletilerden hangisinin ulaştığını ve süresi dolan iletinin teslimini ölçer.

Önceki derste olay uygulamanın kendi içinde bir kullanıcı bildirimi olarak doğuyordu. Çoğu zaman olay sunucudan çıkar: sipariş durumu değişir, yeni bir mesaj gelir, bir ödeme onaylanır. Sunucu ile aygıt arasında bir servis durur ve ileti ona emanet edilir. Aygıt kapalıysa ya da ağdan uzaksa servis iletiyi bekletir. Bu derste de aynı soruyu soruyoruz: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?**

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

Sınıflandırma ilk derstekiyle aynıdır. Kural alıcının listesinde aynı sırayı taşıyan kopyaları sayar ve hiç kopya yoksa aynı anahtarla daha sonraki bir olayın ulaşıp ulaşmadığına bakar. İçeriği ve nedeni görmez.

#### Aygıt Belirteci ve Yük

Bulut mesajlaşma servisinde sunucu iletiyi bir aygıta değil, o aygıttaki uygulamayı tanıtan aygıt belirtecine (device token) gönderir. Belirteç uygulamanın kurulumuna aittir ve zamanla yenilenebilir; uygulama yeni belirteci alınca sunucuya bildirmek zorundadır. Yenilenmeden önceki belirtece gönderilen ileti yeni kurulumda bir alıcı bulamaz.

İletinin yükü (payload) iki türdendir. Veri yükü, uygulamanın kendi anahtar ve değerlerini taşır ve uygulamanın alıcı sınıfına gelir; ne yapılacağına uygulama karar verir. Bildirim yükü ise başlık ve metin taşır ve uygulama arka plandayken platform onu kendisi bir kullanıcı bildirimi olarak gösterebilir. Önceki dersin kanalı ve izni bu ikinci yolda yine kapıdadır.

```kotlin
// Platform kodu: OrderMessageReceiver
import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage

class OrderMessageReceiver : FirebaseMessagingService() {
    override fun onNewToken(token: String) {
        Log.d("siparis", "belirtec yenilendi")
        sendTokenToServer(token)
    }

    override fun onMessageReceived(message: RemoteMessage) {
        val orderId = message.data["siparis"]
        showOrder(orderId)
    }
}
```

Servisin alıcı sınıfı iki yöntem tanımlar. `onNewToken` belirteç yenilenince çağrılır, `onMessageReceived` veri yüküyle gelen iletiyi uygulamaya verir. Bu parça platform kodudur ve burada çıktı, süre ya da ulaşma sayısı iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

#### Veri Yükü ve Bildirim Yükü

İki yükün farkı, iletiyi kimin işleyeceğidir. Veri yükü yalnızca anahtar ve değer çiftleri taşır; platform onu göstermeye çalışmaz, uygulamanın alıcı sınıfına verir. Uygulama o anda ne yapacağına kendi karar verir: bir kullanıcı bildirimi gösterebilir, yerel bir veritabanını güncelleyebilir ya da hiçbir şey yapmayabilir. Bildirim yükü ise başlık ve metin taşır. Uygulama arka plandayken platform bu yükü kendisi bir kullanıcı bildirimi olarak gösterebilir ve bu durumda uygulamanın kodu çalışmaz.

Bu ayrımın teslim açısından bir sonucu vardır. Veri yüküyle gelen ileti alıcıya ulaştığında alıcının kodu çalışır ve ulaşma, alıcı listesinde görünmek demektir; bu dersin modeli de ulaşmayı böyle tanımlar. Bildirim yükünün platform tarafından gösterildiği yolda ise ileti alıcının kodundan hiç geçmeyebilir. Önceki dersin kanal ve izin kapıları o yolda da geçerlidir: kapalı bir kanala ya da verilmemiş izne düşen bildirim yükü, ileti servisten çıkmış olsa bile kullanıcıya ulaşmaz. Bu yüzden "sunucu iletiyi gönderdi" ile "kullanıcı iletiyi gördü" arasında üç ayrı kapı vardır: servis, alıcı sınıfı ve kullanıcı bildirimi kapıları.

#### Daraltma Anahtarı

Aygıt çevrimdışıyken sunucu ileti göndermeye devam edebilir. Servis bunları aygıt için bekletir. Gönderen iletiye bir daraltma anahtarı (collapse key) koymuşsa, aynı anahtarı taşıyan bekleyen iletilerden yalnız en yenisi aygıt çevrimiçi olunca teslim edilir. Önceki durum iletileri için bu yararlıdır: sipariş üç kez güncellendiyse kullanıcının ilk ikisini görmesine gerek yoktur.

Modelde ileti `Message` sınıfıdır. Daraltma anahtarı varsa olayın anahtarı odur. Yoksa ileti yalnızca kendi kimliğiyle tanınır ve kimlik de anahtar olur; bu yüzden daraltılacak bir eşi yoktur.

```kotlin
// Message.kt — sunucunun gonderdigi iletiyi kurar, daraltma anahtari yoksa kimlik anahtar olur
class Message(val id: String, val seq: Int, val collapseKey: String? = null) {
    fun toSignal(): Signal = Signal(collapseKey ?: id, seq)
}
```

Aygıt çevrimdışıyken biriken iletilerde aynı anahtarlıların yalnız sonuncusu kalır.

```kotlin
// Offline.kt — cevrimdisi aygitta biriken iletilerden her anahtarin yalniz sonuncusunu birakir
fun collapseOffline(pending: List<Signal>): List<Signal> =
    pending.filter { s -> pending.none { it.key == s.key && it.seq > s.seq } }
```

Beş ileti gönderiyoruz. Sipariş durumu üç kez, sohbet mesajı ve günlük haber birer kez.

```kotlin
// Main.kt — cevrimdisi aygitta ayni daraltma anahtariyla biriken iletilerden yalniz sonuncusu ulasir
fun main() {
    val messages = listOf(
        Message("m1", 1, "siparis.42"),
        Message("m2", 2, "mesaj.deniz"),
        Message("m3", 3, "siparis.42"),
        Message("m4", 4, "siparis.42"),
        Message("m5", 5, "haber.gunluk"),
    )
    val sent = messages.map { it.toSignal() }
    val received = collapseOffline(sent)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [2, 4, 5]
olay-1: anahtar=siparis.42 | kopya=0 -> ezilen
olay-2: anahtar=mesaj.deniz | kopya=1 -> birebir
olay-3: anahtar=siparis.42 | kopya=0 -> ezilen
olay-4: anahtar=siparis.42 | kopya=1 -> birebir
olay-5: anahtar=haber.gunluk | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 2
kayip: 0
```

Bu dersin teslimi **ezilen**dir. Sipariş durumunun 1 ve 3 numaralı güncellemeleri hiç teslim edilmedi, ama aynı anahtarla 4 numaralı güncelleme ulaştı ve onların yerini aldı. Kullanıcı yalnız en son durumu görür. İki ezilen olay, sipariş anahtarının üç iletisinden iki tanesidir.

K04'te ölçtüğümüz köken, bir okumanın yanıtının zincirin hangi basamağından geldiğini söylüyordu. Ezilen bir köken değildir: bir yanıtın nereden geldiğini değil, bir olayın yerini başka bir olayın aldığını söyler. Orada bir okuma hep yanıt buldu; burada bir olay hiç ulaşmayabilir.

**SY1.** Model, aygıtın çevrimdışı olduğu aralığı tek bir birikme anı olarak kurar: iletiler birikir ve tek hamlede teslim edilir. Platformda aygıt aralıklı bağlanabilir, iletiler birden çok kez teslim turuna girebilir ve hangi iletinin ne zaman gideceği servise aittir; bunlar modelde yoktur.

#### Yaşam Süresi

İletinin bir yaşam süresi (time to live, TTL) vardır. Süre dolduğunda servis iletiyi artık teslim etmez. Aygıt bu süre içinde çevrimiçi olmazsa ileti ulaşmaz. Modelde zaman gerçek saat değil, sıra numarasıyla aynı birimde bir adımdır: ileti `seq` adımında gönderilir, aygıt `onlineAt` adımında bağlanır ve yaşam süresi tamsayı bir adım sayısıdır. Bu sayılar modelin uydurma değerleridir; platformun gerçek süreleri değildir.

Aşağıdaki işlev, aygıt bağlandığında yaşam süresi dolmuş iletileri eler.

```kotlin
// Main.kt — yasam suresi aygit cevrimici olmadan dolan ileti ulasmaz
fun expire(sent: List<Signal>, onlineAt: Int, ttl: Int): List<Signal> =
    sent.filter { onlineAt - it.seq <= ttl }

fun main() {
    val messages = listOf(
        Message("m6", 6, "haber.spor"),
        Message("m7", 7, "siparis.43"),
        Message("m8", 8, "hatirlatma.ilac"),
        Message("m9", 9, "mesaj.nur"),
        Message("m10", 10, "siparis.43"),
    )
    val sent = messages.map { it.toSignal() }
    val received = collapseOffline(expire(sent, onlineAt = 13, ttl = 4))
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [9, 10]
olay-6: anahtar=haber.spor | kopya=0 -> kayip
olay-7: anahtar=siparis.43 | kopya=0 -> ezilen
olay-8: anahtar=hatirlatma.ilac | kopya=0 -> kayip
olay-9: anahtar=mesaj.nur | kopya=1 -> birebir
olay-10: anahtar=siparis.43 | kopya=1 -> birebir
birebir: 2
yinelenen: 0
ezilen: 1
kayip: 2
```

Yaşam süresi dolan üç ileti vardı ve sonuçları farklı. 6 numaralı haber ve 8 numaralı hatırlatma kayıp oldu: ne kopyaları ne de aynı anahtarlı sonraki bir olay ulaştı. 7 numaralı sipariş ise süresi dolduğu hâlde ezilen sayıldı, çünkü aynı anahtarla 10 numaralı güncelleme ulaştı. Kural iletinin süresinin dolduğunu görmez; yalnız ulaşıp ulaşmadığına ve yerini bir olayın alıp almadığına bakar.

**SY2.** Model, yaşam süresini tek bir tamsayı olarak kurar ve süresi dolan iletiyi listeden çıkarır. Platformda yaşam süresinin üst sınırı servise bağlıdır ve sınırın üstündeki bir değerin nasıl ele alındığı modelde yoktur.

#### En Az Bir Kez Teslim

Daraltma ve yaşam süresi iletiyi azaltır. Servisin üçüncü bir davranışı iletiyi çoğaltabilir. Servis iletiyi aygıta teslim ederken bir onay bekler; onay gelmezse iletiyi yeniden gönderir. Bu düzene en az bir kez teslim denir: ileti hiç kaybolmaz ama bir defadan fazla gelebilir. Karşıtı en çok bir kez teslimdir: ileti en fazla bir defa gelir ama hiç gelmeyebilir. Servis kaybı önlemeyi seçtiği için ilkini sunar ve yinelenmenin bedelini alıcıya bırakır.

Modelde yeniden gönderim, onayı gelmemiş sıraların listenin sonuna bir kopya olarak eklenmesidir.

```kotlin
// Retry.kt — onayi gelmeyen iletiyi servis bir kez daha gonderir
fun retry(sent: List<Signal>, unacked: List<Int>): List<Signal> =
    sent + unacked.map { seq -> sent.first { it.seq == seq } }
```

Dört ileti gönderiyoruz; ikisinin onayı gelmiyor. İletilerin daraltma anahtarı yok, kimlikleri anahtar olur.

```kotlin
// Main.kt — onayi gelmeyen iki ileti yeniden gonderilince iki kopya olarak ulasir
fun main() {
    val messages = listOf(
        Message("m16", 16),
        Message("m17", 17),
        Message("m18", 18),
        Message("m19", 19),
    )
    val sent = messages.map { it.toSignal() }
    val received = retry(sent, unacked = listOf(17, 18))
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [16, 17, 18, 19, 17, 18]
olay-16: anahtar=m16 | kopya=1 -> birebir
olay-17: anahtar=m17 | kopya=2 -> yinelenen
olay-18: anahtar=m18 | kopya=2 -> yinelenen
olay-19: anahtar=m19 | kopya=1 -> birebir
birebir: 2
yinelenen: 2
ezilen: 0
kayip: 0
```

17 ve 18 numaralı iletiler iki kopya olarak ulaştı ve yinelenen sayıldı; 16 ve 19 tek kopya ile birebir kaldı. Bu dersin teslimi ezilen olsa da aynı servis yinelenen de üretebilir; kategori iletiye değil, iletinin servisten geçerken yaşadığı olaya bağlıdır. Alıcının çaresi tekilleştirmedir: ileti kimliğini saklar ve ikinci gelişte işlemez. Bu, derin bağlantılar dersinde işlendi işaretiyle ayrıca ölçeceğimiz düzenin aynısıdır.

**SY3.** Model, onayın gelmemesini tek bir sıra için kesin ve bir kez tekrarlanan bir olay olarak kurar. Platformda yeniden gönderimin sayısı ve aralığı servise aittir ve onayın neden gelmediği modelde yoktur.

#### Yaygın Yanılgılar

**"Daraltma anahtarı iletiyi öne alır."** Daraltma anahtarı sıralamayı ya da önceliği değiştirmez; yalnızca aynı anahtarlı bekleyen iletilerden hangisinin kalacağını belirler. Öncelik ayrı bir ayardır.

**"Süresi dolan ileti yine de gecikmeli gelir."** Yaşam süresi dolduğunda servis iletiyi teslim etmez. Ölçümde 6 ve 8 numaralı iletiler bu yüzden kayıp oldu; gecikmeli gelen bir kopya yoktu. Gelmesi gereken bir ileti için yaşam süresi, aygıtın çevrimdışı kalabileceği süreden uzun seçilmelidir.

**"Daraltılan ileti silinmiştir, o hâlde hata vardır."** Ezilen bir iletinin yerini aynı anahtarlı daha yeni bir ileti almıştır. Bu hata değil, gönderenin istediği davranıştır. Hata, aynı anahtarın farklı iki konuya verilmesidir: bu durumda biri gerçekten gerekli olsa bile diğeri onun yerini alır.

**"Aygıt belirteci kalıcı bir kimliktir."** Belirteç, uygulama yeniden kurulduğunda ya da kullanıcı verileri temizlediğinde değişebilir. Sunucu eski belirteci saklamaya devam ederse gönderilen iletiler hiçbir aygıta ulaşmaz ve sunucu tarafında bu, başarılı gönderim gibi görünebilir. Alıcı tarafın görevi yeni belirteci sunucuya bildirmektir.

**"Sunucu iletiyi gönderdiyse kullanıcı görür."** Sunucudan çıkan ileti servisten, alıcı sınıfından ve kullanıcı bildirimi kapılarından geçmek zorundadır. Kural bu kapıların hangisinde kaybolduğunu görmez; yalnızca sonucu sayar.

#### Sınırlayıcı Ölçüm

Daraltma her ileti için çalışmaz: yalnız daraltma anahtarı taşıyan iletileri birleştirir. Anahtarı olmayan iletiler biriktiğinde hiçbiri bir diğerinin yerini almaz.

```kotlin
// Main.kt — daraltma anahtari tasimayan iletiler biriktikten sonra her biri ulasir
fun main() {
    val messages = listOf(
        Message("sohbet.1", 11),
        Message("durum.1", 12, "durum.cevrimici"),
        Message("sohbet.2", 13),
        Message("durum.2", 14, "durum.cevrimici"),
        Message("sohbet.3", 15),
    )
    val sent = messages.map { it.toSignal() }
    val received = collapseOffline(sent)
    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-11: anahtar=sohbet.1 | kopya=1 -> birebir
olay-12: anahtar=durum.cevrimici | kopya=0 -> ezilen
olay-13: anahtar=sohbet.2 | kopya=1 -> birebir
olay-14: anahtar=durum.cevrimici | kopya=1 -> birebir
olay-15: anahtar=sohbet.3 | kopya=1 -> birebir
birebir: 4
yinelenen: 0
ezilen: 1
kayip: 0
```

Üç sohbet iletisi, kimlikleri farklı olduğu için, biriktikten sonra tek tek ve birebir ulaştı. Aynı daraltma anahtarını taşıyan iki durum iletisinden yalnız ikincisi ulaştı; ilki ezilen oldu. Sohbet için bu doğru davranıştır: her mesaj okunmak ister. Durum için de doğrudur: kullanıcının çevrimiçi olduğunu iki kez duymasının anlamı yoktur. Hangi iletiye anahtar konacağı gönderenin kararıdır, kural ise yalnız sonucu sayar.

**SY4.** Model, anahtarsız iletinin kimliğini benzersiz varsayar. Platformda gönderenin iki farklı iletiye aynı kimliği vermesi mümkündür ve o durumda servisin ne yapacağı modelde yoktur.

#### Özet

- Bu dersin teslimi: ezilen. Çevrimdışı aygıtta aynı daraltma anahtarıyla biriken iletilerden yalnız sonuncusu ulaşır ve öncekiler yerini başka bir iletiye bırakır.
- Aygıt belirteci uygulamanın kurulumunu tanıtır; iletinin yükü veri ya da bildirim yüküdür ve önceki dersin kanal ve izin kapıları ikinci yolda yine geçerlidir.
- Beş iletiden ikisi ezilen oldu: sipariş durumunun ilk iki güncellemesi. Köken değil teslimdir; bir olayın yerini başka bir olay almıştır.
- Yaşam süresi dolan iki ileti kayıp, bir ileti ezilen oldu: kayip 2, ezilen 1. Kural süre dolduğunu değil, ulaşıp ulaşmadığını görür.
- Daraltma anahtarı taşımayan üç ileti birebir ulaştı; aynı anahtarlı iki durum iletisinden biri ezilen oldu.
- Servis en az bir kez teslim eder: onayı gelmeyen iki ileti yinelenen oldu, yinelenen 2. Alıcının çaresi tekilleştirmedir.

#### Sonraki Adım

Bu derste olay sunucudan çıkıp aygıta tek bir yolla gitti. Konum sağlayıcısında olay aygıtın içinde, sürekli ve sıklıkla üretilir; alıcı hepsini işleyemeyebilir. Sıradaki ders, `Konum ve Haritalar`, en küçük aralık kuralının hangi konum güncellemesini düşürdüğünü ve izin geri alınınca sonraki güncellemelere ne olduğunu ölçer.

### Konum ve Haritalar

Source: https://academia.sh/tr/kurslar/android-servisler/konum-ve-haritalar

Konum sağlayıcısı konum güncellemelerini bir geri çağrıyla verir; en küçük aralık kuralı hangi güncellemenin alıcıya ulaşacağını belirler, çalışma zamanı izni geri alınınca sonraki güncellemeler gelmez. Ders bu iki kuralın teslimini ölçer.

Önceki derste olay bir sunucudan aygıta geliyordu ve servis biriken iletileri daraltıyordu. Konumda olay aygıtın içinde doğar: sağlayıcı kullanıcının yerini sürekli ölçer ve her ölçümü bir konum güncellemesi olarak uygulamaya verir. Bir kurye uygulamasında bu saniyelerce süren bir akıştır ve alıcının hepsine ihtiyacı yoktur. Bu derste de aynı soruyu soruyoruz: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?**

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

Sınıflandırma ilk derslerdekiyle aynıdır. Kural alıcının listesinde aynı sırayı taşıyan kopyaları sayar; hiç kopya yoksa aynı anahtarla daha sonraki bir güncellemenin ulaşıp ulaşmadığına bakar. İçeriği ve nedeni görmez.

#### İzin ve Sağlayıcı

Konum, çalışma zamanı izni isteyen bir yetenektir. Bildirim dosyasında iki izin tanımlanır: kesin konum ve yaklaşık konum. Kullanıcı yalnız yaklaşık konumu verebilir ya da hiçbirini vermeyebilir; izin verildikten sonra ayarlardan da geri alınabilir. Uygulama bu üç durumu da ele almak zorundadır.

```xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
```

Konum sağlayıcısı güncellemeleri bir geri çağrıyla verir. Uygulama en küçük aralığı isteğe yazar ve sağlayıcı bu aralıktan sık güncelleme üretmez.

```kotlin
// Platform kodu: CourierTracker
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager

class CourierTracker(private val manager: LocationManager) : LocationListener {
    fun start() {
        manager.requestLocationUpdates(
            LocationManager.GPS_PROVIDER, 5000L, 10f, this
        )
    }

    override fun onLocationChanged(location: Location) {
        publish(location.latitude, location.longitude)
    }
}
```

Parça iki sayı verir: milisaniye cinsinden en küçük süre aralığı ve metre cinsinden en küçük uzaklık. İkisi de uygulamanın istediği değerlerdir; sağlayıcının gerçekte ne sıklıkla ürettiğini bu parça söylemez. Model, platformun bu kuralını kurar; model platformun kendisi değildir. Harita görünümü bu modelin dışındadır: çizim bir teslim sorusu değil, bir görüntüleme sorusudur ve bu derste ölçülmez.

#### En Küçük Aralık Kuralı

Modelde güncelleme bir `Signal`dır ve anahtarı kuryenin kimliğidir. Sıra numarası güncellemenin üretilme adımıdır. En küçük aralık kuralı, son kabul edilen güncellemeden bu yana yeterince adım geçmemişse güncellemeyi düşürür. Aralığın sayısı modelin uydurma değeridir; sağlayıcının gerçek aralığı değildir.

```kotlin
// Sampling.kt — en kucuk aralik kuralini ve iznin geri alinmasini kurar
fun sample(sent: List<Signal>, minGap: Int): List<Signal> {
    val kept = mutableListOf<Signal>()
    for (s in sent) {
        if (kept.isEmpty() || s.seq - kept.last().seq >= minGap) kept.add(s)
    }
    return kept
}

fun revoke(delivered: List<Signal>, revokedAt: Int): List<Signal> =
    delivered.filter { it.seq < revokedAt }
```

Sekiz güncelleme üretiyoruz, aralık üç adım.

```kotlin
// Main.kt — en kucuk aralik kurali arada kalan konum guncellemelerini dusurur
fun main() {
    val sent = (1..8).map { Signal("konum.kurye", it) }
    val received = sample(sent, minGap = 3)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [1, 4, 7]
olay-1: anahtar=konum.kurye | kopya=1 -> birebir
olay-2: anahtar=konum.kurye | kopya=0 -> ezilen
olay-3: anahtar=konum.kurye | kopya=0 -> ezilen
olay-4: anahtar=konum.kurye | kopya=1 -> birebir
olay-5: anahtar=konum.kurye | kopya=0 -> ezilen
olay-6: anahtar=konum.kurye | kopya=0 -> ezilen
olay-7: anahtar=konum.kurye | kopya=1 -> birebir
olay-8: anahtar=konum.kurye | kopya=0 -> kayip
birebir: 3
yinelenen: 0
ezilen: 4
kayip: 1
```

Bu dersin teslimi **ezilen**dir. 2 ve 3 numaralı güncellemeler hiç ulaşmadı, ama aynı anahtarla 4 numaralı güncelleme ulaştı ve onların yerini aldı; 5 ve 6 için 7 numaralı güncelleme aynı işi yaptı. Konum için bu istenen davranıştır: yeni konum eskisinin yerini alır. Sekizinci güncelleme ise kayıp oldu, çünkü ondan sonra aynı anahtarla hiçbir güncelleme gelmedi. Kural son güncellemenin düşmesini ezilen saymaz; son durum kullanıcıya hiç gösterilmemiştir.

**SY1.** Model, kuryeyi tek bir anahtarla kurar ve güncellemeleri eşit aralıklı adımlara koyar. Platformda sağlayıcı güncellemeleri düzensiz aralıkla üretir, pil tasarrufu için aralığı uzatabilir ve süre ve uzaklık eşikleri birlikte uygulanır, yani ikisi de sağlanmadan güncelleme gelmez; bunlar modelde yoktur.

#### Kesin ve Yaklaşık Konum

İki izin iki ayrı duyarlık düzeyidir. Kesin konum, uydu ve ağ kaynaklarını birlikte kullanarak sokak düzeyinde bir yer verir; yaklaşık konum ise yalnızca kaba bir bölge verir. Kullanıcı izin ekranında ikisinden birini seçebilir. Yalnızca yaklaşık konumu seçen kullanıcının uygulaması kesin konum isteyen bir çağrı yaptığında, platform isteği yaklaşık düzeye indirir. Çağrı hata vermez; güncellemeler gelir, yalnız duyarlıkları düşüktür.

Bu, teslim modelinin görmediği bir yerdir. Kural bir güncellemenin ulaşıp ulaşmadığını sayar; ulaşan güncellemenin ne kadar kesin olduğunu sormaz. Yaklaşık konumla gelen dört güncelleme ile kesin konumla gelen dört güncelleme kural gözünde aynı sonucu verir: birebir 4. Duyarlık, uygulamanın alıcı tarafında ayrıca denetleyeceği bir özelliktir. Kesin konuma ihtiyaç duyan bir kurye uygulaması, yaklaşık düzeyde gelen bir güncellemeyi kullanıcıya göstermeden önce izin durumuna bakmalı ve kullanıcıdan kesin konumu istemelidir.

#### Arka Planda Konum

Konum güncellemesi ön plandaki uygulama için bir şeydir, arka plandaki uygulama için başka. Uygulama ekrandan çıktığında platform, pil tasarrufu için güncelleme sıklığını kendisi düşürebilir ve ayrı bir arka plan konumu izni olmayan uygulamaya güncelleme hiç vermeyebilir. Bu iki durumun teslim modelindeki karşılığı farklıdır.

Sıklığın düşürülmesi, aralık kuralının büyük bir değerle çalışmasına benzer: arada kalan güncellemeler hiç üretilmez ve ezilen sayılır. Arka plan izninin olmaması ise iznin geri alınmasına benzer: sonraki güncellemeler hiç gelmez ve kayıp sayılır. İki durumda da uygulama bir hata görmez. Kullanıcıya "konumun neden güncellenmediği" sorusunun cevabı, uygulamanın hangi izne sahip olduğunu bilmesinden geçer.

#### Birden Fazla Anahtar

Şimdiye kadar tek bir kurye vardı ve en küçük aralık kuralı bütün güncellemelere tek bir sayaç gibi uygulanıyordu. İki kurye aynı sağlayıcıdan güncelleme aldığında bu hatalı olur: bir kuryenin güncellemesi, diğerinin güncellemesinin aralığı içinde kaldığı için düşürülebilir ve diğer kuryenin yerini bildiren tek güncelleme kaybolur. Doğrusu aralığı anahtar başına uygulamaktır.

```kotlin
// Keys.kt — en kucuk araligi her anahtara ayri uygular
fun sampleByKey(sent: List<Signal>, minGap: Int): List<Signal> =
    sent.groupBy { it.key }.values.flatMap { sample(it, minGap) }
```

İki kurye art arda güncelleme gönderiyor; aralık dört adım.

```kotlin
// Main.kt — aralik her kuryeye ayri uygulanir ve her kurye kendi yerini korur
fun main() {
    val sent = (19..26).map { Signal(if (it % 2 == 1) "konum.kurye1" else "konum.kurye2", it) }
    val received = sampleByKey(sent, minGap = 4)
    println("ulasan siralar: ${received.map { it.seq }.sorted()}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [19, 20, 23, 24]
olay-19: anahtar=konum.kurye1 | kopya=1 -> birebir
olay-20: anahtar=konum.kurye2 | kopya=1 -> birebir
olay-21: anahtar=konum.kurye1 | kopya=0 -> ezilen
olay-22: anahtar=konum.kurye2 | kopya=0 -> ezilen
olay-23: anahtar=konum.kurye1 | kopya=1 -> birebir
olay-24: anahtar=konum.kurye2 | kopya=1 -> birebir
olay-25: anahtar=konum.kurye1 | kopya=0 -> kayip
olay-26: anahtar=konum.kurye2 | kopya=0 -> kayip
birebir: 4
yinelenen: 0
ezilen: 2
kayip: 2
```

Her kurye kendi sayacıyla ilerledi: ikisinin de ilk ve üçüncü güncellemesi birebir ulaştı, aradakiler ezilen oldu ve her kuryenin sonuncusu kayıp oldu. Ezilen güncellemenin yerini alan güncelleme aynı anahtarlıdır; bu yüzden bir kuryenin güncellemesi diğerinin yerini almış sayılmaz. Kayıp iki güncelleme, yani 25 ve 26, aralığın bedelidir: aralıktan sonra gelmeyen güncelleme ezilecek bir ardılı olmadığı için hiç ulaşmaz.

**SY2.** Model, iki kuryeyi iki ayrı anahtar olarak kurar ve güncellemeleri sırayla dağıtır. Platformda her konum isteği kendi aralığını taşır ve aynı sağlayıcıdan birden fazla dinleyici bağımsız aralıklarla güncelleme alabilir.

#### Aralık Bire İnince

Aralığın bire indirilmesi kuralı gevşetir: ardışık iki güncelleme arasında en az bir adım yeter ve bu her zaman sağlanır. Aynı kural, aralık değiştirilerek, ölçümün hiçbir güncellemeyi düşürmediğini gösterir.

```kotlin
// Main.kt — aralik bire inince butun guncellemeler birebir ulasir
fun main() {
    val sent = (9..12).map { Signal("konum.kurye", it) }
    val received = sample(sent, minGap = 1)
    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-9: anahtar=konum.kurye | kopya=1 -> birebir
olay-10: anahtar=konum.kurye | kopya=1 -> birebir
olay-11: anahtar=konum.kurye | kopya=1 -> birebir
olay-12: anahtar=konum.kurye | kopya=1 -> birebir
birebir: 4
yinelenen: 0
ezilen: 0
kayip: 0
```

Dört güncellemenin dördü birebir ulaştı. Bunun bedeli alıcıdadır: alıcı artık her güncellemeyi işlemek zorundadır. Hangi aralığın uygun olduğu uygulamanın kararıdır; kural yalnız sonucu sayar.

**SY3.** Model, alıcının işlem süresini sıfır kabul eder. Platformda yoğun bir alıcı geri çağrıyı geciktirebilir ve bu gecikmenin sonuçları modelde yoktur.

#### İki Eşik ve Dinleyicinin Ömrü

Platform kodundaki parça iki eşik taşıyordu: süre ve uzaklık. Sağlayıcı, ikisi birden sağlanmadan güncelleme vermez; kurye beş saniyede yalnızca iki metre ilerlediyse süre eşiği dolsa bile uzaklık eşiği dolmadığı için güncelleme gelmez. Modelin tek bir adım sayacı kullanması bu yüzden bir sadeleştirmedir. Duran bir kurye için gerçek sağlayıcı hiç güncelleme üretmez ve modeldeki "ezilen" ile "kayıp" ayrımı, durmanın kaybı sayılıp sayılmayacağı sorusunu da gündeme getirir. Durmak bir kayıp değildir: ortada gönderilmiş bir olay yoktur.

Dinleyicinin ömrü ikinci bir kaynaktır. Geri çağrıyı kaydeden uygulama, ekran kapandığında kaydı kaldırmazsa sağlayıcı güncelleme üretmeye devam eder ve alıcı, kullanıcının görmediği bir ekrana işlem yapar. Tersine, kaydı erken kaldıran uygulama kullanıcı geri döndüğünde yeni bir kayıt açana kadar güncelleme almaz ve bu aralıktaki güncellemeler hiç üretilmez. Ders boyunca ölçtüğümüz kayıp ile bu kayıp arasındaki fark, kuralın görebildiği şeydedir: kural yalnızca gönderilmiş olayları sayar; kayıt kaldırıldığı için hiç üretilmeyen bir güncelleme, model açısından, hiç gönderilmemiştir.

Bu ayrımın pratik sonucu şudur: bir güncellemenin gelmemesi, her zaman bir teslim hatası değildir. Uygulama önce güncellemenin üretilip üretilmediğine, sonra üretilen güncellemenin alıcıya ulaşıp ulaşmadığına bakmalıdır. İlk soru sağlayıcının ve kaydın, ikinci soru ise bu dersin ölçtüğü kuralların sorusudur.

#### Yaygın Yanılgılar

**"En küçük aralık güncelleme sıklığını garanti eder."** Aralık bir alt sınırdır, bir söz değildir. Sağlayıcı aralıktan seyrek güncelleme verebilir; pil tasarrufu, zayıf sinyal ya da arka plana geçiş bunu yapar. Aralığı küçültmek güncelleme sayısını artırmaz; yalnızca düşürülecek güncelleme sayısını azaltır.

**"Daha çok güncelleme daha iyi konum demektir."** Her güncelleme alıcıda işlenir. Gereğinden küçük bir aralık, alıcıyı ve pili yorar; ölçümde aralık bire inince bütün güncellemeler birebir ulaştı ama bunun bedeli alıcının her birini işlemesiydi.

**"Kullanıcı izni bir kez verdi, her zaman geçerlidir."** İzin ayarlardan geri alınabilir ve bazı sürümlerde belirli bir süre sonra kendiliğinden sıfırlanabilir. Uygulama her güncelleme isteğinden önce izin durumunu denetlemelidir. İzin yokken geri çağrıyı beklemek, ölçümde olduğu gibi kayıp güncelleme biriktirmekten başka bir şey vermez.

**"Kullanıcı durduysa güncelleme kaybolmuştur."** Duran bir kullanıcı için sağlayıcı yeni güncelleme üretmez; ortada gönderilmiş bir olay olmadığı için kural bir kayıp görmez. Aynı sessizlik, kayıt kaldırıldığında da görülür ve ikisini ayırmak uygulamanın işidir.

**"Harita görünümü konum güncellemesini teslim eder."** Harita yalnızca elindeki konumu çizer. Güncelleme alıcıya ulaşmadıysa harita eski yeri gösterir ve bu bir çizim hatası olarak görünür, oysa kayıp daha önce, sağlayıcı ile alıcı arasında olmuştur.

#### Sınırlayıcı Ölçüm

Aralık kuralı güncellemenin ulaşmasını yalnızca seyreltir. İzin ise ulaşmanın önkoşuludur. Kullanıcı izni ayarlardan geri aldığında sağlayıcı o adımdan sonra güncelleme vermez ve aralık hangi sayıda olursa olsun sonraki güncellemeler gelmez.

```kotlin
// Main.kt — izin geri alinca sonraki guncellemeler ulasmaz
fun main() {
    val sent = (13..18).map { Signal("konum.kurye", it) }
    val received = revoke(sample(sent, minGap = 2), revokedAt = 16)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [13, 15]
olay-13: anahtar=konum.kurye | kopya=1 -> birebir
olay-14: anahtar=konum.kurye | kopya=0 -> ezilen
olay-15: anahtar=konum.kurye | kopya=1 -> birebir
olay-16: anahtar=konum.kurye | kopya=0 -> kayip
olay-17: anahtar=konum.kurye | kopya=0 -> kayip
olay-18: anahtar=konum.kurye | kopya=0 -> kayip
birebir: 2
yinelenen: 0
ezilen: 1
kayip: 3
```

13 ve 15 numaralı güncellemeler ulaştı, 14 ise 15'in yerini aldığı için ezilen sayıldı. İzin 16 numaralı adımda geri alındığı için 16, 17 ve 18 kayıp oldu: sonrasında aynı anahtarla hiçbir güncelleme gelmedi. Kural, kaybın nedeninin geri alınan izin olduğunu görmez. Bunu bilmek için uygulamanın izin durumunu ayrıca denetlemesi gerekir.

**SY4.** Model, iznin geri alınmasını tek bir adımda ve kesin olarak kurar. Platformda izin geri alındığında uygulama sonlandırılabilir, kesin izin yaklaşık izne düşürülebilir ve kullanıcı izni sonra yeniden verebilir; bunlar modelde yoktur.

#### Özet

- Bu dersin teslimi: ezilen. En küçük aralık kuralı arada kalan konum güncellemelerini düşürür ve yerlerini aynı anahtarlı sonraki güncelleme alır.
- Sekiz güncellemeden dördü ezilen oldu, üçü birebir ulaştı, sonuncusu kayıp oldu: birebir 3, ezilen 4, kayip 1.
- Aralık her anahtara ayrı uygulanınca iki kuryenin ilk ve üçüncü güncellemeleri birebir ulaştı, aradakiler ezilen oldu, ikisinin sonuncusu kayıp oldu: birebir 4, ezilen 2, kayip 2.
- Aralık bire inince dört güncellemenin dördü birebir ulaştı; bedel alıcının her güncellemeyi işlemesidir.
- İzin geri alındıktan sonra üretilen üç güncelleme kayıp oldu: kayip 3. Kural nedeni görmez.
- Kesin ve yaklaşık konum ayrı izinlerdir ve kural duyarlığı görmez; harita görünümü bir teslim sorusu değildir ve ölçülmedi.

#### Sonraki Adım

Bu derste olay sürekli bir akıştı ve kural arada kalanları düşürdü. Kamerada olay tek bir kullanıcı eyleminden doğar ve her eylem tek bir sonuç vermelidir. Sıradaki ders, `Kamera ve Medya`, deklanşöre iki kez dokunulduğunda iki ayrı olayın nasıl ulaştığını ölçer.

### Kamera ve Medya

Source: https://academia.sh/tr/kurslar/android-servisler/kamera-ve-medya

Kamera kitaplığı her çekim isteği için tek bir sonuç döndürür; depolama dolu olduğunda sonuç gelmez. Ders çekim isteklerinin teslimini ölçer ve deklanşöre art arda iki kez dokunulduğunda iki ayrı sonucun ulaştığını gösterir.

Önceki derste konum güncellemeleri sürekli bir akıştı ve kural arada kalanları düşürdü. Kamerada olay bir kullanıcı eyleminden doğar: kullanıcı deklanşöre dokunur ve uygulama bir fotoğraf bekler. Bir dokunuşun bir sonuç vermesi beklenir. Bu derste de aynı soruyu soruyoruz: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?**

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

Sınıflandırma önceki derslerdekiyle aynıdır. Kural alıcının listesinde aynı sırayı taşıyan kopyaları sayar; hiç kopya yoksa aynı anahtarla daha sonraki bir olayın ulaşıp ulaşmadığına bakar. İçeriği ve nedeni görmez.

#### İzin ve Çekim İsteği

Kamera da çalışma zamanı izni isteyen bir yetenektir. Bildirim dosyasında tanımlanır ve kullanıcı ilk kullanımda onaylar.

```xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
```

Kamera kitaplığı çekimi iki parçaya böler. Uygulama bir çekim isteği verir; kitaplık sonucu bir geri çağrıyla geri döndürür. Başarıyla biten çekim için dosyanın kaydedildiğini, başarısız olan için bir hata nesnesi bildirir.

```kotlin
// Platform kodu: ShutterController
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException

class ShutterController(private val capture: ImageCapture) {
    fun onShutterTap(options: ImageCapture.OutputFileOptions) {
        capture.takePicture(options, executor, object : ImageCapture.OnImageSavedCallback {
            override fun onImageSaved(result: ImageCapture.OutputFileResults) = openPreview(result)
            override fun onError(error: ImageCaptureException) = logFailure(error)
        })
    }
}
```

Her `takePicture` çağrısı bir sonuç ya da bir hata doğurur. Model, platformun bu kuralını kurar; model platformun kendisi değildir. Bu parça çıktı ya da süre iddia etmez.

#### Her Dokunuş Bir Sonuç

Modelde her çekim isteği bir `Signal`dır. Anahtar fotoğrafın adı, sıra numarası isteğin verildiği adımdır. Kamera kitaplığının modeli her isteğe tek bir sonuç döndürür.

```kotlin
// Camera.kt — her cekim istegine tek bir sonuc dondurur
fun shoot(requests: List<Signal>): List<Signal> =
    requests.map { Signal(it.key, it.seq) }
```

```kotlin
// Main.kt — her cekim istegi tek bir sonuc olarak birebir ulasir
fun main() {
    val sent = listOf(
        Signal("foto.sabah", 1),
        Signal("foto.ogle", 2),
        Signal("foto.aksam", 3),
        Signal("foto.gece", 4),
    )
    val received = shoot(sent)
    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=foto.sabah | kopya=1 -> birebir
olay-2: anahtar=foto.ogle | kopya=1 -> birebir
olay-3: anahtar=foto.aksam | kopya=1 -> birebir
olay-4: anahtar=foto.gece | kopya=1 -> birebir
birebir: 4
yinelenen: 0
ezilen: 0
kayip: 0
```

Bu dersin teslimi **birebir**dir. Dört isteğin dördü tek kopya olarak ulaştı. Önceki iki derste kural bir olayın yerini başka bir olayın almasını ezilen saymıştı; burada anahtarlar birbirinden farklı olduğu için hiçbir olay bir diğerinin yerini alamaz.

**SY1.** Model, her çekim isteğini başarılı kabul eder. Platformda çekim kamera meşgulken, izin geri alınmışken ya da kitaplık yeniden başlatılırken hata verebilir; hata geri çağrısı modelde yoktur.

#### Hata Geri Çağrısı ve Sonuç Sözleşmesi

Kitaplığın sözleşmesi şudur: her çekim isteği için başarı geri çağrısı ya da hata geri çağrısından yalnızca biri çalışır. İkisi birden çalışmaz ve ikisinin de çalışmaması sözleşmeye aykırıdır. Modelin "her isteğe tek sonuç" kuralı bu sözleşmenin sade biçimidir: sonuç ya dosyadır ya da hatadır, ama her zaman tektir.

Uygulama açısından bu, hata geri çağrısının bir lüks olmadığı anlamına gelir. Başarı yolunu yazıp hata yolunu boş bırakan bir ekran, kullanıcıya hiçbir şey söylemeyen bir kayıp üretir: kullanıcı deklanşöre dokunmuş, ekranda bir şey değişmemiştir. Ölçümde iki çekimin kaybolması kural açısından yalnızca bir sayıdır; kullanıcı açısından ise iki ayrı hayal kırıklığıdır. Hata nesnesinin neden taşıdığı bilgi, kullanıcıya ne söylenebileceğini belirler: izin eksikliği için ayarlara yönlendirme, dolu depolama için yer açma önerisi.

#### Ekranın Yaşam Döngüsü

Kamera önizlemesi ekranın yaşam döngüsüne bağlanır. Ekran görünür olduğunda kamera açılır, ekran gizlendiğinde kapanır. Kitaplığın çekim isteği kameranın açık olduğu bir zamanda anlam taşır; kamera kapalıyken verilen istek hata geri çağrısına düşer.

Bunun teslimle ilgisi, isteğin hangi anda verildiğindedir. Kullanıcı deklanşöre dokunup hemen başka bir uygulamaya geçerse, istek verilmiştir ama ekran gizlenirken kamera kapanır. Çekim tamamlanmadan kapanan kamera için kitaplık hata bildirir; fotoğraf hiç oluşmaz. Modelde bu durum, depolama dolduğunda olduğu gibi, bir isteğin kayıp olmasına karşılık gelir. Fark, nedenin depolama değil yaşam döngüsü olmasıdır. Kural bu iki nedeni ayırt etmez; ayırt etmek için hata nesnesine bakmak gerekir.

#### İzin Verilmeden Yapılan İstek

İzin verilmemişken yapılan çekim isteği de hata geri çağrısına düşer. Kullanıcı izin penceresini yanıtlayana kadar uygulamanın verdiği istekler, izin verildikten sonra yeniden işlenmez. Aşağıdaki modelde izin 14 numaralı adımda verilir ve ondan önceki istekler sonuç üretmez. İlk üç istek aynı fotoğraf adını taşıyor: kullanıcı bir belgeyi çekmeye çalışıyor.

```kotlin
// Permission.kt — izin verilmeden onceki cekim isteklerinin sonucunu birakir
fun requirePermission(sent: List<Signal>, grantedAt: Int): List<Signal> =
    sent.filter { it.seq >= grantedAt }
```

```kotlin
// Main.kt — izin verilmeden yapilan istekler sonuc uretmez
fun main() {
    val sent = listOf(
        Signal("foto.belge", 12),
        Signal("foto.belge", 13),
        Signal("foto.belge", 14),
        Signal("foto.sayfa", 15),
    )
    val received = requirePermission(shoot(sent), grantedAt = 14)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [14, 15]
olay-12: anahtar=foto.belge | kopya=0 -> ezilen
olay-13: anahtar=foto.belge | kopya=0 -> ezilen
olay-14: anahtar=foto.belge | kopya=1 -> birebir
olay-15: anahtar=foto.sayfa | kopya=1 -> birebir
birebir: 2
yinelenen: 0
ezilen: 2
kayip: 0
```

12 ve 13 numaralı istekler sonuç üretmedi, ama aynı anahtarla 14 numaralı istek sonuç verdi. Kural bu yüzden ikisini kayıp değil ezilen saydı. Bu sayım kuralın tanımıyla tutarlıdır, ama fotoğraf açısından yanıltıcıdır: izinsiz istekler hiçbir fotoğrafın yerini almadı, çünkü fotoğraf hiç çekilmedi. Ezilen etiketi bu durumda yalnızca "aynı anahtarla daha sonra bir sonuç geldi" demektir; kullanıcının görmediği ve sonradan telafi edilmiş bir kayıp olarak okunmalıdır. Aynı durum anahtarlar farklı olsaydı kayıp olarak sayılacaktı. Kuralı ders kitabı gibi okumak, anahtar seçiminin sayıyı değiştirdiğini unutmamayı gerektirir.

**SY2.** Model, izni tek bir adımda ve kesin olarak verilmiş kabul eder. Platformda izin penceresi kullanıcıya birkaç kez gösterilebilir, "bir daha sorma" seçilirse ilerleyen çağrılar pencere göstermeden reddedilir ve izin sonradan ayarlardan verilebilir.

#### Depolama Dolunca

Çekim sonucu bir dosyadır ve dosyanın yazılacağı bir yer olmalıdır. Depolama dolduğunda kitaplık sonucu kaydedemez ve hata geri çağrısına düşer. Modelde depolamanın kaç sonuç alabileceği tamsayı bir kapasitedir; sayı modelin uydurma değeridir.

```kotlin
// Storage.kt — depolama kapasitesi dolunca sonraki sonuclari birakir
fun store(results: List<Signal>, capacity: Int): List<Signal> =
    results.take(capacity)
```

```kotlin
// Main.kt — depolama dolunca sonraki cekimler ulasmaz
fun main() {
    val sent = listOf(
        Signal("foto.a", 5),
        Signal("foto.b", 6),
        Signal("foto.c", 7),
        Signal("foto.d", 8),
    )
    val received = store(shoot(sent), capacity = 2)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [5, 6]
olay-5: anahtar=foto.a | kopya=1 -> birebir
olay-6: anahtar=foto.b | kopya=1 -> birebir
olay-7: anahtar=foto.c | kopya=0 -> kayip
olay-8: anahtar=foto.d | kopya=0 -> kayip
birebir: 2
yinelenen: 0
ezilen: 0
kayip: 2
```

İlk iki çekim birebir ulaştı; 7 ve 8 numaralı çekimler kayıp oldu. Anahtarları farklı olduğu için hiçbiri bir diğerinin yerini almadı ve kural ezilen saymadı. Kural kaybın nedeninin dolu depolama olduğunu görmez; bunu bilmek için uygulamanın hata geri çağrısını dinlemesi gerekir.

**SY3.** Model, depolamayı sabit bir kapasiteyle kurar. Platformda boş yer kullanıcının başka işlerine göre değişir ve kapasite çekim sırasında tükenebilir; bu modelde yoktur.

#### Medya Seçici ve Ortak Depolama

Her fotoğraf uygulamanın kendi çekimi olmayabilir. Kullanıcı galeriden mevcut bir fotoğrafı seçtiğinde seçim ortak bir medya deposundan gelir ve uygulamaya yalnızca seçilen dosyanın adresi verilir. Seçici ayrı bir izin istemez, çünkü uygulama bütün medyaya değil yalnızca kullanıcının seçtiğine erişir. Bu, teslim modelinde ayrı bir durumdur: seçim bir olaydır, ama olayın sahibi kullanıcıdır ve seçilen dosya gönderilip ulaşan bir sonuç gibi sayılabilir.

Çekimle seçim arasındaki fark, hata yolunun sorumluluğundadır. Çekimde depolama dolabilir ve izin eksik olabilir. Seçicide kullanıcı vazgeçebilir ve bu bir hata sayılmaz: boş bir sonuç döner. Vazgeçme, kayıp ya da ezilen değildir; çünkü hiçbir olay gönderilmemiştir. Bu ayrım, ölçüm modelinin sınırını gösterir: kural gönderilmiş olayları sayar, gönderilmemiş olayları değil.

#### Önizleme Çekim Değildir

Kamera kitaplığı iki ayrı akış sunar: önizleme ve çekim. Önizleme ekranda sürekli güncellenen bir görüntüdür ve kullanıcıya kadrajı gösterir; çekim ise tek bir dosya üretir. Önizlemenin her karesi bir olay gibi görünür, ama uygulamaya teslim edilen bir sonuç değildir: kareler ekrana çizilir ve atılır. Bu dersin modelinde yalnızca çekim vardır, çünkü önizlemede alıcıya ulaşması gereken bir kare yoktur. Kareler atılabildiği için önizleme, konum ve sensör derslerindeki ezilen davranışının görsel karşılığıdır: yavaş bir ekran eski kareleri sessizce bırakır ve kimse bunu kayıp saymaz.

Çekim ise bırakılamaz. Kullanıcı bir fotoğraf istediğinde sonuç ya bir dosya ya da bir hatadır. Dosyanın yazılması zaman alır ve yazma bitmeden başarı geri çağrısı çalışmaz. Dosya yazılırken ekran gizlenirse, yazma çoğu zaman tamamlanır ama başarı geri çağrısı artık görünmeyen bir ekrana ulaşır. Bu durumda dosya vardır ve kullanıcı galeride görür; ama ekranın sonuca tepkisi, örneğin önizleme küçük resmini güncellemesi, gerçekleşmez. Kural açısından bu olay ulaştı sayılır; kullanıcı açısından ise sonucun yarısı kaybolmuştur. Modelin dışında kalan bu fark, sonucu ekrandan bağımsız bir yerde, örneğin bir görünüm modelinde, tutmayı gerektirir.

#### Yaygın Yanılgılar

**"İkinci dokunuş ilk dokunuşun kopyasıdır."** Kopya, aynı sıranın birden çok kez ulaşmasıdır. İki dokunuş iki ayrı istektir ve sonuç da iki ayrıdır. Kullanıcının tek fotoğraf istediği durumda çözüm, ikinci dokunuşu istek verilmeden önce engellemektir; sonuç tarafında tekilleştirme iki fotoğrafı da çekilmiş haliyle bırakır.

**"Çekim isteği verildiyse fotoğraf vardır."** İstek yalnızca niyettir. Fotoğraf, başarı geri çağrısı çalıştığında vardır. Hata geri çağrısı çalıştıysa yoktur.

**"İzin bir kez verildi, kamera hep açılır."** Kamera başka bir uygulama tarafından kullanılıyor olabilir, aygıt kamerayı güç tasarrufu için kapatmış olabilir ya da kullanıcı izni sonradan geri almış olabilir. Her çekim isteğinde hata yolu hazır olmalıdır.

**"Dosya kaydedildiyse kullanıcı görmüştür."** Başarı geri çağrısı dosyanın yazıldığını bildirir, kullanıcının sonucu gördüğünü değil. Ekran o sırada gizliyse sonuç kayıtlıdır ama gösterilmemiştir. Uygulama sonucu ekran yeniden göründüğünde okuyabilecek bir yerde saklamazsa, kullanıcı fotoğrafı yalnızca galeride bulur.

**"Depolama doluluğu çekimden önce bilinemez."** Boş yer çekimden önce sorgulanabilir. Çekimden önce yapılan bir denetim, ölçümdeki iki kaybı kullanıcıya daha deklanşöre dokunmadan söylemeyi mümkün kılar; hata geri çağrısı ise son çaredir.

**"Önizleme görünüyorsa kamera hazırdır."** Önizleme çalışırken çekim hâlâ başarısız olabilir: depolama dolu, izin geri alınmış ya da bir önceki çekim henüz yazılıyor olabilir. Hazır olmayı çekim isteğinin sonucu belirler, önizleme belirlemez.

**"Ezilen yalnızca konum ve sensör için geçerlidir."** Ezilen, kuralın tanımında bir olayın yerini aynı anahtarlı daha yeni bir olayın alması demektir. İzinsiz istek ölçümünde olduğu gibi, çekimde de görülebilir. Kategori olayın türüne değil, anahtarlara ve sıralara bağlıdır.

**"Medya seçici de izin ister."** Seçici, kullanıcının kendi seçtiği dosyaya erişim verir; geniş depolama izni gerekmez.

#### Sınırlayıcı Ölçüm

Deklanşöre art arda iki kez dokunulursa ne olur? Kullanıcı deklanşöre çabuk çabuk iki kez dokunduğunda uygulama iki ayrı çekim isteği verir. Bunlar aynı olayın iki kopyası değildir: iki ayrı istektir ve her biri kendi sırasını taşır.

```kotlin
// Main.kt — deklansore iki dokunus iki ayri sira olarak birebir ulasir
fun main() {
    val sent = listOf(
        Signal("foto.kapak", 9),
        Signal("foto.kapak", 10),
        Signal("foto.detay", 11),
    )
    val received = shoot(sent)
    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-9: anahtar=foto.kapak | kopya=1 -> birebir
olay-10: anahtar=foto.kapak | kopya=1 -> birebir
olay-11: anahtar=foto.detay | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 0
kayip: 0
```

9 ve 10 numaralı çekimler aynı anahtarı taşıyor, ama sıraları farklı olduğu için kural her birini kendi sırasına göre sayar. İkisi de birebir ulaştı; yinelenen sayılmadı. Yinelenen, aynı sıranın birden çok kez ulaşmasıdır. Kullanıcı iki fotoğraf istediyse iki fotoğraf alır. İkinci dokunuşun yoksayılmasını istiyorsak bu, uygulamanın kendi kuralıdır ve çekim isteğini vermeden önce uygulanmalıdır.

**SY4.** Model, iki dokunuşu iki bağımsız istek olarak kurar. Platformda kitaplık aynı anda tek bir çekim işlemeye izin verebilir ve ikinci isteği sıraya alabilir ya da reddedebilir; bu modelde yoktur.

#### Özet

- Bu dersin teslimi: birebir. Her çekim isteği tek bir sonuç olarak ulaştı: birebir 4.
- Anahtarları farklı olan çekimler birbirinin yerini almaz; bu yüzden bu derste ezilen yoktur.
- Depolama dolunca sonraki iki çekim kayıp oldu: kayip 2. Kural nedeni görmez.
- İzin verilmeden yapılan iki istek, aynı anahtarlı sonraki istek ulaştığı için ezilen sayıldı: birebir 2, ezilen 2. Anahtar seçimi sayıyı değiştirir.
- Deklanşöre iki dokunuş iki ayrı sıradır ve ikisi de birebir ulaşır; yinelenen, aynı sıranın birden çok kez ulaşmasıdır.
- Kamera izni çalışma zamanı iznidir; sonuç geri çağrısı başarı ve hatayı ayırır.

#### Sonraki Adım

Bu derste olay tek bir eylemden doğdu ve her isteğe tek bir sonuç verildi. Sensörlerde olay saniyede çok sayıda üretilir ve yavaş bir alıcı hepsine yetişemez. Sıradaki ders, `Sensörler`, yavaş alıcının atladığı sensör okumalarının teslimini ölçer.

### Sensörler

Source: https://academia.sh/tr/kurslar/android-servisler/sensorler

Sensör olayları aygıtın ivmeölçer gibi donanımlarından hızla gelir; yavaş bir alıcı hepsine yetişemez. Ders arabellek taştığında ve alıcı yalnız son okumayı aldığında hangi sensör olayının ulaştığını ve dinleyici kaydı kaldırılınca ne olduğunu ölçer.

Önceki derste olay bir kullanıcı eyleminden doğuyordu ve her eylem tek bir sonuç veriyordu. Sensörde olay donanımdan gelir ve kullanıcıdan bağımsız, sürekli üretilir. İvmeölçer aygıtın hareketini, ışık sensörü ortam ışığını okur. Okumalar alıcının işleyebildiğinden hızlı gelebilir. Bu derste de aynı soruyu soruyoruz: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?**

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

Sınıflandırma önceki derslerdekiyle aynıdır. Kural alıcının listesinde aynı sırayı taşıyan kopyaları sayar; hiç kopya yoksa aynı anahtarla daha sonraki bir olayın ulaşıp ulaşmadığına bakar. İçeriği ve nedeni görmez.

#### Sensör Yöneticisi ve Dinleyici

Platform sensörlere sensör yöneticisi üzerinden erişir. Uygulama bir sensör seçer, bir dinleyici kaydeder ve yöneticiye örnekleme hızını söyler. Hız bir istektir: platform okumaları bu hıza yakın bir sıklıkla verir ama sayıyı garanti etmez. Her okuma bir sensör olayı olarak dinleyicinin geri çağrısına düşer. Okumaların platform tarafından toplu teslim edilmesi de mümkündür; bu durumda okumalar aynı anda birden fazla gelebilir.

```kotlin
// Platform kodu: MotionWatcher
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager

class MotionWatcher(private val manager: SensorManager) : SensorEventListener {
    fun start() {
        val accel = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
        manager.registerListener(this, accel, SensorManager.SENSOR_DELAY_UI)
    }

    fun stop() = manager.unregisterListener(this)

    override fun onSensorChanged(event: SensorEvent) = process(event.values)
    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) = Unit
}
```

`start` dinleyiciyi kaydeder, `stop` kaydı kaldırır. Modelde kaydı kaldırılmış dinleyici artık okuma almaz. Bu parça bir hız sayısı yazmaz ve çıktı iddia etmez. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

#### Örnekleme Hızı Bir İstektir

Dinleyiciyi kaydederken verilen hız sabiti, okumaların ne sıklıkla geleceğine dair bir ipucudur. Platform dört ayrı düzey sunar: en hızlı, oyun için hızlı, kullanıcı arayüzü için orta ve normal; normal en yavaş olanıdır. Daha hızlı düzey daha çok okuma üretir; ama bu bir sözleşme değil, platformun bir yaklaşımıdır. Aygıtın o anki yüküne, pil durumuna ve sensörün donanım sınırlarına göre gerçek sıklık bundan sapabilir.

Bu yüzden iki ayrı teslim sorusu vardır ve ikisi birbirine karıştırılmamalıdır. Birincisi, sensörün kaç okuma ürettiğidir; bunu hız düzeyi belirler. İkincisi, üretilen okumaların alıcıya kaçının ulaştığıdır; bunu bu dersin kuralı ölçer. Hız düzeyini artırmak üretimi artırır ama alıcı yavaşsa ulaşan sayı değişmez: fazladan üretilen okumalar yalnız ezilen olur. Pil ve işlemci bedeli ise üretimle orantılı artar. Hızı artırmanın faydası alıcının o kadar okumayı işleyebildiği durumda vardır.

Yüksek örnekleme hızı bazı sürümlerde ayrı bir izin de ister. İzin bildirilmeden dinleyici kaydedilirse platform hızı sessizce bir üst sınıra çeker; başka bir yoldan daha yüksek hız istenirse güvenlik hatası oluşur. Uygulama istediği sıklığı alıp almadığını kendisi denetlemelidir.

#### Yavaş Alıcı

Modelde her okuma bir `Signal`dır ve anahtarı sensörün adıdır. Alıcı yavaşsa tek bir işleme turunda biriken okumaların yalnız sonuncusunu alır; öncekiler atlanır. Bir tur kaç okumayı kapsıyorsa o sayı modelin uydurma değeridir.

```kotlin
// Sampling.kt — yavas alici her turda yalniz son okumayi alir
fun latestOnly(sent: List<Signal>, perTurn: Int): List<Signal> =
    sent.chunked(perTurn).map { it.last() }
```

```kotlin
// Main.kt — yavas alici her turda yalniz son okumayi alir
fun main() {
    val sent = (1..8).map { Signal("sensor.ivme", it) }
    val received = latestOnly(sent, perTurn = 3)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [3, 6, 8]
olay-1: anahtar=sensor.ivme | kopya=0 -> ezilen
olay-2: anahtar=sensor.ivme | kopya=0 -> ezilen
olay-3: anahtar=sensor.ivme | kopya=1 -> birebir
olay-4: anahtar=sensor.ivme | kopya=0 -> ezilen
olay-5: anahtar=sensor.ivme | kopya=0 -> ezilen
olay-6: anahtar=sensor.ivme | kopya=1 -> birebir
olay-7: anahtar=sensor.ivme | kopya=0 -> ezilen
olay-8: anahtar=sensor.ivme | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 5
kayip: 0
```

Bu dersin teslimi **ezilen**dir. Sekiz okumanın beşi ulaşmadı, ama her birinin ardından aynı anahtarla daha yeni bir okuma ulaştı. Hareket izlemek için bu doğru bir takastır: güncel değer eskisinden değerlidir. Sekizinci okuma turun sonuncusu olduğu için ulaştı; son tur tamamlanmasa bile son okuma alınır.

**SY1.** Model, turları eşit uzunlukta kurar ve her turun sonunu alıcının okuma anı sayar. Platformda tur uzunluğu işleme yüküne göre değişir ve okumalar toplu teslimde birkaç kez gruplanabilir; bunlar modelde yoktur.

#### Toplu Teslim ve Boşaltma

Pil tasarrufu için platform okumaları donanımda biriktirip topluca verebilir. Uygulama, bir okumanın üretimi ile teslimi arasında izin verilen en uzun gecikmeyi belirtir; platform okumaları o süre dolana ya da arabellek dolana kadar bekletir ve sonra hepsini bir arada teslim eder. Ekran kapalıyken bile sensör okuyabilmek bu düzenle mümkün olur.

Toplu teslimin kuralı kısadır: bir yığın ya tamamlanıp teslim edilir ya da bekler. Modelde beklemekte olan yığın, uygulama kaydı kaldırdığında kendiliğinden gelmez. Gecikmeli teslimde okumaları kaybetmemenin yolu, kaydı kaldırmadan önce bekleyen yığını açıkça boşaltmaktır. Modelde yığın boyutu üçtür ve yığını tamamlanmamış son okumalar boşaltılmadığı için teslim edilmez.

```kotlin
// Batch.kt — tamamlanmamis son yigin bosaltilmadan teslim edilmez
fun deliverBatched(sent: List<Signal>, size: Int): List<Signal> =
    sent.chunked(size).filter { it.size == size }.flatten()
```

```kotlin
// Main.kt — tamamlanmamis son yigin teslim edilmez
fun main() {
    val sent = (21..27).map { Signal("sensor.adim", it) }
    val received = deliverBatched(sent, size = 3)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [21, 22, 23, 24, 25, 26]
olay-21: anahtar=sensor.adim | kopya=1 -> birebir
olay-22: anahtar=sensor.adim | kopya=1 -> birebir
olay-23: anahtar=sensor.adim | kopya=1 -> birebir
olay-24: anahtar=sensor.adim | kopya=1 -> birebir
olay-25: anahtar=sensor.adim | kopya=1 -> birebir
olay-26: anahtar=sensor.adim | kopya=1 -> birebir
olay-27: anahtar=sensor.adim | kopya=0 -> kayip
birebir: 6
yinelenen: 0
ezilen: 0
kayip: 1
```

Altı okuma iki tam yığınla teslim edildi ve birebir ulaştı. Yedinci okuma tamamlanmamış yığında kaldı ve ondan sonra aynı anahtarla bir okuma ulaşmadığı için kayıp oldu. Kural bunu sensörün ya da kaydın hatası olarak görmez; yalnızca ulaşmadığını sayar. Alıcı tarafın önlemi, kaydı kaldırmadan önce bekleyen okumaları açıkça istemektir.

**SY2.** Model, yığını sabit boyutlu ve gecikmesiz kurar. Platformda yığın boyutu okuma hızına, gecikme sınırına ve arabellek kapasitesine bağlıdır ve boşaltma isteği de bir geri çağrıyla tamamlanır; bunlar modelde yoktur.

#### Arabellek Taşınca

Alıcının önünde bir arabellek durabilir. Arabellek dolduğunda iki seçenek vardır: en eskiyi atmak ya da yeni gelen okumayı atmak. Önceki bölümdeki alıcı en yeni okumayı tutuyordu; yeni geleni atan arabellek bunun tersini yapar.

```kotlin
// Overflow.kt — arabellek dolunca yeni gelen okumalari birakir
fun overflow(sent: List<Signal>, capacity: Int): List<Signal> =
    sent.take(capacity)
```

```kotlin
// Main.kt — arabellek tasinca en yeni okumalar ulasmaz
fun main() {
    val sent = (9..14).map { Signal("sensor.isik", it) }
    val received = overflow(sent, capacity = 3)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [9, 10, 11]
olay-9: anahtar=sensor.isik | kopya=1 -> birebir
olay-10: anahtar=sensor.isik | kopya=1 -> birebir
olay-11: anahtar=sensor.isik | kopya=1 -> birebir
olay-12: anahtar=sensor.isik | kopya=0 -> kayip
olay-13: anahtar=sensor.isik | kopya=0 -> kayip
olay-14: anahtar=sensor.isik | kopya=0 -> kayip
birebir: 3
yinelenen: 0
ezilen: 0
kayip: 3
```

İlk üç okuma birebir ulaştı; 12, 13 ve 14 numaralı okumalar kayıp oldu. Atılan okumalardan sonra aynı anahtarla yeni bir okuma ulaşmadığı için kural bunları ezilen saymaz. Aynı taşma, en eskiyi atan bir arabellekte ezilen olarak sonuçlanırdı. Hangi okumanın atılacağı alıcının değerine bağlıdır; kural yalnız sonucu sayar.

**SY3.** Model, arabellek kapasitesini sabit bir tamsayı olarak kurar. Platformda arabellek boyutu donanıma ve sürüme bağlıdır ve taşma sırasında hangi okumanın atılacağı sensöre göre değişebilir; bunlar modelde yoktur.

#### Sensör Türleri ve İzin

Sensörlerin hepsi aynı izin düzeyinde değildir. Hareket ve ışık sensörleri izin istemez. Kalp atışı gibi bedene dair sensörler çalışma zamanı izni ister ve kullanıcı bu izni vermezse sensör listede görünmeyebilir ya da okuma vermez. İzin eksikliği kayıtta hata üretmez; dinleyici kaydedilir, ama geri çağrı hiç çalışmaz.

Bu, önceki iki dersten tanıdık bir kayıptır. Konumda izin geri alındıktan sonra güncellemeler gelmedi; kamerada izin verilmeden önceki istekler sonuç üretmedi. Sensörde de izinsiz bir dinleyici sessizdir. Sessizlik üç ayrı durumdan doğabilir: izin yok, sensör donanımda yok, ya da sensör var ama olay üretmiyor. Uygulama bunları ayırmak için önce sensörün varlığına, sonra iznin durumuna, en son okumaların akıp akmadığına bakar.

#### Doğruluk Geri Çağrısı ve Zaman Damgası

Dinleyicinin iki geri çağrısı vardır. Birincisi okumayı verir, ikincisi sensörün doğruluk düzeyi değiştiğinde çalışır. Platform kodundaki parça ikincisini boş bıraktı. Bu çoğu uygulama için yeterlidir, ama bazı sensörler için yanlış bir tercih olabilir: pusula gibi sensörler kalibrasyon gerektirir ve doğruluk düşükken okuma sayıları gelir ama anlamsızdır.

Bu durumun teslim modeliyle ilişkisi şudur. Düşük doğrulukla gelen okuma alıcıya ulaşmıştır ve kural onu birebir sayar. Okumanın kullanılabilir olup olmadığı kuralın görmediği bir özelliktir. Sayı doğru ulaşmış, değer güvenilmez kalmıştır. Alıcı bu iki soruyu ayrı tutmalıdır: ulaştı mı, ve ulaştıysa güvenilir mi.

Her okuma bir zaman damgası da taşır. Damga okumanın donanımda üretildiği anı gösterir, alıcıya teslim edildiği anı değil. Toplu teslimde ya da yavaş alıcıda iki an birbirinden uzaklaşır: okuma geç gelir ama damgası eskidir. Alıcı bir okumayı işlerken damgaya bakarsa eski bir okumayı güncel gibi göstermez. Modelde sıra numarası bu rolü üstlenir: daha büyük sıra daha yeni üretim demektir ve ezilen kuralı bu sıraya dayanır.

#### Yaygın Yanılgılar

**"Hızı artırırsam daha çok okuma alırım."** Üretim artar, alıcıya ulaşan sayı alıcının hızıyla sınırlıdır. Yavaş alıcıda fazladan okumalar yalnız ezilen sayılır; ölçümde sekiz okumanın beşi bu yüzden ezilen oldu.

**"Ezilen okuma kayıptır, bu bir hata."** Ezilen okumanın yerini aynı anahtarla daha yeni bir okuma almıştır. Hareket izleyen bir uygulama için güncel değer eskisinden değerlidir ve bu davranış istenir. Hata, bir okumanın ardından hiçbir okuma gelmediğinde, yani kayıpta aranır.

**"Kayıt kaldırılırsa bekleyen okumalar yine gelir."** Modelde kaydın kaldırılması dinleyiciyi susturur. Toplu teslimde bekleyen yığın kendiliğinden gelmez; uygulama onu önce açıkça boşaltmalıdır.

**"Sensör olayı gerçek zamanlıdır."** Olay, üretildiği andan teslim edildiği ana kadar bekleyebilir. Toplu teslimde ve yavaş alıcıda bu bekleme uzar. Zaman damgası olayın ne zaman üretildiğini söyler; ne zaman işlendiğini söylemez.

**"Sensör olayı her zaman ana akışta işlenir."** Geri çağrı, `Handler` verilmezse ana iş parçacığında, verilirse o `Handler`'ın looper'ında çalışır; yani kayıt nasıl yapıldığına bağlıdır. Ağır işlemi geri çağrıda yapan uygulama alıcıyı yavaşlatır ve yavaş alıcı bu dersin ilk ölçümündeki gibi okumaları ezer. Geri çağrıda yalnızca değeri almak ve işlemi başka bir akışa bırakmak, kaybı değil ezilmeyi seçmek demektir.

**"Dinleyiciyi hep açık bırakmak zararsızdır."** Açık bir dinleyici, ekran kapalıyken bile okuma üretir ve pil harcar. Okumaya ihtiyaç kalmadığında kaydı kaldırmak, hem enerjiyi korur hem de kimsenin işlemediği okumaların birikmesini önler.

**"Okuma sayısı doğruluk demektir."** Çok sayıda okuma, değerlerin güvenilir olduğunu göstermez. Doğruluk düzeyi ayrı bir bilgidir ve sayıdan bağımsız değişir.

#### Sınırlayıcı Ölçüm

Alıcı okumaları zaten işleyebilecek kadar hızlı olsa bile, dinleyici kaydı kaldırıldığı anda okumalar kesilir. Uygulama ekrandan ayrıldığında `stop` çağırmayı unutmazsa pil harcar; çağırırsa o andan sonraki okumalar ulaşmaz.

```kotlin
// Main.kt — dinleyici kaydi kaldirilinca sonraki okumalar ulasmaz
fun main() {
    val sent = (15..20).map { Signal("sensor.ivme", it) }
    val received = latestOnly(sent, perTurn = 2).filter { it.seq < 19 }
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [16, 18]
olay-15: anahtar=sensor.ivme | kopya=0 -> ezilen
olay-16: anahtar=sensor.ivme | kopya=1 -> birebir
olay-17: anahtar=sensor.ivme | kopya=0 -> ezilen
olay-18: anahtar=sensor.ivme | kopya=1 -> birebir
olay-19: anahtar=sensor.ivme | kopya=0 -> kayip
olay-20: anahtar=sensor.ivme | kopya=0 -> kayip
birebir: 2
yinelenen: 0
ezilen: 2
kayip: 2
```

16 ve 18 numaralı okumalar ulaştı; 15 ve 17 ezilen oldu. 19 ve 20 numaralı okumalar kayıp oldu: dinleyici kaydı 19'dan önce kaldırıldığı için sonrasında hiçbir okuma ulaşmadı. Kural kaybın nedeninin kaldırılmış kayıt olduğunu görmez.

**SY4.** Model, kaydın kaldırılmasını tek bir adımda ve anında kurar. Platformda kaldırma çağrısından sonra bile arabellekte kalmış okumalar gelebilir; bu modelde yoktur.

#### Özet

- Bu dersin teslimi: ezilen. Yavaş alıcı her turda yalnız son okumayı alır ve öncekiler yerini aynı anahtarlı yeni okumaya bırakır.
- Sekiz okumadan beşi ezilen, üçü birebir oldu: ezilen 5, birebir 3.
- Yeni gelen okumayı atan arabellek taştığında üç okuma kayıp oldu: kayip 3. En eskiyi atan arabellek aynı okumaları ezilen yapardı.
- Dinleyici kaydı kaldırıldıktan sonra üretilen iki okuma kayıp oldu: kayip 2.
- Tamamlanmamış son yığın boşaltılmadan teslim edilmez: altı okuma birebir ulaştı, yedincisi kayıp oldu: birebir 6, kayip 1.
- Örnekleme hızı bir istektir; platform okumaları bu sıklıkla vermeyi garanti etmez.

#### Sonraki Adım

Bu derste olay donanımdan geldi ve alıcı yetişemeyince ezildi. Derin bağlantıda olay dışarıdan bir bağlantı olarak gelir ve uygulama yeniden oluşturulurken aynı bağlantı bir kez daha işlenebilir. Sıradaki ders, `Derin Bağlantılar ve Kısayollar`, döndürmede yeniden işlenen derin bağlantıyı ölçer.

### Derin Bağlantılar ve Kısayollar

Source: https://academia.sh/tr/kurslar/android-servisler/derin-baglantilar-ve-kisayollar

Derin bağlantı dışarıdan gelen bir adresi uygulamanın belirli bir ekranına bağlar; ekran yeniden oluşturulurken aynı bağlantı bir daha işlenebilir. Ders bu yinelenen teslimi ve işlendi işaretinin kaydedilmiş durumda ya da bellekte tutulmasının sonucunu ölçer.

Önceki derste alıcı yetişemeyince okumalar atlanıyordu. Derin bağlantıda sorun ters yönden çıkar: bir bağlantı alıcıya bir kez gelir, ama alıcı onu bir kez daha işleyebilir. Kullanıcı bir e-postadaki adrese dokunur, uygulama ilgili ürün ekranını açar. Ekran döndürüldüğünde ekran yeniden oluşturulur ve aynı bağlantı bir daha işlenirse kullanıcı ürün ekranını ikinci kez açılmış görür. Bu derste de aynı soruyu soruyoruz: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?**

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

Sınıflandırma önceki derslerdekiyle aynıdır. Kural alıcının listesinde aynı sırayı taşıyan kopyaları sayar; hiç kopya yoksa aynı anahtarla daha sonraki bir olayın ulaşıp ulaşmadığına bakar. İçeriği ve nedeni görmez.

#### Derin Bağlantı ve Kısayol

Derin bağlantı, bir adresin uygulamanın belirli bir ekranını açmasını sağlar. Uygulama bildirim dosyasında hangi adreslerle açılacağını bir `intent` süzgeciyle tanımlar. Doğrulanmış uygulama bağlantısı bir adım ileri gider: adresin sahibi alanı uygulamaya bağladığını doğrular ve platform bağlantıyı kullanıcıya sormadan uygulamaya verir. Kısayol ise uygulama simgesine uzun basınca çıkan, belirli bir ekrana giden bir kayıttır; o da bir `intent` taşır.

```xml
<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="https" android:host="ornek.example" android:pathPrefix="/urun" />
</intent-filter>
```

Bağlantı uygulamaya bir `intent` olarak gelir ve ekran onu açılışta okur.

```kotlin
// Platform kodu: ProductActivity
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class ProductActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val link = intent?.data
        if (link != null) openProduct(link.lastPathSegment)
    }
}
```

`onCreate` ekran her yeniden oluşturulduğunda çalışır ve `intent` aynı kalır. Bu parça çıktı ya da süre iddia etmez. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

#### Yeniden İşlenen Bağlantı

Modelde her bağlantı bir `Signal`dır ve anahtarı ürün adresidir. Ekran yeniden oluşturulunca aynı bağlantı bir daha işlenir; bu, aynı sıranın ikinci kez ulaşmasıdır. Modelde yeniden işleme, belirtilen sıraların listenin sonuna bir kopya olarak eklenmesidir.

```kotlin
// Redeliver.kt — ekran yeniden olusturulunca ayni baglantiyi bir kez daha verir
fun redeliver(sent: List<Signal>, again: List<Int>): List<Signal> =
    sent + again.map { seq -> sent.first { it.seq == seq } }
```

```kotlin
// Main.kt — ekran yeniden olusturulunca iki baglanti iki kez islenir
fun main() {
    val sent = listOf(
        Signal("urun.7", 1),
        Signal("urun.9", 2),
        Signal("urun.12", 3),
        Signal("urun.15", 4),
    )
    val received = redeliver(sent, again = listOf(2, 3))
    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=urun.7 | kopya=1 -> birebir
olay-2: anahtar=urun.9 | kopya=2 -> yinelenen
olay-3: anahtar=urun.12 | kopya=2 -> yinelenen
olay-4: anahtar=urun.15 | kopya=1 -> birebir
birebir: 2
yinelenen: 2
ezilen: 0
kayip: 0
```

Bu dersin teslimi **yinelenen**dir. 2 ve 3 numaralı bağlantılar iki kopya olarak ulaştı; 1 ve 4 numaralı bağlantılar tek kopya ile birebir kaldı. Kullanıcı ürün sayfasının iki kez açıldığını görür. Kural, kopyanın döndürmeden mi yoksa başka bir nedenden mi geldiğini görmez.

**SY1.** Model, yeniden işlemeyi listenin sonuna eklenen bir kopya olarak kurar. Platformda ekran yeniden oluşturulduğunda aynı `intent` ekranın kendi akışı içinde tekrar okunur ve kopyanın tam olarak ne zaman geleceği ekranın yaşam döngüsüne bağlıdır; bu modelde yoktur.

#### Açık Ekrana Yeni Bağlantı

Kullanıcı uygulama açıkken ikinci bir bağlantıya dokunursa platform iki yoldan birini seçer: ekranı yeniden oluşturur ya da var olan ekrana yeni bağlantıyı ayrı bir çağrıyla verir. Ekranın açılış kipi hangisinin seçileceğini belirler. Yeniden oluşturma, ekranın yaşam döngüsünü baştan başlatır ve bağlantı bu sırada açılışta okunur. Var olan ekrana verme ise yeni bağlantıyı ayrı bir geri çağrıyla iletir ve açılışta okunan bağlantıya dokunmaz.

Bu ikilik yinelenmenin bir kaynağıdır. Açılış kipi her seferinde yeni ekran üretiyorsa, eski ekran arka yığında kalır ve geri tuşuna basıldığında eski ekran, hâlâ elinde tuttuğu bağlantıyla yeniden görünür. Kullanıcı aynı ürünü ikinci kez açılmış görür. Hangi kipin seçildiği uygulamanın tercihidir; ölçüm yalnız sonucunu, yani kaç kopyanın işlendiğini sayar.

#### İşlendi İşareti

Yinelenmeyi önlemenin yolu, bağlantının işlendiğini bir işaretle tutmaktır. İşaret kaydedilmiş durumda tutulursa, ekran yeniden oluşturulsa bile bir sonraki okumada bağlantının işlendiği bilinir. Tekilleştirme (deduplication) böyle çalışır: aynı sıra ikinci kez gelirse işlenmez.

```kotlin
// Handled.kt — islendi isareti tutulursa daha once islenen baglantiyi atlar
fun dedupe(arrived: List<Signal>, kept: Boolean, restartAt: Int): List<Signal> {
    val handled = mutableSetOf<Int>()
    val processed = mutableListOf<Signal>()
    arrived.forEachIndexed { i, s ->
        if (i == restartAt && !kept) handled.clear()
        if (handled.add(s.seq)) processed.add(s)
    }
    return processed
}
```

`kept` işaretin süreç yeniden başlasa da korunup korunmadığını, `restartAt` sürecin hangi gelişten önce yeniden başladığını gösterir.

```kotlin
// Main.kt — islendi isareti kaydedilmis durumda tutulursa baglantilar birebir ulasir
fun main() {
    val sent = (5..8).map { Signal("urun.${it * 2}", it) }
    val arrived = redeliver(sent, again = listOf(6, 7))
    val received = dedupe(arrived, kept = true, restartAt = 4)
    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-5: anahtar=urun.10 | kopya=1 -> birebir
olay-6: anahtar=urun.12 | kopya=1 -> birebir
olay-7: anahtar=urun.14 | kopya=1 -> birebir
olay-8: anahtar=urun.16 | kopya=1 -> birebir
birebir: 4
yinelenen: 0
ezilen: 0
kayip: 0
```

Aynı iki yeniden işleme tekilleştirmeden geçti ve dört bağlantının dördü birebir ulaştı. İşlenen liste tek kopya taşır.

**SY2.** Model, işareti saklamanın bedelini sıfır kabul eder. Platformda kaydedilmiş duruma yazılan veri sınırlıdır ve işaretin ne kadar saklanacağı uygulamanın kararıdır; bu modelde yoktur.

#### Kısayollar

Kısayol üç biçimde bulunur. Statik kısayol uygulamayla birlikte gelir ve kaynak dosyasında tanımlıdır; uygulama güncellenene kadar değişmez. Dinamik kısayolu uygulama çalışırken kendisi ekler, günceller ve siler; uygulama verisi temizlendiğinde dinamik kısayollar da silinir. Sabitlenmiş kısayolu kullanıcı ana ekrana yerleştirir; uygulamanın bu kısayolu kaldırması mümkün değildir, yalnız pasifleştirebilir.

Kısayolun teslimle ilgili tarafı hedefidir. Kısayol, bağlantı gibi bir `intent` taşır ve aynı ekran aynı biçimde açar. Bu yüzden derin bağlantı için kurulan tekilleştirme kısayol için de geçerlidir: ekran yeniden oluşturulduğunda kısayolun hedefi de yeniden okunur. Fark, kısayolun kullanıcı tarafından değil uygulama tarafından tanımlanmasıdır. Kaldırılmış bir kısayola dokunan kullanıcı, bağlantıya dokunan kullanıcıdan farklı bir hata görür: bağlantı hiç açılmaz, kısayol ise açıldığı halde hedef ekran bulunamaz.

#### Bağlantı Türleri ve Doğrulama

Uygulamayı açan adresler üç türe ayrılır. Özel şemalı bir adres yalnızca uygulamanın tanımladığı bir önek taşır ve herhangi bir uygulama aynı öneki tanımlayabilir. Bir adresi birden çok uygulama tanıyorsa platform kullanıcıya hangisiyle açacağını sorar. Web adresli bağlantıda aynı soru tarayıcıyla uygulama arasında yaşanır. Doğrulanmış uygulama bağlantısı bu soruyu ortadan kaldırır: adresin sahibi, alanındaki bir dosyayla uygulamanın kimliğini bağladığını bildirir ve platform bağlantıyı doğrudan o uygulamaya verir.

Doğrulama başarısız olursa platform bağlantıyı uygulamaya vermez. Uygulama hata görmez; bağlantı tarayıcıda ya da seçim ekranında açılır. Kullanıcı bir ürün bağlantısına dokunur ve uygulamanın ürün sayfası yerine web sayfasını görür. Bu, teslimin görünmez bir kaybıdır: uygulama için olay hiç gönderilmemiş gibi görünür, kullanıcı için ise gönderilen bir eylem sonuçsuz kalmıştır.

Modelde doğrulama, belirli bir alana ait bağlantıların hepsini uygulamadan geri tutar. Alan anahtarın ilk parçasıdır ve doğrulanmamış alan modelin uydurma bir değeridir.

```kotlin
// Verify.kt — dogrulanmamis alana ait baglantilari uygulamaya vermez
fun verifiedOnly(sent: List<Signal>, unverified: String): List<Signal> =
    sent.filter { !it.key.startsWith("$unverified.") }
```

```kotlin
// Main.kt — dogrulanmamis alanin baglantilari uygulamaya ulasmaz
fun main() {
    val sent = listOf(
        Signal("ornek.urun7", 13),
        Signal("eski.urun3", 14),
        Signal("ornek.urun9", 15),
        Signal("eski.urun3", 16),
    )
    val received = verifiedOnly(sent, unverified = "eski")
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [13, 15]
olay-13: anahtar=ornek.urun7 | kopya=1 -> birebir
olay-14: anahtar=eski.urun3 | kopya=0 -> kayip
olay-15: anahtar=ornek.urun9 | kopya=1 -> birebir
olay-16: anahtar=eski.urun3 | kopya=0 -> kayip
birebir: 2
yinelenen: 0
ezilen: 0
kayip: 2
```

Doğrulanmış alanın iki bağlantısı birebir ulaştı. Doğrulanmamış alanın iki bağlantısı ulaşmadı ve ardından aynı anahtarla bir bağlantı gelmediği için kayıp oldu. Aynı adres iki kez gönderildiği halde, bu kez yinelenen sayılmadı: yinelenen, ulaşan kopyaların sayısıdır, gönderilen kopyaların değil.

**SY3.** Model, doğrulamayı alan bazında hepsi ya da hiçbiri olarak kurar. Platformda doğrulama alan dosyasının indirilmesine, uygulama imzasına ve yol önekine bağlıdır ve kısmi doğrulama da mümkündür; bunlar modelde yoktur.

#### Soğuk Başlangıç ve Bağlantı Verisi

Bağlantı uygulamayı iki ayrı durumda bulabilir. Uygulama hiç çalışmıyorsa süreç sıfırdan başlar, ekran oluşturulur ve bağlantı açılış sırasında okunur. Uygulama arka plandaysa süreç yaşıyordur ve ekran ya yeniden oluşturulur ya da var olan ekrana yeni bağlantı verilir. İşlendi işaretinin ikinci duruma yardımı büyüktür; ilk durumda ise işaret henüz yoktur ve bağlantı tek kopya olarak gelir.

Bağlantıdan gelen veri dış dünyadan gelir ve güvenilir değildir. Adresin son parçası bir ürün numarasıysa, uygulama onu sayıya çevirmeden önce biçimini denetlemelidir; olmayan ya da geçersiz bir ürün için hata ekranı göstermelidir. Teslim açısından bu denetim, kopyaları değil içeriği ilgilendirir ve ölçümün dışında kalır. Kural bağlantının ulaştığını sayar; ulaşan bağlantının geçerli olup olmadığına bakmaz. Geçersiz bir bağlantı birebir ulaşabilir ve yine de kullanıcıya bir ürün göstermeyebilir.

#### Geri Tuşu ve Ekran Yığını

Bağlantı uygulamayı doğrudan iç bir ekrana açtığında, kullanıcının geri tuşuna bastığında nereye gideceği bir tasarım sorusudur. Uygulama çalışmıyorsa ve yığında yalnızca ürün ekranı varsa, geri tuşu kullanıcıyı uygulamadan çıkarır. Uygulama ürün ekranının altına ana ekranı da yerleştirirse kullanıcı uygulamanın içinde kalır. Bu seçim teslimi değiştirmez, ama yinelenmeyi görünür kılar: yığında iki ürün ekranı varsa geri tuşu ilkini gösterir ve kullanıcı aynı ürünü ikinci kez görür. Kural bu iki ekranı yine tek bir kopya olarak ya da iki kopya olarak sayar; hangisi olduğu, bağlantının kaç kez işlendiğine bağlıdır.

#### Yaygın Yanılgılar

**"Doğrulanmış bağlantı her zaman uygulamayı açar."** Doğrulama başarılıysa açar. Başarısız ya da henüz tamamlanmamış doğrulamada bağlantı tarayıcıya gider. Uygulama bunu hata olarak görmez, çünkü kendisine hiçbir olay gönderilmemiştir.

**"Aynı bağlantı iki kez geldiyse platform hata yapmıştır."** Platform bağlantıyı bir kez verir. İkinci kopya, ekranın yeniden oluşturulmasından ve açılışta bağlantının yeniden okunmasından gelir. Kaynak platformun değil, ekranın yaşam döngüsüdür.

**"İşlendi işareti yalnız bellekte tutulabilir."** Bellekteki işaret döndürmeye karşı korur, süreç sonlandırıldığında yok olur. Ölçümde fark yalnız `kept` değeriydi ve bedeli iki yinelenen bağlantıydı. İşaret kaydedilmiş durumda ya da kalıcı bir depoda tutulmalıdır.

**"Tekilleştirme kayıp riskini artırır."** Tekilleştirme yalnız aynı sırayı ikinci kez işlemez. Farklı sıralı bağlantıları bir diğerinin yerine koymaz. Yanlış anahtarla tekilleştirme ise farklı iki ürünü aynı sayar ve ilkinden sonra ikincisini atar; bu, ezilen değil, uygulama hatası kaynaklı kayıptır.

**"Kısayol ile bağlantı aynı şeydir."** İkisi de bir `intent` taşır, ama sahipleri farklıdır. Bağlantıyı dışarıdan biri üretir ve uygulama içeriğini denetlemek zorundadır; kısayolu uygulamanın kendisi tanımlar ve içeriğine daha çok güvenebilir. Yinelenme açısından ikisi aynı davranır.

**"Yinelenen bağlantı zararsızdır."** Ürün ekranını iki kez açmak yalnızca görsel bir kusurdur; ama bağlantı bir ödeme ya da onay işlemini tetikliyorsa ikinci kopya ikinci işlemdir ve geri alınması zordur.

#### Sınırlayıcı Ölçüm

İşaret yalnız bellekte tutulursa, süreç sonlandırıldığında bellek silinir. Sistem bellek baskısıyla uygulamayı arka planda sonlandırabilir ve kullanıcı geri döndüğünde ekran yeniden oluşturulur. Bellekteki işaret kaybolduğu için aynı bağlantı yine işlenir.

```kotlin
// Main.kt — isaret bellekte tutulursa surec sonlaninca baglanti yine yinelenir
fun main() {
    val sent = (9..12).map { Signal("urun.${it * 2}", it) }
    val arrived = redeliver(sent, again = listOf(10, 11))
    val received = dedupe(arrived, kept = false, restartAt = 4)
    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-9: anahtar=urun.18 | kopya=1 -> birebir
olay-10: anahtar=urun.20 | kopya=2 -> yinelenen
olay-11: anahtar=urun.22 | kopya=2 -> yinelenen
olay-12: anahtar=urun.24 | kopya=1 -> birebir
birebir: 2
yinelenen: 2
ezilen: 0
kayip: 0
```

Girdi bir önceki ölçümle aynıdır, ama işaret bellekte kaldığı için 10 ve 11 numaralı bağlantılar yeniden başlatma sonrasında yine işlendi ve yinelenen oldu. Fark yalnız `kept` değerindedir. Bellekte tutulan işaret döndürmeye karşı korur, süreç sonlanmasına karşı korumaz.

**SY4.** Model, sürecin sonlanmasını tek bir noktada kurar. Platformda sonlanma her an olabilir ve kaydedilmiş durumun geri yüklenmesi sonlanma türüne göre değişir; bunlar modelde yoktur.

#### Özet

- Bu dersin teslimi: yinelenen. Ekran yeniden oluşturulunca aynı derin bağlantı iki kez işlenir.
- Dört bağlantıdan ikisi iki kopya olarak ulaştı: yinelenen 2, birebir 2.
- İşlendi işareti kaydedilmiş durumda tutulduğunda dört bağlantının dördü birebir ulaştı.
- İşaret bellekte tutulduğunda süreç sonlandıktan sonra iki bağlantı yine yinelenen oldu: yinelenen 2.
- Doğrulanmamış alanın iki bağlantısı uygulamaya ulaşmadı: birebir 2, kayip 2. Uygulama bunu hata olarak görmez.
- Doğrulanmış uygulama bağlantısı adresin sahibinin uygulamaya bağladığını doğrular; kısayol bir ekrana giden `intent` taşır.

#### Sonraki Adım

Bu derste olay dışarıdan geldi ve aynı olay iki kez işlendi. Pencere öğesinde olay ana ekrandaki küçük bir görünümü güncellemektir ve görünüm hiç yoksa güncelleme alıcı bulamaz. Sıradaki ders, `Pencere Öğeleri`, pencere açılınca çizilen son güncellemeyi ve ana ekrandan kaldırılmış pencere öğesini ölçer.

### Pencere Öğeleri

Source: https://academia.sh/tr/kurslar/android-servisler/pencere-ogeleri

Pencere öğesi ana ekranda yaşayan küçük bir görünümdür ve veri güncellemelerini pencere açılana kadar bekletir. Ders biriken güncellemelerden yalnız sonuncusunun çizildiğini, ana ekrandan kaldırılmış pencere öğesine güncellemenin ulaşmadığını ölçer.

Önceki derste olay dışarıdan geliyor ve ekran onu bir kez daha işleyebiliyordu. Pencere öğesinde güncelleme uygulamanın içinde doğar, ama onu çizecek görünüm ana ekrandadır ve uygulamanın kendi süreci dışında yaşar. Uygulama veriyi güncellediğinde görünüm o an çizilmeyebilir. Bu derste de aynı soruyu soruyoruz: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?**

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

Sınıflandırma önceki derslerdekiyle aynıdır. Kural alıcının listesinde aynı sırayı taşıyan kopyaları sayar; hiç kopya yoksa aynı anahtarla daha sonraki bir olayın ulaşıp ulaşmadığına bakar. İçeriği ve nedeni görmez.

#### Sağlayıcı ve Yerleşim

Pencere öğesinin üç parçası vardır. Bildirim dosyasındaki tanım pencere öğesini platforma tanıtır, yerleşim görünümün nasıl çizileceğini söyler ve pencere öğesi sağlayıcısı güncelleme isteklerini alır. Sağlayıcı, platform bir güncelleme istediğinde çağrılır ve yeni görünümü platforma geri verir.

```kotlin
// Platform kodu: WeatherWidget
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews

class WeatherWidget : AppWidgetProvider() {
    override fun onUpdate(context: Context, manager: AppWidgetManager, ids: IntArray) {
        for (id in ids) {
            val views = RemoteViews(context.packageName, R.layout.widget_weather)
            views.setTextViewText(R.id.temperature, latestTemperature())
            manager.updateAppWidget(id, views)
        }
    }
}
```

`onUpdate` bir sayı yazmaz: yenileme aralığı pencere öğesinin tanımında verilir ve platform bu aralığı bir istek olarak ele alır. Parça çıktı ya da süre iddia etmez. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

Güncellemenin ertelenmesi ayrı bir teslim değildir. M12/K04'te bir işin koştu mu ertelendi mi diye sorulmuştu; bu kursta ertelenen bir güncelleme sonunda ulaşırsa birebir, ulaşmazsa kayıptır. M12/K01'deki sessiz kanal da kayıp değildir: sessiz kanaldaki kullanıcı bildirimi ulaşır ve birebirdir, yalnız ses çıkarmaz.

#### Yenileme Aralığı ve Çizim

Pencere öğesinin tanımında bir yenileme aralığı yazılır, ama bu aralık bir istektir. Platform, pil tasarrufu için aralığı kendisi uzatabilir ve çok kısa verilen bir aralığı belirli bir alt sınıra çeker. Uygulama bu aralıkla yetinmek zorunda değildir: veri değiştiğinde güncellemeyi kendisi tetikleyebilir. İki yol arasındaki fark, kimin saati tuttuğudur. Aralıkla yenilenen pencere öğesinde saati platform tutar ve görünüm veri değişse bile bir sonraki aralığı bekler. Uygulamanın tetiklediği güncellemede saat uygulamadadır ve pencere öğesi veri değiştiği anda yenilenir.

Bunun teslimle ilişkisi şudur: ana ekran pencere öğesini her zaman çizmez. Pencere öğesi görünmüyorsa, örneğin başka bir ana ekran sayfasındaysa ya da ekran kapalıysa, platform görünümü yalnızca saklar. Pencere görünür olduğunda saklanmış son görünüm çizilir. Aradaki görünümler çizilmez ve aynı pencere öğesinin yerini alan sonraki görünüm ezilen demektir. Bu, modelin "pencere açılana kadar biriken güncellemelerin yalnız sonuncusu çizilir" kuralının platformdaki karşılığıdır. Bu yüzden pencere öğesi için her ara durumun gösterilmesini beklemek yanlıştır; hedef, görünür olunduğunda doğru durumu göstermektir.

#### Pencere Açılınca Çizilen Son Güncelleme

Modelde her veri güncellemesi bir `Signal`dır; anahtarı pencere öğesinin adıdır. Pencere açık olmadığında uygulama güncellemeleri birbiri ardına verir. Pencere açıldığında yalnız son durum çizilir; açıkken gelen her güncelleme ise tek tek çizilir. Pencerenin açıldığı adım modelin uydurma değeridir.

```kotlin
// Drawn.kt — pencere acilana kadar biriken guncellemelerden her anahtarin yalniz sonuncusu cizilir
fun drawn(sent: List<Signal>, openAt: Int): List<Signal> {
    val before = sent.filter { it.seq < openAt }
    val waiting = before.filter { s -> before.none { it.key == s.key && it.seq > s.seq } }
    return waiting + sent.filter { it.seq >= openAt }
}
```

```kotlin
// Main.kt — pencere acilana kadar biriken guncellemelerden yalniz sonuncusu cizilir
fun main() {
    val sent = (1..6).map { Signal("pencere.hava", it) }
    val received = drawn(sent, openAt = 4)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [3, 4, 5, 6]
olay-1: anahtar=pencere.hava | kopya=0 -> ezilen
olay-2: anahtar=pencere.hava | kopya=0 -> ezilen
olay-3: anahtar=pencere.hava | kopya=1 -> birebir
olay-4: anahtar=pencere.hava | kopya=1 -> birebir
olay-5: anahtar=pencere.hava | kopya=1 -> birebir
olay-6: anahtar=pencere.hava | kopya=1 -> birebir
birebir: 4
yinelenen: 0
ezilen: 2
kayip: 0
```

Bu dersin teslimi **ezilen**dir. Pencere 4 numaralı adımda açıldı. 1 ve 2 numaralı güncellemeler çizilmedi, çünkü aynı anahtarla 3 numaralı güncelleme onların yerini aldı. Pencere açıkken gelen 4, 5 ve 6 numaralı güncellemelerin her biri birebir ulaştı.

**SY1.** Model, açılmadan önceki bütün güncellemeleri tek bir bekleme kümesi sayar. Platformda pencere öğesinin yenilenmesi platformun kendi zamanlamasına bağlıdır ve bekleyen güncellemelerin ne kadar süreyle tutulacağı modelde yoktur.

#### İki Pencere Öğesi

Ana ekranda iki pencere öğesi yaşıyorsa her birinin anahtarı kendisine aittir. Birinin güncellemesi diğerinin yerini almaz; ezme yalnız aynı anahtarlı güncellemeler arasında olur.

```kotlin
// Main.kt — iki pencere ogesinde ezme yalniz ayni anahtar icinde olur
fun main() {
    val sent = listOf(
        Signal("pencere.hava", 7),
        Signal("pencere.takvim", 8),
        Signal("pencere.hava", 9),
        Signal("pencere.takvim", 10),
        Signal("pencere.hava", 11),
        Signal("pencere.takvim", 12),
    )
    val received = drawn(sent, openAt = 10)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [8, 9, 10, 11, 12]
olay-7: anahtar=pencere.hava | kopya=0 -> ezilen
olay-8: anahtar=pencere.takvim | kopya=1 -> birebir
olay-9: anahtar=pencere.hava | kopya=1 -> birebir
olay-10: anahtar=pencere.takvim | kopya=1 -> birebir
olay-11: anahtar=pencere.hava | kopya=1 -> birebir
olay-12: anahtar=pencere.takvim | kopya=1 -> birebir
birebir: 5
yinelenen: 0
ezilen: 1
kayip: 0
```

Pencere 10 numaralı adımda açıldı. Açılmadan önceki üç güncellemeden yalnız 7 numaralı hava güncellemesi ezildi: 9 numaralı güncelleme onun yerini aldı. 8 numaralı takvim güncellemesi, aynı anahtarlı başka güncelleme olmadığı için birebir ulaştı.

**SY2.** Model, iki pencere öğesini birbirinden bağımsız kurar. Platformda aynı sağlayıcı birden çok pencere öğesine hizmet verebilir ve güncellemenin hangi örneğe gideceği tanıtıcı numarasına bağlıdır; bu modelde yoktur.

#### Yapılandırma Ekranı

Bazı pencere öğeleri kullanıcıdan bir ayar ister: hangi şehrin havası, hangi notun gösterileceği. Kullanıcı pencere öğesini ana ekrana eklediğinde platform önce uygulamanın yapılandırma ekranını açar ve kullanıcı ayarı tamamlayana kadar pencere öğesini bekletir. Yapılandırma bitmeden gelen güncellemeler çizilecek bir ayara sahip değildir ve düşer. Aşağıdaki modelde not gösteren pencere öğesi 23 numaralı adımda yapılandırılıyor; hava pencere öğesi ise baştan hazır.

```kotlin
// Configured.kt — yapilandirma bitmeden gelen guncellemeleri birakir
fun configured(sent: List<Signal>, key: String, doneAt: Int): List<Signal> =
    sent.filter { it.key != key || it.seq >= doneAt }
```

```kotlin
// Main.kt — yapilandirma bitmeden gelen guncellemeler cizilmez
fun main() {
    val sent = listOf(
        Signal("pencere.not", 19),
        Signal("pencere.hava", 20),
        Signal("pencere.not", 21),
        Signal("pencere.hava", 22),
        Signal("pencere.not", 23),
    )
    val received = configured(sent, key = "pencere.not", doneAt = 23)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [20, 22, 23]
olay-19: anahtar=pencere.not | kopya=0 -> ezilen
olay-20: anahtar=pencere.hava | kopya=1 -> birebir
olay-21: anahtar=pencere.not | kopya=0 -> ezilen
olay-22: anahtar=pencere.hava | kopya=1 -> birebir
olay-23: anahtar=pencere.not | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 2
kayip: 0
```

Hava pencere öğesinin iki güncellemesi birebir ulaştı. Not pencere öğesinin 19 ve 21 numaralı güncellemeleri çizilmedi, çünkü yapılandırma 23 numaralı adımda bitti ve aynı anahtarla gelen 23 numaralı güncelleme onların yerini aldı. Kural bunları ezilen saydı; kullanıcı açısından ise bunlar henüz ayarı olmayan bir pencere öğesine gönderilmiş, bu yüzden hiç anlam taşımayan güncellemelerdi. Yapılandırma bitiminden sonra aynı anahtarla hiçbir güncelleme gelmeseydi, aynı iki güncelleme kayıp sayılacaktı.

**SY3.** Model, yapılandırmayı tek bir adımda ve kesin olarak bitmiş kabul eder. Platformda kullanıcı yapılandırma ekranından vazgeçebilir ve bu durumda pencere öğesi hiç eklenmez; ayrıca yapılandırma ekranı açıkken platformun güncelleme isteklerine nasıl davrandığı modelde yoktur.

#### Neden Her Güncelleme Tüm Görünümü Taşır

Pencere öğesi uygulamanın süreci içinde değil, ana ekran uygulamasının sürecinde çizilir. Uygulama görünümü doğrudan değiştiremez; bunun yerine görünümün bir tarifini üretir ve platform bu tarifi ana ekrana iletir. Tarif, tek bir alanın değişikliği olarak değil çoğunlukla bütün görünümün yeni hali olarak verilir. Sonuç olarak her güncelleme bir öncekini tamamen değiştirir.

Ezilen teslimin bu dersteki doğal yeri budur. İki güncelleme birleştirilemez, çünkü ikinci güncelleme birincinin üzerine eklenmez, yerine geçer. Bir sıcaklık değerini içeren ilk görünüm ile nem değerini içeren ikinci görünüm arasında, ilkini tutarak ikincisini eklemek mümkün değildir. İkinci görünüm sıcaklığı da içermiyorsa pencere öğesinde sıcaklık görünmez olur. Bu yüzden her güncelleme, gösterilecek bütün durumu taşımalıdır.

Aynı kural uygulamaya şunu da söyler: bir güncellemenin ezilmesi bilgi kaybı değildir, yeter ki sonraki güncelleme bütün durumu taşısın. Bilginin kaybolduğu durum, sonraki güncellemenin yalnızca değişen alanı taşıdığı durumdur. Model bunu ayırt etmez; her güncellemeyi aynı anahtarlı bir sıra sayar ve ardıl güncelleme eksik taşısa bile ezilen der. Ardılın tam olup olmadığı uygulamanın tasarım kararıdır.

#### Boyut Değişince Ne Olur

Kullanıcı pencere öğesini yeniden boyutlandırdığında platform sağlayıcıya yeni boyutu bildirir ve uygulamadan yeni bir görünüm bekler. Bu çağrı bir güncelleme isteğidir ve aralığa bağlı değildir; kullanıcı ne zaman boyut değiştirirse o an gelir. Uygulama yeni boyuta uygun bir tarif üretmezse eski tarif yeni boyuta sığmayabilir: metin kesilir, görsel taşar ya da pencere öğesi boş görünür.

Bu, ezilen teslimin ikinci bir kaynağıdır. Aralıktan gelen güncelleme ile boyuttan gelen güncelleme aynı anahtarı taşır, çünkü ikisi de aynı pencere öğesinin durumudur. Boyut değişikliği hemen ardından aralıktan bir güncelleme gelirse, ikisinden yalnız sonuncusu çizilir. Sonuncu yeni boyutu bilmiyorsa kullanıcı boyutlandırmanın etkisiz kaldığını görür. Bu yüzden her tarif, o anki boyuta göre üretilmelidir; boyutu bir kez okuyup saklayan uygulama, ardıl güncellemelerde eski boyutu kullanır.

#### Yaygın Yanılgılar

**"Yenileme aralığı bir sözdür."** Aralık bir istektir. Platform onu uzatabilir ve pencere görünür değilse görünümü yalnızca saklar. Her güncellemenin çizilmesini bekleyen bir uygulama, ölçümdeki ilk bloktaki gibi ezilen güncellemeler bulur.

**"Ezilen güncelleme bir hatadır."** Pencere öğesi için son durum eskisinden değerlidir. Çizilmeyen ara durum, kullanıcının hiç görmeyeceği bir şeydir ve kaybı değil, tasarımı gösterir. Hata, son durumun hiç çizilmediği durumdur: bu kayıptır.

**"Ertelenen güncelleme ayrı bir teslimdir."** Bu kursun dört teslimi vardır. Ertelenen güncelleme ulaşırsa birebir, bir sonraki güncellemeyle yer değiştirirse ezilen, hiç ulaşmazsa kayıptır. Erteleme bir teslim türü değil, ulaşma zamanına dair bir özelliktir.

**"Kaldırılan pencere öğesine güncelleme göndermek zararsızdır."** Güncelleme zarar vermez ama boşunadır: çizilecek görünüm kalmamıştır. Pil harcayan ve hiçbir yere ulaşmayan bu güncellemelerin durdurulması için uygulama, kaldırma çağrısını dinlemelidir.

**"Pencere öğesi uygulama kapalıyken güncellenemez."** Sağlayıcı, uygulamanın ön planda olmasından bağımsız çalışır. Platform sağlayıcıyı güncelleme için uyandırır ve iş bitince süreç yeniden uykuya geçebilir. Bu yüzden güncelleme için gereken veri sağlayıcının çağrısında hazır olmalı ya da hızlıca alınabilmelidir.

**"Her pencere öğesi aynı görünümü gösterir."** Aynı sağlayıcıdan eklenmiş iki pencere öğesi farklı boyutta ve farklı ayarda olabilir; her birinin kendi anahtarı ve kendi güncelleme dizisi vardır.

#### Sınırlayıcı Ölçüm

Kullanıcı pencere öğesini ana ekrandan kaldırdığında sağlayıcı artık güncelleme alıcısı değildir. Uygulama güncellemeleri üretmeye devam eder, ama çizilecek bir görünüm kalmamıştır.

```kotlin
// Removed.kt — ana ekrandan kaldirilan pencere ogesine guncelleme ulasmaz
fun removed(drawn: List<Signal>, removedAt: Int): List<Signal> =
    drawn.filter { it.seq < removedAt }
```

```kotlin
// Main.kt — ana ekrandan kaldirilan pencere ogesine guncelleme ulasmaz
fun main() {
    val sent = (13..18).map { Signal("pencere.hava", it) }
    val received = removed(drawn(sent, openAt = 15), removedAt = 17)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
ulasan siralar: [14, 15, 16]
olay-13: anahtar=pencere.hava | kopya=0 -> ezilen
olay-14: anahtar=pencere.hava | kopya=1 -> birebir
olay-15: anahtar=pencere.hava | kopya=1 -> birebir
olay-16: anahtar=pencere.hava | kopya=1 -> birebir
olay-17: anahtar=pencere.hava | kopya=0 -> kayip
olay-18: anahtar=pencere.hava | kopya=0 -> kayip
birebir: 3
yinelenen: 0
ezilen: 1
kayip: 2
```

13 numaralı güncelleme ezildi, 14, 15 ve 16 ulaştı. Pencere öğesi 17 numaralı adımdan önce kaldırıldığı için 17 ve 18 numaralı güncellemeler kayıp oldu: sonrasında aynı anahtarla hiçbir güncelleme ulaşmadı. Kural kaybın nedeninin kaldırılmış pencere öğesi olduğunu görmez; bunu bilmek için uygulamanın hangi pencere öğesi tanıtıcılarının yaşadığını ayrıca sorması gerekir.

**SY4.** Model, kaldırmayı anında ve tek adımda kurar. Platformda kaldırılan pencere öğesi için silme çağrısı sağlayıcıya gelir ve uygulama güncellemeyi o noktada durdurabilir; bu modelde yoktur.

#### Özet

- Bu dersin teslimi: ezilen. Pencere açılana kadar biriken güncellemelerden her anahtarın yalnız sonuncusu çizilir.
- Altı güncellemeden ikisi ezildi, dördü birebir ulaştı: ezilen 2, birebir 4.
- İki pencere öğesinde ezme yalnız aynı anahtar içinde oldu: bir güncelleme ezildi, beşi birebir ulaştı.
- Ana ekrandan kaldırılan pencere öğesine iki güncelleme kayıp oldu: kayip 2.
- Ertelenen güncelleme ayrı bir kategori değildir; sessiz kanaldaki kullanıcı bildirimi ulaşır ve birebirdir.
- Yapılandırması bitmemiş pencere öğesinin iki güncellemesi, bitişten sonra aynı anahtarla gelen güncelleme yüzünden ezilen sayıldı: birebir 3, ezilen 2.

#### Sonraki Adım

Bu dersle sistem yeteneklerinin sonuna geldik: kullanıcı bildirimi, bulut mesajlaşma, konum, kamera, sensör, derin bağlantı ve pencere öğesi aynı soruyla ölçüldü. Sıradaki konuda uygulamanın kendi verisi ve anahtarları konuşulur: `Şifreli Depolama ve Anahtar Deposu` dersi, saklanan verinin ve anahtarın kimin eline düşebileceğini ele alır.

## Güvenlik ve Dış Servisler

Şifreli depolama ve anahtar deposu, ağ güvenliği yapılandırması, kimlik doğrulama akışları, uygulama imzalama, analitik ve çökme raporlama, uzaktan yapılandırma, uygulama içi satın alma ve reklam.

### Şifreli Depolama ve Anahtar Deposu

Source: https://academia.sh/tr/kurslar/android-servisler/sifreli-depolama-ve-anahtar-deposu

Uygulamanın yazdığı sır anahtar deposundaki bir anahtarla şifrelenir; anahtar kullanıcı doğrulamasına bağlıysa kilit ekranı kalkınca geçersizleşir ve kayıt okunamaz. Ders yazılan sırrın sonraki okumaya kaç kopya olarak ulaştığını ölçer.

Önceki derste olaylar aygıtın içinde bir ekrana kadar ilerliyordu. Bu derste olay bir ekrana değil, bir depoya yazılır: uygulama bir sır üretir, saklar ve sonraki oturumda geri okur. Yazan ile okuyan aynı uygulamadır, ama aralarında zaman ve bir anahtar vardır. Kursun açılış dersinde sekiz olayın dağılımı birebir 3, yinelenen 1, ezilen 2, kayıp 2 olarak ölçülmüştü. Bu derste sorulan soru aynıdır: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?** Olay yazılan sırdır, alıcı sonraki okumadır.

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

Kural kopya sayısına bakar. Aynı `seq` ile bir kopya ulaştıysa teslim birebirdir, birden çoksa yinelenendir. Kopya yoksa ve aynı anahtarla daha büyük bir `seq` ulaştıysa olay ezilmiştir. Hiçbiri yoksa kayıptır. Kural içeriği, nedeni ve kaydın şifreli olup olmadığını görmez.

#### Anahtar Deposu ve Takma Ad

Platformun anahtar deposu (`KeyStore`), şifreleme anahtarlarını uygulamanın belleğinden ve dosyalarından ayrı tutar. Uygulama anahtarın kendisini görmez; bir **anahtar takma adı** (alias) ile ona seslenir ve işlemi anahtarı görmeden ister. Takma ad bir kayıt defterinin satır başlığı gibidir: aynı ad altında bir anahtar bulunur ve şifrelenen her kayıt hangi takma adın anahtarıyla şifrelendiğini bilir.

Anahtar üretilirken bir koşul eklenebilir: anahtar yalnız kullanıcı doğrulandıktan sonra kullanılabilsin.

```kotlin
// Platform kodu: KeyGenParameterSpec
val spec = KeyGenParameterSpec.Builder(
    "kilit",
    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT,
)
    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    .setUserAuthenticationRequired(true)
    .build()

val generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
generator.init(spec)
val key = generator.generateKey()
```

`setUserAuthenticationRequired(true)` anahtarı kullanıcı doğrulamasına bağlar. Platformun belgesine göre böyle bir anahtar, güvenli kilit ekranı kaldırılınca ya da zorla sıfırlanınca geri dönüşsüz biçimde geçersizleşir. Geçersizleşmiş anahtarla işlem başlatan kod bir istisna alır.

```kotlin
// Platform kodu: KeyPermanentlyInvalidatedException
try {
    cipher.init(Cipher.DECRYPT_MODE, key)
} catch (e: KeyPermanentlyInvalidatedException) {
    keyStore.deleteEntry("kilit")
}
```

Şifreli tercih deposu bu anahtarı hazır bir kabukla sarar. Uygulama değeri yazar, kabuk anahtarı kullanarak şifreler.

```kotlin
// Platform kodu: EncryptedSharedPreferences
val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

val prefs = EncryptedSharedPreferences.create(
    context,
    "sirlar",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
prefs.edit().putString("sir.kart", "gizli-deger").apply()
```

Bu şifreli tercih deposu kitaplığı kullanımdan kaldırılmıştır; yeni kodda örnek alınmamalıdır. Güncel yol, anahtarı doğrudan anahtar deposunda üretip veriyi uygulamanın kendisinin şifrelemesi ya da veri deposunu bir şifreleme kitaplığıyla birlikte kullanmaktır.

Bu üç parça platform kodudur ve burada çıktı, süre ya da sayı iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir. Modelde kasa, kayıtları takma ada bağlı tutar ve bir takma adın anahtarı geçersizleşince o adın kayıtları okunamaz.

```kotlin
// Vault.kt — kayitlari anahtar takma adina baglar, gecersizlesen anahtarin kayitlarini okutmaz
class Vault {
    private val records = mutableListOf<Pair<String, Signal>>()
    private val invalid = mutableSetOf<String>()

    fun write(alias: String, secret: Signal) {
        records.add(alias to secret)
    }

    fun invalidateKey(alias: String) {
        invalid.add(alias)
    }

    fun migrate(from: String, to: String) {
        records.filter { it.first == from }.forEach { records.add(to to it.second) }
    }

    fun readAll(): List<Signal> = records.filter { it.first !in invalid }.map { it.second }
}
```

Alıcı, `readAll` işlevinin dönüşüdür. Her ölçümde uygulama sırları yazar ve sonra geri okur.

```kotlin
// Probe.kt — gonderilen sirlar icin kasadan okunan listeyi alici sayar
fun probe(vault: Vault, sent: List<Signal>): List<Arrival> {
    val received = vault.readAll()
    return sent.mapIndexed { i, s -> Arrival("olay-${i + 1}", s, received) }
}
```

#### Anahtar Geçerliyken

Üç sır aynı takma adla yazılır ve anahtara dokunulmaz. Anahtar geçerli olduğu sürece okuma her kaydı döndürür.

```kotlin
// Main.kt — anahtar gecerli kaldikca her kayit okunur
fun main() {
    val vault = Vault()
    val sent = listOf(Signal("sir.oturum", 1), Signal("sir.kart", 2), Signal("sir.not", 3))
    sent.forEach { vault.write("kilit", it) }
    val arrivals = probe(vault, sent)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=sir.oturum | kopya=1 -> birebir
olay-2: anahtar=sir.kart | kopya=1 -> birebir
olay-3: anahtar=sir.not | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 0
kayip: 0
```

Üç yazının üçü okunur. Şifreli depolamanın sağladığı güvence burada yoktur: kasa, kaydın şifreli olup olmadığını modellemez, yalnız okunup okunamadığını.

#### Kilit Ekranı Kalkınca

Dört sır iki takma adın altında durur. Bir kısmı kullanıcı doğrulamasına bağlı `kilit` anahtarıyla, bir kısmı bağlı olmayan `genel` anahtarıyla şifrelenmiştir. Kullanıcı kilit ekranını kaldırır ve `kilit` anahtarı geçersizleşir.

```kotlin
// Main.kt — gecersizlesen anahtarin kayitlari okunamaz
fun main() {
    val vault = Vault()
    val sent = listOf(
        Signal("sir.oturum", 1),
        Signal("sir.kart", 2),
        Signal("sir.not", 3),
        Signal("sir.ayar", 4),
    )
    vault.write("genel", sent[0])
    vault.write("kilit", sent[1])
    vault.write("kilit", sent[2])
    vault.write("genel", sent[3])
    vault.invalidateKey("kilit")
    val arrivals = probe(vault, sent)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=sir.oturum | kopya=1 -> birebir
olay-2: anahtar=sir.kart | kopya=0 -> kayip
olay-3: anahtar=sir.not | kopya=0 -> kayip
olay-4: anahtar=sir.ayar | kopya=1 -> birebir
birebir: 2
yinelenen: 0
ezilen: 0
kayip: 2
```

Kayıtlar diskte durmaya devam eder, ama okuma onları döndürmez. Kural bunu kayıp sayar: olay yazıldı, alıcıya hiçbir kopya ulaşmadı ve aynı anahtarla daha büyük bir `seq` da gelmedi. Bu dersin teslimi: kayıp. Sebebi, kaydın var olması ile okunabilir olmasının aynı şey olmamasıdır.

#### Taşıma Kopya Bırakırsa

Uygulama kullanıcı doğrulamasına bağlı olmayan kayıtları yeni bir takma ada taşımak ister. Taşıma kayıtları yeni ada kopyalar, eskileri silmez. Eski anahtar geçerli kaldığı için iki ad altındaki kayıt da okunur.

```kotlin
// Main.kt — tasima eski kaydi silmezse okuma iki kopya dondurur
fun main() {
    val vault = Vault()
    val sent = listOf(Signal("sir.oturum", 1), Signal("sir.kart", 2), Signal("sir.not", 3))
    vault.write("genel", sent[0])
    vault.write("genel", sent[1])
    vault.write("kilit", sent[2])
    vault.migrate("genel", "genel-2")
    val arrivals = probe(vault, sent)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=sir.oturum | kopya=2 -> yinelenen
olay-2: anahtar=sir.kart | kopya=2 -> yinelenen
olay-3: anahtar=sir.not | kopya=1 -> birebir
birebir: 1
yinelenen: 2
ezilen: 0
kayip: 0
```

Taşınan iki sır iki kopya olarak ulaştı. Okuma kodu bu kopyaları birleştirmiyorsa uygulama aynı sırrı iki kez görür. Yinelenen sonuç, taşımanın yarım kaldığını gösteren bir işarettir; kural ise yalnız sayıyı verir.

**GD1.** Model, her kaydı tek bir takma adın anahtarıyla ilişkilendirir. Platformda bir kayıt, anahtarın sürümüne, şifreleme kipine ve başlatma vektörüne de bağlıdır; bunlar modelde yoktur.

**GD2.** Model, anahtarın geçersizleşmesini tek bir çağrı olarak kurar. Platformda geçersizleşme, kilit ekranının kaldırılması ya da zorla sıfırlanması gibi bir olaydan sonra anahtar kullanılmak istendiğinde fark edilir.

#### Geçersizleşmenin Sınırları

Geçersizleşmenin neden bir kayıp ürettiğini anlamak için üç ayrımı birbirinden ayırmak gerekir. Birincisi, anahtar ile kayıt ayrı şeylerdir: kayıt uygulamanın dosyasında durur, anahtar ise anahtar deposunda. Anahtar gidince kayıt yerinde kalır, ama onu açacak bir şey kalmaz. İkincisi, anahtarı uygulama yaratmış olsa da uygulama onu geri getiremez; yalnız aynı takma adla yeni bir anahtar üretebilir ve yeni anahtar eski kaydı açmaz. Üçüncüsü, geçersizleşme uygulamanın kontrolünde bir olay değildir. Tetikleyen, kullanıcının kilit ekranıyla yaptığı bir işlemdir ve uygulama bunu ancak sonraki okumada, anahtarı kullanmak istediğinde fark eder.

Bu üç ayrımın modeldeki karşılığı basittir. Kasa her kaydı bir takma adla işaretler. `invalidateKey` bir takma adı geçersiz sayar. `readAll` geçersiz takma adların kayıtlarını döndürmez. Modelde kaydın içeriği hiçbir zaman görünmez; kural yalnız dönen listeye bakar.

Burada platformun söylediği ile modelin söylediği arasındaki fark açık kalmalıdır. Platform, kullanıcı doğrulamasına bağlı bir anahtarın kilit ekranı kaldırılınca geçersizleştiğini belgeler. Anahtarın başka hangi koşullarda geçersizleşebileceği anahtarın üretim parametrelerine bağlıdır ve bu derste ölçülmez. Model yalnız `invalidateKey` çağrısını bilir.

#### Sınırlayıcı Ölçüm

Kasa, kaybın nedenini taşımaz. Kilit ekranı kalkınca `kilit` anahtarı geçersizleşir ve iki sır okunamaz olur. Uygulama bunu fark edip yeni bir anahtar üretir ve yalnız kartı yeniden yazar: aynı anahtar adı `sir.kart`, bu kez yeni bir `seq` ve yeni bir takma adla.

```kotlin
// Main.kt — ayni anahtara yeniden yazilan sir eski kaydi ezer
fun main() {
    val vault = Vault()
    val sent = listOf(
        Signal("sir.oturum", 1),
        Signal("sir.kart", 2),
        Signal("sir.not", 3),
        Signal("sir.kart", 4),
    )
    vault.write("genel", sent[0])
    vault.write("kilit", sent[1])
    vault.write("kilit", sent[2])
    vault.invalidateKey("kilit")
    vault.write("kilit-2", sent[3])
    val arrivals = probe(vault, sent)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=sir.oturum | kopya=1 -> birebir
olay-2: anahtar=sir.kart | kopya=0 -> ezilen
olay-3: anahtar=sir.not | kopya=0 -> kayip
olay-4: anahtar=sir.kart | kopya=1 -> birebir
birebir: 2
yinelenen: 0
ezilen: 1
kayip: 1
```

Okunamayan iki sırrın ikisi de aynı nedenle, geçersizleşen aynı anahtar yüzünden okunamaz. Buna karşın kurala göre biri ezilen, öteki kayıptır. `sir.kart` için aynı anahtarla daha büyük bir `seq` ulaştığı için ezilendir; `sir.not` için böyle bir kopya olmadığı için kayıptır. Kural kaybın nedenini görmez, yalnız yerini alan bir kopya olup olmadığını görür.

Bu ayrım uygulama açısından önemlidir. Ezilen bir sır, kullanıcı için kaybolmuş sayılmaz; yeni sürümü okunur. Kayıp bir sır ise yeniden girilmesi gereken bir veridir. Kural ikisini ayırır ama hangisinin kullanıcıya daha pahalıya geldiğini söylemez.

Kayıp, kullanıcının eylemiyle ezilene dönüşebilir. Kullanıcı geçersizleşmeden sonra kartı ve notu yeniden girer; uygulama ikisini yeni takma adla, yeni `seq` değerleriyle yazar.

```kotlin
// Main.kt — kullanici sirlari yeniden girerse kayiplar ezilene doner
fun main() {
    val vault = Vault()
    val sent = listOf(
        Signal("sir.oturum", 1),
        Signal("sir.kart", 2),
        Signal("sir.not", 3),
        Signal("sir.kart", 4),
        Signal("sir.not", 5),
    )
    vault.write("genel", sent[0])
    vault.write("kilit", sent[1])
    vault.write("kilit", sent[2])
    vault.invalidateKey("kilit")
    vault.write("kilit-2", sent[3])
    vault.write("kilit-2", sent[4])
    val arrivals = probe(vault, sent)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=sir.oturum | kopya=1 -> birebir
olay-2: anahtar=sir.kart | kopya=0 -> ezilen
olay-3: anahtar=sir.not | kopya=0 -> ezilen
olay-4: anahtar=sir.kart | kopya=1 -> birebir
olay-5: anahtar=sir.not | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 2
kayip: 0
```

Önceki ölçümde kayıp olan `sir.not` artık ezilendir, çünkü yerini alan daha büyük bir `seq` ulaştı. Okunamayan eski kayıtlar yerinde durur; sayım değişti, çünkü kural artık yerini alan bir kopya görüyor. Kullanıcıya yeniden girdirmek yeniden üretilebilen sırlar için bir toparlanma yoludur. Yeniden girilemeyen bir sır için ise aynı ölçüm kayıp olarak kalır.

**GD3.** Model, yeniden yazmayı yeni bir takma adla yapar ve yeni sırrı eskisinin üzerine koymaz. Platformda aynı takma adla yeni bir anahtar üretmek de mümkündür; model bu ayrımı kurmaz.

#### Okuyan Kodun Yapacakları

Alıcı tarafında yapılacaklar ölçümden çıkar. Okuma sonucu beklenenden kısaysa bunun bir hata değil bir teslim sonucu olduğu kabul edilir ve üç şey yapılır.

- **Eksiği tanımak.** Okuyan kod, hangi sırları beklediğini bilmelidir. Beklenen listeyi okunan listeyle karşılaştırmadan kayıp fark edilmez. Modelde bu karşılaştırmayı `probe` yapar.
- **Kopyaları birleştirmek.** Aynı anahtar için birden çok kayıt dönüyorsa, en büyük `seq` değerli olan alınır. Taşıma kopya bırakabildiği için okuma kodu kopyayı hata saymaz, ama iki kez işlemez de.
- **Yeniden üretilebilirliği ayırmak.** Sunucudan yeniden alınabilen bir oturum anahtarı ile yalnız kullanıcının bildiği bir parola aynı kayıp sınıfında değildir. İlki sessizce yenilenir, ikincisi kullanıcıdan istenir. Bu ayrım yazma anında, sır depoya girmeden önce yapılmalıdır.

Okuma sonucunun boş dönmesi ile hata vermesi de ayrı ele alınır. Boş sonuç hiç kayıt yazılmadığını, hata ise kaydın var olduğunu ama açılamadığını söyleyebilir; ikisini aynı dala koyan kod, geçersizleşmeyi ilk açılış sanır.

Okuma kodunun yapmaması gereken bir şey de vardır: okunamayan kaydı sessizce silmek. Kayıt silinirse sonraki ölçümde ne ezilen ne kayıp ayırt edilir; hiçbir iz kalmaz.

#### Yaygın Yanılgılar

**Şifreli kayıt her zaman okunur.** Şifreleme kaydı korur, ama okunabilirliğini garanti etmez. Anahtar kaybolursa kayıt korunmuş ve ulaşılamaz olur. İki ölçüm bunu gösterdi: anahtar geçerliyken üç kayıt birebir, geçersizleşince iki kayıt kayıp.

**Diskte duran kayıt teslim edilmiştir.** Teslim, alıcıya ulaşan kopyadır. Diskteki okunamayan kayıt alıcıya ulaşmaz. Kural bu yüzden diskteki baytlara değil, okumanın dönüşüne bakar.

**Ezilen kötü haberdir.** Ezilen, yerine yenisi ulaştığı için çoğu zaman iyi haberdir. Kötü olan, ezen kopyanın hiç gelmemesidir; o durumda aynı olay kayıp olarak sayılır. Son ölçüm ikisini yan yana gösterir.

**Yinelenen veri bozulmasıdır.** Yinelenen yalnız aynı kopyanın birden çok kez ulaşmasıdır. Taşıma sırasında bırakılan kopya bir bozulma değildir, tamamlanmamış bir temizliktir.

**Kayıt yedeklenirse anahtar da yedeklenmiş olur.** Olmaz. Kayıt yalnız şifreli baytlardır; onu açan anahtar kayıttan ayrı durur. Kaydı taşıyıp anahtarı taşımayan bir düzende okuma, ölçümdeki geçersizleşmeyle aynı sonucu verir: kayıtlar vardır, döndürülmez, kural kayıp sayar. Platformun yedekleme davranışı bu derste ölçülmez.

**Anahtarı yeniden üretmek kaydı geri getirir.** Getirmez. Aynı takma adla yeni bir anahtar üretmek, eski anahtarla şifrelenmiş kaydı açmaz; yalnız bundan sonra yazılacak kayıtlar için çalışan bir anahtar bırakır.

#### Özet

- Bu dersin teslimi: kayıp. Kilit ekranı kalkınca kullanıcı doğrulamasına bağlı anahtar geçersizleşir ve o anahtarla şifrelenmiş kayıt okunamaz.
- Anahtar takma adı kaydı anahtara bağlar; aynı adın altındaki kayıtlar birlikte okunamaz hâle gelir.
- Geçerli anahtarda üç yazının üçü birebir ulaşır. Kullanıcı doğrulamasına bağlı iki sır geçersizleşince kayıp 2, birebir 2 olur.
- Taşıma eski kaydı silmezse iki sır iki kopya olarak okunur: yinelenen 2, birebir 1.
- Aynı anahtarla yeniden yazılan sır eski kaydı ezilen yapar; yeniden yazılmayan sır kayıptır. Kullanıcı iki sırrı da yeniden girerse ezilen 2, birebir 3 olur. Kural nedeni değil kopyaları görür.

#### Sonraki Adım

Bu derste kayıt diske yazıldı ve anahtar geçersizleşince okunamadı. Sonraki derste olay ağa çıkar: uygulama bir isteği gönderir, ama istek düz metin diye engellenirse hiçbir kopya sunucuya ulaşmaz. Ağ güvenliği yapılandırması bu engelin kuralını yazar.

### Ağ Güvenliği Yapılandırması

Source: https://academia.sh/tr/kurslar/android-servisler/ag-guvenligi-yapilandirmasi

Ağ güvenliği yapılandırması düz metin trafiğine, güvenilen sertifika kaynaklarına ve alan adına özel kurallara karar verir. Ders düz metin diye engellenen bir isteğin sunucuya kaç kopya olarak ulaştığını ve sabitlenen sertifikayla eşleşmeyen bağlantının sonucunu ölçer.

Önceki derste sır diskte duruyordu ve onu okunamaz yapan şey bir anahtardı. Bu derste olay aygıtın dışına çıkar: uygulama sunucuya bir istek gönderir. İstek, ağa çıkmadan önce platformun yapılandırmasından geçer ve o yapılandırma isteği engelleyebilir. Soru kursun sorusudur: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?** Olay istektir, alıcı sunucudur.

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

Kural sunucuya ulaşan kopyaları sayar. İsteğin neden ulaşmadığını, hangi kuralın engellediğini ve engellemenin doğru olup olmadığını bilmez.

#### Yapılandırmanın Üç Kararı

Ağ güvenliği yapılandırması bir XML dosyasıdır ve uygulamanın bildirim dosyasından adıyla çağrılır. Üç şeye karar verir. Birincisi **düz metin trafiğidir**: `http://` ile başlayan bir adrese istek gidebilir mi. İkincisi **güven çapasıdır**: sunucunun sertifika zincirini hangi kök sertifikalara karşı doğrulayacağı. Üçüncüsü **sertifika sabitlemedir** (certificate pinning): belirli bir alan adı için yalnız önceden bildirilen sertifika özetleriyle eşleşen bağlantı kabul edilir.

```xml
<!-- Platform kodu: network-security-config -->
<network-security-config>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="false">yerel.test</domain>
    </domain-config>
    <domain-config>
        <domain includeSubdomains="true">api.ornek.test</domain>
        <pin-set>
            <pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
        </pin-set>
    </domain-config>
</network-security-config>
```

Bu parça platform yapılandırmasıdır; çalıştırılmaz ve burada çıktı iddia etmiyoruz. Özet değeri örnek bir yer tutucudur. `base-config` bütün trafiğin varsayılanını yazar: düz metin kapalı, güven çapası sistemin sertifikaları. İlk `domain-config` tek bir adresi, `yerel.test`'i, düz metne açar. İkincisi `api.ornek.test` ve alt alan adları için sertifika özetini sabitler.

Model, platformun bu kuralını kurar; model platformun kendisi değildir. Modelde bir politika, düz metne açık adreslerin kümesini ve isteğe bağlı bir sabit sertifika özetini tutar. İstek HTTPS değilse yalnız açık adresler geçer. HTTPS ise ve politika bir özet sabitlemişse, istekteki sertifika onunla eşleşmelidir.

```kotlin
// Policy.kt — duz metin ve sabitleme kurallarini isteklere uygular
class Request(val signal: Signal, val url: String, val cert: String = "ortak")

class NetworkPolicy(
    private val cleartextHosts: Set<String> = emptySet(),
    private val pin: String? = null,
) {
    private fun host(url: String) = url.substringAfter("://").substringBefore("/")

    fun passes(request: Request): Boolean {
        if (!request.url.startsWith("https://")) return host(request.url) in cleartextHosts
        return pin == null || request.cert == pin
    }
}

fun rejectCleartext(policy: NetworkPolicy, requests: List<Request>): List<Signal> =
    requests.filter { policy.passes(it) }.map { it.signal }
```

Sunucuya ulaşan liste, `rejectCleartext` işlevinin dönüşüdür. Ölçümde kopya sayısını bu liste verir.

```kotlin
// Probe.kt — gonderilen istekler icin sunucuya ulasan listeyi alici sayar
fun probe(policy: NetworkPolicy, requests: List<Request>, sent: List<Signal>): List<Arrival> {
    val received = rejectCleartext(policy, requests)
    return sent.mapIndexed { i, s -> Arrival("olay-${i + 1}", s, received) }
}
```

#### İsteğin İki Kapısı

`passes` işlevi isteği iki kapıdan geçirir ve kapılar birbirinden bağımsızdır. İlk kapı adresin biçimine bakar. İstek `https://` ile başlamıyorsa karar tamamen düz metin listesine bağlıdır: adres listedeyse istek geçer, değilse düşer. Bu dalda sertifikaya hiç bakılmaz, çünkü düz metin bir istekte sertifika yoktur.

İkinci kapı yalnız HTTPS istekleri için çalışır. Politika bir özet sabitlememişse istek geçer; sabitlemişse isteğin sertifikası o özetle eşleşmek zorundadır. Burada düz metin listesi bir anlam taşımaz: listedeki bir adrese HTTPS ile gidilirse, yine de sabitleme kontrol edilir.

Bu ayrımın iki sonucu vardır. Birincisi, sabitleme düz metin isteği korumaz; açık adrese giden `http://` isteği hiçbir özetle karşılaştırılmaz. İkincisi, düz metin engelinin kaldırılması sabitlemeyi de kaldırmaz; iki karar ayrı ayrı yazılır ve ayrı ayrı sonuç verir. Ölçümlerde kayıp bazen birinci kapıdan, bazen ikinci kapıdan gelir ve kural ikisini ayırmaz: yalnız sunucuya hiçbir kopyanın ulaşmadığını görür.

Platformda kuralların uygulanma sırası ve alan adı eşleşmesinin ayrıntıları yapılandırma dosyasının yazımına bağlıdır; bu derste ölçülen yalnız modelin iki kapısıdır.

#### Hepsi HTTPS İken

Üç istek de HTTPS adresine gider. Politikada açık adres ve sabit özet yoktur.

```kotlin
// Main.kt — https istekleri engele takilmaz
fun main() {
    val sent = listOf(Signal("siparis.41", 1), Signal("siparis.42", 2), Signal("stok.7", 3))
    val requests = listOf(
        Request(sent[0], "https://api.ornek.test/siparis"),
        Request(sent[1], "https://api.ornek.test/siparis"),
        Request(sent[2], "https://api.ornek.test/stok"),
    )
    val arrivals = probe(NetworkPolicy(), requests, sent)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=siparis.41 | kopya=1 -> birebir
olay-2: anahtar=siparis.42 | kopya=1 -> birebir
olay-3: anahtar=stok.7 | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 0
kayip: 0
```

Üç isteğin üçü sunucuya birer kopya olarak ulaştı. Düz metin kapalı olduğunda HTTPS trafiği bu engele takılmaz.

#### Düz Metin Diye Engellenen İstek

Dört istekten ikisi `http://` ile başlar. Bunlardan biri, açık adres kümesindeki `yerel.test`'e gider; öteki gitmez.

```kotlin
// Main.kt — duz metin istegi acik adres disinda engellenir
fun main() {
    val sent = listOf(
        Signal("siparis.41", 1),
        Signal("siparis.42", 2),
        Signal("stok.7", 3),
        Signal("kur.3", 4),
    )
    val requests = listOf(
        Request(sent[0], "http://api.ornek.test/siparis"),
        Request(sent[1], "https://api.ornek.test/siparis"),
        Request(sent[2], "http://yerel.test/stok"),
        Request(sent[3], "http://kur.ornek.test/kur"),
    )
    val arrivals = probe(NetworkPolicy(setOf("yerel.test")), requests, sent)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=siparis.41 | kopya=0 -> kayip
olay-2: anahtar=siparis.42 | kopya=1 -> birebir
olay-3: anahtar=stok.7 | kopya=1 -> birebir
olay-4: anahtar=kur.3 | kopya=0 -> kayip
birebir: 2
yinelenen: 0
ezilen: 0
kayip: 2
```

Engellenen iki istek sunucuya hiç ulaşmadı. Bu dersin teslimi: kayıp. İstek yazıldı, gönderildi, ama ağa çıkmadan önce platformun yapılandırması onu durdurdu. `yerel.test` adresi alan adına özel kuralla açıldığı için o istek birebir ulaştı. Kural `http://` ile `https://` arasındaki farkı değil, yalnız sonucu görür.

#### Yeniden Deneme Kopya Bırakırsa

Bir ağ istemcisi zaman aşımına uğrayan isteği yeniden gönderebilir. İstek iki kez HTTPS ile gider ve ikisi de yapılandırmadan geçer.

```kotlin
// Main.kt — yeniden denenen istek iki kez gecerse sunucu iki kopya alir
fun main() {
    val sent = listOf(Signal("siparis.41", 1), Signal("siparis.42", 2), Signal("stok.7", 3))
    val requests = listOf(
        Request(sent[0], "https://api.ornek.test/siparis"),
        Request(sent[1], "https://api.ornek.test/siparis"),
        Request(sent[1], "https://api.ornek.test/siparis"),
        Request(sent[2], "http://api.ornek.test/stok"),
    )
    val arrivals = probe(NetworkPolicy(), requests, sent)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=siparis.41 | kopya=1 -> birebir
olay-2: anahtar=siparis.42 | kopya=2 -> yinelenen
olay-3: anahtar=stok.7 | kopya=0 -> kayip
birebir: 1
yinelenen: 1
ezilen: 0
kayip: 1
```

Yapılandırma yinelenen isteği ayıklamaz: ikisi de aynı kuralları sağladığı için ikisi de geçer. Yinelenmeyi önlemek istemcinin ya da sunucunun işidir. Üçüncü olayın kayıp olması ayrı bir şeydir: o istek düz metin olduğu için hiç geçmedi ve yeniden denenmedi. Aynı ölçümde bir olay yinelenen, bir olay kayıp çıkabilir.

#### Açık Adresin Kapsamı

Düz metne açılan adres, adın yazıldığı kadarını kapsar. XML örneğinde `yerel.test` için `includeSubdomains="false"` yazılıdır; yani yalnız bu ad serbesttir, altındaki adlar değildir. Modelde karşılığı `cleartextHosts` kümesinin tam eşleşmesidir: küme `yerel.test` içeriyorsa `tablo.yerel.test` içermez.

Üç istek vardır. İlki açık adresin kendisine düz metinle gider. İkincisi aynı adın bir alt adına düz metinle gider. Üçüncüsü o alt adrese HTTPS ile gider.

```kotlin
// Main.kt — acik adres alt adlari kapsamaz, https ise yine gecer
fun main() {
    val sent = listOf(Signal("tablo.1", 1), Signal("tablo.2", 2), Signal("tablo.3", 3))
    val requests = listOf(
        Request(sent[0], "http://yerel.test/tablo"),
        Request(sent[1], "http://tablo.yerel.test/tablo"),
        Request(sent[2], "https://tablo.yerel.test/tablo"),
    )
    val arrivals = probe(NetworkPolicy(setOf("yerel.test")), requests, sent)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=tablo.1 | kopya=1 -> birebir
olay-2: anahtar=tablo.2 | kopya=0 -> kayip
olay-3: anahtar=tablo.3 | kopya=1 -> birebir
birebir: 2
yinelenen: 0
ezilen: 0
kayip: 1
```

Açık adresin kendisi geçti, alt adı geçmedi, HTTPS ise adres ne olursa olsun düz metin kuralına takılmadı. Geliştirme ortamında bir adresi açıp alt adlarının da açıldığını varsaymak bu yüzden sık görülen bir yanlıştır. Ölçüm bunu üç satırda gösterir: kayıp yalnız ikinci istektedir.

**GD1.** Model, yeniden denemeyi aynı isteğin listede iki kez bulunması olarak kurar. Platformda yeniden deneme istemcinin ara katmanında olur ve yapılandırma onu ayrıca görmez.

**GD2.** Model, sabitlemeyi tek bir özetle ve bütün HTTPS adresleri için kurar. Platformda sabitleme alan adı başına ve birden çok özetle yazılır.

#### Sınırlayıcı Ölçüm

Politika bir özet sabitlemiştir. Dört gönderim vardır. Birinci istek düz metin diye engellenir. Uygulama aynı isteği, aynı anahtarla, HTTPS adresine yeniden gönderir. Üçüncü istek HTTPS'tir ama sunucunun sertifikası sabitlenen özetle eşleşmez.

```kotlin
// Main.kt — yeniden gonderilen istek ilkini ezer, sabitleme uyusmazligi kayiptir
fun main() {
    val sent = listOf(
        Signal("siparis.41", 1),
        Signal("siparis.42", 2),
        Signal("stok.7", 3),
        Signal("siparis.41", 4),
    )
    val requests = listOf(
        Request(sent[0], "http://api.ornek.test/siparis"),
        Request(sent[1], "https://api.ornek.test/siparis", "sabit-a"),
        Request(sent[2], "https://api.ornek.test/stok", "baska"),
        Request(sent[3], "https://api.ornek.test/siparis", "sabit-a"),
    )
    val arrivals = probe(NetworkPolicy(pin = "sabit-a"), requests, sent)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=siparis.41 | kopya=0 -> ezilen
olay-2: anahtar=siparis.42 | kopya=1 -> birebir
olay-3: anahtar=stok.7 | kopya=0 -> kayip
olay-4: anahtar=siparis.41 | kopya=1 -> birebir
birebir: 2
yinelenen: 0
ezilen: 1
kayip: 1
```

İlk istek ağa çıkmadı; ama aynı anahtarla daha büyük bir `seq`, yani yeniden gönderilen istek sunucuya ulaştı. Kural bu yüzden ilk isteği kayıp değil ezilen sayar. `stok.7` ise sabitlenen sertifikayla eşleşmediği için kayıptır ve aynı anahtarla ulaşan başka bir istek olmadığından ezilen de değildir. Birinci isteğin nedeni düz metin, üçüncünün nedeni sabitleme uyuşmazlığıdır. Kural bu iki nedeni ayırmaz: ikisi de kopya yok demektir. Neden yalnız yapılandırmaya ve günlüğe bakınca öğrenilir.

**GD3.** Model, sertifika uyuşmazlığını istekteki bir etiketle kurar. Platformda uyuşmazlık, el sıkışma sırasında zincirin doğrulanamaması olarak görünür ve istek hiç gönderilmeden bağlantı hatası verir.

#### İstemcinin Yapacakları

Engel yapılandırma düzeyinde olduğu için sorumluluk iki yere dağılır. İsteği gönderen kod, istek ağa çıkmadan düşmüş olabileceğini bilmelidir. Yapılandırmayı yazan kişi ise hangi adreslerin açık olduğunu belgelemelidir.

- **Engeli ağ hatasından ayırmak.** Düz metin engeli ile bağlantının kopması istemciye farklı hatalar olarak görünür. Kod ikisini aynı yeniden deneme döngüsüne sokarsa engellenen bir isteği boşuna tekrarlar; model bunu gösterir, çünkü engellenen istek kaç kez denenirse denensin kopya sıfırdır.
- **Yeniden denemeyi tekilleştirmek.** Yapılandırma kopyaları ayıklamadığı için isteğe bir tekrar kimliği eklemek istemcinin işidir. Sunucu aynı kimliği iki kez görürse ikincisini yok sayar. Bu durumda teslim yine yinelenendir, ama sonuç yinelenmez: kimlik, teslimin kaç kez olduğunu değil, sonucun kaç kez işlendiğini sınırlar.
- **Sabitlemeyi yenilemeye hazırlamak.** Model tek bir özet kurar. Sunucunun sertifikası değişirse, özet güncellenene kadar bütün HTTPS istekleri kayıp olur. Gerçek yapılandırmada birden çok özet yazılabilir, ama hangi özetlerin yazılacağı ve ne zaman değişeceği bu dersin ölçtüğü şey değildir.
- **Hata mesajını kullanıcıya göstermemek.** Yapılandırma nedeniyle düşen bir istek için kullanıcıya gösterilecek metin ağın kapalı olduğunu söylememelidir. Ağ açık, istek yapılandırmadan dönmüştür.

#### Sabitlemenin Bedeli

Sabitleme güvenliği artıran bir karardır, ama bedeli ölçümde görülür. Sabit özet, sunucunun şu anki sertifikasına bağlanır ve sertifika değiştiğinde istemci ile sunucu artık aynı şeyi beklemez. Sınırlayıcı ölçümde bu, tek bir isteğin kaybı olarak göründü; gerçek bir uygulamada aynı alan adına giden her HTTPS isteği aynı kapıya takılır ve kayıp tek istekle sınırlı kalmaz.

Bedel iki biçimde yönetilir. Birincisi, yeni sertifikanın özeti, sertifika değişmeden önce yapılandırmaya eklenir; böylece eski ve yeni sertifika bir süre birlikte kabul edilir. İkincisi, uygulamanın güncellenmesi için geçecek sürenin hesaba katılmasıdır: yapılandırma uygulamayla birlikte gider, sunucudaki sertifika ise hemen değişebilir. Kullanıcı eski sürümde kalırsa, yeni sertifika geldiği an eski sürümdeki bütün sabitli istekler kayıp olur. Bu derste model tek özet kurduğu için yalnız bu ikinci hâli ölçer. Sonuç şudur: sabitleme güvenlik kazandırır, ama sertifika yenileme takvimini yapılandırma takvimine bağlar. Bu bağ, yayın planında sertifika değişikliğinin uygulama sürümünden önce hazırlanmasını gerektirir; aksi hâlde yeni sertifika geldiği gün eski sürümlerdeki sabitli istekler sessizce düşer.

#### Yaygın Yanılgılar

**HTTPS olan her istek sunucuya ulaşır.** Sabitleme varsa ulaşmaz. Sınırlayıcı ölçümde `stok.7` HTTPS ile gitti, ama sertifikası sabitlenen özetle eşleşmediği için kayıp oldu.

**Yapılandırma tekrarları önler.** Önlemez. Aynı isteğin iki kopyası aynı kuralları sağladığında ikisi de geçer. Yinelenen ölçümü bunu gösterdi: yapılandırma yalnız izin verir ya da vermez, kaç kez geçtiğini saymaz.

**Bir adresi açmak onun alt adlarını da açar.** Yukarıdaki ölçüm bunu çürüttü: açık adres tam eşleşmedir. Alt adları kapsamak ayrı bir ayardır ve bu dersin modeli onu kurmaz.

**Sabitleme düz metin isteği de korur.** Korumaz. Düz metin istekte sertifika yoktur; model bu dalda özete hiç bakmaz, istek yalnız adres listesine göre geçer ya da düşer.

**Engellenen istek en azından bir yerde kayıtlıdır.** Sunucu tarafında kayıt yoktur, çünkü istek sunucuya hiç gitmedi. Kayıp olay yalnız istemcinin kendi günlüğünde iz bırakır; bu yüzden istemci günlüğü bu tür kaybın tek kaynağıdır.

#### Özet

- Bu dersin teslimi: kayıp. Düz metin diye engellenen istek sunucuya hiç ulaşmaz.
- Yapılandırma düz metin trafiğine, güven çapasına ve sertifika sabitlemeye karar verir; alan adına özel kural tek bir adresi serbest bırakabilir.
- Dört istekten ikisi düz metin olduğunda iki istek birebir, ikisi kayıp ulaşır: birebir 2, kayıp 2.
- Yeniden denenen istek iki kez geçerse sunucu iki kopya alır; yapılandırma yinelenmeyi ayıklamaz.
- Aynı anahtarlı istek HTTPS'e yeniden gönderilince ilk istek ezilen olur. Sabitleme uyuşmazlığı kayıptır; iki neden tek kategoridir.

#### Sonraki Adım

İstek artık ağa çıkabiliyor. Ama sunucu isteği kabul etmek için bir erişim belirteci ister ve belirteç süresi dolunca yenilenmesi gerekir. Sonraki derste eşzamanlı iki istek aynı anda yetkisiz yanıt alır ve ikisi de yenileme başlatır; sunucuya aynı yenileme olayı iki kopya ulaşır.

### Kimlik Doğrulama Akışları

Source: https://academia.sh/tr/kurslar/android-servisler/kimlik-dogrulama-akislari

Biyometrik doğrulama aygıttaki kapıyı açar, hesap tabanlı giriş ise sunucuya erişim ve yenileme belirteci verir. Ders eşzamanlı isteklerin aynı yenilemeyi iki kez başlatmasının sunucuya kaç kopya olarak ulaştığını ve tek uçuşun bunu nasıl değiştirdiğini ölçer.

Önceki derste istek ağa çıkabiliyordu. Sunucu ise isteği kabul etmeden önce kimin gönderdiğini sorar. Bu ders o sorunun iki yüzünü ele alır: aygıtta kullanıcıyı tanıyan biyometrik doğrulama ve sunucuda kullanıcıyı tanıyan hesap tabanlı giriş. İkisinin kesiştiği yer belirteçlerdir ve sorun orada, belirteç süresi dolduğunda çıkar. Soru kursun sorusudur: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?** Olay yenileme isteğidir, alıcı sunucudur.

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

#### İki Doğrulama, İki Belirteç

Biyometrik doğrulama (`BiometricPrompt`) yereldir: parmak izi ya da yüz, kullanıcının aygıtı açmaya yetkili olduğunu gösterir ve sunucuya hiçbir şey söylemez. Uygulama bu sonucu bir kapı olarak kullanır; örneğin yenileme belirtecini yalnız doğrulamadan sonra okur.

```kotlin
// Platform kodu: BiometricPrompt
val prompt = BiometricPrompt(
    activity,
    ContextCompat.getMainExecutor(activity),
    object : BiometricPrompt.AuthenticationCallback() {
        override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
            startRefresh()
        }
    },
)
val info = BiometricPrompt.PromptInfo.Builder()
    .setTitle("Kimliginizi dogrulayin")
    .setNegativeButtonText("Vazgec")
    .build()
prompt.authenticate(info)
```

Hesap tabanlı giriş ise sunucuyla yapılır ve iki belirteç döndürür. **Erişim belirteci** kısa ömürlüdür ve her isteğe eklenir. **Yenileme belirteci** uzun ömürlüdür ve yalnız yeni bir erişim belirteci almak için kullanılır. Erişim belirtecinin süresi dolunca sunucu isteğe yetkisiz yanıt verir; istemci yenileme belirtecini sunucuya gönderir ve yeni bir erişim belirteci alır.

```kotlin
// Platform kodu: Authenticator
class TokenAuthenticator(private val store: TokenStore) : Authenticator {
    override fun authenticate(route: Route?, response: Response): Request? {
        val fresh = store.refresh() ?: return null
        return response.request.newBuilder()
            .header("Authorization", "Bearer $fresh")
            .build()
    }
}
```

İki parça platform kodudur ve burada çıktı, süre ya da sayı iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir. Modelde bir çağrı, elindeki erişim belirtecini ve yetkisiz yanıt alırsa göndereceği yenileme olayını taşır. Geçerli belirteci olmayan her çağrı yenileme gönderir.

```kotlin
// Flow.kt — belirteci gecersiz her cagri kendi yenileme olayini gonderir
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class Call(val name: String, val token: String, val refresh: Signal)

fun refreshAll(valid: String, calls: List<Call>): List<Signal> =
    calls.filter { it.token != valid }.map { it.refresh }

fun singleFlight(valid: String, calls: List<Call>): List<Signal> = runBlocking {
    val mutex = Mutex()
    val inFlight = mutableSetOf<String>()
    val started = mutableListOf<Signal>()
    calls.filter { it.token != valid }.map { call ->
        async {
            mutex.withLock {
                if (inFlight.add(call.refresh.key)) started.add(call.refresh)
            }
        }
    }.awaitAll()
    started
}
```

`refreshAll` korumasızdır: her yetkisiz çağrı yenileme gönderir. `singleFlight` ise bir yenileme sürerken aynı anahtarın ikinci yenilemesini beklemeden düşürür; kontrol ve kayıt bir `Mutex` altında yapılır, böylece iki çağrı aynı anda "henüz yok" görmez.

```kotlin
// Probe.kt — gonderilen yenilemeler icin sunucuya ulasan listeyi alici sayar
fun probe(sent: List<Signal>, received: List<Signal>): List<Arrival> =
    sent.mapIndexed { i, s -> Arrival("olay-${i + 1}", s, received) }
```

#### Yarış Neden Doğar

Yenileme yarışı, üç adımın aralarında boşluk bırakmasından doğar: çağrı yetkisiz yanıt alır, yenileme belirtecini okur, yenileme isteğini gönderir. Bu adımlar sıralı görünür, ama bir ekranın birden çok isteği aynı anda yola çıktığında her isteğin kendi sırası vardır. İlk istek yetkisiz yanıtı aldığında yenileme başlar; ikinci istek henüz yeni belirteç gelmeden yetkisiz yanıtı alırsa o da yenileme başlatır. Kimse ötekinin başladığını bilmez.

Korumasız akışın modeli `refreshAll` işlevidir ve bu yarışı tek satırda kurar: geçerli belirteci olmayan her çağrı kendi yenilemesini gönderir. Çağrılar birbirini görmez.

Tek uçuşun (single flight) işi çağrılara birbirini göstermektir. Bir ortak kayıt tutar: bir yenileme sürüyorsa kaydına anahtarı yazılır, sonra gelen çağrı kayıtta anahtarı görürse kendi yenilemesini göndermez ve sonucu bekler. Burada kritik olan, "kayıtta var mı" sorusu ile "kayda yaz" işleminin bölünmemesidir. İkisi ayrı adımlar olursa iki çağrı arada aynı anda "yok" görür ve ikisi de gönderir. Modeldeki `Mutex` bu bölünmeyi önler: bir çağrı kilidi alınca kontrol ve kayıt birlikte yapılır, ötekiler sırada bekler.

Kilidin bir bedeli de vardır: sıradaki çağrılar kilit açılana kadar bekler. Modelde bekleme çok kısadır ve ölçümü etkilemez; gerçek bir uygulamada ağ isteği kilit içinde yapılırsa bekleme uzar, bu yüzden kilit kontrol ve kayıtla sınırlı tutulur, ağ isteği kilit dışında yapılır.

#### Her Çağrının Kendi Belirteci Varsa

Üç çağrıdan ikisinin erişim belirteci eskidir ve iki ayrı oturuma aittir. Üçüncünün belirteci geçerlidir ve yenileme göndermez.

```kotlin
// Main.kt — ayri oturumlarin yenilemeleri birer kopya ulasir
fun main() {
    val a = Signal("yenileme.a", 1)
    val b = Signal("yenileme.b", 2)
    val c = Signal("yenileme.c", 3)
    val calls = listOf(Call("ekran-1", "eski", a), Call("ekran-2", "eski", b), Call("ekran-3", "yeni", c))
    val received = refreshAll("yeni", calls)
    val arrivals = probe(listOf(a, b), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=yenileme.a | kopya=1 -> birebir
olay-2: anahtar=yenileme.b | kopya=1 -> birebir
birebir: 2
yinelenen: 0
ezilen: 0
kayip: 0
```

Gönderilen iki yenilemenin ikisi birer kopya ulaştı. Geçerli belirteci olan çağrı yenileme göndermediği için ölçümde bir olay değildir.

#### Eşzamanlı İki İstek

Bir ekran açılırken birden çok istek aynı anda çıkar. Erişim belirteci eskiyse bu isteklerin hepsi yetkisiz yanıt alır ve hepsi yenileme belirtecini sunucuya gönderir. Aynı oturumun yenileme belirteci aynı olduğu için, sunucuya ulaşan olay aynı olaydır.

```kotlin
// Main.kt — ayni oturumun eszamanli cagrilari yenilemeyi birden cok kez gonderir
fun main() {
    val a = Signal("yenileme.a", 1)
    val b = Signal("yenileme.b", 2)
    val c = Signal("yenileme.c", 3)
    val calls = listOf(
        Call("ekran-1", "eski", a),
        Call("ekran-2", "eski", a),
        Call("ekran-3", "eski", b),
        Call("ekran-4", "eski", c),
        Call("ekran-5", "eski", c),
        Call("ekran-6", "eski", c),
    )
    val received = refreshAll("yeni", calls)
    val arrivals = probe(listOf(a, b, c), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=yenileme.a | kopya=2 -> yinelenen
olay-2: anahtar=yenileme.b | kopya=1 -> birebir
olay-3: anahtar=yenileme.c | kopya=3 -> yinelenen
birebir: 1
yinelenen: 2
ezilen: 0
kayip: 0
```

Bu dersin teslimi: yinelenen. Tek bir oturum, aynı yenilemeyi iki ve üç kez göndermiş olabilir; kural kopya sayısını okur. Sunucu yenileme belirtecini her kullanımda yeniliyorsa bu fazlalık sorun doğurabilir, ama bunu ölçüm göstermez; kural yalnız kaç kopya ulaştığını söyler.

Aynı altı çağrı tek uçuşla çalışırsa ne olur? Altı çağrının üçü farklı anahtarı, üçü tekrarı taşır; tek uçuş her anahtardan yalnız birini bırakır.

```kotlin
// Main.kt — tek ucus ayni olayin kopyalarini tek kopyaya indirir
fun main() {
    val a = Signal("yenileme.a", 1)
    val b = Signal("yenileme.b", 2)
    val c = Signal("yenileme.c", 3)
    val calls = listOf(
        Call("ekran-1", "eski", a),
        Call("ekran-2", "eski", a),
        Call("ekran-3", "eski", b),
        Call("ekran-4", "eski", c),
        Call("ekran-5", "eski", c),
        Call("ekran-6", "eski", c),
    )
    val received = singleFlight("yeni", calls)
    val arrivals = probe(listOf(a, b, c), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=yenileme.a | kopya=1 -> birebir
olay-2: anahtar=yenileme.b | kopya=1 -> birebir
olay-3: anahtar=yenileme.c | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 0
kayip: 0
```

Korumasız akışta yinelenen 2 olan sonuç, tek uçuşla birebir 3 oldu. Bu, tek uçuşun amacıdır: aynı olayın kopyalarını tek kopyaya indirmek. Farklı olayları düşürmesi ise bir sonraki bölümde görülen yan etkisidir.

**GD1.** Model, aynı oturumun çağrılarının aynı yenileme olayını taşıdığını varsayar. Platformda yenileme belirtecinin her kullanımda yenilenmesi, ikinci kopyayı sunucuda başka bir sonuca götürebilir; model bunu kurmaz.

**GD2.** Model, biyometrik doğrulamayı ölçüme katmaz. Doğrulama kapıyı açar; kapının ardındaki yenileme akışı ölçülür.

#### Sınırlayıcı Ölçüm

Dört çağrı vardır. İlk ikisi aynı anahtarlı yenilemeyi taşır ama farklı olaylardır: ikincisi daha sonra, daha büyük bir `seq` ile gönderilmiştir. Son ikisi aynı olayı paylaşır. Önce korumasız akışa bakalım.

```kotlin
// Main.kt — korumasiz akista ayni oturumun cagrilari ayri olaylar gonderir
fun main() {
    val a1 = Signal("yenileme.a", 1)
    val a2 = Signal("yenileme.a", 2)
    val b = Signal("yenileme.b", 3)
    val calls = listOf(
        Call("ekran-1", "eski", a1),
        Call("ekran-2", "eski", a2),
        Call("ekran-3", "eski", b),
        Call("ekran-4", "eski", b),
    )
    val received = refreshAll("yeni", calls)
    val arrivals = probe(listOf(a1, a2, b), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=yenileme.a | kopya=1 -> birebir
olay-2: anahtar=yenileme.a | kopya=1 -> birebir
olay-3: anahtar=yenileme.b | kopya=2 -> yinelenen
birebir: 2
yinelenen: 1
ezilen: 0
kayip: 0
```

Şimdi aynı dört çağrı tek uçuşla çalışır. İlk yenileme sürerken aynı anahtarın ikincisi düşürülür.

```kotlin
// Main.kt — tek ucus ayni anahtarin sonraki yenilemesini dusurur
fun main() {
    val a1 = Signal("yenileme.a", 1)
    val a2 = Signal("yenileme.a", 2)
    val b = Signal("yenileme.b", 3)
    val calls = listOf(
        Call("ekran-1", "eski", a1),
        Call("ekran-2", "eski", a2),
        Call("ekran-3", "eski", b),
        Call("ekran-4", "eski", b),
    )
    val received = singleFlight("yeni", calls)
    val arrivals = probe(listOf(a1, a2, b), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=yenileme.a | kopya=1 -> birebir
olay-2: anahtar=yenileme.a | kopya=0 -> kayip
olay-3: anahtar=yenileme.b | kopya=1 -> birebir
birebir: 2
yinelenen: 0
ezilen: 0
kayip: 1
```

Tek uçuş, aynı olayı paylaşan iki çağrıyı tek kopyaya indirdi: `yenileme.b` yinelenenden birebire geçti. Farklı olay olan ikinci `yenileme.a` ise sunucuya hiç ulaşmadı ve kayıp oldu. Bu olay ezilen değildir. Ezilmek için aynı anahtarla daha büyük bir `seq`in ulaşması gerekir; ulaşan tek kopya `seq` 1'dir ve düşürülen olaydan küçüktür. Kural yalnız sonra gelen olayın yerini almasını görür, önce gelen olayın sonrakinin yerini almasını görmez.

Bunun bedeli uygulama için gerçektir. Düşürülen yenileme, ikinci çağrının kendi belirtecini sunucuya hiç göndermediği anlamına gelir; çağrı ilk yenilemenin sonucunu beklemelidir. Ölçüm bu beklemeyi değil, yalnız hangi olayın ulaştığını gösterir.

**GD3.** Model, tek uçuşu anahtar bazında tutar ve yenilemenin bittiğini hiç işaretlemez; bir yenileme sürerken aynı anahtarın ikincisi her zaman düşer. Platformda yenileme biter ve sonraki yenileme yeniden başlayabilir.

#### Alıcı Tarafında Yapılacaklar

Alıcı sunucudur, ama kopyaları azaltmanın büyük kısmı istemcide yapılır. Üç iş vardır.

- **Yenilemeyi tek noktadan başlatmak.** Çağrıların her biri yenileme göndermez; yetkisiz yanıtı alan çağrı ortak bir yenileme işlevine gider ve işlev tek uçuş kuralını uygular. Böylece kural tek yerde yazılır ve ekranlar onu bilmek zorunda kalmaz.
- **Düşürülen çağrıyı beklemeye almak.** Yenilemeyi göndermeyen çağrı vazgeçmez; sürmekte olan yenilemenin sonucunu bekler ve yeni belirteçle kendi isteğini yeniden gönderir. Bu adım modelde yoktur, ama düşürülen olayın kayıp kalması bunu zorunlu kılar.
- **Sunucunun belirteci yenileme davranışını bilmek.** Sunucu yenileme belirtecini her kullanımda değiştiriyorsa ikinci kopya geçersiz belirteçle gelir ve reddedilir; bunun kullanıcıyı çıkış yaptırıp yaptırmayacağı sunucunun kuralıdır ve burada ölçülmez. İstemci açısından tek uçuş, bu soruyu sormamanın en ucuz yoludur.

Yenileme akışının tek bir yerde durması, hata ayıklamayı da kolaylaştırır: kopya sayısı yükseldiğinde bakılacak yer tektir. Kullanıcıya gösterilecek ekran da bu işlevde karar verilir: yenileme başarılıysa kullanıcı hiçbir şey görmez, başarısızsa oturum açma ekranına yönlendirilir. Düşürülen çağrının sonucu yenilemenin sonucuna bağlandığı için çağrı ayrı bir hata ekranı göstermemelidir.

Sunucu tarafında ise yenileme isteğine bir istek kimliği eklemek, kopyaları tekilleştirmeyi mümkün kılar. Bu, teslimi değiştirmez: kopya yine ulaşmıştır, ama sonuç yinelenmez.

#### Yaygın Yanılgılar

**Biyometrik doğrulama sunucuyu doğrular.** Doğrulama yereldir; sunucuya kullanıcının kim olduğunu söylemez. Sunucu kimliği belirteçten öğrenir. Bu yüzden modelde doğrulama ölçülmedi, yalnız kapıdan sonraki yenileme ölçüldü.

**Tek uçuş hiçbir olayı kaybettirmez.** Kaybettirir. Aynı anahtarın farklı olayını düşürdü ve kural onu kayıp saydı. Tek uçuşun kazancı yinelenenin birebire inmesi, bedeli düşen olaydır.

**Düşürülen olay ezilendir.** Ezilmek için sonradan aynı anahtarla daha büyük bir `seq` ulaşmalıdır. Düşürülen olay ikincidir ve ondan önceki kopya ulaşmıştır; kural bunu ezilen değil kayıp sayar.

**Aynı belirteçle gelen kopya zararsızdır.** Sunucu belirteci kullanımda yeniliyorsa değildir. Zararsız olması sunucunun kuralına bağlıdır, istemcinin varsayımına değil.

**Her çağrı kendi belirtecini yenilerse sorun yoktur.** Ölçümde her çağrının kendi belirteci olduğunda iki yenileme birebir ulaştı, üçüncü çağrı yenileme göndermedi. Ama bu sonuç, her çağrının ayrı bir yenileme olayı taşıdığı bir akışa aittir. Çağrılar aynı oturumun aynı olayını taşıdığında, eşzamanlı ölçümdeki gibi kopya oluşur.

**Yinelenen sayısı çağrı sayısına eşittir.** Eşit değildir. Kopya sayısı, kaç çağrının aynı olayı taşıdığına bağlıdır. Altı çağrılı ölçümde bir olayın kopyası iki, bir başkasının üç oldu; çağrı sayısı altıydı, kopya sayısı bunlardan hiçbirine eşit değildi.

**Yenilemeyi kullanıcı görmez.** Görmez, ama sonucunu görür. Yenileme başarısız olursa kullanıcı oturumdan atılır; bu ders başarısızlığı ölçmez, yalnız kaç yenilemenin sunucuya ulaştığını sayar.

#### Özet

- Bu dersin teslimi: yinelenen. Aynı oturumun eşzamanlı çağrıları aynı yenileme olayını sunucuya birden çok kez gönderir.
- Biyometrik doğrulama aygıttaki kapıdır; hesap tabanlı giriş sunucuya erişim ve yenileme belirteci verir.
- Ayrı oturumların yenilemeleri birer kopya ulaşır: birebir 2. Aynı oturumun iki ve üç çağrısı iki ve üç kopya üretir: yinelenen 2, birebir 1.
- Tek uçuş aynı olayın kopyalarını tek kopyaya indirir: korumasız akışta yinelenen 2 olan altı çağrı, tek uçuşla birebir 3 olur. Ama aynı anahtarın sonraki, farklı olayını düşürür: birebir 2, kayıp 1.
- Düşürülen olay ezilen değildir, çünkü ulaşan kopya ondan küçük `seq` taşır.

#### Sonraki Adım

Belirteç yenilendi ve istekler sunucuya ulaşıyor. Bir sonraki soru, uygulamanın kendisinin ne olduğudur: aygıta kurulan paketin kimden geldiği. Sonraki derste yayımlanan sürümün aygıttaki kurulu uygulamayı aynı sertifikayla imzalandığı sürece güncelleyip güncellemediğine bakıyoruz.

### Uygulama İmzalama

Source: https://academia.sh/tr/kurslar/android-servisler/uygulama-imzalama

Her yayımlanan sürüm bir imzalama anahtarıyla imzalanır ve aygıt, güncellemeyi yalnız kurulu uygulamayla aynı sertifikadan geliyorsa kurar. Ders yayımlanan sürümün aygıttaki kurulu uygulamaya kaç kopya olarak ulaştığını ve farklı anahtarla imzalanan sürümün sonucunu ölçer.

Önceki derste olay, aygıttan sunucuya giden bir yenilemeydi. Bu derste yön tersine döner: geliştirici bir sürüm yayımlar ve o sürüm aygıtta kurulu uygulamaya ulaşmak ister. Olay yayımlanan sürümdür, alıcı aygıttaki kurulu uygulamadır. Aradaki kapı bir imzadır. Soru kursun sorusudur: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?**

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

Bu derste anahtar uygulamanın kimliğidir ve `seq` sürüm sırasıdır. Aynı uygulamanın sürümleri aynı anahtarı, artan `seq` değerlerini taşır.

#### İmzalama Anahtarı ve Sertifika

Bir uygulama paketi, **imzalama anahtarıyla** imzalanır. Anahtarın açık yarısını taşıyan belge **sertifikadır** ve paketle birlikte aygıta iner. Aygıt yeni bir sürümü kurmadan önce, onun sertifikasını kurulu uygulamanın sertifikasıyla karşılaştırır. Eşleşiyorsa güncelleme kurulur; eşleşmiyorsa reddedilir ve kurulu sürüm yerinde kalır.

Bu karşılaştırma bir kimlik sorusunun cevabıdır: yeni sürümü gönderen, kurulu uygulamayı gönderenle aynı taraf mı. Anahtar değişirse aygıt, anahtar dönüşümü gibi özel düzenler dışında, yeni sürümü eski uygulamayla aynı uygulama saymaz. Bu yüzden imzalama anahtarı, sürüm sürüm aynı kalması gereken tek şeydir.

Bazı mağaza düzenlerinde geliştirici paketi bir **yükleme anahtarıyla** imzalayıp mağazaya yükler; mağaza paketi kendi tuttuğu imzalama anahtarıyla yeniden imzalayıp aygıtlara dağıtır. Bu durumda aygıtın gördüğü sertifika yükleme anahtarının değil, mağazadaki imzalama anahtarının sertifikasıdır. Uygulama içinden kendi sertifikasının özetine bakılabilir.

```kotlin
// Platform kodu: PackageManager.getPackageInfo
val info = packageManager.getPackageInfo(
    packageName,
    PackageManager.GET_SIGNING_CERTIFICATES,
)
val signers = info.signingInfo?.apkContentsSigners ?: emptyArray()
val digest = MessageDigest.getInstance("SHA-256").digest(signers[0].toByteArray())
```

Bu parça platform kodudur; burada çıktı ya da özet değeri iddia etmiyoruz. İmzalamanın hangi araçla yapıldığı bu dersin konusu değildir ve yazılmaz. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

Modelde bir sürüm, olay ile onu imzalayan sertifikayı taşır. Aygıt, güvendiği tek bir sertifikayı tutar ve `verifySignature` işleviyle sürümün sertifikasını onunla karşılaştırır. Kuruluma geçen sürümler alıcının listesidir.

```kotlin
// Install.kt — aygit guvendigi sertifikayla eslesmeyen surumu kurmaz
class Release(val signal: Signal, val cert: String)

class Device(private val trusted: String) {
    fun verifySignature(release: Release): Boolean = release.cert == trusted
}

fun installUpdates(device: Device, releases: List<Release>): List<Signal> =
    releases.filter { device.verifySignature(it) }.map { it.signal }
```

```kotlin
// Probe.kt — yayimlanan surumler icin kurulan listeyi alici sayar
fun probe(sent: List<Signal>, received: List<Signal>): List<Arrival> =
    sent.mapIndexed { i, s -> Arrival("olay-${i + 1}", s, received) }
```

#### Kimlik Olarak İmza

Karşılaştırmanın neden bu kadar katı olduğunu anlamak için aygıtın neyi korumaya çalıştığına bakmak gerekir. Aygıtın elinde bir paket adı vardır ve ad herkesin yazabileceği bir dizedir. Adı aynı olan ikinci bir paket kurulu uygulamanın yerine geçebilseydi, kullanıcının verisi kimin elinde olduğu belirsiz bir uygulamaya devrolurdu. Sertifika bu boşluğu kapatır: ad kimlik iddia eder, sertifika onu kanıtlar.

Bunun iki sonucu vardır. Birincisi, güncellemenin kurulması için paketin içeriğinin doğru olması yetmez; imzanın da doğru anahtardan gelmesi gerekir. İkincisi, imza doğru olsa bile geliştirici anahtarı kaybeder ya da değiştirirse sonraki sürümler kurulu uygulamaya ulaşamaz. Aygıt bunu bir yapılandırma hatası olarak değil, farklı bir tarafın paketi olarak okur.

Modelde bu iki sonuç da `Device` sınıfında tek satırdır: `release.cert == trusted`. Model sertifikanın nasıl üretildiğini, hangi algoritmayla imzalandığını ve anahtar dönüşümü gibi düzenleri bilmez. Yalnız eşleşme vardır ya da yoktur.

#### Aynı Sertifika

Üç sürüm yayımlanır ve üçü de aygıtın güvendiği sertifikayla imzalıdır.

```kotlin
// Main.kt — ayni sertifikayla imzalanan her guncelleme kurulur
fun main() {
    val v1 = Signal("uygulama.ana", 1)
    val v2 = Signal("uygulama.ana", 2)
    val v3 = Signal("uygulama.ana", 3)
    val releases = listOf(
        Release(v1, "sertifika-a"),
        Release(v2, "sertifika-a"),
        Release(v3, "sertifika-a"),
    )
    val received = installUpdates(Device("sertifika-a"), releases)
    val arrivals = probe(listOf(v1, v2, v3), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=uygulama.ana | kopya=1 -> birebir
olay-2: anahtar=uygulama.ana | kopya=1 -> birebir
olay-3: anahtar=uygulama.ana | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 0
kayip: 0
```

Üç sürümün üçü kurulur ve her biri birer kopya ulaşır. Bu dersin teslimi: birebir. Aynı sertifikayla imzalanan güncelleme, aygıtın karşılaştırmasından geçer ve kurulu uygulamaya olduğu gibi ulaşır.

#### Aynı Sürüm İki Yoldan

Bir güncelleme aygıta iki yoldan gelebilir: mağazadan ve elle yüklenen bir dosyadan. İkisi de aynı sertifikayı taşıyorsa ikisi de karşılaştırmadan geçer.

```kotlin
// Main.kt — ayni surum iki yoldan gelirse iki kopya ulasir
fun main() {
    val v1 = Signal("uygulama.ana", 1)
    val v2 = Signal("uygulama.ana", 2)
    val releases = listOf(
        Release(v1, "sertifika-a"),
        Release(v2, "sertifika-a"),
        Release(v2, "sertifika-a"),
    )
    val received = installUpdates(Device("sertifika-a"), releases)
    val arrivals = probe(listOf(v1, v2), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=uygulama.ana | kopya=1 -> birebir
olay-2: anahtar=uygulama.ana | kopya=2 -> yinelenen
birebir: 1
yinelenen: 1
ezilen: 0
kayip: 0
```

İmza karşılaştırması bir sürümün ikinci kez gelip gelmediğine bakmaz; yalnız sertifikaya bakar. Gerçek aygıtta aynı sürümün ikinci kurulumu çoğunlukla sonuç değiştirmez, ama kural kopya sayısını sayar ve ikiyi yinelenen olarak yazar.

**GD1.** Model, kurulumu bir listeye ekleme olarak kurar. Platformda aynı sürümün ikinci kurulumu kurulu uygulamayı yerinde bırakabilir ya da yeniden yazabilir; model bu ayrımı yapmaz.

**GD2.** Model, sertifika karşılaştırmasını bir dize eşitliği olarak kurar. Platformda karşılaştırma imzalama düzenine, anahtar dönüşüm geçmişine ve birden çok imzalayana göre değişir.

#### Sınırlayıcı Ölçüm

Dört sürüm yayımlanır. Birinci ve üçüncü güvenilen sertifikayla imzalıdır; ikinci ve dördüncü farklı bir anahtarla imzalanmıştır.

```kotlin
// Main.kt — farkli anahtarla imzalanan surum reddedilir
fun main() {
    val v1 = Signal("uygulama.ana", 1)
    val v2 = Signal("uygulama.ana", 2)
    val v3 = Signal("uygulama.ana", 3)
    val v4 = Signal("uygulama.ana", 4)
    val releases = listOf(
        Release(v1, "sertifika-a"),
        Release(v2, "sertifika-b"),
        Release(v3, "sertifika-a"),
        Release(v4, "sertifika-b"),
    )
    val received = installUpdates(Device("sertifika-a"), releases)
    val arrivals = probe(listOf(v1, v2, v3, v4), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=uygulama.ana | kopya=1 -> birebir
olay-2: anahtar=uygulama.ana | kopya=0 -> ezilen
olay-3: anahtar=uygulama.ana | kopya=1 -> birebir
olay-4: anahtar=uygulama.ana | kopya=0 -> kayip
birebir: 2
yinelenen: 0
ezilen: 1
kayip: 1
```

İkinci ve dördüncü sürüm aynı nedenle reddedildi: sertifikaları aygıtın güvendiğiyle eşleşmiyor. Kurala göre ise biri ezilen, öteki kayıptır. İkinci sürümden sonra aynı anahtarla daha büyük bir `seq` ulaştı, üçüncü sürüm; bu yüzden ikinci ezilendir. Dördüncüden sonra böyle bir kopya gelmedi; kurulu sürüm 3, dördüncüden küçüktür. Kurulu sürüm sonra gelen bir olay olmadığı için dördüncüyü ezmiş sayılmaz, bu yüzden dördüncü kayıptır.

Kullanıcı açısından fark şudur. Ezilen sürüm artık gerekmeyen eski bir sürümdür; kayıp sürüm ise hiç gelmeyen yeni bir sürümdür ve kullanıcı onun düzeltmelerini alamaz. Kural bu farkı bir kategoriyle gösterir ama nedenini, yani anahtarın yanlış olduğunu, söylemez.

Anahtar kaybının en ağır hâli, bir sürümden sonra bütün güncellemelerin farklı bir anahtarla imzalanmasıdır. İlk sürüm güvenilen sertifikayladır, sonraki üçü yeni bir anahtarladır.

```kotlin
// Main.kt — anahtar degisince sonraki butun surumler reddedilir
fun main() {
    val v1 = Signal("uygulama.ana", 1)
    val v2 = Signal("uygulama.ana", 2)
    val v3 = Signal("uygulama.ana", 3)
    val v4 = Signal("uygulama.ana", 4)
    val releases = listOf(
        Release(v1, "sertifika-a"),
        Release(v2, "sertifika-b"),
        Release(v3, "sertifika-b"),
        Release(v4, "sertifika-b"),
    )
    val received = installUpdates(Device("sertifika-a"), releases)
    val arrivals = probe(listOf(v1, v2, v3, v4), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=uygulama.ana | kopya=1 -> birebir
olay-2: anahtar=uygulama.ana | kopya=0 -> kayip
olay-3: anahtar=uygulama.ana | kopya=0 -> kayip
olay-4: anahtar=uygulama.ana | kopya=0 -> kayip
birebir: 1
yinelenen: 0
ezilen: 0
kayip: 3
```

İlk sürüm ulaştı, üç yeni sürüm ulaşmadı. Hiçbiri ezilen değildir, çünkü aygıtta kurulu olan en yeni sürüm birinci sürümdür ve onları yerinden edecek daha büyük bir `seq` ulaşmadı. Anahtar bir kez değiştiğinde kayıp tek sürümle sınırlı kalmaz; sonraki her sürüm aynı kapıya takılır. Önceki ölçümde reddedilen sürümün ardından güvenilen sertifikayla yeni bir sürüm gelince ezilen oluşuyordu; burada böyle bir sürüm yoktur.

Aynı kurulum, aygıt yeni anahtara geçtikten sonra tersine döner. Kullanıcı eski uygulamayı kaldırıp yeni anahtarla imzalı sürümü temiz kurar; aygıtın güvendiği sertifika artık `sertifika-b`'dir.

```kotlin
// Main.kt — temiz kurulumdan sonra aygit yeni sertifikaya guvenir
fun main() {
    val v1 = Signal("uygulama.ana", 1)
    val v2 = Signal("uygulama.ana", 2)
    val v3 = Signal("uygulama.ana", 3)
    val releases = listOf(
        Release(v1, "sertifika-a"),
        Release(v2, "sertifika-b"),
        Release(v3, "sertifika-b"),
    )
    val received = installUpdates(Device("sertifika-b"), releases)
    val arrivals = probe(listOf(v1, v2, v3), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=uygulama.ana | kopya=0 -> ezilen
olay-2: anahtar=uygulama.ana | kopya=1 -> birebir
olay-3: anahtar=uygulama.ana | kopya=1 -> birebir
birebir: 2
yinelenen: 0
ezilen: 1
kayip: 0
```

Eski anahtarla imzalı birinci sürüm artık reddedilir; ama yerini alan daha yeni sürümler ulaştığı için ezilendir. Aynı üç sürüm, güvenilen sertifika değişince kayıp yerine ezilen üretti. Bu, kuralın nedeni değil yalnız kopyaların konumunu gördüğünü bir kez daha gösterir. Temiz kurulumun kullanıcıya bedeli ise ölçümde görünmez: eski uygulamanın verisi kaldırmayla gider ve ölçüm bunu saymaz.

**GD3.** Model, reddedilen sürümü kurulu sürümden ayrı bir olay olarak ele alır. Platformda reddedilen güncelleme bir hata iletisiyle sonuçlanır ve kurulu uygulama değişmez.

#### Reddin Sessizliği

`installUpdates` işlevine bir kez daha bakınca modelin en önemli özelliği görünür: reddedilen sürüm için hiçbir şey üretilmez. Listeye bir hata kaydı eklenmez, işaretçi bırakılmaz, alıcının elinde yalnız kurulanlar kalır. Ölçümü yapan `probe` de bu yüzden reddedilen sürümü "kopya sıfır" olarak görür. Kayıp ile birebir arasındaki fark, listede bir satırın olup olmamasından ibarettir.

Bu tasarım bilinçlidir, çünkü dersin sorusu alıcıya kaç kopya ulaştığıdır, neden ulaşmadığı değil. Ama okuyan için bir sonuç doğurur: bir sürümün kayıp olması, kaybın nerede yaşandığını söylemez. Sertifika uyuşmazlığı da, paketin yolda bozulması da, kurulumun hiç denenmemesi de aynı sıfırı verir. Üç ayrı neden tek bir sayıda toplanır; neden aramak için ölçümün dışına, yayımlayanın kendi kayıtlarına bakmak gerekir.

Sınıflandırma kuralı ise sıfırı iki kola ayırır. Aynı anahtarla daha büyük bir `seq` ulaştıysa sıfır ezilen, ulaşmadıysa kayıptır. Sertifika reddi bu ayrımı kendisi yapmaz; ayrımı sonradan gelen sürümler yapar. Aynı reddedilmiş sürüm, ardından güvenilen sertifikayla bir sürüm geldiğinde ezilen, gelmediğinde kayıp yazılır. Bu yüzden bir yayımlayan için en güvenli okuma şudur: kayıp sayısı sıfırdan büyükse, o sürümlerden sonra aygıta hiçbir şey ulaşmamış demektir ve bu, kullanıcının eski sürümde takılı kaldığı anlamına gelir.

**Sürümler sırayla gelmek zorunda değildir.** Model sürümleri gönderim sırasıyla işler, ama gerçek bir aygıt bir sürümü atlayıp sonrakine geçebilir. Bu durumda atlanan sürüm için kopya sıfırdır ve sonraki sürüm ulaştığı için sınıflandırma ezilen olur, kayıp olmaz. Yani sertifika reddinden bağımsız olarak, "sürüm aygıta hiç inmedi" ile "sürüm indi ama bir sonraki onu yerinden etti" ayrımı yalnız sonradan gelen sürüme bağlıdır.

Sürüm numarası ile sertifika ayrı kimliklerdir. Sürüm numarası kurulumun yönünü belirler, sertifika kurulumun izin verilip verilmediğini. İkisi birbirinin yerine geçmez; büyük numaralı sürüm yanlış sertifikayla gelirse reddedilir, doğru sertifikayla gelen küçük numaralı sürüm ise ölçümde yalnız sıra olarak değerlendirilir.

#### Yayımlayanın Yapacakları

Bu dersin alıcısı aygıttır, ama kaybı önleyecek olan yayımlayandır. Kontrol listesi kısadır.

- **İmzalama anahtarını sürümden bağımsız saklamak.** Anahtar, sürümün bir parçası değil, uygulamanın kimliğidir. Kaybolursa yukarıdaki üç kayıplı ölçümün hâli yaşanır. Anahtarın yedeği, onu kullanan ortamdan ayrı bir yerde tutulmalıdır.
- **Yayın öncesi sertifika özetini karşılaştırmak.** Yeni paketin sertifika özeti, bir önceki sürümünkiyle eşleşmiyorsa yayın durdurulur. Bu karşılaştırma aygıtın yapacağı karşılaştırmanın aynısıdır, yalnız kullanıcıdan önce yapılır.
- **İki yoldan dağıtımı bilinçli yapmak.** Aynı sürüm hem mağazadan hem elle dağıtılıyorsa, ikisinin sertifikası aynı olmalıdır. Aksi hâlde biri kurulur, öteki reddedilir ve hangisinin hangisi olduğu kullanıcıya görünmez.
- **Reddi izlenebilir kılmak.** Reddedilen güncelleme sunucuya hiçbir şey bildirmez. Yayımlayanın bunu fark etmesinin yolu, kurulu sürüm dağılımına bakmaktır: yeni sürümü hiç görmeyen bir kitle varsa kayıp o kitlededir.

#### Yaygın Yanılgılar

**Paket adı aynıysa güncelleme kurulur.** Ad yetmez; sertifika da eşleşmelidir. Dört sürümlü ölçümde bütün sürümlerin adı aynıydı ve ikisi yine de reddedildi.

**Reddedilen güncelleme her zaman kayıptır.** Değildir. Sonradan güvenilen sertifikayla daha yeni bir sürüm ulaştıysa ezilendir. Kayıp ile ezilen arasındaki fark reddin nedeninde değil, sonradan ne geldiğindedir.

**Anahtar değişikliği yalnız bir sürümü etkiler.** Etkilemez. Anahtar değişince sonraki her sürüm reddedildi; üç sürümün üçü kayıp oldu.

**Aynı sürümün iki kez gelmesi bir hatadır.** Aygıt açısından bir hata olmayabilir; ama kural iki kopya ulaştığını sayar ve yinelenen yazar. Hata olup olmadığı yeniden kurulumun sonucuna bağlıdır, sayıya değil.

#### Özet

- Bu dersin teslimi: birebir. Aygıtın güvendiği sertifikayla imzalanan güncelleme karşılaştırmadan geçer ve kurulu uygulamaya bir kopya olarak ulaşır.
- İmzalama anahtarı sürümden sürüme aynı kalması gereken tek şeydir; sertifika paketle aygıta iner.
- Üç sürümün üçü aynı sertifikayla birebir ulaşır: birebir 3.
- Aynı sürüm iki yoldan kurulursa iki kopya sayılır: birebir 1, yinelenen 1.
- Farklı anahtarla imzalanan sürüm reddedilir: sonradan gelen sürüm varsa ezilen, yoksa kayıp. Dört sürümde birebir 2, ezilen 1, kayıp 1. Anahtar bir sürümden sonra değişirse üç sürüm de kayıp olur: birebir 1, kayıp 3.

#### Sonraki Adım

Sürüm artık aygıttadır ve çalışır. Çalışırken uygulamanın neler yaptığını bilmek için olay toplamak gerekir. Sonraki derste uygulama analitik olayları ve çökme raporları gönderir; gönderim yanıtı kaybolunca aynı olay tekilleştirmeden yeniden denenir.

### Analitik ve Çökme Raporlama

Source: https://academia.sh/tr/kurslar/android-servisler/analitik-ve-cokme-raporlama

Analitik olayları ve çökme raporları bir gönderim kuyruğundan sunucuya gider; yanıt kaybolursa istemci aynı olayı yeniden dener. Ders gönderilen olayın sunucuya kaç kopya ulaştığını, sınırlı kuyruğun en eski olayları düşürmesini ve tekilleştirmenin teslimi neden değiştirmediğini ölçer.

Önceki derste sürüm aygıta kuruldu. Çalışan uygulamanın ne yaptığını bilmenin yolu, olay göndermektir: bir ekran açıldı, bir sepete ürün eklendi, uygulama çöktü. Bu olaylar üretildikleri anda sunucuya gitmez; bir kuyrukta birikir ve ağ uygun olunca gönderilir. Soru kursun sorusudur: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?** Olay raporlanan olaydır, alıcı sunucudur.

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

#### Olay, Rapor ve Oturum

**Analitik olayı**, kullanıcının uygulamada yaptığı bir adımın kaydıdır: ekran açıldı, düğmeye basıldı. **Çökme raporu**, uygulama beklenmedik bir hatayla durduğunda üretilen kayıttır; hata türünü ve yığını taşır. **Çökmesiz oturum** (crash-free session) ise bir oturumun çökmeden bitmesidir ve çökme oranı hesabının paydasını oluşturur. Üçü de aynı yoldan gider: bir kuyruğa girer ve sunucuya gönderilir.

Çökme raporunu üreten kanca platformun çökme raporlama çerçevesinde durur. Uygulama çöktüğünde süreç kapanmadan bir işleyici çağrılır ve rapor kuyruğa eklenir.

```kotlin
// Platform kodu: Thread.UncaughtExceptionHandler
val previous = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, error ->
    reportQueue.enqueue(CrashReport(thread.name, error))
    previous?.uncaughtException(thread, error)
}
```

Kuyruğun boşaltılması arka planda, ağ bağlıyken yapılır.

```kotlin
// Platform kodu: WorkManager
val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .build()
val request = OneTimeWorkRequestBuilder<UploadWorker>()
    .setConstraints(constraints)
    .build()
WorkManager.getInstance(context).enqueue(request)
```

İki parça platform kodudur ve burada çıktı, süre ya da sayı iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir. Yeniden deneme çoğu istemcide üstel geri çekilmeyle yapılır: her başarısız denemeden sonra bekleme uzar. Bu bekleme denemelerin aralığını değiştirir, sayısını değiştirmez; modelde bekleme yoktur.

#### Yanıt Kaybolursa

Gönderim, iki yarıdan oluşur: istek sunucuya ulaşır, sunucu bir onay yanıtı döner. Yanıt yolda kaybolursa istemci isteğin ulaşıp ulaşmadığını bilemez ve onay gelene kadar aynı olayı yeniden gönderir. Modelde her olay, onayın kaç kez kaybolacağını taşır; her gönderim sunucuya ulaşır, yalnız onay kaybolur.

```kotlin
// Report.kt — onay kaybolan olay, onay gelene kadar yeniden gonderilir
class Report(val signal: Signal, val lostAcks: Int = 0)

fun retryUntilAck(reports: List<Report>): List<Signal> =
    reports.flatMap { r -> List(r.lostAcks + 1) { r.signal } }

fun boundedQueue(capacity: Int, queued: List<Signal>): List<Signal> = queued.takeLast(capacity)

fun dedupeById(received: List<Signal>): List<Signal> =
    received.distinctBy { it.key to it.seq }
```

```kotlin
// Probe.kt — gonderilen olaylar icin sunucuya ulasan listeyi alici sayar
fun probe(sent: List<Signal>, received: List<Signal>): List<Arrival> =
    sent.mapIndexed { i, s -> Arrival("olay-${i + 1}", s, received) }
```

#### Onay Her Seferinde Gelirse

Dört olay gönderilir: iki analitik olayı, bir çökme raporu ve bir çökmesiz oturum kaydı. Hiçbirinin onayı kaybolmaz.

```kotlin
// Main.kt — onay gelirse her olay bir kez gonderilir
fun main() {
    val open = Signal("olay.acilis", 1)
    val cart = Signal("olay.sepet", 2)
    val crash = Signal("cokme.oturum", 3)
    val clean = Signal("oturum.saglam", 4)
    val reports = listOf(Report(open), Report(cart), Report(crash), Report(clean))
    val received = retryUntilAck(reports)
    val arrivals = probe(listOf(open, cart, crash, clean), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=olay.acilis | kopya=1 -> birebir
olay-2: anahtar=olay.sepet | kopya=1 -> birebir
olay-3: anahtar=cokme.oturum | kopya=1 -> birebir
olay-4: anahtar=oturum.saglam | kopya=1 -> birebir
birebir: 4
yinelenen: 0
ezilen: 0
kayip: 0
```

Dört olayın dördü birer kopya ulaştı. Yeniden denemeye gerek kalmadı.

#### Aynı Olay Yeniden Denenirse

Şimdi sepet olayının onayı bir kez, çökme raporunun onayı iki kez kaybolur. İstemci onay gelene kadar gönderir ve sunucu her gönderimi ayrı bir kopya olarak alır.

```kotlin
// Main.kt — onayi kaybolan olay tekillestirmeden yeniden denenir
fun main() {
    val open = Signal("olay.acilis", 1)
    val cart = Signal("olay.sepet", 2)
    val crash = Signal("cokme.oturum", 3)
    val clean = Signal("oturum.saglam", 4)
    val reports = listOf(Report(open), Report(cart, 1), Report(crash, 2), Report(clean))
    val received = retryUntilAck(reports)
    val arrivals = probe(listOf(open, cart, crash, clean), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=olay.acilis | kopya=1 -> birebir
olay-2: anahtar=olay.sepet | kopya=2 -> yinelenen
olay-3: anahtar=cokme.oturum | kopya=3 -> yinelenen
olay-4: anahtar=oturum.saglam | kopya=1 -> birebir
birebir: 2
yinelenen: 2
ezilen: 0
kayip: 0
```

Bu dersin teslimi: yinelenen. Yanıtı kaybolan olay sunucuya ikinci ve üçüncü kez ulaştı. İstemci bu davranışta bir hata yapmadı: onay gelmeyen bir gönderimi yeniden denemek, hiçbir şey göndermemekten güvenlidir. Bedeli sunucuya fazladan kopyadır. Çökme oranı gibi bir sayı sunucuda kopyalarla hesaplanırsa, çöken oturumlar olduğundan fazla sayılır.

Burada bir ayrım yapmak gerekir. Aynı kopyayı iki kez işleyen alıcı olayı yinelemiş olmaz. Yinelenen, bir kopyanın alıcıya ikinci kez ulaşmasıdır; alıcının onu iki kez işlemesi, kopyaların sayısını değiştirmez. Bu derste kopyalar sunucuya ulaşırken sayılır, sunucunun içinde ne olduğuna bakılmaz.

**GD1.** Model, her gönderimin sunucuya ulaştığını, yalnız onayın kaybolduğunu varsayar. Platformda istek de yolda kaybolabilir; o durumda yeniden deneme olayı yinelemez, ilk kez ulaştırır.

**GD2.** Model, yeniden denemeyi onay gelene kadar sürdürür ve bir üst sınır koymaz. Platformda deneme sayısı ve bekleme çalışma ilkesinden gelir.

#### Kuyruk Neden Sınırlıdır

Kuyruğun sınırsız olmaması bir tasarım zorunluluğudur. Aygıtın belleği ve depolaması sınırlıdır; ağ günlerce yoksa ve uygulama her ekran açılışını kuyruğa yazıyorsa kuyruk uygulamanın kendisinden büyük olabilir. Bu yüzden istemciler bir üst sınır koyar ve sınır dolunca iki yoldan birini seçer: en eskiyi düşürmek ya da en yeniyi kabul etmemek.

İki seçimin sonucu aynı değildir. En eskiyi düşüren kuyruk, ağ döndüğünde yakın geçmişi gönderir ve uzak geçmişi kaybeder; en yeniyi reddeden kuyruk uzak geçmişi korur ama ağ döndüğünde güncel durumu bilmez. Çökme raporları için genellikle ikinci seçim daha az zararlıdır, çünkü en eski çökme çoğu kez ilk ve en açıklayıcı olandır. Model ilk seçimi kurar: `takeLast(capacity)` en yeni olayları tutar.

Kayıp, bu tasarımın bedeli olarak ortaya çıkar ve istemci bunu kendi başına göremez. Düşürülen olay için gönderim hiç denenmediğinden onay beklenmez, hata üretilmez, hiçbir şey kaydedilmez. Alıcı da bir şey bilmez; ona yalnız ulaşanlar görünür. Sayıdaki eksiklik, ancak üretilen olay sayısıyla ulaşan olay sayısı karşılaştırılırsa fark edilir.

Yeniden deneme yukarıda ölçüldü, kuyruk sınırı aşağıda ölçülecek; gerçek bir istemcide ikisi aynı olay akışında çalışır. Kuyruğa giren olayların bir kısmı düşer, kalanlar gönderilir ve gönderilenlerin bir kısmının onayı kaybolur. Aşağıdaki ölçümde beş olay üç yerlik kuyruğa girer; kuyrukta kalan ödeme olayının onayı bir kez kaybolur.

#### Sınırlayıcı Ölçüm

Yeniden denemenin tek riski kopya değildir. Ağ uzun süre yoksa olaylar kuyrukta birikir ve kuyruğun boyu sınırlıdır. Kuyruk dolunca yenisine yer açmak için en eski olaylar düşer. Beş olay birikir ve kuyruk üç olay alır.

```kotlin
// Main.kt — sinirli kuyruk dolunca en eski olaylar dusurulur
fun main() {
    val sent = listOf(
        Signal("olay.acilis", 1),
        Signal("olay.sepet", 2),
        Signal("cokme.oturum", 3),
        Signal("olay.odeme", 4),
        Signal("oturum.saglam", 5),
    )
    val received = boundedQueue(3, sent)
    val arrivals = probe(sent, received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=olay.acilis | kopya=0 -> kayip
olay-2: anahtar=olay.sepet | kopya=0 -> kayip
olay-3: anahtar=cokme.oturum | kopya=1 -> birebir
olay-4: anahtar=olay.odeme | kopya=1 -> birebir
olay-5: anahtar=oturum.saglam | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 0
kayip: 2
```

En eski iki olay düştü ve sunucuya hiç ulaşmadı. Anahtarları farklı olduğu için bunları yerinden eden başka bir kopya yoktur; kural onları kayıp sayar. Kuyruk en eskiyi attığı için ilk açılış olayları kaybolur, en yeni olaylar kalır. Bu seçim bir politikadır: çökme raporu yeterince eskiyse o da düşebilir.

Şimdi sunucunun olay kimliğiyle tekilleştirdiğini düşünelim. Önceki yeniden deneme senaryosu aynen yaşanır, ama sunucu aynı anahtar ve sırayla gelen kopyaları tek kayda indirir.

```kotlin
// Main.kt — tekillestiren sunucu sonucu korur ama teslim yinelenen kalir
fun main() {
    val open = Signal("olay.acilis", 1)
    val cart = Signal("olay.sepet", 2)
    val crash = Signal("cokme.oturum", 3)
    val clean = Signal("oturum.saglam", 4)
    val reports = listOf(Report(open), Report(cart, 1), Report(crash, 2), Report(clean))
    val received = retryUntilAck(reports)
    val arrivals = probe(listOf(open, cart, crash, clean), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
    println("ulasan: ${received.size}")
    println("saklanan: ${dedupeById(received).size}")
}
```

```text
olay-1: anahtar=olay.acilis | kopya=1 -> birebir
olay-2: anahtar=olay.sepet | kopya=2 -> yinelenen
olay-3: anahtar=cokme.oturum | kopya=3 -> yinelenen
olay-4: anahtar=oturum.saglam | kopya=1 -> birebir
birebir: 2
yinelenen: 2
ezilen: 0
kayip: 0
ulasan: 7
saklanan: 4
```

Sunucu yedi kopyayı dört kayda indirdi, yani sonuç doğrudur: çökme oranı kopyalarla şişmez. Ama teslim yine yinelenendir: sunucuya yedi kopya ulaştı ve kural ulaşan kopyaları sayar. Tekilleştirme alıcının içindeki bir iştir; teslim ise kopyanın alıcıya ulaşmasıdır. İkisi birbirini değiştirmez.

Yeniden deneme ve kuyruk birlikte çalışınca iki etki aynı akışta görünür.

```kotlin
// Main.kt — dusen olaylar kayip, onayi kaybolan olay yinelenen olur
fun main() {
    val sent = listOf(
        Signal("olay.acilis", 1),
        Signal("olay.sepet", 2),
        Signal("cokme.oturum", 3),
        Signal("olay.odeme", 4),
        Signal("oturum.saglam", 5),
    )
    val queued = boundedQueue(3, sent)
    val reports = queued.map { Report(it, if (it.seq == 4) 1 else 0) }
    val received = retryUntilAck(reports)
    val arrivals = probe(sent, received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=olay.acilis | kopya=0 -> kayip
olay-2: anahtar=olay.sepet | kopya=0 -> kayip
olay-3: anahtar=cokme.oturum | kopya=1 -> birebir
olay-4: anahtar=olay.odeme | kopya=2 -> yinelenen
olay-5: anahtar=oturum.saglam | kopya=1 -> birebir
birebir: 2
yinelenen: 1
ezilen: 0
kayip: 2
```

Aynı akışta üç farklı teslim görüldü. Kayıp kuyruğun sınırından, yinelenen kaybolan onaydan gelir; ikisi birbirinden bağımsızdır ve biri diğerini düzeltmez. Kuyruğa girmeyen olay yeniden denenmediği için hiçbir zaman yinelenen olmaz; yeniden denenen olay da kuyruktan düşmüş olamaz, çünkü gönderim kuyrukta kalan olaylar için yapılır.

**GD3.** Model, tekilleştirmeyi anahtar ve sıra çiftiyle yapar. Platformda sunucu bir olay kimliği, bir zaman damgası ya da ikisini birlikte kullanabilir; model yalnız birincisini kurar.

#### Sunucunun Yapacakları

Bu dersin alıcısı sunucudur ve kopyaları ondan başkası düzeltemez. Üç iş sıralanır.

- **Her olaya bir kimlik vermek ve kimliğe göre tekilleştirmek.** Kimliği istemci üretir ve yeniden denemede aynı kimlik gönderilir; sunucu aynı kimliği ikinci kez gördüğünde olayı saymaz. Kimlik her denemede yeni üretilirse tekilleştirme çalışmaz, çünkü sunucu iki kopyayı iki ayrı olay olarak görür.
- **Tekilleştirme penceresini bilmek.** Sunucu gördüğü kimlikleri sonsuza dek tutamaz. Pencere kısa tutulursa geç gelen kopya yeni olay sayılır; uzun tutulursa bellek şişer. Pencerenin uzunluğu, istemcinin yeniden deneme süresinden büyük olmalıdır.
- **Kopya sayısını ayrıca saymak.** Tekilleştirme sonucu korur, ama kaç kopya geldiğini gizler. Yeniden deneme oranı sağlıklı bir ağın göstergesidir; kopya sayısı yükselmişse onaylar kayboluyordur ve bu, sonucu değiştirmese de bilinmesi gereken bir şeydir.

Kopya sayısını sağlıklı ağ ile karşılaştırmak için sunucunun elinde bir taban olmalıdır: onayı kaybolmayan olayların oranı bilinmeden kopya artışı yorumlanamaz.

İstemci tarafında yapılacak iş bir olayı bir kez üretip aynı kimlikle göndermektir. Kimliğin üretildiği an, yeniden denemeden önce olmalıdır.

#### Yaygın Yanılgılar

**Geri çekilme sayıyı değil aralığı değiştirir.** Üstel geri çekilme, istemcinin ağı yormadan denemesini sağlar; ama onay gelene kadar gönderen istemci için kopya sayısı yine kaybolan onay sayısının bir fazlasıdır. Geri çekilme, sunucunun aynı anda gelen kopyalardan korunmasına yardım eder, kopyaları ortadan kaldırmaz.

**Kuyruğa yazılan olay sunucuya ulaşmış sayılmaz.** Üretim ile ulaşım arasındaki aralıkta olay kuyruktadır ve sürecin kapanması bu aralıkta olayı yok edebilir. Ölçüm kuyruğun sürekli bellekte kaldığını varsayar; kalıcı olmayan bir kuyruk uygulama kapanınca bütün bekleyenleri kaybeder.

**Tekilleştiren sunucuda yinelenen yoktur.** Vardır. Ölçümde yedi kopya dört kayda indi ve teslim yine yinelenen 2 kaldı. Tekilleştirme sonucu düzeltir, ulaşan kopyaların sayısını değiştirmez.

**Yeniden deneme olayın kaybolmasını önler.** Yalnız onayı kaybolan olayı önler. Kuyrukta düşen olay hiç gönderilmediği için yeniden deneme ona ulaşmaz; ölçümde düşen iki olay yine kayıp kaldı.

**Kuyruk büyüdükçe daha güvenlidir.** Büyük kuyruk daha az olay düşürür, ama aygıtın belleğini ve ağ döndüğünde gönderilecek yükü artırır. Sınır bir güvenlik değil, bir dengedir.

**Bir olayın kimliği, sunucunun ona bakmasına bağlıdır.** Kimliği yalnız istemci taşıyorsa sunucu kopyayı ayıramaz. Tekilleştirme iki tarafın birlikte uyguladığı bir sözleşmedir: istemci aynı kimliği gönderir, sunucu aynı kimliği tek kez sayar.

**Çökme raporu da diğer olaylar gibi sayılır.** Sayılır, ve bu yüzden kopyalı sayılan çökme raporu çökme oranını şişirir. Ölçümde çökme raporu üç kopya olarak ulaştı; tekilleştirme yoksa o oturum üç çökme sayılırdı.

#### Özet

- Bu dersin teslimi: yinelenen. Onayı kaybolan olay, tekilleştirmeden yeniden denenince sunucuya birden çok kopya ulaşır.
- Analitik olayı bir adımı, çökme raporu bir çöküşü, çökmesiz oturum bir sağlam bitişi kaydeder; üçü aynı kuyruktan gider.
- Onay her seferinde gelirse dört olayın dördü birebir ulaşır. Onayı kaybolan iki olayda yinelenen 2, birebir 2 olur.
- Beş olay üç yerlik kuyruğa girince en eski ikisi düşer: kayıp 2, birebir 3.
- Yeniden deneme ve kuyruk birlikte çalışınca kayıp 2, birebir 2, yinelenen 1 görülür; ikisi birbirini düzeltmez.
- Olay kimliğiyle tekilleştiren sunucu yedi kopyayı dört kayda indirir, ama teslim yine yinelenen 2'dir. Aynı kopyayı iki kez işleyen alıcı olayı yinelemiş olmaz.

#### Sonraki Adım

Olaylar sunucuya ulaşıyor ve uygulamanın üretimde nasıl davrandığı görünüyor. Davranışı değiştirmek için her seferinde yeni sürüm yayımlamak gerekmez. Sonraki derste uygulama sunucudan bir yapılandırma getirir; etkinleştirmeden önce iki kez getirilen yapılandırmada aynı anahtarın son değeri kazanır.

### Uzaktan Yapılandırma

Source: https://academia.sh/tr/kurslar/android-servisler/uzaktan-yapilandirma

Uzaktan yapılandırmada uygulama sunucudan değerleri getirir ve ayrı bir adımda etkinleştirir. Ders etkinleştirmeden önce iki kez getirilen aynı anahtarın son değerinin kazanmasını, ilk değerin ezilmesini ve getirilip etkinleştirilmeyen değerin kayıp olduğunu ölçer.

Önceki derste olaylar sunucuya gitti. Bu derste yön tersine döner: sunucu uygulamaya bir değer gönderir, örneğin bir özelliğin açık mı kapalı mı olduğunu ya da bir üst sınırı. Böylece davranış, yeni sürüm yayımlanmadan değişir. Değer iki adımda uygulamaya girer: önce getirilir, sonra etkinleştirilir. Soru kursun sorusudur: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?** Burada olay bir yapılandırma değeridir, alıcı çalışan uygulamadır.

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

#### Getirme ve Etkinleştirme

**Getirme**, değerin sunucudan alınıp aygıtta bekletilmesidir; uygulama onu henüz okumaz. **Etkinleştirme**, bekleyen değerin uygulamanın okuduğu değer olmasıdır. İki adımın ayrı olmasının nedeni, ekranda çalışan bir özelliğin değerinin ortasında değişmemesidir. Etkinleştirme uygun bir anda, örneğin bir sonraki açılışta yapılır.

Platformda bu ayrımın en yalın karşılığı, değeri bir yerde bekletip sonra uygulamanın okuduğu depoya yazmaktır.

```kotlin
// Platform kodu: SharedPreferences
val pending = context.getSharedPreferences("bekleyen", Context.MODE_PRIVATE)
val active = context.getSharedPreferences("etkin", Context.MODE_PRIVATE)
pending.edit().putString("ayar.tema", "koyu").apply()
active.edit().putString("ayar.tema", pending.getString("ayar.tema", null)).apply()
```

Bu parça platform kodudur ve burada çıktı, süre ya da sayı iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir. Getirme sıklığını ve etkinleştirme anını platform ya da kullanılan çerçeve belirler; modelde ikisi de bir listedir.

Modelde getirilen değerler sıralı bir listedir. Etkinleştirme, her anahtar için listedeki son değeri seçer. Aynı anahtarın daha eski bir değeri, ayrı bir anahtarı etkilemez.

```kotlin
// Config.kt — etkinlestirme her anahtar icin getirilen son degeri secer
fun activateLatest(fetched: List<Signal>): List<Signal> =
    fetched.groupBy { it.key }.map { (_, values) -> values.maxBy { it.seq } }

fun activateUpTo(fetched: List<Signal>, count: Int): List<Signal> =
    activateLatest(fetched.take(count))
```

```kotlin
// Probe.kt — gonderilen degerler icin uygulamanin okudugu listeyi alici sayar
fun probe(sent: List<Signal>, received: List<Signal>): List<Arrival> =
    sent.mapIndexed { i, s -> Arrival("olay-${i + 1}", s, received) }
```

#### Her Anahtar Bir Kez Getirilirse

Üç ayrı anahtar bir kez getirilir ve etkinleştirilir: tema, bir üst sınır ve bir özellik bayrağı.

```kotlin
// Main.kt — her anahtar bir kez getirilirse hepsi etkinlesir
fun main() {
    val theme = Signal("ayar.tema", 1)
    val limit = Signal("ayar.limit", 2)
    val flag = Signal("ayar.bayrak", 3)
    val sent = listOf(theme, limit, flag)
    val arrivals = probe(sent, activateLatest(sent))
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=ayar.tema | kopya=1 -> birebir
olay-2: anahtar=ayar.limit | kopya=1 -> birebir
olay-3: anahtar=ayar.bayrak | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 0
kayip: 0
```

Üç değerin üçü birer kopya olarak uygulamaya ulaştı. Hiçbir anahtar yarışmadı.

#### Etkinleştirmeden Önce İki Kez Getirilirse

Şimdi tema değeri, etkinleştirme gelmeden önce iki kez getirilir: ilkinde açık tema, ikincisinde koyu tema. Sunucu arada değeri değiştirmiştir.

```kotlin
// Main.kt — ayni anahtar iki kez getirilirse son deger kazanir
fun main() {
    val first = Signal("ayar.tema", 1)
    val second = Signal("ayar.tema", 2)
    val limit = Signal("ayar.limit", 3)
    val sent = listOf(first, second, limit)
    val arrivals = probe(sent, activateLatest(sent))
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=ayar.tema | kopya=0 -> ezilen
olay-2: anahtar=ayar.tema | kopya=1 -> birebir
olay-3: anahtar=ayar.limit | kopya=1 -> birebir
birebir: 2
yinelenen: 0
ezilen: 1
kayip: 0
```

Bu dersin teslimi: ezilen. İlk tema değeri uygulamaya hiç ulaşmadı; aynı anahtarın daha büyük sıralı ikinci değeri onun yerini aldı. Kural nedeni görmez: ilk değerin yanlış olduğunu ya da ikincisinin düzeltme olduğunu bilmez, yalnızca aynı anahtarla daha yeni bir değerin ulaştığını görür. Önceki derslerdeki yinelenenle bir fark vardır: orada aynı olayın birden çok kopyası ulaşıyordu, burada ilk değerin hiçbir kopyası ulaşmadı.

Bu davranış yapılandırma için çoğunlukla istenendir. Uygulamanın ihtiyacı en güncel değerdir; eski değeri görmek bir kazanç sayılmaz. Bedeli, ilk değerin sıfır kopyayla geçmesidir; ilk değeri bir sayaca ya da bir deneye bağlayan taraf onun hiç uygulanmadığını bilmelidir.

**GD1.** Model, sıralı numaraları getirme anına göre doğru verilmiş varsayar. Platformda iki getirmenin sırası ağ gecikmesiyle karışabilir ve sunucudan gelen sürüm bilgisi bunun için kullanılır; modelde bu bilgi doğrudan sıralı numaradır.

**GD2.** Model, etkinleştirmeyi tek bir anda yapar ve bekleyen tüm değerleri birlikte etkinleştirir. Platformda etkinleştirme yarıda kalabilir ya da bazı anahtarlar sonraya bırakılabilir.

#### Getirme Döngüsü

Uzaktan yapılandırma, tek bir istekle bitmez; bir döngüdür. Uygulama belli aralıklarla sunucuya sorar, sunucu o anki değerleri döner, uygulama değerleri bekleyen depoya yazar. Döngünün her turu bir getirmedir ve her tur öncekinden bağımsızdır: sunucu bu turda hangi değeri dönerse onu alır, uygulama önceki turda ne aldığını sunucuya söylemek zorunda değildir.

Bu bağımsızlığın bir sonucu vardır. İki tur arasında sunucudaki değer değiştiyse, bekleyen depoda aynı anahtar için iki değer birikebilir. Etkinleştirme henüz yapılmadığı için hiçbiri uygulamanın okuduğu değer değildir. Döngü getirdiği sürece bekleyen liste büyür ve uygulamanın gördüğü tek şey, etkinleştirmenin o an bıraktığı değerdir.

Getirme sıklığının neden sınırlandığı da buradan çıkar. Her tur bir ağ isteğidir; çok sık sormak pili ve ağı yorar, çok seyrek sormak da değişikliğin geç ulaşmasına yol açar. Modelde bu denge yoktur, çünkü model zamanı hiç taşımaz: getirilen değerler sıralı bir listedir ve iki tur arasındaki bekleme görünmez.

#### Ezilen ve Kayıp Arasındaki Çizgi

İki sonucun arasındaki çizgi ince ama kesindir. Ezilen, değerin kopyası ulaşmadan aynı anahtarı taşıyan daha sonraki bir değerin ulaşıp onun yerini aldığını söyler; uygulamanın en güncel değerde olduğunu söylemez. Kayıp ise uygulamanın sunucunun son kararına ulaşamadığını söyler; uygulama daha eski bir değerle ya da varsayılanla çalışıyordur. Bir yayımlayan için ilki çoğu zaman beklenen bir sonuçtur, ikincisi izlenmesi gereken bir durumdur.

Bu yüzden iki sayıyı ayrı raporlamak gerekir. Ezilen sayısının yüksekliği sunucunun hızlı değiştiğini, kayıp sayısının yüksekliği ise etkinleştirmenin geride kaldığını gösterir. Tek bir "ulaşmayan değer" sayısına indirilirse iki farklı sorun aynı rakamda saklanır.

#### Ölçümde Neyi Saymıyoruz

Kuralın görmediği şeyler, yapılandırma için en az kuraldaki kadar önemlidir. Kural bir değerin uygulamaya ulaşıp ulaşmadığını söyler; uygulamanın o değeri doğru kullandığını söylemez. Etkinleşmiş bir bayrak, bayrağı okuyan kod hatalıysa yine bir şey açmaz. Kural bir değerin ezilmesini ya da kaybolmasını işaretler, ama değerin sunucuda neden değiştirildiğini bilmez.

Bu sınırlar ölçümü zayıflatmaz, kapsamını gösterir. Ders tek bir soruyu yanıtlar: sunucunun gönderdiği değer uygulamanın okuduğu değer oldu mu. Değerin doğru olup olmadığı, doğru zamanda etkinleşip etkinleşmediği ve kullanıcıya ne gösterdiği başka sorulardır.

#### Değer Ne Zaman Etkinleşir

Etkinleştirmenin zamanı, hangi değerin uygulamaya ulaşacağını belirler. Etkinleştirme bir önceki turdan hemen sonra yapılırsa her getirilen değer etkinleşir ve ezilen hiç oluşmaz; her tur sonunda tek bir değer vardır. Etkinleştirme birkaç tur sonra yapılırsa aynı anahtar için birikmiş değerlerden yalnız sonuncusu etkinleşir, öncekiler ezilir. Etkinleştirme hiç yapılmazsa hepsi kayıp kalır.

Yani ezilen, yapılandırmada bir hata değil, etkinleştirmenin geciktirilmesinin doğal sonucudur. Etkinleştirme uygulamanın bir sonraki açılışına bırakıldığında, açılışa kadar getirilen değerlerin sonuncusu kazanır. Aynı etkinleştirme noktası değişir, sonuç değişir: model bunu `activateUpTo` işlevinin `count` parametresiyle taklit eder. Etkinleştirmenin kaç değeri kapsadığı, bekleyen listede nerede durduğuna göre ezileni ya da kaybı üretir.

Bir anahtar üç kez getirilir, ardından başka bir anahtar gelir ve etkinleştirme yalnız ilk ikisini kapsar.

```kotlin
// Main.kt — etkinlestirme noktasi ezileni ve kaybi belirler
fun main() {
    val a = Signal("ayar.tema", 1)
    val b = Signal("ayar.tema", 2)
    val c = Signal("ayar.tema", 3)
    val limit = Signal("ayar.limit", 4)
    val sent = listOf(a, b, c, limit)
    val arrivals = probe(sent, activateUpTo(sent, 2))
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=ayar.tema | kopya=0 -> ezilen
olay-2: anahtar=ayar.tema | kopya=1 -> birebir
olay-3: anahtar=ayar.tema | kopya=0 -> kayip
olay-4: anahtar=ayar.limit | kopya=0 -> kayip
birebir: 1
yinelenen: 0
ezilen: 1
kayip: 2
```

Aynı anahtarın üç değerinden ikincisi etkinleşti. Birincisi ikincisi tarafından ezildi; üçüncüsü etkinleştirmenin kapsamı dışında kaldığı için ulaşmadı ve ondan daha büyük bir sıra ulaşmadığı için ezilen değil, kayıp yazıldı. Kural yalnız ulaşanlara bakar: üçüncü değerin kendisinden büyük bir kardeşi uygulamaya inmedi.

Burada bir tuzak vardır. Son getirilen değer, sunucunun en güncel kararıdır; ama etkinleştirme onu kapsamadıysa uygulama bir önceki değerle çalışır. Uygulamanın çalıştığı değer, sunucunun şu an istediği değer olmayabilir. Ölçümde "tema" için etkin değer ikincidir, ama sunucunun gönderdiği son değer üçüncüdür.

#### Sınırlayıcı Ölçüm

Getirme tek başına teslim değildir. Değer getirilmiş ama etkinleştirilmemişse uygulama onu okuyamaz. Üç değer getirilir, etkinleştirme yalnız ilkini kapsayana kadar sürer ve uygulama kapanır.

```kotlin
// Main.kt — getirilip etkinlestirilmeyen deger uygulamaya ulasmaz
fun main() {
    val theme = Signal("ayar.tema", 1)
    val limit = Signal("ayar.limit", 2)
    val flag = Signal("ayar.bayrak", 3)
    val sent = listOf(theme, limit, flag)
    val arrivals = probe(sent, activateUpTo(sent, 1))
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=ayar.tema | kopya=1 -> birebir
olay-2: anahtar=ayar.limit | kopya=0 -> kayip
olay-3: anahtar=ayar.bayrak | kopya=0 -> kayip
birebir: 1
yinelenen: 0
ezilen: 0
kayip: 2
```

Limit ve bayrak getirildi ama etkinleşmedi; kural onları kayıp sayar. Ezilenden farkı şudur: onları ezen, aynı anahtarla daha yeni bir değer yoktur. Değer yalnızca bekleyen depoda kalmıştır.

Bu durumda uygulama ne okur? Sabit bir yedek, yani uygulama içi varsayılan. Varsayılan sunucudan gelen bir değer değildir; uygulamanın kendi içinde taşıdığı bir değerdir. Bu yüzden teslim sayılmaz. Hiç getirme olmadığında da aynı şey olur.

```kotlin
// Main.kt — hic getirilmeyen deger kayiptir, varsayilan teslim sayilmaz
fun main() {
    val theme = Signal("ayar.tema", 1)
    val limit = Signal("ayar.limit", 2)
    val sent = listOf(theme, limit)
    val arrivals = probe(sent, activateUpTo(sent, 0))
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=ayar.tema | kopya=0 -> kayip
olay-2: anahtar=ayar.limit | kopya=0 -> kayip
birebir: 0
yinelenen: 0
ezilen: 0
kayip: 2
```

İki değer de kayıp. Uygulama yine de çalışır, çünkü varsayılan değerleri vardır; ama bu değerler sunucunun gönderdiği şey değildir. Ölçüm, çalışan uygulamanın sunucuyla uyumlu olup olmadığını söylemez, yalnızca sunucunun gönderdiği değerin ulaşıp ulaşmadığını söyler.

**GD3.** Model, getirme ile etkinleştirme arasında değerin kaybolmadığını varsayar. Platformda bekleyen değer, depo temizlenirse ya da uygulama verisi silinirse gider.

#### Sunucunun Rolü

Alıcı uygulamadır, ama değerin kaynağı sunucudur ve sunucunun yaptığı seçimler alıcıdaki sayıyı etkiler. Sunucu bir anahtarı sık değiştirirse, getirme aralığı kadar kısa sürede birden çok değer birikir ve ezilen sayısı artar. Sunucu anahtarı seyrek değiştirirse bekleyen listede aynı anahtar için tek değer kalır ve ezilen oluşmaz.

Bu yüzden ezilenin yüksek çıkması çoğu kez sunucunun değerleri çok hızlı değiştirdiğinin işaretidir, uygulamanın bir sorununun değil. Hangi değerin hangi uygulamada etkin olduğunu merak eden taraf, sunucudaki sürüm geçmişi ile uygulamanın kaydettiği etkin değeri yan yana koymalıdır. Biri olmadan öteki yanıltır: sunucu değeri gönderdiğini söyler, uygulama onu hiç görmemiş olabilir.

#### Uygulamanın Yapacakları

Bu dersin alıcısı çalışan uygulamadır; yapılandırmanın uygulamaya doğru ulaşması için uygulama tarafında dört iş yapılır.

- **Her anahtar için bir varsayılan tanımlamak.** Varsayılan teslim sayılmaz, ama getirme başarısız olduğunda uygulamayı ayakta tutar. Varsayılanı, sunucunun göndermesi beklenen güvenli değere eşitlemek, yapılandırmanın ulaşmadığı durumlarda davranışı öngörülebilir kılar.
- **Etkinleştirme anını bilinçli seçmek.** Etkinleştirme hemen yapılırsa ekrandaki bir özellik ortada değişebilir; bir sonraki açılışa bırakılırsa son değer bir açılış geç gelir. İkisinin de bedeli vardır; hangisinin kabul edilebilir olduğu özelliğe bağlıdır.
- **Hangi değerin etkin olduğunu kaydetmek.** Bir deneyde ya da bir sayaçta kullanılan değerin sunucudaki değerle aynı olduğunu varsaymak yanlıştır. Etkin değeri ayrıca kaydeden uygulama, ezilen ve kayıp değerleri sonradan ayırt edebilir.
- **Getirme başarısızlığını sessiz bırakmamak.** Getirme hiç yapılamadıysa uygulama varsayılanla çalışır ve bunu kimse fark etmez. Başarısız getirmenin sayılması, kaybın sayısını dolaylı biçimde görünür kılar.

#### Yaygın Yanılgılar

**Getirilen değer uygulamada geçerlidir.** Değildir. Ölçümde iki değer getirildi ama etkinleşmedi ve kayıp yazıldı. Getirme, değeri yalnız aygıta bırakır.

**Ezilen bir hatadır.** Değildir. Aynı anahtarın son değeri kazandı ve ilk değerin kopyası sıfır kaldı; yapılandırma için istenen davranış budur. Hata, ezilenin farkına varılmadan bir sayaca ya da deneye bağlanmasıdır.

**Varsayılan değer teslim edilmiş sayılır.** Sayılmaz. Hiç getirme olmayan ölçümde iki değer kayıptı, uygulama yine de çalıştı. Çalışması, sunucunun değerinin ulaştığı anlamına gelmez.

**Etkinleştirme her zaman en güncel değeri uygular.** Uygulamaz. Etkinleştirmenin kapsamı, getirmenin ilerisinde kalabilir; ölçümde üçüncü tema değeri getirildi ama etkinleşmedi, uygulama ikinciyle çalıştı.

#### Özet

- Bu dersin teslimi: ezilen. Etkinleştirmeden önce iki kez getirilen aynı anahtarda son değer kazanır ve ilk değerin hiçbir kopyası ulaşmaz.
- Getirme değeri aygıtta bekletir, etkinleştirme onu uygulamanın okuduğu değer yapar; ikisi ayrı adımdır.
- Üç ayrı anahtar bir kez getirilince üçü birebir ulaşır. İki kez getirilen tema için ilk değer ezilen 1, ikincisi birebir olur.
- Getirilip etkinleştirilmeyen değer kayıptır: birebir 1, kayıp 2. Hiç getirilmeyen değer de kayıptır.
- Etkinleştirme noktası ezileni ve kaybı ayırır: üç tema değerinden ikincisi etkinleşince birinci ezilen, üçüncü kayıp olur.
- Uygulama içi varsayılan sunucudan gelmediği için teslim sayılmaz.

#### Sonraki Adım

Bu derste son değer kazandı ve eski değer ezildi; çünkü yapılandırmada yalnız güncel olan işe yarar. Her dış hizmet böyle değildir. Sonraki derste uygulama içi satın alma ve reklam gelir: onaylanana kadar yeniden teslim edilen bir satın alma, bu dersteki sonuca benzemez. Orada neyin ulaştığını ve neyin ulaşmadığını yine ölçeceğiz.

### Uygulama İçi Satın Alma ve Reklam

Source: https://academia.sh/tr/kurslar/android-servisler/uygulama-ici-satin-alma-ve-reklam

Uygulama içi satın almada onaylanmayan satın alma, platform satın almaları her sorguladığında yeniden teslim edilir. Ders onaylanmayan satın almanın yinelenen, onaylananın birebir ulaşmasını ve geri ödenen satın almanın kayıp olmasını ölçer; ardından 14 dersin teslimini tek tabloda toplar.

Önceki derste son değer kazandı ve eski değer ezildi. Satın alma tersini yapar: platform bir satın almayı uygulamaya bildirir ve uygulama onu onaylayana kadar aynı güncellemeyi yeniden verir. Soru kursun sorusudur: **gönderilen bu olay alıcıya kaç kopya olarak ulaştı?** Burada olay bir satın alma güncellemesidir, alıcı uygulamanın satın almayı işleyen kodudur.

```kotlin
// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}
```

#### Satın Alma ve Onaylama

Uygulama içi satın alma (in-app purchase), kullanıcının uygulama içinde uygulama mağazası üzerinden ödeme yapmasıdır. Ödeme sonucu uygulamaya bir güncelleme olarak gelir. **Onaylama** (acknowledgement), uygulamanın "bu satın almayı işledim, yeni bir ürün verdim" demesidir. Onaylanmayan satın alma işlenmemiş sayılır ve uygulama satın almaları her sorguladığında platform onu yeniden döndürür.

Platformda onay, faturalama kitaplığının istemci sınıfına yapılan bir çağrıdır.

```kotlin
// Platform kodu: BillingClient
val params = AcknowledgePurchaseParams.newBuilder()
    .setPurchaseToken(purchase.purchaseToken)
    .build()
grantItem(purchase)
billingClient.acknowledgePurchase(params) { result ->
    if (result.responseCode != BillingClient.BillingResponseCode.OK) retryAcknowledge(purchase)
}
```

Bu parça platform kodudur ve burada çıktı, süre ya da sayı iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir. Modelde onay, bir sıra numarasının kümesine girmesidir; yeniden teslim, onaylanmamış satın almaları listeye yeniden eklemektir; platformun satın almaları sorgulaması modelde bir açılışla temsil edilir.

```kotlin
// Billing.kt — onaylanmayan satin alma her acilista yeniden teslim edilir
fun redeliver(purchases: List<Signal>, acknowledged: Set<Int>, opens: Int): List<Signal> =
    (1..opens).flatMap { open ->
        if (open == 1) purchases else purchases.filter { it.seq !in acknowledged }
    }

fun expire(received: List<Signal>, refunded: Set<Int>): List<Signal> =
    received.filter { it.seq !in refunded }
```

```kotlin
// Probe.kt — gonderilen satin almalar icin uygulamanin isledigi listeyi alici sayar
fun probe(sent: List<Signal>, received: List<Signal>): List<Arrival> =
    sent.mapIndexed { i, s -> Arrival("olay-${i + 1}", s, received) }
```

**GD1.** Model, ilk açılışta her satın almanın bir kez teslim edildiğini varsayar. Platformda teslim, uygulama çalışırken de gelebilir ve açılış sayısı gerçek kullanımda değişir.

#### Onaylanmayan Satın Alma

Üç satın alma yapılır ve uygulama hiçbirini onaylamaz. Uygulama iki kez açılır.

```kotlin
// Main.kt — onaylanmayan satin alma her acilista yeniden teslim edilir
fun main() {
    val coins = Signal("satin.jeton", 1)
    val premium = Signal("satin.premium", 2)
    val adFree = Signal("satin.reklamsiz", 3)
    val sent = listOf(coins, premium, adFree)
    val arrivals = probe(sent, redeliver(sent, emptySet(), 2))
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=satin.jeton | kopya=2 -> yinelenen
olay-2: anahtar=satin.premium | kopya=2 -> yinelenen
olay-3: anahtar=satin.reklamsiz | kopya=2 -> yinelenen
birebir: 0
yinelenen: 3
ezilen: 0
kayip: 0
```

Bu dersin teslimi: yinelenen. Üç satın almanın üçü de iki kopya olarak ulaştı. Önceki derslerde yinelenen, kaybolan bir yanıttan ya da bir döndürmeden doğuyordu; burada yineleme platformun kuralıdır: platform onaylanmamış satın almayı her sorguladığında yeniden döndürür ve bu onay gelene kadar sürer. M30/K05'ten ayrım: orada yinelenen isteği etkisiz kılmak ve sonucu tek yapmak ölçülüyordu; bu kurs sonucu değil kopya sayısını ölçer. Satın almayı tekilleştiren ya da etkisiz işleyen bir alıcı da yinelenen olay almıştır.

#### Onaylama Zinciri Keser

Aynı üç satın alma yapılır, ama uygulama ilk ikisini onaylar.

```kotlin
// Main.kt — onaylanan satin alma bir kez, onaylanmayan her acilista ulasir
fun main() {
    val coins = Signal("satin.jeton", 1)
    val premium = Signal("satin.premium", 2)
    val adFree = Signal("satin.reklamsiz", 3)
    val sent = listOf(coins, premium, adFree)
    val arrivals = probe(sent, redeliver(sent, setOf(1, 2), 2))
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=satin.jeton | kopya=1 -> birebir
olay-2: anahtar=satin.premium | kopya=1 -> birebir
olay-3: anahtar=satin.reklamsiz | kopya=2 -> yinelenen
birebir: 2
yinelenen: 1
ezilen: 0
kayip: 0
```

Onaylanan iki satın alma ilk açılıştan sonra gelmedi ve birebir sayıldı. Onaylanmayan üçüncü satın alma ikinci açılışta yine geldi. Kural nedeni görmez: üçüncünün işlenmediğini mi yoksa onayın kaybolduğunu mu ayırt etmez, yalnızca iki kopyanın ulaştığını görür.

Bu davranış korumadır. Onaylanmayan satın alma, kullanıcının parasını almış ama ürünü verilmemiş bir durumdur; yeniden teslim, uygulamanın ürünü vermesi için ikinci şanstır. Bedeli kopyadır: ürünü veren kod, aynı satın almayı iki kez görmeye hazır olmalıdır.

#### Abonelik Yenilemesi

Abonelik, aynı ürünün dönem dönem yenilenmesidir. Her yenileme yeni bir satın alma güncellemesidir ve kendi sıra numarasını taşır. Yenileme eskinin yerini almaz; çünkü onaylanmamış eski dönem hâlâ yeniden teslim edilir.

```kotlin
// Main.kt — onaylanmayan abonelik donemleri eskiyi ezmez
fun main() {
    val first = Signal("abonelik.pro", 1)
    val second = Signal("abonelik.pro", 2)
    val third = Signal("abonelik.pro", 3)
    val sent = listOf(first, second, third)
    val arrivals = probe(sent, redeliver(sent, setOf(1), 2))
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=abonelik.pro | kopya=1 -> birebir
olay-2: anahtar=abonelik.pro | kopya=2 -> yinelenen
olay-3: anahtar=abonelik.pro | kopya=2 -> yinelenen
birebir: 1
yinelenen: 2
ezilen: 0
kayip: 0
```

İlk dönem onaylandığı için birebir, sonraki iki dönem yinelenen. Aynı anahtarın daha büyük sırası ulaştı ama ezilen oluşmadı, çünkü ezilen için küçük sıranın hiç ulaşmaması gerekir. Önceki derste yapılandırma böyle davranıyordu; abonelikte eski dönem de ulaşmaya devam eder.

#### Sınırlayıcı Ölçüm

Yeniden teslim sonsuz sürmez. Onaylanmayan satın alma bir süre sonra geri ödenir ve ürün verilmeden satın alma iptal olur. Süreyi platform belirler; modelde ve bu derste bir sayı yazmıyoruz. Geri ödenen satın almanın güncellemesi artık gelmez.

```kotlin
// Main.kt — geri odenen satin almanin teslimi gelmez
fun main() {
    val coins = Signal("satin.jeton", 1)
    val premium = Signal("satin.premium", 2)
    val adFree = Signal("satin.reklamsiz", 3)
    val theme = Signal("satin.tema", 4)
    val sent = listOf(coins, premium, adFree, theme)
    val received = expire(redeliver(sent, setOf(1), 2), setOf(2, 3))
    val arrivals = probe(sent, received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
```

```text
olay-1: anahtar=satin.jeton | kopya=1 -> birebir
olay-2: anahtar=satin.premium | kopya=0 -> kayip
olay-3: anahtar=satin.reklamsiz | kopya=0 -> kayip
olay-4: anahtar=satin.tema | kopya=2 -> yinelenen
birebir: 1
yinelenen: 1
ezilen: 0
kayip: 2
```

İkinci ve üçüncü satın alma geri ödendi ve uygulamaya hiç ulaşmadı; yerlerini alan aynı anahtarlı bir kopya olmadığı için ezilen değil, kayıp yazıldı. Onaylanan birebir, süresi dolmamış onaysız dördüncü yinelenen kaldı. Kayıp burada kullanıcı için kayıp değildir: parası iade edilmiştir. Kural bunu ayırt etmez, yalnız kopyanın ulaşmadığını görür.

**GD2.** Model, geri ödemeyi ilk teslimden önce olmuş sayar. Platformda geri ödeme teslimden sonra da gelebilir; o durumda kopya zaten ulaşmıştır ve ürünün geri alınması ayrı bir iştir.

#### Reklam Yerleşimi

Reklam, satın almadan farklı bir gelir yoludur. Uygulama bir yer ayırır ve reklam kitaplığı oraya içeriği yükler. Yükleme sunucudan gelir, başarısız olabilir ve ne zaman biteceği uygulamanın elinde değildir.

```kotlin
// Platform kodu: reklam yerlesimi
val slot = findViewById<FrameLayout>(R.id.reklam_alani)
bannerView.loadAd(request)
slot.addView(bannerView)
```

Bu parça da platform kodudur ve çıktı ya da sayı iddia etmiyoruz. Reklam gösterimini bu derste ölçmüyoruz: gösterimin sayılması reklam ağının tanımına bağlıdır ve modelde karşılığı yoktur. Ölçtüğümüz şey satın alma güncellemesinin kopya sayısıdır.

#### Uygulamanın Yapacakları

- **Satın almayı işledikten sonra onaylamak.** Ürün önce verilir, onay sonra gönderilir. Onayı öne almak, süreç ürünü vermeden ölürse kullanıcıyı parası alınmış ama ürünsüz bırakır; ürünü verip hiç onaylamamak ise kopyanın sürmesine yol açar.
- **Satın alma kimliğini kaydetmek.** Aynı satın almanın ikinci kopyası geldiğinde ürün tekrar verilmemelidir. Kimliği kaydeden uygulama kopyayı etkisiz kılar; ölçüm yine de iki kopya sayar.
- **Abonelik dönemlerini ayrı işlemek.** Yeni dönem eskinin yerini almaz; ikisi de ulaşır.
- **Reklam yüklemesinin başarısızlığına hazırlanmak.** Reklam gelmezse ekran boş kalmamalıdır.

#### Yaygın Yanılgılar

**Onay çağrısı satın almayı bir kez teslim eder.** Eder, ama yalnız çağrı başarılı olursa. Onay kaybolursa satın alma yine yinelenir.

**Geri ödenen satın alma uygulamada bir iz bırakır.** Bırakmaz. Ölçümde iki satın alma hiç ulaşmadı ve kayıp yazıldı.

**Abonelik yenilemesi eski dönemi ezer.** Ezmez. Onaylanmayan iki dönem yinelenen olarak ulaştı.

**Kayıp her zaman bir hatadır.** Değildir. Geri ödenen satın alma kayıptır ve doğru sonuçtur.

#### Özet

- Bu dersin teslimi: yinelenen. Onaylanmayan üç satın alma, platform onları yeniden sorguladığında (modelde ikinci açılış) yeniden teslim edildi: yinelenen 3.
- Onaylanan satın alma bir kez ulaşır. İki onaylı, bir onaysız satın almada birebir 2, yinelenen 1 çıktı.
- Abonelik yenilemesi eski dönemi ezmez. Birebir 1, yinelenen 2.
- Geri ödenen satın alma kayıp olur: birebir 1, yinelenen 1, kayıp 2.
- Satın almayı tekilleştiren alıcı da yinelenen olay alır; kurs sonucu değil kopya sayısını ölçer.
- Reklam yerleşimi platform kodudur ve bu derste ölçülmez.

#### Kurs Kapanışı

Kurs 14 derste aynı soruyu sordu: gönderilen bu olay alıcıya kaç kopya olarak ulaştı? Her dersin teslimi o dersin `## Özet` bölümündeki ilk maddedir; sınırlayıcı ölçüm sütunundaki sayılar kaynak dersin çıktısında görünür.

| Ders | Ölçülen olay | Teslim | Sınırlayıcı ölçüm |
|---|---|---|---|
| Bildirimler | kullanıcı bildirimi | kayıp | aynı kimlikli ikinci kullanıcı bildirimi birincinin yerini alır: ezilen 1 |
| Bulut Mesajlaşma | sunucudan gelen ileti | ezilen | daraltma anahtarı taşımayan iletiler: birebir 4, ezilen 1 |
| Konum ve Haritalar | konum güncellemesi | ezilen | izin geri alınınca sonrakiler gelmez: kayıp 3 |
| Kamera ve Medya | yakalanan fotoğraf | birebir | iki dokunuş iki ayrı sıra: birebir 3 |
| Sensörler | sensör okuması | ezilen | dinleyici kaldırılınca okumalar kesilir: kayıp 2 |
| Derin Bağlantılar ve Kısayollar | derin bağlantı | yinelenen | işaret bellekte kalırsa yinelenen 2 |
| Pencere Öğeleri | pencere öğesi güncellemesi | ezilen | kaldırılan öğeye güncelleme gelmez: kayıp 2 |
| Şifreli Depolama ve Anahtar Deposu | saklanan sır | kayıp | yeniden yazılan sır eskiyi ezer: ezilen 1, kayıp 1 |
| Ağ Güvenliği Yapılandırması | giden ağ isteği | kayıp | yeniden gönderilen istek ilkini ezer: ezilen 1, kayıp 1 |
| Kimlik Doğrulama Akışları | belirteç yenilemesi | yinelenen | tek uçuş ikinci yenilemeyi düşürür: kayıp 1 |
| Uygulama İmzalama | uygulama sürümü | birebir | farklı anahtarlı sürümler: birebir 1, kayıp 3 |
| Analitik ve Çökme Raporlama | analitik olayı | yinelenen | kuyruk dolunca eski olaylar düşer: kayıp 2 |
| Uzaktan Yapılandırma | yapılandırma değeri | ezilen | etkinleşmeyen değer: birebir 1, kayıp 2 |
| Uygulama İçi Satın Alma ve Reklam | satın alma güncellemesi | yinelenen | geri ödenen satın alma: kayıp 2 |

14 dersin 2'si birebir, 4'ü yinelenen, 5'i ezilen, 3'ü kayıp teslimle kapanır. Açılış dersindeki çekirdeğin dağılımı başkadır: sekiz olay 3 birebir, 1 yinelenen, 2 ezilen, 2 kayıp çıktı. İki dağılım aynı şeyi saymaz; çekirdek sekiz olayı, tablo on dört dersi sayar.

Sıradaki kurs M31/K06, Test, Başarım ve Dağıtım konu alanına geçer.

