---
title: 'Android Platformu ve Araçlar'
source: 'https://academia.sh/tr/kurslar/android-platformu'
language: tr
updated: '2026-10-21T05:02:35+00:00'
license: 'CC BY-SA 4.0'
lessons: 15
outcomes:
    - 'Uygulama bileşenlerini ve yaşam döngülerini açıklar'
    - 'Bildirim dosyası ve intent sistemini kullanır'
    - 'Derleme yapılandırmasını ve kaynak sistemini yönetir'
    - 'Platformun bir isteği tam mı, ikameyle mi karşıladığını, isteğin bir koşulu mu beklediğini yoksa karşılıksız mı kaldığını ayırt eder'
---

# Android Platformu ve Araçlar

Bir Android uygulaması platformdan sürekli bir şey ister ve platform her isteği aynı biçimde karşılamaz: bildirim dosyasında adı geçen activity istendiği gibi açılır, adı geçmeyen hiç açılmaz, kesin konum istenen yerde kullanıcı yaklaşık konumu verebilir, aygıtın diline uyan metin yoksa varsayılan metin gelir, koşulu sağlanmamış bir iş o koşulu bekler. Kurs her düzeneği tek bir soruyla ölçer — bu isteği platform nasıl karşılıyor? Mimari katmanlar, activity'ler ve yaşam döngüleri, fragment'lar, servisler, yayın alıcıları, içerik sağlayıcılar, intent'ler, görevler ve geri yığını, bildirim dosyası, kaynak sistemi, derleme sistemi, ürün çeşitleri, geliştirme ortamı ve izin modeli aynı çerçeveden geçirilir.

## Uygulama Bileşenleri

Çekirdekten uygulama çerçevesine Android mimari katmanları, activity'ler ve yaşam döngüleri, fragment'lar, servisler, yayın alıcıları, içerik sağlayıcılar, açık ve örtük intent'ler ile intent süzgeçleri, görevler ve geri yığını.

### Android Mimari Katmanları

Source: https://academia.sh/tr/kurslar/android-platformu/android-mimari-katmanlari

Bir uygulamanın platformdan istediği şeyin tam, ikame, bekleyen ya da karşılıksız karşılandığının ve isteğin Linux çekirdeğinden uygulama çerçevesine uzanan katmanlardan geçişinin ölçümü.

Önceki kurs, Kotlin'de iki değeri eşit yapan şeyin ne olduğunu ölçtü ve `==` ile `===` arasındaki ayrımı dil düzeyinde kurdu. Bu kurs aynı dille platformun kendisine geçer. Bir Android uygulaması platformdan sürekli bir şey ister: bir activity'yi başlatmak, bir izin almak, aygıtın diline uyan metni bulmak, bir işi uygun bir anda koşturmak. Platform her isteğe bir yanıt verir, ama her zaman istenen yanıtı vermez. Bildirim dosyasında adı geçen activity istendiği gibi açılır, adı geçmeyen hiç açılmaz. Kesin konum istenen yerde kullanıcı yaklaşık konumu verebilir. Aygıtın diline uyan metin yoksa varsayılan metin gelir. Koşulu sağlanmamış bir iş o koşulu bekler.

Kursun sorusu bu yüzden tek bir cümledir: **bu isteği platform nasıl karşılıyor?** Dört yanıt vardır ve kurs boyunca adları değişmez. Kategorinin üst adı **karşılama**dır.

- **tam**: platform bir değer verir ve verilen, istenenin kendisidir.
- **ikame**: platform bir değer verir ama verilen, istenenden başkadır.
- **bekleyen**: platform henüz değer vermez; istek bir koşulu bekler.
- **karşılıksız**: platform isteği reddeder; değer gelmez ve beklenecek bir koşul da yoktur.

Önceki kursun `==`'sı burada tek bir iş görür: verilen değerin istenene eşit olup olmadığını söyler, yani tam ile ikameyi ayırır. Eşitliğin ölçütü bu kursta bir kategori değildir; çekirdek iki dizgiyi karşılaştırır ve dizgilerin eşitliği içeriktir.

#### Karşılama Kuralı

Karşılamayı etiket değil kural verir. Aşağıdaki yardımcı dosya kuralı kurar ve kursun her dersinde ilk Kotlin bloğu olarak aynen yinelenir. `Ask` sınıfı üç şey taşır: isteğin etiketi, istenen değer ve platform modelinin döndürdüğü sonuç. Sonuç, mühürlü `Outcome` arayüzünün üç alt tipinden biridir: `Given` bir değer verildiğini, `Waiting` bir koşulun beklendiğini, `Refused` isteğin reddedildiğini söyler.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

`fulfillment` soruyu sırayla sorar. Sonuç `Waiting` ise karşılama bekleyendir. `Refused` ise karşılıksızdır. `Given` ise verilen değer istenene eşitse tamdır, değilse ikamedir. `tally` dört kategoriyi sabit sırayla sayar ve sıfır olan kategoriyi de basar.

Ölçümün bir koşulu vardır: her istek tek bir istenen değer taşır ve sonuç elle yazılmaz. `Ask("...", "X", Outcome.Given("X"))` biçiminde sonucu doğrudan yazan bir satır ölçüm değildir, kuralın kendisini göstermekten öteye gitmez. İstenen değer isteğin kendi sözcüğüyle yazılır, sonuç ise platformun o düzeneği nasıl karşıladığını kuran küçük bir Kotlin fonksiyonundan gelir.

#### Sekiz İstek, Dört Karşılama

Aşağıdaki ölçüm kursun dört konusundan sekiz istek kurar: uygulama bileşenlerinden iki, izin modelinden üç, kaynak sisteminden iki ve platform servislerinden bir. Dört fonksiyon, platformun dört düzeneğini taklit eder. `startExplicit` bildirim dosyasında bildirilen activity'leri açar. `grant` izni önce normal ya da tehlikeli diye ayırır, tehlikeli izinde kullanıcının seçimini bekler. `pickResource` aygıtın diline uyan kaynak dizinini arar, yoksa varsayılan dizine düşer. `schedule` bir işi koşullar sağlanınca verir, sağlanmayan koşulları sıralı toplar.

```kotlin
// Main.kt — sekiz istek, dort karsilama
fun startExplicit(target: String, declared: Set<String>): Outcome =
    if (target in declared) Outcome.Given(target) else Outcome.Refused("bildirimde-yok")

fun grant(permission: String, dangerous: Boolean, choice: String?): Outcome = when {
    !dangerous -> Outcome.Given(permission)
    choice == null -> Outcome.Waiting("kullanici")
    choice == "reddet" -> Outcome.Refused("kullanici-reddetti")
    else -> Outcome.Given(choice)
}

fun pickResource(qualifier: String, available: List<String>): Outcome = when {
    "values-$qualifier" in available -> Outcome.Given("values-$qualifier")
    "values" in available -> Outcome.Given("values")
    else -> Outcome.Refused("kaynak-yok")
}

fun schedule(work: String, needs: Set<String>, met: Set<String>): Outcome {
    val missing = (needs - met).sorted()
    return if (missing.isEmpty()) Outcome.Given(work) else Outcome.Waiting(missing.joinToString(","))
}

fun main() {
    val declared = setOf("MainActivity", "ProfileActivity")
    val folders = listOf("values", "values-de")
    val asks = listOf(
        Ask("acik-baslatma", "ProfileActivity", startExplicit("ProfileActivity", declared)),
        Ask("bildirilmemis", "SettingsActivity", startExplicit("SettingsActivity", declared)),
        Ask("normal-izin", "INTERNET", grant("INTERNET", dangerous = false, choice = null)),
        Ask("tehlikeli-izin", "CAMERA", grant("CAMERA", dangerous = true, choice = null)),
        Ask("kesin-konum", "ACCESS_FINE_LOCATION",
            grant("ACCESS_FINE_LOCATION", dangerous = true, choice = "ACCESS_COARSE_LOCATION")),
        Ask("almanca-metin", "values-de", pickResource("de", folders)),
        Ask("turkce-metin", "values-tr", pickResource("tr", folders)),
        Ask("zamanli-is", "senkron", schedule("senkron", setOf("ag", "sarj"), setOf("sarj"))),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```
acik-baslatma: istenen=ProfileActivity | verilen=ProfileActivity -> tam
bildirilmemis: istenen=SettingsActivity | neden=bildirimde-yok -> karsiliksiz
normal-izin: istenen=INTERNET | verilen=INTERNET -> tam
tehlikeli-izin: istenen=CAMERA | bekledigi=kullanici -> bekleyen
kesin-konum: istenen=ACCESS_FINE_LOCATION | verilen=ACCESS_COARSE_LOCATION -> ikame
almanca-metin: istenen=values-de | verilen=values-de -> tam
turkce-metin: istenen=values-tr | verilen=values -> ikame
zamanli-is: istenen=senkron | bekledigi=ag -> bekleyen
tam: 3
ikame: 2
bekleyen: 2
karsiliksiz: 1
```

Sekiz isteğin üçü tam, ikisi ikame, ikisi bekleyen, biri karşılıksız çıktı. Bu dağılım kursun bağlayıcı sayısıdır ve sonraki derslerde atıfla anılır, değiştirilmez. Her satırın hangi sorudan geçtiğini okumak kolaydır. `acik-baslatma` bildirilen bir activity'yi istedi ve aynısını aldı, tam. `bildirilmemis` bildirilmeyen bir activity'yi istedi ve reddedildi; beklenecek bir koşul olmadığı için karşılıksız. `tehlikeli-izin` kullanıcının henüz seçmediği bir izni istedi ve `kullanici`yı bekliyor, bekleyen. `kesin-konum` kesin konumu istedi, kullanıcı yaklaşık konumu seçti; bir değer verildi ama başka bir değer, ikame. `turkce-metin` Türkçe kaynak dizinini istedi ve aygıt, yalnız `values` ile `values-de` taşıdığı için varsayılan dizini verdi, yine ikame. `zamanli-is` iki koşul ister, ikisinden biri sağlanmıştır; model eksik koşulları sıralı toplar ve ilkini, `ag`'ı basar.

**UB1.** Dört fonksiyon platformun kurallarını sadeleştirir: gerçek bir iznin normal ya da tehlikeli sayılması izin tanımına, kaynak seçiminin sırası aygıt yapılandırmasının tüm niteleyicilerine bağlıdır. Model, platformun bu kurallarını kurar; platformun kendisi ölçülmedi.

#### Mimari Katmanlar

İstek nereye gider? Uygulamanın yazdığı `startActivity` ya da bir izin sorgusu yalnız uygulama kodunda kalmaz, bir katman zincirinden geçer. Android'in mimarisi aşağıdan yukarıya şöyle sıralanır.

- **Linux çekirdeği.** Süreçleri, belleği, aygıt sürücülerini ve güvenlik düzeneğinin temelini yönetir. Her uygulama ayrı bir süreçte, ayrı bir kullanıcı kimliğiyle çalışır; bu ayrım üstteki izin düzeneğinin dayanağıdır. Linux müfredatında anlatılan çekirdek budur.
- **Donanım soyutlama katmanı (HAL).** Kamera, algılayıcılar, ses gibi donanımı üst katmanlara standart arayüzlerle açar. Üstteki kod belirli bir donanımı bilmez, arayüzü bilir.
- **Android çalışma zamanı (ART).** Uygulamanın derlenmiş kodunu çalıştırır. Her uygulama kendi çalışma zamanı örneğinde koşar.
- **Yerel kitaplıklar.** C ve C++ ile yazılmış, grafik, ortam ve veritabanı gibi işleri yapan kitaplıklardır; çerçeve çoğu zaman bunları kullanır.
- **Uygulama çerçevesi.** Uygulamanın doğrudan çağırdığı katmandır: activity'ler, servisler, yayın alıcıları, içerik sağlayıcılar ve kaynaklar bu katmanın sunduğu uygulama bileşenleridir. Bu kursun konuları buradadır.
- **Sistem uygulamaları.** Telefon, mesajlaşma, kişiler, ayarlar gibi aygıtla gelen uygulamalardır. Bir uygulamanın kullanıcıya gösterdiği ilk yüz çoğu zaman bunlardır.

Uygulama isteği çerçeveye bırakır, çerçeve gerekirse yerel kitaplıklara iner, çalışma zamanı kodu koşturur, donanıma değen her şey HAL'den ve çekirdekten geçer. Bu zincir bir karşılamanın neden her zaman uygulamanın elinde olmadığını da açıklar: bir istek katmanlardan birinde reddedilebilir, bekletilebilir ya da başka bir değere çevrilebilir. İstenen izin çerçevede kullanıcıya sorulur, istenen donanım HAL'in sunduğu arayüze bağlıdır, istenen API de aygıttaki çerçevenin sürümüne bağlıdır.

#### Çerçeve API'si ve Aygıtın API Düzeyi

Çerçeve, aygıtın sistem sürümüyle birlikte gelir. Her sürüm bir **API düzeyi** taşır ve bir API'nin hangi düzeyde eklendiği bellidir. Uygulama, eklendiği düzeyden eski bir aygıtta o API'yi çağırırsa çağrı karşılanmaz. Bu sorunun bir çözümü uyumluluk kitaplığıdır: çerçeve API'sinin karşılığını uygulamayla birlikte taşır ve eski aygıtta o karşılık çalışır. Karşılık çoğu zaman isteneni birebir vermez, ona benzer bir davranış verir.

Platformda bu karar uygulama kodunda sürüm denetimiyle verilir. Aşağıdaki parça sadeleştirilmiş bir örnektir. Platform sınıfı içe aldığı için derlenmez ve çıktısı iddia edilmez.

```kotlin
// Platform kodu: aygit API duzeyine gore cerceve cagrisi ya da uyumluluk karsiligi
import android.os.Build

fun applyFeature() {
    if (Build.VERSION.SDK_INT >= 33) {
        // cerceve API'si dogrudan cagrilir
    } else {
        // uyumluluk kitapligindaki karsilik cagrilir
    }
}
```

Model, platformun bu kuralını kurar. Aşağıdaki yardımcı dosya üç girdi alır: API'nin eklendiği düzey, aygıtın düzeyi ve uyumluluk kitaplığının karşılık sunduğu API'ler. Aygıtın düzeyi eşit ya da yüksekse çerçeve API'si istenen biçimde verilir. Düşükse ve karşılık varsa verilen karşılıktır. İkisi de yoksa istek reddedilir. Dosya çıktısızdır ve sonraki ölçülen bloklarla birlikte derlenir.

```kotlin
// Levels.kt — aygit API duzeyine gore cerceve cagrisini kurar
fun callFramework(name: String, since: Int, level: Int, compat: Set<String>): Outcome = when {
    level >= since -> Outcome.Given(name)
    name in compat -> Outcome.Given("$name-uyumluluk")
    else -> Outcome.Refused("api-yok")
}
```

Üç API istenir ve aygıtın düzeyi 31'dir. İlk API 26'da eklenmiştir, ikincisi 33'te eklenmiştir ve uyumluluk karşılığı vardır, üçüncüsü 34'te eklenmiştir ve karşılığı yoktur.

```kotlin
// Main.kt — cerceve API'si ve aygit API duzeyi
fun main() {
    val level = 31
    val compat = setOf("ApiB")
    val asks = listOf(
        Ask("dusuk-duzey-api", "ApiA", callFramework("ApiA", since = 26, level = level, compat = compat)),
        Ask("uyumlu-api", "ApiB", callFramework("ApiB", since = 33, level = level, compat = compat)),
        Ask("eksik-api", "ApiC", callFramework("ApiC", since = 34, level = level, compat = compat)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```
dusuk-duzey-api: istenen=ApiA | verilen=ApiA -> tam
uyumlu-api: istenen=ApiB | verilen=ApiB-uyumluluk -> ikame
eksik-api: istenen=ApiC | neden=api-yok -> karsiliksiz
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

Aynı aygıt, aynı kural, üç farklı karşılama. İlk API aygıtta vardır ve istendiği gibi verildi. İkinci API aygıtta yoktur, ama uyumluluk kitaplığı onun karşılığını taşıdığı için verilen `ApiB-uyumluluk` oldu; değer istenenden başkadır, bu yüzden ikame. Üçüncü API hem aygıtta hem uyumluluk kitaplığında yoktur ve istek karşılıksız kaldı. Bu dersin ölçütü olan karşılamayı, aygıtın düzeyi ve uyumluluk kitaplığının varlığı birlikte belirledi.

**UB2.** `ApiA`, `ApiB` ve `ApiC` örnek adlardır; düzeyleri model girdisidir. Gerçek bir API'nin hangi düzeyde eklendiği bu ölçümden çıkmaz.

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

Karşılama kuralı sonucu ve eşitliği görür, nedeni görmez. Bu cümlenin iki yüzü vardır ve aşağıdaki ölçüm ikisini birlikte kurar. İlk yüzü, aynı karşılamanın iki ayrı nedenden gelmesidir: bildirim dosyasında bildirilen bir activity ve normal bir izin ikisi de tamdır, ama birincisi activity'nin bildirilmiş olmasından, ikincisi iznin normal olmasından gelir. İkinci yüzü, farklı iki karşılamanın aynı eksik API'den çıkabilmesidir: aynı API aynı düzeyde yoktur, birinde uyumluluk karşılığı vardır, ötekinde yoktur.

```kotlin
// Main.kt — kural nedeni gormez
fun openDeclared(name: String, declared: Set<String>): Outcome =
    if (name in declared) Outcome.Given(name) else Outcome.Refused("bildirimde-yok")

fun usePermission(name: String, normal: Set<String>): Outcome =
    if (name in normal) Outcome.Given(name) else Outcome.Waiting("kullanici")

fun main() {
    val asks = listOf(
        Ask("bildirilen-activity", "ProfileActivity", openDeclared("ProfileActivity", setOf("ProfileActivity"))),
        Ask("normal-izin", "INTERNET", usePermission("INTERNET", setOf("INTERNET"))),
        Ask("uyumluluk-var", "ApiD", callFramework("ApiD", since = 34, level = 31, compat = setOf("ApiD"))),
        Ask("uyumluluk-yok", "ApiD", callFramework("ApiD", since = 34, level = 31, compat = emptySet())),
    )
    for (ask in asks) {
        ask.report()
    }
    println(asks.filter { it.fulfillment == "tam" }.map { it.label })
}
```

```
bildirilen-activity: istenen=ProfileActivity | verilen=ProfileActivity -> tam
normal-izin: istenen=INTERNET | verilen=INTERNET -> tam
uyumluluk-var: istenen=ApiD | verilen=ApiD-uyumluluk -> ikame
uyumluluk-yok: istenen=ApiD | neden=api-yok -> karsiliksiz
[bildirilen-activity, normal-izin]
```

Son satır kuralın görüşünü gösterir: iki tam isteği tek listede toplar ve hangisinin hangi nedenden tam olduğunu söylemez. Aynı biçimde `uyumluluk-var` ile `uyumluluk-yok` aynı `ApiD` için aynı düzeyde başarısız olur; aralarındaki tek fark uyumluluk kitaplığının karşılığı taşıyıp taşımamasıdır, ve kural yalnız verilen değerin var olup olmadığını ve istenene eşit olup olmadığını görür. Neden, `neden=` ve `bekledigi=` alanında yazılıdır; kategori onu kullanmaz. Bu sınırı bilmek ölçümü okurken yardımcı olur: iki satırın karşılaması aynıysa nedenlerinin de aynı olduğu sonucu çıkmaz.

**UB3.** Bu ölçüm iki tam, bir ikame ve bir karşılıksız isteği içindir. Aynı nedenden gelen iki bekleyen ya da farklı nedenden gelen iki karşılıksız istek ölçülmedi.

#### Özet

- Bu dersin ölçütü: **karşılama**. Platform her isteğe tam, ikame, bekleyen ya da karşılıksız bir yanıt verir ve kural bunu verilen değerden okur. Bu dersin karşılaması: **tam**; dersin odak istekleri olan bildirilen activity ve normal izin tam karşılandı; uyumluluk karşılığı ikamedir ve odak isteğe girmez.
- Sekiz isteğin dağılımı 3/2/2/1 çıktı: üç tam, iki ikame, iki bekleyen, bir karşılıksız. Kursun sonraki dersleri bu sayıyı atıfla anar.
- Mimari katmanlar aşağıdan yukarıya Linux çekirdeği, donanım soyutlama katmanı, Android çalışma zamanı ile yerel kitaplıklar, uygulama çerçevesi ve sistem uygulamalarıdır.
- Aygıtın API düzeyi bir çerçeve API'sini tam, uyumluluk kitaplığı varsa ikame, o da yoksa karşılıksız bırakır.
- Karşılama kuralı sonucu ve eşitliği görür, nedeni görmez: aynı karşılama iki nedenden, farklı iki karşılama aynı eksik API'den gelebilir.

#### Sonraki Adım

Sekiz isteğin biri karşılıksız kaldı: bildirilmeyen bir activity hiç açılmadı. Sıradaki ders, `Activity'ler`, bu isteği yakından okur. Activity'nin ne olduğunu, bildirim dosyasında nasıl bildirildiğini ve başka bir uygulamanın onu açıp açamayacağını aynı kuralla ölçer.

### Activity'ler

Source: https://academia.sh/tr/kurslar/android-platformu/activityler

Activity'nin ne olduğu, bildirim dosyasında nasıl bildirildiği ve bildirilmemiş ya da dışa kapalı bir activity isteğinin platformca nasıl karşılandığının ölçümü.

Önceki derste sekiz isteğin biri karşılıksız kaldı: bildirim dosyasında adı geçmeyen bir activity hiç açılmadı. Bu ders o satırın arkasındaki düzeneği okur. Activity, kullanıcıyla etkileşen ve genellikle tek bir ekranı sahiplenen uygulama bileşenidir. Bir uygulama birden çok activity taşıyabilir: bir giriş ekranı, bir profil ekranı, bir ayarlar ekranı. Platform bunların hangisinin var olduğunu uygulamanın koduna bakarak değil, bildirim dosyasına bakarak öğrenir.

Dersin ölçütü yine **karşılama**dır: bir activity istendiğinde platform isteği nasıl karşılıyor? Aynı çekirdek kural çalışır. Verilen değer istenene eşitse tam, eşit değilse ikame, platform bir koşulu bekliyorsa bekleyen, reddediyorsa karşılıksızdır.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

#### Bildirim Dosyasındaki Activity

Her activity bildirim dosyasında bir `<activity>` öğesiyle bildirilir. Platform bir activity'yi başlatmadan önce bu bildirime bakar. Bildirilmemişse çağrı bir istisnayla sona erer ve activity açılmaz. Başlatıcı ekranı da aynı dosyadan gelir: `MAIN` eylemini ve `LAUNCHER` kategorisini taşıyan bir intent süzgeci, activity'yi başlatıcıda görünür kılar.

```xml
<application>
    <activity android:name=".MainActivity" android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity android:name=".ProfileActivity" android:exported="false" />
</application>
```

Kodda bir activity'yi başlatmak tek satırdır. Platform çağrıyı bildirim dosyasıyla karşılaştırır.

```kotlin
// Platform kodu: ayni uygulamadaki bir activity'yi acik intent ile baslatir
import android.content.Intent

fun openProfile() {
    startActivity(Intent(this, ProfileActivity::class.java))
}
```

Model, platformun bu kuralını kurar. Aşağıdaki ölçüm bildirilen üç activity ve istenen beş activity ile çalışır. `launchActivity` yalnız tek bir şeye bakar: istenen ad bildirilenler kümesinde var mı. Varsa istenen verilir, yoksa istek reddedilir ve neden `bildirimde-yok` olarak yazılır.

```kotlin
// Main.kt — bildirilen ve bildirilmeyen activity istekleri
fun launchActivity(target: String, declared: Set<String>): Outcome =
    if (target in declared) Outcome.Given(target) else Outcome.Refused("bildirimde-yok")

fun main() {
    val declared = setOf("MainActivity", "ProfileActivity", "DetailActivity")
    val asks = listOf(
        Ask("ana-ekran", "MainActivity", launchActivity("MainActivity", declared)),
        Ask("profil", "ProfileActivity", launchActivity("ProfileActivity", declared)),
        Ask("detay", "DetailActivity", launchActivity("DetailActivity", declared)),
        Ask("ayarlar", "SettingsActivity", launchActivity("SettingsActivity", declared)),
        Ask("yazim-hatasi", "ProfilActivity", launchActivity("ProfilActivity", declared)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
ana-ekran: istenen=MainActivity | verilen=MainActivity -> tam
profil: istenen=ProfileActivity | verilen=ProfileActivity -> tam
detay: istenen=DetailActivity | verilen=DetailActivity -> tam
ayarlar: istenen=SettingsActivity | neden=bildirimde-yok -> karsiliksiz
yazim-hatasi: istenen=ProfilActivity | neden=bildirimde-yok -> karsiliksiz
tam: 3
ikame: 0
bekleyen: 0
karsiliksiz: 2
```

Beş istekten üçü tam, ikisi karşılıksız çıktı. Bildirilen üç activity istendiği gibi verildi. `SettingsActivity` sınıfı kodda var olabilir, ama bildirim dosyasında yazılı olmadığı için platform onu tanımaz. `ProfilActivity` ise bir yazım hatasıdır ve aynı yoldan reddedilir. İkisinin de karşılaması karşılıksızdır; ikame ya da bekleyen çıkmaz, çünkü platform "buna yakın bir activity açayım" ya da "bir koşul sağlansın" demez. Bildirilmemiş bir activity için bekleyecek bir koşul da yoktur.

**UB1.** Model gerçek çağrıdaki istisnayı fırlatmaz; reddi `Refused` olarak döndürür. Platformda başlatma çağrısı bir istisnayla sona erer, modelde aynı olay karşılıksız bir satır olur.

#### Başlatıcı Süzgeci

Kullanıcı uygulama simgesine dokunduğunda hangi activity açılır? Cevabı kod vermez, bildirim dosyasındaki süzgeçler verir. `LAUNCHER` kategorisini taşıyan activity başlatıcının giriş noktasıdır. Bir uygulama geliştiricinin "ana ekran bu olsun" dediği activity ile başlatıcıda gerçekten açılan activity farklı olabilir: süzgeç yanlış activity'ye konmuşsa ya da hiçbirine konmamışsa karşılama değişir.

Aşağıdaki ölçümde istenen her seferinde `MainActivity`'dir; süzgeçlerin yeri değişir. `launcherEntry` kategorisinde `LAUNCHER` bulunan activity'leri ada göre sıralar ve ilkini verir. Hiçbiri yoksa isteği `baslatici-yok` nedeniyle reddeder.

```kotlin
// Main.kt — baslatici sugecinin yerine gore karsilama
fun launcherEntry(filters: Map<String, Set<String>>): Outcome {
    val entries = filters.filter { "LAUNCHER" in it.value }.keys.sorted()
    return if (entries.isEmpty()) Outcome.Refused("baslatici-yok") else Outcome.Given(entries.first())
}

fun main() {
    val onMain = mapOf("MainActivity" to setOf("MAIN", "LAUNCHER"), "ProfileActivity" to emptySet<String>())
    val none = mapOf("MainActivity" to setOf("MAIN"), "ProfileActivity" to emptySet<String>())
    val onProfile = mapOf("MainActivity" to setOf("MAIN"), "ProfileActivity" to setOf("MAIN", "LAUNCHER"))
    val asks = listOf(
        Ask("suzgec-main-uzerinde", "MainActivity", launcherEntry(onMain)),
        Ask("suzgec-yok", "MainActivity", launcherEntry(none)),
        Ask("suzgec-profil-uzerinde", "MainActivity", launcherEntry(onProfile)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
suzgec-main-uzerinde: istenen=MainActivity | verilen=MainActivity -> tam
suzgec-yok: istenen=MainActivity | neden=baslatici-yok -> karsiliksiz
suzgec-profil-uzerinde: istenen=MainActivity | verilen=ProfileActivity -> ikame
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

Aynı istek, süzgecin üç ayrı yerleşiminde üç ayrı karşılama aldı. Süzgeç `MainActivity` üzerindeyken istek tam karşılandı. Süzgeç hiçbir activity'de yokken uygulama başlatıcıda görünmez ve istek karşılıksız kaldı. Süzgeç `ProfileActivity` üzerindeyken başlatıcı bir activity açar, ama istenen değil, başkası; bu ikamedir. Üçüncü satır, geliştiricinin ana ekranı sandığı activity ile kullanıcının gördüğü activity'nin neden ayrışabildiğini gösterir.

**UB2.** Model süzgeçlerin yalnız kategorisine bakar. Gerçek süzgeç eylemi, kategoriyi ve veriyi birlikte eşler; bu eşleme Intent'ler ve Intent Süzgeçleri dersinde ölçülür.

#### Çağırana Dönen Sonuç

Bir activity başka bir activity'nin isteğiyle açılabilir ve işi bitince çağırana bir sonuç döndürebilir. Seçici ekranlar, onay ekranları ve form ekranları bu biçimde çalışır. Çağıran taraf sonucu bir geri çağrıda alır; sonuç iki parçadan oluşur: bir sonuç kodu ve isteğe bağlı bir veri intent'i. Sonuç kodu iki hazır değerden biridir: `RESULT_OK` ya da `RESULT_CANCELED`. Kullanıcı geri tuşuna basarak activity'den çıkarsa platform, activity'nin hiçbir şey ayarlamamış olması hâlinde `RESULT_CANCELED` döndürür.

```kotlin
// Platform kodu: sonuc isteyen activity baslatma
import androidx.activity.result.contract.ActivityResultContracts

val pick = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode == RESULT_OK) {
        showSelection(result.data)
    }
}
```

Çağıranın istediği şey bir seçimdir. Verilen ise kullanıcının ne yaptığına bağlıdır. Aşağıdaki ölçümde istenen her seferinde `tamam`dır. Kullanıcı kaydeder, geri tuşuna basar, activity çöker ya da kullanıcı henüz karar vermemiştir. `resultOf` bu dört durumu dört sonuca çevirir: kaydedilirse `tamam`, geri tuşuyla `iptal`, çökmede ret nedeni `activity-coktu`, karar yoksa `kullanici` beklenir.

```kotlin
// Main.kt — activity sonucunun dort hali
fun resultOf(event: String): Outcome = when (event) {
    "kaydet" -> Outcome.Given("tamam")
    "geri" -> Outcome.Given("iptal")
    "cokme" -> Outcome.Refused("activity-coktu")
    else -> Outcome.Waiting("kullanici")
}

fun main() {
    val events = listOf("kaydet", "geri", "cokme", "dusunuyor")
    val asks = events.map { Ask("sonuc-$it", "tamam", resultOf(it)) }
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
sonuc-kaydet: istenen=tamam | verilen=tamam -> tam
sonuc-geri: istenen=tamam | verilen=iptal -> ikame
sonuc-cokme: istenen=tamam | neden=activity-coktu -> karsiliksiz
sonuc-dusunuyor: istenen=tamam | bekledigi=kullanici -> bekleyen
tam: 1
ikame: 1
bekleyen: 1
karsiliksiz: 1
```

Dört durum dört karşılamayı birer kez üretti. Burada ders için asıl bilgi şudur: `iptal` bir hata değildir. Platform geri tuşuna basan kullanıcıya bir sonuç koduyla yanıt verir ve çağıran kod bunu normal bir yol olarak karşılamak zorundadır. Çağıranın yalnızca `RESULT_OK` dalını yazması, `ikame` satırını ele almayan bir kod demektir. Çöken activity ise çağırana bir sonuç bile döndürmez; çağıranın gördüğü şey "sonuç yok"tur ve bu `karsiliksiz` satırında modellenmiştir. `bekleyen` satırı gerçek bir sonuç değildir; kullanıcının henüz karar vermediğini söyler.

**UB3.** Model sonuç kodunu ve verilen değeri tek bir dizgede birleştirir. Gerçek platformdaki ayrık `resultCode` ve `data` ikilisi, ve sonucun süreç sonlanmasından sonra çağıran activity yeniden oluşturulduğunda teslim edilmesi ölçülmedi.

#### exported Bildirimi Eksikse

Sınırlayıcı ölçümde `android:exported` değeri bir girdi olarak alınır. Gerçek kural bir adım daha içerir: bir activity bir intent süzgeci taşıyorsa `exported` değeri bildirimde açıkça yazılmak zorundadır. Süzgeç varsa ve değer yazılmamışsa, derleme aracı bildirim dosyasını reddeder ve uygulama yüklenemez. Süzgeç yoksa ve değer yazılmamışsa activity dışa kapalı sayılır.

Kuralın mantığı şudur: süzgeç taşıyan bir activity başkalarınca bulunmak için yazılmıştır, ama bu bulunabilirliğin gerçekten istenip istenmediği yazarın açık kararına bırakılır. Aşağıdaki ölçüm istenen şeyi `kurulum` olarak alır. Verilen, bildirimin kuralı geçip geçmediğidir.

```kotlin
// Main.kt — exported bildirimi ve suzgec
fun checkEntry(hasFilter: Boolean, exportedWritten: Boolean?): Outcome =
    if (hasFilter && exportedWritten == null) Outcome.Refused("exported-eksik")
    else Outcome.Given("kurulum")

fun main() {
    val asks = listOf(
        Ask("suzgec-var-exported-yok", "kurulum", checkEntry(true, null)),
        Ask("suzgec-var-exported-true", "kurulum", checkEntry(true, true)),
        Ask("suzgec-yok-exported-yok", "kurulum", checkEntry(false, null)),
        Ask("suzgec-yok-exported-false", "kurulum", checkEntry(false, false)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
suzgec-var-exported-yok: istenen=kurulum | neden=exported-eksik -> karsiliksiz
suzgec-var-exported-true: istenen=kurulum | verilen=kurulum -> tam
suzgec-yok-exported-yok: istenen=kurulum | verilen=kurulum -> tam
suzgec-yok-exported-false: istenen=kurulum | verilen=kurulum -> tam
tam: 3
ikame: 0
bekleyen: 0
karsiliksiz: 1
```

Reddedilen tek durum, süzgeç taşıyıp `exported` değerini yazmayan activity'dir. Bu ret, uygulama çalışırken değil, kurulum ya da derleme anında gelir; bu yüzden hata, kullanıcının karşısına çıkmadan geliştiricinin önüne çıkar. Bu kuralın getirdiği alışkanlık: her süzgeçli activity için `exported` değerini bilerek yazmak. Başlatıcı activity'si `true` olmak zorundadır; çünkü başlatıcı uygulamanın dışındaki bir sistem bileşenidir.

**UB4.** Model, derleme aracının ve platformun bu kontrolü hangi sürümden itibaren yaptığını ayırt etmez. Kuralın uygulandığı en düşük hedef sürüm ve eski sürümlerdeki örtük varsayılan ölçülmedi.

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

Karşılama kuralı nedeni görmez, ama nedenin değiştiği yer vardır: `android:exported`. Bu öznitelik activity'nin başka uygulamalardan çağrılıp çağrılamayacağını söyler. `false` ise yalnız aynı uygulamanın kodu activity'yi açabilir. Bildirilmiş ve dışa kapalı bir activity, çağıranın kim olduğuna göre iki farklı karşılama alır. Yani aynı activity, aynı bildirim, farklı çağıran: tam ile karşılıksız arasındaki fark çağıranın paketinden gelir.

Aşağıdaki ölçüm `openFrom` fonksiyonuyla bunu kurar. Activity bildirimde yoksa ret nedeni `bildirimde-yok`, dışa kapalıysa ve çağıran başka bir uygulamaysa `disa-kapali` olur. Bunların dışında istenen activity verilir.

```kotlin
// Main.kt — disa kapali activity ve cagiran uygulama
fun openFrom(target: String, exported: Map<String, Boolean>, caller: String, owner: String): Outcome {
    val isExported = exported[target] ?: return Outcome.Refused("bildirimde-yok")
    return if (!isExported && caller != owner) Outcome.Refused("disa-kapali") else Outcome.Given(target)
}

fun main() {
    val exported = mapOf("ProfileActivity" to false, "ShareActivity" to true)
    val owner = "app.ana"
    val asks = listOf(
        Ask("ayni-uygulama", "ProfileActivity", openFrom("ProfileActivity", exported, "app.ana", owner)),
        Ask("baska-uygulama", "ProfileActivity", openFrom("ProfileActivity", exported, "app.diger", owner)),
        Ask("disa-acik", "ShareActivity", openFrom("ShareActivity", exported, "app.diger", owner)),
        Ask("bildirilmemis", "SettingsActivity", openFrom("SettingsActivity", exported, "app.diger", owner)),
    )
    for (ask in asks) {
        ask.report()
    }
    println(asks.filter { it.fulfillment == "karsiliksiz" }.map { it.label })
}
```

```text
ayni-uygulama: istenen=ProfileActivity | verilen=ProfileActivity -> tam
baska-uygulama: istenen=ProfileActivity | neden=disa-kapali -> karsiliksiz
disa-acik: istenen=ShareActivity | verilen=ShareActivity -> tam
bildirilmemis: istenen=SettingsActivity | neden=bildirimde-yok -> karsiliksiz
[baska-uygulama, bildirilmemis]
```

`ayni-uygulama` ve `baska-uygulama` aynı activity'yi ister; ilki tam, ikincisi karşılıksızdır ve fark yalnız çağıranın paketidir. `disa-acik` dışa açık bir activity'yi başka uygulamadan ister ve tam karşılanır. Son satır, iki karşılıksız isteği tek listede toplar: ikisinin karşılaması aynı, nedenleri farklıdır. Birincisi bildirimde bulunan ama dışa kapalı bir activity'den, ikincisi bildirimde hiç bulunmayan bir activity'den gelir. Karşılama kategorisi bu ayrımı taşımaz; ayrım `neden=` alanındadır.

**UB5.** Model `exported` öznitelik değerini bir girdi olarak alır. Gerçek platformda varsayılan değer süzgecin bulunup bulunmamasına bağlıdır ve burada ölçülmedi.

#### Özet

- Bu dersin ölçütü: **karşılama**. Activity, kullanıcıyla etkileşen uygulama bileşenidir ve platform onu bildirim dosyasındaki `<activity>` öğesinden tanır. Bu dersin karşılaması: **karşılıksız**; ölçülen istek bildirilmemiş ya da dışa kapalı activity'dir ve platform ona yakın bir activity açmaz.
- Bildirilmeyen bir activity isteği karşılıksız kalır; yazım hatası da aynı yoldan reddedilir. Beş istekte 3 tam ve 2 karşılıksız çıktı.
- Başlatıcı süzgecinin yeri karşılamayı belirler: `MainActivity` üzerinde tam, hiçbirinde karşılıksız, `ProfileActivity` üzerinde ikame.
- `android:exported="false"` olan activity aynı uygulamadan tam, başka uygulamadan karşılıksız karşılanır.
- İki karşılıksız istek farklı nedenlerden gelebilir; kural nedeni görmez, neden ayrı bir alanda tutulur.

#### Sonraki Adım

Bildirilen bir activity açıldığında yaşamı orada bitmez: kullanıcı ekranı döndürür, başka uygulamaya geçer, geri döner. Sıradaki ders, `Activity Yaşam Döngüsü`, bu geçişlerde activity'nin durumunun nasıl korunduğunu ve nasıl kaybolduğunu aynı kuralla ölçer.

### Activity Yaşam Döngüsü

Source: https://academia.sh/tr/kurslar/android-platformu/activity-yasam-dongusu

Activity'nin yaşam döngüsü geri çağrıları, yapılandırma değişikliğinde ve süreç sonlanmasında hangi durumun korunduğu ve durdurulmuş bir activity'ye gelen arayüz isteğinin nasıl karşılandığının ölçümü.

Bildirilen bir activity açıldı; kullanıcı ekranı döndürdü. Activity yok edilip yeniden kurulur, ve yeni activity'nin alanları ilk kurulumdaki değerleri taşır. Bu ders, yeniden kurulumda hangi değerin geri geldiğini ölçer. Ölçüt yine **karşılama**dır, ama burada isteğin kendisi bir değeri geri istemektir: döndürmeden önce `5` olan sayaç, sonra platformdan `5` olarak mı istenir ve `5` olarak mı gelir?

Bu dersin ölçüsü, önceki kurslardaki yaşam döngüsü ölçülerinden ayrıdır. M12/K04'te "ertelendi" ile "bekleyen" ayrı tutuldu; M30/K01'in yaşam döngüsü dersinde soru, ifadenin ne zaman değerlendirildiğiydi. Burada soru başkadır: isteğin ne zaman değerlendirildiği değil, activity'nin o anki yaşam döngüsü durumunda nasıl karşılandığı ölçülür.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

#### Yaşam Döngüsü Geri Çağrıları

Bir activity belli durumlardan geçer ve platform her geçişte bir yaşam döngüsü geri çağrısı çağırır. Kurulum sırası şöyledir.

- `onCreate`: activity kurulur, arayüz hazırlanır. Kaydedilen örnek durumu varsa buraya gelir.
- `onStart`: activity ekranda görünür hale gelir.
- `onResume`: activity ön plandadır ve kullanıcıyla etkileşir.
- `onPause`: activity ön planı bırakır; bir başka activity öne gelmektedir.
- `onStop`: activity artık görünmez.
- `onDestroy`: activity yok edilir.

Ekran döndürmek gibi bir yapılandırma değişikliğinde platform activity'yi önce `onPause`, `onStop` ve `onDestroy` ile yıkar, sonra yeni yapılandırmayla `onCreate`, `onStart` ve `onResume` ile yeniden kurar. Alanlar bu arada yok olur. Yıkılmadan önce çağrılan `onSaveInstanceState` küçük bir paket tutar; yeniden kurulan activity bu paketi `onCreate`'te alır.

```kotlin
// Platform kodu: kaydedilen ornek durumuna yalniz sayac yazilir
import android.os.Bundle

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putInt("sayac", counter)
}
```

Burada `counter` kaydedilir; `draft` gibi başka bir alan yazılmamıştır. Aynı zamanda sistem, bellek azaldığında arka plandaki bir sürecin tamamını sonlandırabilir. Bu süreç sonlanması da aynı pakete dayanır: kayıtlı olmayan alan geri gelmez.

Model, platformun bu kuralını kurar. Yardımcı dosya tek kuralı taşır: anahtar kaydedilen pakette varsa o değer verilir, yoksa alanın varsayılan değeri verilir. Girdi değerleri düz dizgilerdir.

```kotlin
// Restore.kt — kaydedilen ornek durumundan alan geri yukleme
fun restoreField(key: String, saved: Map<String, String>, defaults: Map<String, String>): Outcome =
    Outcome.Given(saved[key] ?: defaults.getValue(key))
```

#### Döndürmede Üç Alan

Döndürmeden önce üç alan bir değer taşır: `sayac` 5, `ad` Ada, `taslak` merhaba. `onSaveInstanceState` yalnız ilk ikisini yazar. İstenen her zaman döndürmeden önceki değerdir.

```kotlin
// Main.kt — yapilandirma degisikliginde alanlarin geri gelisi
fun main() {
    val before = mapOf("sayac" to "5", "ad" to "Ada", "taslak" to "merhaba")
    val saved = mapOf("sayac" to "5", "ad" to "Ada")
    val defaults = mapOf("sayac" to "0", "ad" to "bos", "taslak" to "bos")
    val asks = before.map { (key, value) -> Ask(key, value, restoreField(key, saved, defaults)) }
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
sayac: istenen=5 | verilen=5 -> tam
ad: istenen=Ada | verilen=Ada -> tam
taslak: istenen=merhaba | verilen=bos -> ikame
tam: 2
ikame: 1
bekleyen: 0
karsiliksiz: 0
```

Kaydedilen iki alan istendiği gibi geri geldi: tam. Kaydedilmeyen `taslak` varsayılan değerle geldi: bir değer verildi, ama `merhaba` değil `bos`; ikame. Kullanıcı ekran döndürmeden önce yazdığı metnin gittiğini görür, ve kod hata vermez. Bu yüzden ikame, karşılıksızdan daha sessiz bir sorun üretir. Karşılıksız bir istek kodu durdurur; ikame akar ve yanlış değeri taşır.

**UB1.** Model tek bir varsayılan değer alır. Gerçek alanın başlangıç değeri `onCreate`'teki kuruluma bağlıdır ve bir `ViewModel` gibi durumu yapılandırma değişikliğinden sağ çıkaran düzenekler bu derste ölçülmedi.

#### Üç Olay, Üç Karşılama

Aynı pakete farklı olaylardan dönülür. Döndürme ve süreç sonlanması paketi korur. Kullanıcı activity'yi kendisi kapatırsa, örneğin geri düğmesiyle, platform durumu atar: kaydedilen paket de bırakılır. Aşağıdaki model olayı da girdi alır.

```kotlin
// Main.kt — olaya gore durumun korunmasi
fun restoreAfter(event: String, key: String, saved: Map<String, String>, defaults: Map<String, String>): Outcome =
    if (event == "kullanici-kapatti") Outcome.Refused("durum-atildi") else restoreField(key, saved, defaults)

fun main() {
    val saved = mapOf("sayac" to "5")
    val defaults = mapOf("sayac" to "0", "taslak" to "bos")
    val asks = listOf(
        Ask("dondurme-sayac", "5", restoreAfter("dondurme", "sayac", saved, defaults)),
        Ask("sonlanma-taslak", "merhaba", restoreAfter("surec-sonlanmasi", "taslak", saved, defaults)),
        Ask("kapatma-sayac", "5", restoreAfter("kullanici-kapatti", "sayac", saved, defaults)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
dondurme-sayac: istenen=5 | verilen=5 -> tam
sonlanma-taslak: istenen=merhaba | verilen=bos -> ikame
kapatma-sayac: istenen=5 | neden=durum-atildi -> karsiliksiz
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

Döndürmede kaydedilen alan tam geri geldi. Süreç sonlanmasında kaydedilmeyen alan ikame ile döndü. Kullanıcı activity'yi kapattığında ise durum hiç geri gelmedi: karşılıksız. Kapatma bir hata değildir, kullanıcının isteğidir; platform kapatılmış bir activity'den kalan durumu sonraki açılışa taşımaz.

**UB2.** Model üç olayı yalnız bu adlarla ayırır. Sistemin activity'yi hangi koşulda sonlandırdığı ve paketin boyut sınırı ölçülmedi.

#### Geri Çağrı Sırası

Yaşam döngüsü geri çağrılarının sırası, activity'ye hangi olayın geldiğine göre değişir. Sıra beklenenden saptığında en sık görülen hata, bir geri çağrıda yapılması gereken işin hiç çalışmamasıdır. Bu bölüm dört olayı sırasıyla ölçer. İstenen, kurulum sırası bölümünde anlatılan tam zincirdir; verilen ise olaya göre platformun gerçekten çağırdığı geri çağrılardır.

Bir uygulama yapılandırma değişikliğini kendisi karşılamayı seçebilir. Bu durumda activity yıkılmaz; yalnız `onConfigurationChanged` çağrılır. Seçim bildirim dosyasında `configChanges` özniteliğiyle yapılır.

```xml
<activity android:name=".EditorActivity"
          android:configChanges="orientation|screenSize" />
```

Model dört olayı kurar. `ac` ilk açılıştır. `dondur` varsayılan yapılandırma değişikliğidir. `dondur-kendi-karsilar` aynı olayın `configChanges` bildirilmiş hâlidir. `surec-sonlandi` ise sistemin arka plandaki süreci kapatmasıdır; bu durumda `onDestroy` çağrısının gelmesi garanti edilmez.

```kotlin
// Main.kt — olaya gore geri cagri zinciri
fun callbacksFor(event: String): Outcome = when (event) {
    "ac" -> Outcome.Given("onCreate>onStart>onResume")
    "dondur" -> Outcome.Given("onPause>onStop>onDestroy>onCreate>onStart>onResume")
    "dondur-kendi-karsilar" -> Outcome.Given("onConfigurationChanged")
    else -> Outcome.Refused("onDestroy-garanti-degil")
}

fun main() {
    val asks = listOf(
        Ask("ac", "onCreate>onStart>onResume", callbacksFor("ac")),
        Ask("dondur", "onPause>onStop>onDestroy>onCreate>onStart>onResume", callbacksFor("dondur")),
        Ask("dondur-kendi-karsilar", "onPause>onStop>onDestroy>onCreate>onStart>onResume", callbacksFor("dondur-kendi-karsilar")),
        Ask("surec-sonlandi", "onDestroy", callbacksFor("surec-sonlandi")),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
ac: istenen=onCreate>onStart>onResume | verilen=onCreate>onStart>onResume -> tam
dondur: istenen=onPause>onStop>onDestroy>onCreate>onStart>onResume | verilen=onPause>onStop>onDestroy>onCreate>onStart>onResume -> tam
dondur-kendi-karsilar: istenen=onPause>onStop>onDestroy>onCreate>onStart>onResume | verilen=onConfigurationChanged -> ikame
surec-sonlandi: istenen=onDestroy | neden=onDestroy-garanti-degil -> karsiliksiz
tam: 2
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

İlk iki olay istenen zinciri aynen verdi. `dondur-kendi-karsilar` satırı ikame çıktı: uygulama yapılandırma değişikliğini üstlendiği için activity yıkılmadı, bu yüzden alanlar yerinde kaldı. Burada ikame zararsızdır, hatta istenen bir sonuçtur; ama `onCreate` içinde yapılan bir kurulum ya da `onSaveInstanceState` içinde yapılan bir kayıt, bu yolda hiç çalışmaz. `configChanges` bildiren her activity, yapılandırmaya duyarlı kaynakları kendisi yeniden yüklemekle yükümlüdür. Dördüncü satır karşılıksızdır: `onDestroy` temizliğine güvenen bir kod, süreç bir anda sonlandığında bu temizliği yapamaz. Kalıcı olması gereken veriler `onStop` ya da daha önce kaydedilmelidir.

**UB3.** Model `configChanges` değerlerini tek bir mantıksal bayrağa indirger. Gerçek öznitelik her yapılandırma türü için ayrı ayrı belirtilir; bildirilmeyen bir tür yine de activity'yi yıkar. Hangi türün hangi koşulda yıkıma yol açtığı ölçülmedi.

#### Yarı Saydam Üst Activity

`onStop` geri çağrısı, activity'nin artık hiç görünmediği anlamına gelir. Üstüne yeni bir activity açıldığında bu her zaman doğru değildir: yeni activity ekranı tamamen kapatmıyorsa, örneğin bir iletişim kutusu biçiminde ya da yarı saydam bir tema ile açılmışsa, alttaki activity görünür kalır. Bu durumda alttaki activity yalnız `onPause` alır; `onStop` çağrılmaz.

```kotlin
// Platform kodu: yari saydam temali activity bildirimi
// <activity android:name=".ConfirmActivity"
//           android:theme="@style/ConfirmDialogTheme" />
```

Aşağıdaki ölçüm üst activity'nin örtme biçimine göre alttaki activity'ye gelen geri çağrıları verir. İstenen `onPause>onStop`'tur.

```kotlin
// Main.kt — ust activity ve alttaki activity'nin geri cagrilari
fun belowCallbacks(coverage: String): Outcome = when (coverage) {
    "tam-ekran" -> Outcome.Given("onPause>onStop")
    "iletisim-kutusu" -> Outcome.Given("onPause")
    "ayni-gorev-ayni-ekran" -> Outcome.Waiting("ust-activity-kapanmasi")
    else -> Outcome.Refused("bilinmeyen-ortme")
}

fun main() {
    val asks = listOf("tam-ekran", "iletisim-kutusu", "ayni-gorev-ayni-ekran", "yok").map {
        Ask("ust-$it", "onPause>onStop", belowCallbacks(it))
    }
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
ust-tam-ekran: istenen=onPause>onStop | verilen=onPause>onStop -> tam
ust-iletisim-kutusu: istenen=onPause>onStop | verilen=onPause -> ikame
ust-ayni-gorev-ayni-ekran: istenen=onPause>onStop | bekledigi=ust-activity-kapanmasi -> bekleyen
ust-yok: istenen=onPause>onStop | neden=bilinmeyen-ortme -> karsiliksiz
tam: 1
ikame: 1
bekleyen: 1
karsiliksiz: 1
```

Tam ekran bir activity açıldığında alttaki activity durduruldu: tam. İletişim kutusu biçiminde bir activity açıldığında alttaki activity yalnız `onPause` aldı, durdurulmadı: ikame. Alttaki activity hâlâ ekranda görünür, ve `onStop` içinde bırakılacak kaynakları bırakmaz. Bu fark, kamera ya da konum güncellemesi gibi kaynakları `onStop`'ta kapatan kodun neden kimi zaman çalışmadığını açıklar: iletişim kutusu açıkken `onStop` hiç gelmez. Üçüncü satır bir başka kullanımı gösterir: üst activity kapanana kadar beklenen bir durum bekleyen olarak görünür.

**UB4.** Model örtme biçimini dört adla ayırır. Çok pencereli kullanımda ve resim içinde resim kipinde alttaki activity'nin görünürlüğü ve hangi geri çağrıların geldiği ölçülmedi.

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

Yaşam döngüsünün ikinci etkisi, bir isteğin hangi durumda yapıldığıdır. Arayüzü güncellemek isteyen kod, activity'nin durumuna göre farklı bir karşılama alır. Ön planda olan activity güncellemeyi hemen uygular. Durdurulmuş activity henüz görünür değildir; güncelleme, activity yeniden başlatılana kadar bekler. Yok edilmiş bir activity'ye güncelleme ise hiçbir yere gitmez.

```kotlin
// Main.kt — yasam dongusu durumuna gore arayuz guncelleme
fun updateUi(state: String): Outcome = when (state) {
    "resumed" -> Outcome.Given("guncellendi")
    "stopped" -> Outcome.Waiting("baslatilmis-durum")
    else -> Outcome.Refused("activity-yok")
}

fun main() {
    val asks = listOf(
        Ask("on-plan", "guncellendi", updateUi("resumed")),
        Ask("durdurulmus", "guncellendi", updateUi("stopped")),
        Ask("yok-edilmis", "guncellendi", updateUi("destroyed")),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(asks.filter { it.fulfillment == "bekleyen" }.map { it.label })
}
```

```text
on-plan: istenen=guncellendi | verilen=guncellendi -> tam
durdurulmus: istenen=guncellendi | bekledigi=baslatilmis-durum -> bekleyen
yok-edilmis: istenen=guncellendi | neden=activity-yok -> karsiliksiz
tam: 1
ikame: 0
bekleyen: 1
karsiliksiz: 1
[durdurulmus]
```

Üç durumda aynı güncelleme isteği üç ayrı karşılama aldı. Bekleyen satırı önemlidir: `durdurulmus` reddedilmedi, ertelenmedi de; belirli bir koşulu, `baslatilmis-durum`u bekliyor ve koşul gelince uygulanacak. Bu, M12/K04'teki ayrımın activity karşılığıdır: bekleyen, koşulu adı belli olan bir istektir.

**UB5.** Model üç durumu ayırır; `started` ve `paused` ayrı bir satır almadı. Bu iki durumda görünürlüğün tam olarak ne olduğu platform sürümüne ve çok pencereli kullanıma göre değişir.

#### Özet

- Bu dersin ölçütü: **karşılama**. İstek, activity'nin yaşam döngüsü durumuna göre karşılanır. Bu dersin karşılaması: **ikame**; kaydedilmeyen alan hata vermeden varsayılan değerle döner.
- Yapılandırma değişikliğinde kaydedilen alanlar tam, kaydedilmeyen alan varsayılan değerle ikame geri geldi: 2 tam ve 1 ikame.
- Kullanıcı activity'yi kapattığında durum atılır; aynı istek karşılıksız kalır.
- Durdurulmuş activity'ye gelen arayüz isteği bekleyen kalır ve koşulunu adıyla taşır: `baslatilmis-durum`.
- İkame sessizdir, karşılıksız sesli: kod hata vermeden yanlış değerle devam eder.

#### Sonraki Adım

Activity'nin durumu `onSaveInstanceState` ile korunur; ekranın bölümleri ise ayrı yaşam döngüleri taşır. Sıradaki ders, `Fragment'lar`, bir activity'nin içindeki fragment'ların nasıl eklendiğini ve ev sahibi activity durmuşken fragment işleminin nasıl karşılandığını ölçer.

### Fragment'lar

Source: https://academia.sh/tr/kurslar/android-platformu/fragmentlar

Fragment'ın bir activity içindeki yeri, FragmentManager işlemleri ve ev sahibi activity durmuşken ya da durumu kaydedilmişken fragment işleminin nasıl karşılandığının ölçümü.

Activity'nin kaydettiği paket küçüktür ve ekranın tamamı tek bir activity'ye yüklenirse her değişiklik bütün ekranı etkiler. Fragment bu yükü böler. Fragment, bir activity'nin içinde yaşayan, kendi arayüzü ve kendi yaşam döngüsü olan yeniden kullanılabilir bir arayüz bölümüdür. Bir liste ekranı ile ayrıntı ekranı iki fragment olabilir; telefonda aynı activity'de sırayla, büyük ekranda yan yana gösterilir.

Bir fragment'ın ömrü ev sahibi activity'ye bağlıdır. Dersin sorusu bu yüzden bir öncekiyle akrabadır: fragment işlemi istendiğinde, platform bu isteği ev sahibinin durumuna göre nasıl **karşılıyor**?

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

#### FragmentManager İşlemleri

Fragment'lar `FragmentManager` ile yönetilir. Bir işlem `beginTransaction` ile başlar, bir ya da birkaç adımdan oluşur ve `commit` ile bitirilir. En sık adımlar `add` ve `replace`'dir: ilki bir kapsayıcıya fragment ekler, ikincisi kapsayıcıdaki fragment'ı başkasıyla değiştirir. İşlem geri yığınına `addToBackStack` ile eklenirse geri düğmesi onu geri alır.

```kotlin
// Platform kodu: kapsayiciya fragment ekler ve geri yiginina yazar
supportFragmentManager.beginTransaction()
    .replace(R.id.container, DetailFragment())
    .addToBackStack(null)
    .commit()
```

Model, platformun bu kuralını kurar. `commit` işlemi hemen uygulamaz, ev sahibinin durumuna bakar. Ev sahibi başlatılmışsa işlem uygulanır. Ev sahibi durmuşsa platform işlemi sıraya alır ve ev sahibi yeniden başlatılana kadar bekletir. Model bu sırayı iki girdiyle kurar: işlemin adı ve ev sahibinin durumu.

```kotlin
// Main.kt — ev sahibinin durumuna gore fragment islemi
fun commit(op: String, hostState: String): Outcome =
    if (hostState == "stopped") Outcome.Waiting("ev-sahibi-baslatilmis") else Outcome.Given(op)

fun main() {
    val add = "add:ListFragment"
    val replace = "replace:DetailFragment"
    val asks = listOf(
        Ask("ekle-on-plan", add, commit(add, "resumed")),
        Ask("degistir-on-plan", replace, commit(replace, "resumed")),
        Ask("ekle-duran", add, commit(add, "stopped")),
        Ask("degistir-duran", replace, commit(replace, "stopped")),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
ekle-on-plan: istenen=add:ListFragment | verilen=add:ListFragment -> tam
degistir-on-plan: istenen=replace:DetailFragment | verilen=replace:DetailFragment -> tam
ekle-duran: istenen=add:ListFragment | bekledigi=ev-sahibi-baslatilmis -> bekleyen
degistir-duran: istenen=replace:DetailFragment | bekledigi=ev-sahibi-baslatilmis -> bekleyen
tam: 2
ikame: 0
bekleyen: 2
karsiliksiz: 0
```

Ön planda iki işlem de istendiği gibi uygulandı: tam. Ev sahibi durmuşken ikisi de bekledi, ve ikisinin de beklediği koşul aynıdır: `ev-sahibi-baslatilmis`. Beklemek reddetmek değildir; işlem kaybolmaz, ev sahibi yeniden başlatıldığında uygulanır. Dersin karşılaması bu yüzden **bekleyen**dir: fragment işleminin tipik sonucu, koşulunu adıyla taşıyan bir beklemedir.

**UB1.** Model ev sahibi durumunu iki değere indirir. Gerçek platform `created`, `started` ve `resumed` arasında işlem kuyruğunu ayrı ayrı yönetir; bu ayrım ölçülmedi.

#### Geri Yığını ve Geri Düğmesi

`replace` kapsayıcıdaki eski fragment'ı atar. Geri yığınına eklenmemiş bir `replace` işleminde geri düğmesi eski fragment'a dönmez: activity'nin kendisinden çıkar. Kullanıcının beklediği, bir önceki ekrandır.

```kotlin
// Main.kt — geri yigini ile geri dugmesi
fun goBack(previous: String, addedToBackStack: Boolean): Outcome =
    if (addedToBackStack) Outcome.Given(previous) else Outcome.Given("activity-cikisi")

fun main() {
    val asks = listOf(
        Ask("yigitli-geri", "ListFragment", goBack("ListFragment", addedToBackStack = true)),
        Ask("yigitsiz-geri", "ListFragment", goBack("ListFragment", addedToBackStack = false)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
yigitli-geri: istenen=ListFragment | verilen=ListFragment -> tam
yigitsiz-geri: istenen=ListFragment | verilen=activity-cikisi -> ikame
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 0
```

Geri yığınına eklenen işlem `ListFragment`'ı geri getirdi: tam. Eklenmeyen işlemde de bir değer geldi, ama istenen değil: `activity-cikisi`. Geri düğmesi bir şey yaptı, kullanıcının istediğini yapmadı; bu ikamedir. Geri yığınının ayrıntısı, activity'ler arası geri yığınıyla birlikte, bu kursun son dersinde yeniden ele alınır.

**UB2.** Model tek bir önceki fragment'ı tutar. Gerçek geri yığını bir liste taşır ve kaç işlemin birden geri alınacağı `popBackStack` çağrısına bağlıdır.

#### Fragment ve Görünümünün Ayrı Ömürleri

Bir fragment'ın iki ömrü vardır ve bunlar aynı anda bitmez. Fragment'ın kendisi, yani nesne, geri yığınında beklerken yaşamaya devam eder. Fragment'ın görünümü ise geri yığınına alındığı anda `onDestroyView` ile yok edilir ve kullanıcı geri dönünce `onCreateView` ile yeniden kurulur. Bu yüzden görünüme tuttuğunuz her referans, fragment geri yığınındayken bayatlar: nesne vardır, ama gösterdiği görünüm artık yoktur.

Bu fark, fragment'ta arayüze erişen kodun hangi geri çağrılar arasında güvenli olduğunu belirler. Görünüme erişim `onCreateView` ile `onDestroyView` arasındadır. Fragment'a bağlı bir gözlemci, görünümün ömrüne bağlanmalıdır.

```kotlin
// Platform kodu: gozlemciyi fragment degil gorunum omruna baglar
import androidx.lifecycle.Observer

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    viewModel.items.observe(viewLifecycleOwner, Observer { items ->
        showItems(items)
    })
}
```

Ölçümde istenen her zaman "arayüzü güncelle"dir. Fragment henüz görünüm kurmamışsa istek `onCreateView`i bekler. Görünüm kuruluysa tam karşılanır. Geri yığınında beklerken görünüm yoktur; istek karşılıksız kalır ve neden `gorunum-yok` olur.

```kotlin
// Main.kt — fragment gorunumunun omru
fun updateView(stage: String): Outcome = when (stage) {
    "eklendi" -> Outcome.Waiting("onCreateView")
    "gorunum-kurulu" -> Outcome.Given("guncellendi")
    "geri-yiginda" -> Outcome.Refused("gorunum-yok")
    else -> Outcome.Refused("fragment-yok")
}

fun main() {
    val asks = listOf("eklendi", "gorunum-kurulu", "geri-yiginda", "cikarildi").map {
        Ask("asama-$it", "guncellendi", updateView(it))
    }
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
asama-eklendi: istenen=guncellendi | bekledigi=onCreateView -> bekleyen
asama-gorunum-kurulu: istenen=guncellendi | verilen=guncellendi -> tam
asama-geri-yiginda: istenen=guncellendi | neden=gorunum-yok -> karsiliksiz
asama-cikarildi: istenen=guncellendi | neden=fragment-yok -> karsiliksiz
tam: 1
ikame: 0
bekleyen: 1
karsiliksiz: 2
```

İki karşılıksız satır aynı sonuca farklı yollardan varır. `fragment-yok`ta nesne de yoktur; kodun elinde bir referans bile olmaması beklenir. `gorunum-yok`ta ise nesne yaşar ve referans geçerli görünür, ama gösterdiği görünüm yoktur. İkincisi daha tehlikelidir, çünkü hata ancak çalışma anında ortaya çıkar. Çözüm, görünüm referansını `onDestroyView` içinde sıfırlamak ve gözlemcileri görünümün yaşam döngüsü sahibine bağlamaktır.

**UB3.** Model dört aşamayı ad olarak ayırır. Aşamalar arasındaki geçişlerin sırası, `onViewStateRestored` gibi ara geri çağrılar ve görünüm referansının nasıl sıfırlandığı ölçülmedi.

#### Argümanlar ve Yeniden Oluşturma

Platform bir fragment'ı yeniden oluşturabilir: yapılandırma değişikliğinde ve süreç sonlanmasından sonra. Yeniden oluştururken fragment'ın yapıcısına parametre vermez. Varsayılan fabrika parametresiz yapıcıyı çağırır ve ardından fragment'a, oluşturulurken verilmiş olan `arguments` paketini bağlar. Yapıcı parametresi olarak verilen bir değer bu paketin içinde değilse, yeniden oluşturmada kaybolur.

```kotlin
// Platform kodu: degeri yapici yerine arguments paketiyle tasir
class ListFragment : Fragment() {
    companion object {
        fun newInstance(category: String) = ListFragment().apply {
            arguments = bundleOf("category" to category)
        }
    }
}
```

Ölçümde istenen kategori `kitap`tır. Değer üç ayrı yolla fragment'a verilmiştir: paketle, yapıcı parametresiyle ve parametresiz yapıcısı olmayan bir sınıf üzerinden.

```kotlin
// Main.kt — fragment yeniden olusturulurken degerin yolu
fun recreate(way: String): Outcome = when (way) {
    "arguments" -> Outcome.Given("kitap")
    "yapici-parametresi" -> Outcome.Given("tum")
    else -> Outcome.Refused("bos-yapici-yok")
}

fun main() {
    val asks = listOf("arguments", "yapici-parametresi", "yapici-yalniz-parametreli").map {
        Ask("yol-$it", "kitap", recreate(it))
    }
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
yol-arguments: istenen=kitap | verilen=kitap -> tam
yol-yapici-parametresi: istenen=kitap | verilen=tum -> ikame
yol-yapici-yalniz-parametreli: istenen=kitap | neden=bos-yapici-yok -> karsiliksiz
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

Paketle verilen değer yeniden oluşturmadan sağ çıktı: tam. Yapıcı parametresiyle verilen değer ilk oluşturmada çalışır, ama yeniden oluşturmada varsayılana düşer; `tum` bir değerdir, istenen değil: ikame. Üçüncü yolda yeniden oluşturma hiç başlamaz, çünkü platform parametresiz bir yapıcı bulamaz. İkame burada en sinsi sonuçtur: uygulama çökmez, yalnız yanlış kategoriyi gösterir.

**UB4.** Model değerin kaybını varsayılana düşme olarak sabitler. Gerçek davranış yapıcının içinde değere ne yapıldığına bağlıdır, ve özel bir fragment fabrikası kullanıldığında parametreli yapıcı da çalışabilir; bu ölçülmedi.

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

Ev sahibi durmuşken işlem bekledi. Peki ev sahibi durumunu çoktan kaydetmişse? `onSaveInstanceState` çağrıldıktan sonra platform durumu paketlenmiş sayar; paketlenmiş durumun üstüne yeni bir işlem eklemek, süreç sonlanıp activity yeniden kurulduğunda işlemin kayıp olması demektir. Platform bu yüzden `commit`'i bu aşamada reddeder. `commitAllowingStateLoss` ise kaybı göze alarak işlemi uygular, ama işlemin durumu paketlenmez.

Aynı işlem bu yüzden üç farklı karşılama alabilir.

```kotlin
// Main.kt — ayni islem, durum kaydedildikten sonra uc karsilama
fun commitAfterSave(op: String, stateSaved: Boolean, allowLoss: Boolean): Outcome = when {
    !stateSaved -> Outcome.Given(op)
    allowLoss -> Outcome.Given("$op-durumsuz")
    else -> Outcome.Refused("durum-kaydedildi")
}

fun main() {
    val op = "add:ListFragment"
    val asks = listOf(
        Ask("kaydedilmemis", op, commitAfterSave(op, stateSaved = false, allowLoss = false)),
        Ask("kaydedilmis-commit", op, commitAfterSave(op, stateSaved = true, allowLoss = false)),
        Ask("kaydedilmis-kayipli", op, commitAfterSave(op, stateSaved = true, allowLoss = true)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
kaydedilmemis: istenen=add:ListFragment | verilen=add:ListFragment -> tam
kaydedilmis-commit: istenen=add:ListFragment | neden=durum-kaydedildi -> karsiliksiz
kaydedilmis-kayipli: istenen=add:ListFragment | verilen=add:ListFragment-durumsuz -> ikame
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

Durum kaydedilmemişken işlem tam uygulandı. Kaydedildikten sonra `commit` karşılıksız kaldı; neden `durum-kaydedildi`. `commitAllowingStateLoss` ise bir değer verdi, ama işlemin kendisi değil, durumsuz hali: ikame. Üç satır üç farklı karşılamadır ve işlem aynıdır.

Bu satırlar bekleyenle karşılıksız arasındaki sınırı da çizer. Ev sahibi durmuşken platform işlemin sonra uygulanabileceğini bilir ve bekletir. Durum kaydedildikten sonra işlemin bekleyeceği bir koşul yoktur: koşul geçmiştir, ve işlem artık güvenle uygulanamaz.

**UB5.** Model gerçek platformdaki istisnayı fırlatmaz; reddi `Refused` olarak döndürür. Platformda `commit` bu durumda bir istisnayla sona erer.

#### Özet

- Bu dersin ölçütü: **karşılama**. Fragment işlemi, ev sahibi activity'nin durumuna göre karşılanır. Bu dersin karşılaması: **bekleyen**.
- Ev sahibi ön planda iken işlemler tam, durmuşken bekleyen karşılandı: 2 tam ve 2 bekleyen.
- Geri yığınına eklenmeyen işlemde geri düğmesi activity'den çıkar; istenen fragment yerine başka bir değer gelir: ikame.
- Durum kaydedildikten sonra `commit` karşılıksız, `commitAllowingStateLoss` ikame kalır; aynı işlem üç karşılama alır.
- Bekleyen, koşulu gelecekte sağlanabilen istektir; karşılıksız, koşulun geçtiği istektir.

#### Sonraki Adım

Fragment'lar arayüz bölümleridir; arka planda çalışan işin arayüzü yoktur. Sıradaki ders, `Servisler`, arayüzsüz uygulama bileşenini ve platformun arka plandan başlatılan bir servise koyduğu kuralı ölçer.

### Servisler

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

Başlatılan, bağlı ve ön plan servisinin farkı, servisin iş parçacığı olmadığı ve arka plandan servis başlatma isteğinin platformca nasıl karşılandığının ölçümü.

Fragment'ın arayüzü vardı; ev sahibi durmuşken işlemi bekledi. Servisin arayüzü yoktur. Servis, kullanıcı ekranda başka bir şeye bakarken arka planda iş yürüten uygulama bileşenidir: müzik çalmak, bir dosyayı yüklemek, bir veriyi eşitlemek. Platform bu işleri kullanıcının görmediği bir yerde koşturduğu için, hangi servise ne zaman izin vereceği konusunda da en sıkı kurallara sahip uygulama bileşenidir.

Dersin sorusu aynıdır: servis istendiğinde platform isteği nasıl **karşılıyor**? Bu derste ikame, bekleyen ve karşılıksız aynı konunun içinde belirir.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

#### Üç Servis Türü

Servisler kullanım biçimine göre üçe ayrılır.

- **Başlatılan servis**: bir uygulama bileşeni `startService` ile başlatır; servis iş bitene kadar çalışır ve kendi kendini durdurur.
- **Bağlı servis**: bir istemci `bindService` ile servise bağlanır ve onunla bir arayüz üzerinden konuşur. Bağlanan istemci kalmayınca servis sonlanır.
- **Ön plan servisi**: kullanıcının fark ettiği işi yürütür, örneğin çalan bir müzik. Bir ön plan servisi kullanıcı bildirimi göstermek zorundadır; kullanıcı onu fark eder.

Bunlar birbirinin alternatifi değildir: bir servis hem başlatılabilir hem bağlanabilir. Ama hangi türde istenirse istensin, servis bildirim dosyasında bildirilir; bildirilmeyen servis, bildirilmeyen activity gibi başlatılamaz.

```xml
<application>
    <service android:name=".SyncService" android:exported="false" />
</application>
```

```kotlin
// Platform kodu: ayni uygulamadan bir servisi baslatir
startService(Intent(this, SyncService::class.java))
```

Servis ayrı bir iş parçacığı **değildir**. Servisin geri çağrıları, yani `onStartCommand` gibi yöntemleri, uygulamanın ana iş parçacığında çalışır. Uzun süren işi servis kendi başına başka bir iş parçacığına taşımaz; bunu işi yazan yapar. Bu karıştırma sık bir hata kaynağıdır ve karşılama kuralıyla ölçülebilir.

Model, platformun bu kuralını kurar. Aşağıdaki ölçüm iki servisten istenen iş parçacığını sorar: biri varsayılan bırakılmış, öteki işi kendi iş parçacığına taşımış.

```kotlin
// Main.kt — servis ve is parcacigi
fun threadFor(ownThread: Boolean): Outcome =
    Outcome.Given(if (ownThread) "arka-is-parcacigi" else "ana-is-parcacigi")

fun main() {
    val wanted = "arka-is-parcacigi"
    val asks = listOf(
        Ask("servis-varsayilan", wanted, threadFor(ownThread = false)),
        Ask("servis-kendi-parcacigi", wanted, threadFor(ownThread = true)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
servis-varsayilan: istenen=arka-is-parcacigi | verilen=ana-is-parcacigi -> ikame
servis-kendi-parcacigi: istenen=arka-is-parcacigi | verilen=arka-is-parcacigi -> tam
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 0
```

Varsayılan servis, istenen arka iş parçacığı yerine ana iş parçacığını verdi: ikame. Bu ikame sessizdir; servis çalışır, ama uzun iş ana iş parçacığını bloke eder. İşi kendi iş parçacığına taşıyan servis istenen değeri aldı: tam.

**UB1.** Model iş parçacığı adını bir dizge olarak verir. Gerçek ayrım, çalışma zamanındaki iş parçacığı kimliğiyle gözlenir ve bu derste ölçülmedi.

#### Arka Plandan Başlatma

Kullanıcı uygulamayı kapatıp başka bir şeye geçtiyse uygulama arka plandadır. Platform, arka plandaki bir uygulamanın serbestçe servis başlatmasına belli bir API düzeyinden itibaren izin vermez. Düzeyin altında sınır yoktur, düzeyden sonra arka plandan `startService` reddedilir. Sınır, pil ve bellek harcayan görünmez işin önünü kesmek içindir.

Model sınırı iki girdiyle kurar: isteğin geldiği yer ve aygıtın API düzeyi. Düzey, platformun gerçek eşiğine dair bir iddia değil, model girdisidir; sınır 26 olarak seçilmiştir.

```kotlin
// Main.kt — arka plandan servis baslatma
fun startService(name: String, origin: String, level: Int): Outcome =
    if (origin == "arka-plan" && level >= 26) Outcome.Refused("arka-plan-siniri") else Outcome.Given(name)

fun main() {
    val asks = listOf(
        Ask("on-plan-baslatma", "SyncService", startService("SyncService", "on-plan", 31)),
        Ask("arka-plan-yeni", "SyncService", startService("SyncService", "arka-plan", 31)),
        Ask("arka-plan-eski", "SyncService", startService("SyncService", "arka-plan", 24)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
on-plan-baslatma: istenen=SyncService | verilen=SyncService -> tam
arka-plan-yeni: istenen=SyncService | neden=arka-plan-siniri -> karsiliksiz
arka-plan-eski: istenen=SyncService | verilen=SyncService -> tam
tam: 2
ikame: 0
bekleyen: 0
karsiliksiz: 1
```

Ön plandan başlatma tam karşılandı. Arka plandan başlatma, sınır düzeyini geçen aygıtta karşılıksız kaldı; sınırın altındaki aygıtta aynı istek tam karşılandı. Aynı kod, aynı servis, iki aygıt, iki karşılama. Bu dersin karşılaması bu yüzden **karşılıksız**dır: servis için platformun en sık verdiği olumsuz yanıt, arka plandan gelen isteğe verilendir.

**UB2.** 26, modelin seçtiği bir girdidir. Platformun sınırı hangi API düzeyinde uyguladığı ve hangi muafiyetleri tanıdığı ölçülmedi.

#### Süreç Sonlanınca Servis

Sistem bellek azaldığında, servisi barındıran süreci sonlandırabilir. Başlatılan bir servis için bu, iş yarım kalmışken servisin ortadan kalkması demektir. Servisin sonlandırıldıktan sonra yeniden başlayıp başlamayacağını ve yeniden başlarsa hangi intent'le başlayacağını `onStartCommand` yönteminin döndürdüğü değer belirler.

- `START_NOT_STICKY`: servis sonlandırılırsa yeniden başlatılmaz. Bekleyen başlatma istekleri varsa onlar gelir; yoksa servis kendiliğinden geri dönmez.
- `START_STICKY`: servis yeniden başlatılır, ama son intent teslim edilmez; `onStartCommand` boş (`null`) bir intent'le çağrılır.
- `START_REDELIVER_INTENT`: servis yeniden başlatılır ve son teslim edilen intent, iş `stopSelf` ile tamamlanana kadar yeniden teslim edilir.

```kotlin
// Platform kodu: yeniden baslatmada son intent'in yeniden teslim edilmesi
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    enqueue(intent)
    return START_REDELIVER_INTENT
}
```

İstenen, işin yarım kaldığı noktadan aynı intent'le sürmesidir: `ayni-intent`. Verilen, dönüş değerine göre değişir.

```kotlin
// Main.kt — surec sonlandiktan sonra servisin donusu
fun afterKill(policy: String): Outcome = when (policy) {
    "START_REDELIVER_INTENT" -> Outcome.Given("ayni-intent")
    "START_STICKY" -> Outcome.Given("bos-intent")
    "START_NOT_STICKY" -> Outcome.Refused("yeniden-baslamaz")
    else -> Outcome.Waiting("sistem-karari")
}

fun main() {
    val policies = listOf("START_REDELIVER_INTENT", "START_STICKY", "START_NOT_STICKY", "bilinmiyor")
    val asks = policies.map { Ask(it, "ayni-intent", afterKill(it)) }
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
START_REDELIVER_INTENT: istenen=ayni-intent | verilen=ayni-intent -> tam
START_STICKY: istenen=ayni-intent | verilen=bos-intent -> ikame
START_NOT_STICKY: istenen=ayni-intent | neden=yeniden-baslamaz -> karsiliksiz
bilinmiyor: istenen=ayni-intent | bekledigi=sistem-karari -> bekleyen
tam: 1
ikame: 1
bekleyen: 1
karsiliksiz: 1
```

Dört dönüş değeri dört karşılama verdi. `START_STICKY` yeniden başlar, ama intent'i taşımaz; `onStartCommand` içinde `intent` boş olabilir ve servis bunu ele almak zorundadır. Aksi hâlde servis çalışır görünür, ama ne yapacağını bilmeden çalışır: ikamenin sessiz tehlikesi buradadır. `START_NOT_STICKY` ise işin sonlandırıldığında kaybolması kabul edilebilir olan servisler içindir; yeniden başlamaz ve çağıran bunu öğrenmez. Dördüncü satır, platformun bu konuda her zaman bir söz vermediğini gösterir: dönüş değeri sistemin geri dönüş kararına bir öneridir, kesin bir sözleşme değildir.

**UB3.** Model üç dönüş değerini, sonlanmanın gerçekleştiği varsayımıyla ölçer. Sonlanmanın ne zaman olduğu, ne kadar beklendikten sonra servisin yeniden başladığı ve sistemin yeniden başlatmayı geciktirme kuralları ölçülmedi.

#### Bağlı Servisin Ömrü

Bağlı servisin ömrü, bağlı istemcilerin sayısına bağlıdır. Son istemci `unbindService` ile bağını koparınca, servis yalnız bağlı olarak kullanılmışsa platform onu yok eder. Aynı servis aynı zamanda `startService` ile başlatılmışsa, bağlar koparılsa bile çalışmaya devam eder ve ancak `stopSelf` ya da `stopService` ile durur.

```kotlin
// Platform kodu: istemci servise baglanir ve isi bitince baglantiyi koparir
bindService(Intent(this, PlayerService::class.java), connection, Context.BIND_AUTO_CREATE)
unbindService(connection)
```

Ölçümde istenen, son istemci ayrıldıktan sonra servisin çalışmaya devam etmesidir (`calisiyor`). Dört kullanım biçimi dört sonuç verir.

```kotlin
// Main.kt — son istemci ayrildiktan sonra servis
fun afterLastUnbind(started: Boolean, stoppedSelf: Boolean, foreground: Boolean): Outcome = when {
    foreground -> Outcome.Waiting("kullanici-bildirimi-kapanmasi")
    started && !stoppedSelf -> Outcome.Given("calisiyor")
    started && stoppedSelf -> Outcome.Given("durdu")
    else -> Outcome.Given("yok-edildi")
}

fun main() {
    val asks = listOf(
        Ask("yalniz-bagli", "calisiyor", afterLastUnbind(started = false, stoppedSelf = false, foreground = false)),
        Ask("baslatilmis-ve-bagli", "calisiyor", afterLastUnbind(started = true, stoppedSelf = false, foreground = false)),
        Ask("kendini-durdurmus", "calisiyor", afterLastUnbind(started = true, stoppedSelf = true, foreground = false)),
        Ask("on-plan", "calisiyor", afterLastUnbind(started = true, stoppedSelf = false, foreground = true)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
yalniz-bagli: istenen=calisiyor | verilen=yok-edildi -> ikame
baslatilmis-ve-bagli: istenen=calisiyor | verilen=calisiyor -> tam
kendini-durdurmus: istenen=calisiyor | verilen=durdu -> ikame
on-plan: istenen=calisiyor | bekledigi=kullanici-bildirimi-kapanmasi -> bekleyen
tam: 1
ikame: 2
bekleyen: 1
karsiliksiz: 0
```

Yalnız bağlı olarak kullanılan servis son istemciyle birlikte yok edildi: ikame. Başlatılmış ve bağlı servis çalışmaya devam etti: tam. Başlatılmış ama kendini durdurmuş servis, bağlar kopunca çalışmayı bıraktı: ikame. Ön plan servisi dördüncü satırda bekleyen çıktı: kullanıcı bildirimi görünür kaldığı sürece servis çalışır ve kullanıcının onu kapatması ya da uygulamanın servisi durdurması beklenir. İki ayrı ömür kuralı bir arada işlediği için, bir servisin ne zaman duracağını söylemek için hem nasıl başlatıldığına hem kaç istemcisi olduğuna bakmak gerekir.

**UB4.** Model üç bayrağı sabit bir öncelik sırasıyla değerlendirir. Gerçek platformda `BIND_AUTO_CREATE` bayrağı ve servisin hem bağlı hem başlatılmış olması hâlinde `onUnbind` ile `onRebind` geri çağrılarının işleyişi ölçülmedi.

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

Sınırın iki çıkışı vardır. Birincisi ön plan servisidir: arka plandan servis istenir, uygulama kullanıcı bildirimi gösterme sözü verirse platform ön plan servisi olarak başlatır. İstenen arka plan servisidir, verilen ön plan servisidir: ikame. İkincisi koşullu zamanlanmış iştir: iş bir servis olarak değil, platformun zamanlayıcısına verilen koşullu bir istek olarak yazılır, ve koşullar sağlanınca platform işi çalıştırır.

M12/K04'te "ertelendi" ile "bekleyen" ayrı tutuldu. Zamanlanmış iş ertelenmiş değil bekleyendir, çünkü beklediği koşulun adı bellidir: `ag`, `sarj`. Model eksik koşulları sıralı toplar ve virgülle birleştirir.

```kotlin
// Main.kt — on plan servisi ve kosullu zamanlanmis is
fun startAs(wanted: String, fromBackground: Boolean, hasUserNotice: Boolean): Outcome = when {
    !fromBackground -> Outcome.Given(wanted)
    hasUserNotice -> Outcome.Given("on-plan-servisi")
    else -> Outcome.Refused("arka-plan-siniri")
}

fun scheduleJob(work: String, needs: Set<String>, met: Set<String>): Outcome {
    val missing = (needs - met).sorted()
    return if (missing.isEmpty()) Outcome.Given(work) else Outcome.Waiting(missing.joinToString(","))
}

fun main() {
    val needs = setOf("ag", "sarj")
    val asks = listOf(
        Ask("on-plan-verilen", "arka-plan-servisi", startAs("arka-plan-servisi", true, true)),
        Ask("zamanli-sarj", "senkron", scheduleJob("senkron", needs, setOf("sarj"))),
        Ask("zamanli-hic", "senkron", scheduleJob("senkron", needs, emptySet())),
        Ask("zamanli-tamam", "senkron", scheduleJob("senkron", needs, setOf("ag", "sarj"))),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(asks.filter { it.fulfillment == "bekleyen" }.map { it.label })
}
```

```text
on-plan-verilen: istenen=arka-plan-servisi | verilen=on-plan-servisi -> ikame
zamanli-sarj: istenen=senkron | bekledigi=ag -> bekleyen
zamanli-hic: istenen=senkron | bekledigi=ag,sarj -> bekleyen
zamanli-tamam: istenen=senkron | verilen=senkron -> tam
tam: 1
ikame: 1
bekleyen: 2
karsiliksiz: 0
[zamanli-sarj, zamanli-hic]
```

İlk satır ikamedir: arka plan servisi istendi, ön plan servisi geldi. Üç zamanlanmış iş satırı bekleyenin üç yüzünü gösterir. Yalnız şarj sağlandığında iş `ag`ı bekliyor; hiçbiri sağlanmadığında iki koşulu birden bekliyor; ikisi de sağlandığında iş tam karşılandı. Bekleyen satırları, kural nedeni görmese de, `bekledigi=` alanında koşulu adıyla taşır. Son satır kuralın yalnız kategoriyi saydığını gösterir: iki bekleyen aynı listede toplanır ve eksik koşulları ayrı kalır.

**UB5.** Model iki koşulu ve bir kullanıcı bildirimi bayrağını girdi alır. Gerçek zamanlayıcının koşul türleri ve iş sürelerine koyduğu sınırlar ölçülmedi. Ön plan servisi çıkışı da koşulsuz değildir: API 31 ve üzerini hedefleyen bir uygulama arka plandayken ön plan servisini de başlatamaz, yalnız platformun belgelediği istisnalarda başlatabilir. Model bu sınırı kurmaz; `startAs` kullanıcı bildirimi bayrağını yeterli sayar.

#### Özet

- Bu dersin ölçütü: **karşılama**. Servis bildirim dosyasında bildirilir; başlatılan, bağlı ve ön plan servisi olarak üç biçimde kullanılır. Bu dersin karşılaması: **karşılıksız**; arka plandan gelen başlatma isteği sınır düzeyinde reddedilir.
- Servis iş parçacığı değildir: varsayılan servis ana iş parçacığını verir, istenen arka iş parçacığı yerine; 1 tam ve 1 ikame.
- Arka plandan servis başlatma, sınır düzeyini geçen aygıtta karşılıksız kalır: 2 tam ve 1 karşılıksız.
- Ön plan servisi, arka plan servisi isteğine verilen ikamedir; kullanıcı bildirimi bunun bedelidir.
- Koşullu zamanlanmış iş bekleyen kalır ve eksik koşulu adıyla taşır: `ag`, `sarj` ya da `ag,sarj`.

#### Sonraki Adım

Servis uygulamanın kendi isteğiyle başlar; sistem ise uygulamayı kendi olaylarıyla çağırır. Sıradaki ders, `Yayın Alıcıları`, sistemin bir olayı yayınladığında bildirilen alıcıya neyin ulaşıp neyin ulaşmadığını ölçer.

### Yayın Alıcıları

Source: https://academia.sh/tr/kurslar/android-platformu/yayin-alicilari

Yayın alıcısının bildirim dosyasında ve çalışma zamanında kaydedilmesi, örtük sistem yayınının bildirilen alıcıya ulaşmaması ve aynı alıcı sınıfının üç yoldan farklı karşılama almasının ölçümü.

Servis uygulamanın kendi isteğiyle çalışır. Sistem ise uygulamayı kendi olaylarıyla çağırır: ekran açıldı, aygıt yeniden başladı, şarj bağlandı, ağ değişti. Bu olaylar bir yayın olarak sisteme gönderilir ve ilgilenen uygulama bileşenleri onu alır. Yayını alan uygulama bileşeni yayın alıcısıdır (`BroadcastReceiver`). Yayın alıcısının arayüzü yoktur ve tek işi `onReceive` geri çağrısında kısa bir iş yapmaktır.

Dersin sorusu: bir yayın istendiğinde, yani bir alıcı belli bir yayını almak istediğinde platform isteği nasıl **karşılıyor**? Cevap, alıcının platforma hangi yoldan kaydedildiğine bağlıdır.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

#### İki Kayıt Yolu

Bir yayın alıcısı iki yoldan kaydedilir.

- **Bildirim dosyasında**: alıcı `<receiver>` öğesiyle bildirilir ve bir intent süzgeci hangi yayını istediğini söyler. Uygulama çalışmıyorken bile platform alıcıyı kurabilir.
- **Çalışma zamanında**: kod `registerReceiver` ile alıcıyı kaydeder ve `unregisterReceiver` ile siler. Alıcı yalnız kayıtlı olduğu sürece yayın alır.

```xml
<application>
    <receiver android:name=".ScreenReceiver" android:exported="false">
        <intent-filter>
            <action android:name="android.intent.action.SCREEN_ON" />
        </intent-filter>
    </receiver>
</application>
```

```kotlin
// Platform kodu: ayni alicinin calisma zamaninda kaydi
import android.content.IntentFilter

val filter = IntentFilter("android.intent.action.SCREEN_ON")
registerReceiver(ScreenReceiver(), filter)
```

İki yol eskiden aynı sonucu verirdi. Bugünün platformunda fark, yayının **örtük** ya da **açık** olmasından çıkar. Örtük yayın belli bir uygulamayı hedeflemez, herkese gönderilir; ekranın açılması gibi sistem olayları böyledir. Açık yayın ise bir uygulamanın paket adını taşır ve yalnız o uygulamaya gider. Platform, bildirim dosyasında bildirilen alıcıların örtük sistem yayınlarını almasını, bir API düzeyinden itibaren kısıtlamıştır; bir istisna listesindeki yayınlar bu kısıttan muaftır. Çalışma zamanında kaydedilen alıcılar kısıtlanmaz.

Model bu kuralı tek bir yardımcı dosyada kurar. Girdiler yayının adı, kayıt yolu, yayının örtük sistem yayını olup olmadığı, paket adıyla gönderilip gönderilmediği ve istisna listesidir. Liste ve düzey modelin girdisidir.

```kotlin
// Delivery.kt — bir yayinin alici sinifina ulasmasini kurar
fun deliver(
    action: String,
    way: String,
    systemImplicit: Boolean,
    explicitPackage: Boolean,
    exempt: Set<String>,
): Outcome = when {
    way == "calisma-zamani" -> Outcome.Given(action)
    explicitPackage -> Outcome.Given(action)
    systemImplicit && action !in exempt -> Outcome.Refused("ortuk-yayin-siniri")
    else -> Outcome.Given(action)
}
```

#### Bildirilen Alıcıya Ulaşan ve Ulaşmayan Yayın

Üç alıcı üç yayın ister. Ekranın açılması örtük bir sistem yayınıdır ve istisna listesinde yoktur. Aygıtın açılması da örtük bir sistem yayınıdır, ama istisna listesindedir. Üçüncüde ekran yayını, bu kez çalışma zamanında kaydedilen bir alıcıya gönderilir.

```kotlin
// Main.kt — ortuk sistem yayini ve kayit yolu
fun main() {
    val exempt = setOf("BOOT_COMPLETED")
    val asks = listOf(
        Ask("bildirim-ortuk", "SCREEN_ON", deliver("SCREEN_ON", "bildirim", true, false, exempt)),
        Ask("bildirim-istisna", "BOOT_COMPLETED", deliver("BOOT_COMPLETED", "bildirim", true, false, exempt)),
        Ask("calisma-ortuk", "SCREEN_ON", deliver("SCREEN_ON", "calisma-zamani", true, false, exempt)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
bildirim-ortuk: istenen=SCREEN_ON | neden=ortuk-yayin-siniri -> karsiliksiz
bildirim-istisna: istenen=BOOT_COMPLETED | verilen=BOOT_COMPLETED -> tam
calisma-ortuk: istenen=SCREEN_ON | verilen=SCREEN_ON -> tam
tam: 2
ikame: 0
bekleyen: 0
karsiliksiz: 1
```

Bildirim dosyasındaki alıcıya ekran yayını ulaşmadı: karşılıksız. Aynı yoldan, istisna listesindeki aygıt açılışı ulaştı: tam. Çalışma zamanında kaydedilen alıcıya ekran yayını ulaştı: tam. Bu dersin karşılaması **karşılıksız**dır: bildirim dosyasında bir alıcıyı bildirmek, örtük bir sistem yayınının ona ulaşacağını garanti etmez.

**UB1.** İstisna listesi ve kısıtın başladığı API düzeyi model girdisidir. Listede hangi yayınların bulunduğu ve sürümler arası değişimi ölçülmedi.

#### Çalışma Zamanı Kaydının Ömrü

Çalışma zamanı kaydı yoluyla yayın alan alıcı, kayıt var olduğu sürece alır. Kaydı yapan activity yok edilir ve alıcı silinmezse sızıntı olur; silinirse alıcı artık alıcı değildir. Bildirim dosyasındaki kayıt ise uygulamanın ömrüne bağlıdır.

```kotlin
// Main.kt — calisma zamani kaydinin omru
fun deliverRegistered(action: String, way: String, active: Boolean): Outcome =
    if (way == "calisma-zamani" && !active) Outcome.Refused("kayit-yok") else Outcome.Given(action)

fun main() {
    val asks = listOf(
        Ask("calisma-kayitli", "SCREEN_ON", deliverRegistered("SCREEN_ON", "calisma-zamani", active = true)),
        Ask("calisma-silinmis", "SCREEN_ON", deliverRegistered("SCREEN_ON", "calisma-zamani", active = false)),
        Ask("bildirim-her-zaman", "BOOT_COMPLETED", deliverRegistered("BOOT_COMPLETED", "bildirim", active = false)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
calisma-kayitli: istenen=SCREEN_ON | verilen=SCREEN_ON -> tam
calisma-silinmis: istenen=SCREEN_ON | neden=kayit-yok -> karsiliksiz
bildirim-her-zaman: istenen=BOOT_COMPLETED | verilen=BOOT_COMPLETED -> tam
tam: 2
ikame: 0
bekleyen: 0
karsiliksiz: 1
```

Kayıtlı alıcı yayını aldı: tam. Kaydı silinen alıcı almadı: karşılıksız. Bildirim dosyasındaki alıcı için `active` değeri anlamsızdır; kayıt uygulamanın var olduğu sürece geçerlidir. İki karşılıksız arasındaki fark yine nedendedir: ilkinde `ortuk-yayin-siniri` platformun kuralıdır, ikincisinde `kayit-yok` kodun kendi eseridir.

**UB2.** Model `active` değerini bir girdi olarak alır. Alıcının hangi yaşam döngüsü geri çağrısında silinmesi gerektiği bu derste ölçülmedi.

#### Sıralı Yayın ve İptal

Yayınlar iki biçimde gönderilir. Normal yayında platform yayını bütün alıcılara birlikte teslim eder; alıcılar arasında bir sıra yoktur ve biri diğerini etkilemez. Sıralı yayında platform yayını alıcılara tek tek, öncelik sırasıyla teslim eder. Her alıcı yayını alır, bir sonuç ekleyebilir ya da yayını iptal ederek zincirin sonrakilere ulaşmasını durdurabilir. Öncelik, süzgeçte `android:priority` özniteliğiyle ya da çalışma zamanı kaydında `IntentFilter` üzerinde verilir.

```kotlin
// Platform kodu: sirali yayin gonderme ve zinciri durdurma
sendOrderedBroadcast(Intent("app.action.CHECK"), null)

// alicinin icinde, zinciri durdurmak icin
abortBroadcast()
```

Ölçümde üç alıcı öncelik sırasıyla dizilmiştir. İstenen, her alıcının yayını almasıdır. Sıralı yayında ikinci alıcı iptal ederse üçüncüye hiç ulaşılmaz. Normal yayında iptal çağrısı bir etki yapmaz.

```kotlin
// Main.kt — sirali yayin ve iptal
fun receive(index: Int, ordered: Boolean, abortAt: Int?): Outcome =
    if (ordered && abortAt != null && index > abortAt) Outcome.Refused("yayin-iptal")
    else Outcome.Given("alindi")

fun main() {
    val asks = mutableListOf<Ask>()
    for (ordered in listOf(true, false)) {
        val kind = if (ordered) "sirali" else "normal"
        for (index in 0..2) {
            asks.add(Ask("$kind-alici-${index + 1}", "alindi", receive(index, ordered, abortAt = 1)))
        }
    }
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
sirali-alici-1: istenen=alindi | verilen=alindi -> tam
sirali-alici-2: istenen=alindi | verilen=alindi -> tam
sirali-alici-3: istenen=alindi | neden=yayin-iptal -> karsiliksiz
normal-alici-1: istenen=alindi | verilen=alindi -> tam
normal-alici-2: istenen=alindi | verilen=alindi -> tam
normal-alici-3: istenen=alindi | verilen=alindi -> tam
tam: 5
ikame: 0
bekleyen: 0
karsiliksiz: 1
```

Sıralı yayında iptal eden alıcı yayını aldı: tam; kendisinden sonraki alıcı almadı: karşılıksız. Normal yayında aynı iptal hiçbir şeyi değiştirmedi: üç alıcının üçü de aldı. Bundan çıkan ders, bir alıcının başka bir alıcıya bağımlı olduğunu varsaymamaktır. Sıralı yayında zincirin ortasındaki bir alıcı yayını durdurabilir; bu alıcı kendi uygulamanızda olmayabilir. Sistemin bazı yayınları iptal edilemez, ve bazı yayınlar yalnız normal biçimde gönderilir.

**UB3.** Model iptali yalnız bir indeks olarak alır. Alıcıların sonuç eklemesi, aynı önceliğe sahip alıcıların sırası ve sistem yayınlarından hangilerinin sıralı gönderildiği ölçülmedi.

#### Alıcının Çalışabildiği Süre

Yayın alıcısı bir uygulama bileşeni olarak çok kısa ömürlüdür: yalnız `onReceive` yöntemi çalıştığı sürece vardır. Yöntem döndüğünde platform, alıcıyı artık etkin saymaz ve alıcıyı barındıran süreci sonlandırabilir. Yöntem ana iş parçacığında çalışır ve uzun sürerse platform bir yanıt vermeme hatası üretir. Bu yüzden `onReceive` içinde uzun iş yapmak, yalnız iş parçacığı açmakla çözülmez; çünkü alıcı döndüğü anda süreç öncelik kaybeder.

```kotlin
// Platform kodu: onReceive donmeden once isin zamanlanmasi
override fun onReceive(context: Context, intent: Intent) {
    val pending = goAsync()
    thread {
        doWork(intent)
        pending.finish()
    }
}
```

Ölçüm, işin süresini ve işin yapılma biçimini girdi alır. Sınır modelde on saniyedir; bu bir girdidir, platformun sabit bir iddiası değildir. İstenen `tamamlandi`'dir. Kısa iş doğrudan biter. Uzun iş `onReceive` içinde yapılırsa süre aşılır. Uzun iş zamanlanmış işe devredilirse alıcı hızla döner ve iş sonradan yapılır: istenen değil, onun yerine geçen bir sonuç. Ağ bekleyen bir iş ise ağ gelene kadar bekler.

```kotlin
// Main.kt — onReceive icindeki isin suresi
fun runIn(onReceive: Boolean, seconds: Int, handOff: Boolean, needsNetwork: Boolean, limit: Int): Outcome = when {
    needsNetwork -> Outcome.Waiting("ag")
    handOff -> Outcome.Given("is-zamanlandi")
    onReceive && seconds > limit -> Outcome.Refused("sure-asildi")
    else -> Outcome.Given("tamamlandi")
}

fun main() {
    val limit = 10
    val asks = listOf(
        Ask("kisa-is", "tamamlandi", runIn(true, 3, handOff = false, needsNetwork = false, limit = limit)),
        Ask("uzun-is", "tamamlandi", runIn(true, 15, handOff = false, needsNetwork = false, limit = limit)),
        Ask("uzun-is-devredilmis", "tamamlandi", runIn(true, 15, handOff = true, needsNetwork = false, limit = limit)),
        Ask("agli-is", "tamamlandi", runIn(true, 3, handOff = false, needsNetwork = true, limit = limit)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
kisa-is: istenen=tamamlandi | verilen=tamamlandi -> tam
uzun-is: istenen=tamamlandi | neden=sure-asildi -> karsiliksiz
uzun-is-devredilmis: istenen=tamamlandi | verilen=is-zamanlandi -> ikame
agli-is: istenen=tamamlandi | bekledigi=ag -> bekleyen
tam: 1
ikame: 1
bekleyen: 1
karsiliksiz: 1
```

Dört iş dört karşılama aldı. Kısa iş sınırın altında bitti: tam. Uzun iş alıcının içinde yapılırsa karşılıksız kaldı. Uzun işi zamanlanmış işe devretmek ikame verdi: iş `onReceive` içinde tamamlanmadı, ama tamamlanması garanti altına alındı. Bu, çoğu zaman doğru çözümdür. Ağ bekleyen iş de doğrudan yapılmaya çalışılmaz; bekleyen olarak kalır ve koşulunu taşır. Bu ayrım M12/K04'te zamanlanmış işler için kurulan ayrımın alıcıdaki karşılığıdır.

**UB4.** On saniye modelin seçtiği bir girdidir. Gerçek sürenin yayının türüne ve uygulamanın ön planda olup olmamasına göre değiştiği, ve `goAsync` ile elde edilen sürenin tam sınırı ölçülmedi.

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

Aynı yayın, aynı alıcı sınıfı: üç yol. İlki bildirim dosyasında bildirilen alıcıya örtük yayındır ve karşılıksız kalır. İkincisi aynı alıcı sınıfının çalışma zamanında kaydedilmesidir ve tam karşılanır. Üçüncüsü yayının, uygulamanın paket adıyla açık olarak gönderilmesidir: bildirim dosyasındaki alıcıya da ulaşır.

```kotlin
// Main.kt — ayni yayin, ayni alici sinifi, uc yol
fun main() {
    val exempt = setOf("BOOT_COMPLETED")
    val asks = listOf(
        Ask("bildirim-ortuk", "SCREEN_ON", deliver("SCREEN_ON", "bildirim", true, false, exempt)),
        Ask("calisma-ortuk", "SCREEN_ON", deliver("SCREEN_ON", "calisma-zamani", true, false, exempt)),
        Ask("bildirim-acik", "SCREEN_ON", deliver("SCREEN_ON", "bildirim", true, true, exempt)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(asks.map { it.fulfillment }.distinct().sorted())
}
```

```text
bildirim-ortuk: istenen=SCREEN_ON | neden=ortuk-yayin-siniri -> karsiliksiz
calisma-ortuk: istenen=SCREEN_ON | verilen=SCREEN_ON -> tam
bildirim-acik: istenen=SCREEN_ON | verilen=SCREEN_ON -> tam
tam: 2
ikame: 0
bekleyen: 0
karsiliksiz: 1
[karsiliksiz, tam]
```

Üç yolun ikisi tam, biri karşılıksız. Son satır kuralın görüşünü yine gösterir: üç isteğin karşılamaları yalnız iki farklı değerdir, ve hangi yolun hangisine karşılık geldiği kategoriden okunmaz; yol `label` alanındadır. Alıcının çalışması yayının ona hangi yoldan geldiğine bağlıdır, yayının kendisine değil.

**UB5.** Model açık yayını paket adıyla gönderilmiş her yayın sayar. Gerçekte açık yayının yolu, hedef bileşenin bildirilmiş ve dışa açık olmasına da bağlıdır; bu derste ölçülmedi.

#### Özet

- Bu dersin ölçütü: **karşılama**. Yayın alıcısı bildirim dosyasında ya da çalışma zamanında kaydedilir ve bu iki yol farklı karşılama alır. Bu dersin karşılaması: **karşılıksız**; bildirilen alıcıya örtük sistem yayını ulaşmaz.
- Bildirilen alıcıya örtük sistem yayını ulaşmaz: karşılıksız. İstisna listesindeki yayın ve çalışma zamanı kaydı tam karşılandı: 2 tam ve 1 karşılıksız.
- Çalışma zamanı kaydı silinince alıcı yayın almaz; kayıt ömrü kodun sorumluluğudur.
- Açık yayın, bildirim dosyasındaki alıcıya da ulaşır.
- Karşılama kategorisi yolu taşımaz: iki karşılıksız farklı nedenden gelebilir.

#### Sonraki Adım

Yayın alıcısı bir olay bildirir, veri taşımaz. Verinin kendisini başka uygulamayla paylaşmak için sıradaki ders, `İçerik Sağlayıcılar`, içerik URI'sinin nasıl eşlendiğini ve izinsiz sorgunun nasıl karşılandığını ölçer.

### İçerik Sağlayıcılar

Source: https://academia.sh/tr/kurslar/android-platformu/icerik-saglayicilar

İçerik sağlayıcının içerik URI'sini URI eşleyicisiyle nasıl çözdüğü, bilinmeyen yolun, olmayan satırın ve olmayan sütunun nasıl karşılandığı ve izinsiz sorgunun karşılıksız kalması.

Yayın alıcısı bir olay bildirir, veri taşımaz. Veriyi başka bir uygulamayla paylaşmak gerektiğinde platform başka bir uygulama bileşeni sunar: içerik sağlayıcı (`ContentProvider`). Kişiler, takvim kayıtları ve ortam dosyaları bu yolla paylaşılır; kendi uygulamanızın verisini başka uygulamaya açmak isterseniz aynı yolu izlersiniz.

İçerik sağlayıcıya doğrudan bir nesne gibi erişilmez. Çağıran taraf bir **içerik URI'si** verir, platform URI'nin yetki kısmından hangi sağlayıcının sorumlu olduğunu bulur ve isteği ona iletir. Çağıran tarafın elindeki araç içerik çözümleyicidir (`ContentResolver`): sorguyu, ekleme ve silmeyi sağlayıcıya o yönlendirir. Sağlayıcının kendi içinde ise URI'yi bir koda çeviren bir **URI eşleyicisi** (`UriMatcher`) bulunur.

Dersin sorusu: içerik URI'si ile bir veri istendiğinde platform isteği nasıl **karşılıyor**? Üç farklı noktada üç farklı cevap çıkacak: yolun eşleşmesinde, satırın ve sütunun var olmasında, çağıranın iznindedir.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

#### İçerik URI'sinin Yapısı

İçerik URI'si üç parçadan kurulur: `content://` şeması, sağlayıcının yetkisi (`authority`) ve yol. Yolun sonundaki sayı çoğunlukla bir satırın kimliğidir.

Örnek olarak `content://com.example.library/books` tüm kitapları, `content://com.example.library/books/7` kimliği 7 olan kitabı gösterir.

Sağlayıcı bildirim dosyasında `<provider>` öğesiyle bildirilir. Yetki burada verilir; `android:exported` ve izin öznitelikleri de buradadır. Bildirim dosyasındaki bu öğe olmadan sistem URI'yi hiçbir sağlayıcıya yönlendiremez.

```xml
<application>
    <provider
        android:name=".BookProvider"
        android:authorities="com.example.library"
        android:exported="true"
        android:readPermission="com.example.library.READ_BOOKS" />
</application>
```

Sağlayıcının kodu `ContentProvider` sınıfından türer ve altı yöntemi uygular: `onCreate`, `query`, `insert`, `update`, `delete` ve `getType`. Çağıran tarafın hiçbir zaman doğrudan çağırmadığı bu yöntemler, içerik çözümleyici aracılığıyla çalışır.

```kotlin
// Platform kodu: sorgunun sagliciya ulasmasi
import android.content.ContentProvider
import android.net.Uri

class BookProvider : ContentProvider() {
    private val matcher = UriMatcher(UriMatcher.NO_MATCH).apply {
        addURI("com.example.library", "books", 1)
        addURI("com.example.library", "books/#", 2)
    }

    override fun query(uri: Uri, projection: Array<String>?, selection: String?,
                       args: Array<String>?, order: String?): Cursor? =
        when (matcher.match(uri)) {
            1 -> database.listBooks(projection)
            2 -> database.findBook(uri.lastPathSegment, projection)
            else -> throw IllegalArgumentException("Bilinmeyen URI: $uri")
        }
}
```

Eşleyicinin iki desen sözcüğü vardır: `#` bir sayıyı, `*` herhangi bir metni karşılar. Eşleşme yoksa `match` değeri `-1` (`NO_MATCH`) döner ve sağlayıcı bu durumu bir istisnayla bildirmeyi seçer. Sağlayıcıyı yazan kişi bu seçimi kendisi yapar; yukarıdaki kural yaygın olandır ve bu dersin modeli onu izler.

#### Yolun Eşleşmesi

Model URI eşleyicisini ve sorguyu iki yardımcı dosyada kurar. Birincisi yolu desenlerle karşılaştırır ve eşleşen kodu döner, eşleşme yoksa `-1` döner. İkincisi kodu bir sonuca çevirir.

```kotlin
// Matcher.kt — yolu desenlerle karsilastirir, kodu doner
fun matchUri(path: String, patterns: Map<String, Int>): Int {
    val parts = path.split("/")
    for ((pattern, code) in patterns) {
        val want = pattern.split("/")
        if (want.size != parts.size) continue
        val fits = want.zip(parts).all { (w, p) ->
            when (w) {
                "#" -> p.isNotEmpty() && p.all { it.isDigit() }
                "*" -> p.isNotEmpty()
                else -> w == p
            }
        }
        if (fits) return code
    }
    return -1
}
```

```kotlin
// Provider.kt — eslesen kodu sonuca cevirir
val patterns = mapOf("books" to 1, "books/#" to 2)
val rows = setOf(7, 9)

fun queryBooks(path: String): Outcome = when (matchUri(path, patterns)) {
    1 -> Outcome.Given("kitap-listesi")
    2 -> {
        val id = path.substringAfter("/").toInt()
        if (id in rows) Outcome.Given("kitap-satiri") else Outcome.Given("bos-imlec")
    }
    else -> Outcome.Refused("eslesme-yok")
}
```

`rows` modelin girdisidir: tabloda hangi kimliklerin bulunduğunu söyler. İlk ölçüm dört istek yapar: liste, var olan bir satır, hiç tanımlı olmayan bir yol ve sayı yerine metin taşıyan bir kimlik.

```kotlin
// Main.kt — URI eslesmesi
fun main() {
    val asks = listOf(
        Ask("liste", "kitap-listesi", queryBooks("books")),
        Ask("satir", "kitap-satiri", queryBooks("books/7")),
        Ask("bilinmeyen", "kitap-listesi", queryBooks("authors")),
        Ask("metin-kimlik", "kitap-satiri", queryBooks("books/abc")),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
liste: istenen=kitap-listesi | verilen=kitap-listesi -> tam
satir: istenen=kitap-satiri | verilen=kitap-satiri -> tam
bilinmeyen: istenen=kitap-listesi | neden=eslesme-yok -> karsiliksiz
metin-kimlik: istenen=kitap-satiri | neden=eslesme-yok -> karsiliksiz
tam: 2
ikame: 0
bekleyen: 0
karsiliksiz: 2
```

İki desenle eşleşen iki istek tam karşılandı. Eşleşmeyen iki istek karşılıksız kaldı: `authors` yolu hiçbir desende yoktur, `books/abc` ise `#` desenine uymaz çünkü kimlik sayı değildir. İkinci durum ince bir yerdir: yol `books/` ile başladığı için kullanıcı eşleşme bekler, ama eşleyici deseni sözcük sözcük değil parça parça karşılaştırır ve `#` yalnız rakam kabul eder. Platformun bu istekleri geri çevirmesi istisnayla olur; modelde bu `Refused` ile gösterilir.

**UB1.** Model `books/#` eşleşmesinden sonra kimliği `toInt` ile okur. Taşan uzunlukta bir sayının davranışı ölçülmedi. Gerçek eşleyici de aynı parça karşılaştırmasını yapar ama desen öncelik sırası (kesin metin, sonra `#`, sonra `*`) bu derste iki desenle sınırlı tutuldu.

#### Olmayan Satır ve Boş Yol

Bir yol deseni karşılayabilir ama içeriği boş çıkabilir. Kimliği 8 olan kitap tabloda yoktur, ama `books/8` yolu `books/#` desenine uyar. Sağlayıcı bu durumda istisna atmaz: sıfır satırlı bir imleç döner. Çağıran taraf istediği satırı alamamıştır, ama hata da almamıştır.

Üç istek yapılır. Biri var olan satırı ister. Biri var olmayan satırı ister. Üçüncüsü `books/` biçiminde, son parçası boş bir yol verir.

```kotlin
// Main.kt — var olmayan satir ve bos parca
fun main() {
    val asks = listOf(
        Ask("var-olan", "kitap-satiri", queryBooks("books/7")),
        Ask("olmayan", "kitap-satiri", queryBooks("books/8")),
        Ask("bos-parca", "kitap-satiri", queryBooks("books/")),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
var-olan: istenen=kitap-satiri | verilen=kitap-satiri -> tam
olmayan: istenen=kitap-satiri | verilen=bos-imlec -> ikame
bos-parca: istenen=kitap-satiri | neden=eslesme-yok -> karsiliksiz
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

Var olan satır tam. Olmayan satır **ikame** oldu: çağıran taraf bir kitap satırı bekliyordu, platform boş bir imleç verdi. İstek hataya dönmediği için karşılıksız değildir, ama istenen de verilmediği için tam değildir. Boş parça ise eşleşmedi: `#` boş metni sayı saymaz.

Bu ayrımın pratik karşılığı çağıran kodda görünür. İmlecin boş olması normal bir sonuçtur ve kodun onu kontrol etmesi gerekir. Eşleşme hatası ise bir istisnadır ve kontrolsüz bırakılırsa uygulamayı sonlandırır.

**UB2.** Model boş imleci hata saymaz; çünkü sağlayıcının kendisi hata fırlatmaz. Çağıran taraf imlecin boşluğunu kontrol etmezse yaşanacak sonuç ölçülmedi.

#### Olmayan Sütun

Sorgu yalnız satırı değil, hangi sütunların istendiğini de taşır. Sütun listesine `projection` denir. Sağlayıcı SQLite gibi bir tabloya dayanıyorsa var olmayan bir sütun adı sorguyu düşürür ve çağıran taraf bir istisna alır. Sağlayıcı sütunları kendisi süzüyorsa kural farklı olabilir; bu ders SQLite tabanlı ve en yaygın olan durumu kurar.

```kotlin
// Main.kt — sutun listesi
val known = setOf("_id", "title", "author")

fun queryColumns(wanted: List<String>): Outcome {
    val missing = wanted.filter { it !in known }
    return if (missing.isEmpty()) {
        Outcome.Given(wanted.joinToString(","))
    } else {
        Outcome.Refused("sutun-yok")
    }
}

fun columnAsk(label: String, wanted: List<String>): Ask =
    Ask(label, wanted.joinToString(","), queryColumns(wanted))

fun main() {
    val asks = listOf(
        columnAsk("tum-sutunlar", listOf("title", "author")),
        columnAsk("eksik-sutun", listOf("title", "isbn")),
        columnAsk("kimlikli", listOf("_id", "title", "author")),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
tum-sutunlar: istenen=title,author | verilen=title,author -> tam
eksik-sutun: istenen=title,isbn | neden=sutun-yok -> karsiliksiz
kimlikli: istenen=_id,title,author | verilen=_id,title,author -> tam
tam: 2
ikame: 0
bekleyen: 0
karsiliksiz: 1
```

İki istek bilinen sütunlarla geldi ve tam karşılandı. Üçüncüsü `isbn` sütununu istedi; tabloda yoktur ve istek karşılıksız kaldı. Platform burada satırı bulup eksik sütunu atlamaz: bir sütun bile bilinmiyorsa sorgunun tamamı reddedilir. Kısmi sonuç, kısmi ikame değildir; bütün ya da hiç kuralı işler.

**UB3.** `known` kümesi modelin girdisidir. Gerçekte sütun adlarının sorgu sırasında nasıl doğrulandığı, kimlik sütununun özel adı (`_id`) ve çok tablolu birleşim sorguları ölçülmedi.

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

Aynı içerik URI'si, aynı sorgu: yalnız çağıranın durumu değişir. Sağlayıcı `android:readPermission` ile korunuyorsa, izni olmayan çağıran sorguya ulaşamaz. Sistem izni sağlayıcıya girmeden önce kontrol eder; bu yüzden yolun eşleşmesi ya da satırın varlığı artık sorulmaz. İzin yoksa yolun doğru olması da bir şey değiştirmez.

Bir yol daha vardır: sağlayıcıyı yazan taraf tek bir URI için geçici izin verebilir (`grantUriPermission`). Bu izin yalnız o URI'yi kapsar, ve sağlayıcıdaki başka satırlara geçmez.

```kotlin
// Main.kt — ayni sorgu, uc cagiran
fun access(path: String, caller: String, allowed: Set<String>): Outcome = when {
    caller == "izinli" -> queryBooks(path)
    path in allowed -> queryBooks(path)
    else -> Outcome.Refused("izin-yok")
}

fun main() {
    val allowed = setOf("books/7")
    val asks = listOf(
        Ask("izinli", "kitap-satiri", access("books/7", "izinli", allowed)),
        Ask("uri-izinli", "kitap-satiri", access("books/7", "diger", allowed)),
        Ask("baska-satir", "kitap-satiri", access("books/9", "diger", allowed)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(asks.map { it.fulfillment }.distinct().sorted())
}
```

```text
izinli: istenen=kitap-satiri | verilen=kitap-satiri -> tam
uri-izinli: istenen=kitap-satiri | verilen=kitap-satiri -> tam
baska-satir: istenen=kitap-satiri | neden=izin-yok -> karsiliksiz
tam: 2
ikame: 0
bekleyen: 0
karsiliksiz: 1
[karsiliksiz, tam]
```

İzin sahibi çağıran tam karşılandı. İzni olmayan ama `books/7` için geçici URI izni verilen çağıran, aynı satırı aynı biçimde tam aldı. Aynı çağıran `books/9` satırını isteyince karşılıksız kaldı: URI izni yalnız verildiği URI'ye uygulanır. Son satır iki farklı karşılama değeri olduğunu gösterir; çağıranın kim olduğu değil, URI'nin izin kümesinde bulunup bulunmadığı belirleyicidir.

Bu derste ölçülen karşılama **tam**dır: eşleşen, var olan, izinli bir istek içerik sağlayıcıdan istenen veriyi aynen alır. Karşılama bozulduğunda bozulan yer her zaman aynıdır: eşleşme, satır, sütun ya da izin. Hangisinin bozulduğu çağıran tarafın aldığı sonuçtan okunur.

**UB4.** Model izni iki değerle kurar: sağlayıcının izninin varlığı ve tek tek URI izinleri. Yazma izni (`writePermission`), yol bazlı izinler (`path-permission`) ve işlem sırasında izin kaybı ölçülmedi.

#### Özet

- Bu dersin ölçütü: **karşılama**. İçerik sağlayıcı verisini içerik URI'siyle sunar; içerik çözümleyici istemcinin, URI eşleyicisi sağlayıcının tarafındadır. Bu dersin karşılaması: **tam**; eşleşen, var olan, izinli istek veriyi aynen alır.
- Eşleşen yol tam, eşleşmeyen yol karşılıksız karşılandı: 2 tam ve 2 karşılıksız. `books/abc` gibi sayı olmayan kimlik `#` desenine uymaz.
- Var olmayan satır hata değildir: boş imleç **ikame** olarak döner. Var olmayan sütun ise sorgunun tamamını karşılıksız bırakır.
- İzin sağlayıcıya girmeden önce denetlenir. Geçici URI izni yalnız verildiği URI'yi kapsar.
- Karşılamayı etiket değil kural verir: boş imleç ile eşleşme hatası farklı kategorilere düşer.

#### Sonraki Adım

İçerik sağlayıcıyı bir URI ile çağırdınız. Başka bir uygulama bileşenini çağırmanın genel yolu ise intent'tir. Sıradaki ders, `Intent'ler ve Intent Süzgeçleri`, bir intent'in hangi uygulama bileşenine ulaştığını ve aynı intent'i birden çok uygulamanın karşıladığı durumu ölçer.

### Intent'ler ve Intent Süzgeçleri

Source: https://academia.sh/tr/kurslar/android-platformu/intentler-ve-intent-suzgecleri

Açık ve örtük intent'in farkı, intent süzgecinin eylem, kategori ve veriyle eşleşmesi, birden çok uygulamanın aynı intent'i karşıladığı durumda bekleyen karşılama ve varsayılan uygulama kaldırılınca değişen sonuç.

İçerik sağlayıcıyı bir URI ile çağırdınız. Başka bir uygulama bileşenini çağırmanın genel yolu ise intent'tir. Önceki derslerde activity'yi, servisi ve yayın alıcısını farklı yollarla başlattınız: `startActivity`, `startService`, `sendBroadcast`. Üçünün ortak girdisi `Intent` nesnesidir. Intent, "şunu yap" diyen bir mesajdır ve hedefi iki biçimde söyler.

- **Açık intent**: hedef uygulama bileşeninin sınıfını ya da paket adını doğrudan verir. Platform eşleştirme yapmaz, o uygulama bileşenini başlatır.
- **Örtük intent**: hedefi vermez, yapılacak işi söyler: bir eylem, bir kategori ve bir veri. Platform kurulu uygulamaların intent süzgeçlerine bakar ve işi karşılayabilecek uygulama bileşenini bulur.

Dersin sorusu: bir intent gönderildiğinde platform isteği nasıl **karşılıyor**? Örtük intent'te bu soru tek bir cevapla bitmez: sıfır, bir ya da birden çok uygulama eşleşebilir. Ders üç sonucu da ölçer.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

#### Intent Süzgeci

Bir uygulama bileşeninin hangi intent'leri alacağını bildirim dosyasındaki `<intent-filter>` öğesi söyler. Süzgeç üç ölçüt taşır.

- **Eylem** (`action`): yapılacak işin adı. `VIEW` göstermek, `SEND` göndermek içindir. Intent'in eylemi, süzgeçte bildirilen eylemlerden biri olmalıdır.
- **Kategori** (`category`): uygulama bileşeninin nasıl çağrılabileceği. Intent'in taşıdığı her kategori süzgeçte bulunmalıdır.
- **Veri** (`data`): işlenecek içeriğin şeması ve türü. Süzgeç veri bildirmişse intent'in verisi onunla uyuşmalıdır; süzgeç veri bildirmemişse intent de veri taşımamalıdır.

```xml
<activity android:name=".ImageViewerActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="image" />
    </intent-filter>
</activity>
```

Burada ince bir kural vardır. `startActivity` ile örtük intent gönderildiğinde platform intent'e kendiliğinden `DEFAULT` kategorisini ekler. Bu yüzden `startActivity` ile ulaşılmak istenen bir activity'nin süzgecinde `DEFAULT` kategorisi bulunmak zorundadır. Süzgeçte eylem ve veri doğru olsa bile bu kategori yoksa eşleşme olmaz. Kural sık atlanır: activity çalışır görünür, ama örtük intent'e ulaşmaz.

```kotlin
// Platform kodu: ortuk ve acik intent gonderme
import android.content.Intent
import android.net.Uri

val implicit = Intent(Intent.ACTION_VIEW, Uri.parse("geo:41.0,29.0"))
startActivity(implicit)

val explicit = Intent().setClassName("com.example.maps", "com.example.maps.MapActivity")
startActivity(explicit)
```

Örtük intent yalnız eşleşen uygulama yoksa değil, birden çok uygulama eşleşiyorsa da özel bir davranış gösterir: platform kullanıcıya seçim ekranı gösterir. Kullanıcı bir uygulamayı varsayılan yaparsa sonraki intent'ler seçim sormadan o uygulamaya gider. Bu ders bu iki davranışı ölçer.

#### Eşleşme Modeli

Model intent'i eylem, veri şeması ve isteğe bağlı bir varsayılan seçimle kurar. İlk yardımcı dosya eşleşme kuralını ve sonuç kuralını, ikincisi örnek uygulamaların süzgeçlerini taşır.

```kotlin
// Resolver.kt — intent suzgecleriyle eslesmeyi ve sonucu kurar
class Filter(val app: String, val action: String, val categories: Set<String>, val scheme: String?)

fun matches(filter: Filter, action: String, scheme: String?): Boolean =
    filter.action == action && "DEFAULT" in filter.categories && filter.scheme == scheme

fun resolve(action: String, scheme: String?, filters: List<Filter>, chosen: String?): Outcome {
    val hits = filters.filter { matches(it, action, scheme) }.map { it.app }.sorted()
    return when {
        hits.isEmpty() -> Outcome.Refused("eslesme-yok")
        hits.size == 1 -> Outcome.Given(hits[0])
        chosen != null && chosen in hits -> Outcome.Given(chosen)
        else -> Outcome.Waiting("kullanici-secimi")
    }
}

fun resolveExplicit(app: String, installed: Set<String>): Outcome =
    if (app in installed) Outcome.Given(app) else Outcome.Refused("uygulama-yok")
```

```kotlin
// Installed.kt — ornek uygulamalarin suzgecleri
val galeri = Filter("galeri", "VIEW", setOf("DEFAULT"), "image")
val duzenleyici = Filter("duzenleyici", "VIEW", setOf("DEFAULT"), "image")
val kamera = Filter("kamera", "VIEW", setOf("DEFAULT"), "image")
val harita = Filter("harita", "VIEW", setOf("DEFAULT"), "geo")
val tarayici = Filter("tarayici", "VIEW", setOf("DEFAULT", "BROWSABLE"), "https")
val sadeceTarama = Filter("sade-tarama", "VIEW", setOf("BROWSABLE"), "image")
```

`resolve` platformun bütün ayrıntısını modellemez; üç ölçütü (eylem, `DEFAULT` kategorisi, şema) ve sonuç kuralını taşır. Sonuç kuralı şudur: sıfır eşleşme karşılıksız, tek eşleşme o uygulama, birden çok eşleşmede varsayılan seçili ve eşleşenler arasındaysa o uygulama, değilse kullanıcının seçimini bekleyen karşılama.

**UB1.** Model veri ölçütünü yalnız şemayla kurar. Veri türü (`mimeType`), yol örüntüleri ve birden çok `<data>` öğesinin birleşimi ölçülmedi. Kategorilerden yalnız `DEFAULT` denetlenir.

#### Açık ve Örtük Intent

İlk ölçüm dört istek yapar. Açık intent kurulu uygulamayı doğrudan adıyla çağırır. Örtük intent bir şema ve eylemle eşleşme arar. Son iki istek karşılıksız kalır: biri kurulu olmayan bir uygulamayı açıkça ister, diğeri hiçbir uygulamanın süzgecinde bulunmayan bir şemayı gönderir.

```kotlin
// Main.kt — acik ve ortuk intent
fun main() {
    val installed = setOf("galeri", "harita", "tarayici")
    val filters = listOf(galeri, harita, tarayici)
    val asks = listOf(
        Ask("acik", "harita", resolveExplicit("harita", installed)),
        Ask("ortuk-geo", "harita", resolve("VIEW", "geo", filters, null)),
        Ask("ortuk-ftp", "tarayici", resolve("VIEW", "ftp", filters, null)),
        Ask("acik-yok", "muzik", resolveExplicit("muzik", installed)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
acik: istenen=harita | verilen=harita -> tam
ortuk-geo: istenen=harita | verilen=harita -> tam
ortuk-ftp: istenen=tarayici | neden=eslesme-yok -> karsiliksiz
acik-yok: istenen=muzik | neden=uygulama-yok -> karsiliksiz
tam: 2
ikame: 0
bekleyen: 0
karsiliksiz: 2
```

Açık intent ve eşleşen örtük intent tam karşılandı: ikisi de aynı uygulamaya ulaştı. `ftp` şeması hiçbir süzgeçte olmadığı için örtük intent karşılıksız kaldı; kurulu olmayan uygulamayı adıyla çağıran açık intent de öyle. İki karşılıksız arasındaki fark nedende görünür: `eslesme-yok` platformun aradığı eşleşmenin olmayışıdır, `uygulama-yok` ise hedefin kendisinin olmayışıdır.

Pratik sonuç: örtük intent'i gönderen kod, eşleşme olmayabileceğini hesaba katmak zorundadır. Platform bu durumda `ActivityNotFoundException` fırlatır. Gönderenin önceden `resolveActivity` ya da paket görünürlük bildirimiyle kontrol etmesi gerekir.

**UB2.** Modelde paket görünürlüğü yoktur. Gerçekte bir uygulama, başka uygulamaların kurulu olup olmadığını görmek için bildirim dosyasında `<queries>` öğesi bildirmek zorundadır; bu ölçülmedi.

#### Süzgeçte Eksik Kategori

Süzgeç doğru eylemi ve doğru şemayı taşıyabilir ve yine de intent'e ulaşmaz. Eksik `DEFAULT` kategorisi bunun en yaygın nedenidir. `sade-tarama` uygulaması `VIEW` eylemini ve `image` şemasını bildirmiştir, ama süzgecinde yalnız `BROWSABLE` kategorisi vardır.

```kotlin
// Main.kt — varsayilan kategori
fun main() {
    val asks = listOf(
        Ask("kategori-var", "galeri", resolve("VIEW", "image", listOf(galeri), null)),
        Ask("kategori-yok", "sade-tarama", resolve("VIEW", "image", listOf(sadeceTarama), null)),
        Ask("eylem-yok", "galeri", resolve("SEND", "image", listOf(galeri), null)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
kategori-var: istenen=galeri | verilen=galeri -> tam
kategori-yok: istenen=sade-tarama | neden=eslesme-yok -> karsiliksiz
eylem-yok: istenen=galeri | neden=eslesme-yok -> karsiliksiz
tam: 1
ikame: 0
bekleyen: 0
karsiliksiz: 2
```

`DEFAULT` kategorisini taşıyan süzgeç tam karşılandı. Kategoriyi taşımayan süzgeç, eylemi ve şeması doğru olduğu hâlde karşılıksız kaldı. Eylemi tutmayan intent de karşılıksız kaldı. İkisinin nedeni aynı etiketle (`eslesme-yok`) görünür; sebebi bulmak için süzgecin üç ölçütüne tek tek bakılır.

**UB3.** `BROWSABLE` kategorisi yalnız tarayıcıdan gelen bağlantılar için gerekir ve `startActivity` ile gelen örtük intent'e kendiliğinden eklenmez. Model bu ayrımı tek bir kuralla, `DEFAULT` denetimiyle kurar.

#### Birden Çok Eşleşme

İki uygulama aynı intent'i karşılayabiliyorsa platform seçimi kullanıcıya bırakır. Seçim ekranı gösterilmiştir ama kullanıcı henüz seçmemiştir: istek ne karşılanmış ne de reddedilmiştir. Bu **bekleyen** karşılamadır ve beklediği koşul `kullanici-secimi`dir.

```kotlin
// Main.kt — tek eslesme, cok eslesme, varsayilan
fun main() {
    val filters = listOf(galeri, duzenleyici, harita)
    val asks = listOf(
        Ask("tek-eslesme", "harita", resolve("VIEW", "geo", filters, null)),
        Ask("cok-eslesme", "galeri", resolve("VIEW", "image", filters, null)),
        Ask("varsayilan", "galeri", resolve("VIEW", "image", filters, "galeri")),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
tek-eslesme: istenen=harita | verilen=harita -> tam
cok-eslesme: istenen=galeri | bekledigi=kullanici-secimi -> bekleyen
varsayilan: istenen=galeri | verilen=galeri -> tam
tam: 2
ikame: 0
bekleyen: 1
karsiliksiz: 0
```

Tek eşleşme tam. `image` şemasına iki uygulama uyduğu için ikinci istek bekleyen kaldı; çağıran tarafın kodu seçim ekranını göremez, yalnız `startActivity` döndüğünü görür. Üçüncü istekte kullanıcı `galeri`yi varsayılan yapmıştır ve platform seçim sormadan o uygulamayı başlattı: tam.

M12/K04'te "ertelendi" ile "bekleyen" ayrı tutuldu. Burada da aynı ayrım geçerlidir: seçim ekranı bir erteleme değil, adı belli bir koşulu bekleyen karşılamadır. Koşul `kullanici-secimi` olarak taşınır ve koşul karşılandığında sonuç tam ya da ikame olur.

**UB4.** Model varsayılan seçimi tek bir ad olarak taşır. Gerçekte varsayılan, intent türüne ve veri şemasına göre ayrı ayrı kaydedilir; bu ölçülmedi.

#### Varsayılan Uygulama Kaldırılınca

Varsayılan seçili uygulama kaldırılabilir. Bu durumda aynı intent artık başka sonuç verir. Kullanıcı `galeri`yi varsayılan yapmıştır; sonra `galeri` silinir. Geriye `duzenleyici` kalırsa tek eşleşme olur ve intent ona gider: kullanıcı galeriyi istiyordu, düzenleyici verildi. Geriye iki uygulama kalırsa seçim yeniden sorulur.

```kotlin
// Main.kt — varsayilan uygulama kaldirilinca
fun main() {
    val asks = listOf(
        Ask("duruyor", "galeri", resolve("VIEW", "image", listOf(galeri, duzenleyici), "galeri")),
        Ask("tek-kaldi", "galeri", resolve("VIEW", "image", listOf(duzenleyici), "galeri")),
        Ask("iki-kaldi", "galeri", resolve("VIEW", "image", listOf(duzenleyici, kamera), "galeri")),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
}
```

```text
duruyor: istenen=galeri | verilen=galeri -> tam
tek-kaldi: istenen=galeri | verilen=duzenleyici -> ikame
iki-kaldi: istenen=galeri | bekledigi=kullanici-secimi -> bekleyen
tam: 1
ikame: 1
bekleyen: 1
karsiliksiz: 0
```

Varsayılan duruyorsa tam. Varsayılan kalkıp geriye tek uygulama kalınca istek **ikame** edildi: intent hata vermedi, ama istenen uygulamaya gitmedi. Geriye iki uygulama kalınca kullanıcı yeniden sormaya geri döner: bekleyen. Aynı intent, aynı kod, kurulu uygulama kümesinin değişmesiyle üç farklı karşılama alabildi.

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

Aynı intent, aynı süzgeçler: yalnız kurulu uygulama sayısı değişir. Sıfır uygulama karşılıksız, bir uygulama tam, iki uygulama bekleyen.

```kotlin
// Main.kt — ayni intent, uc kurulu uygulama kumesi
fun main() {
    val asks = listOf(
        Ask("kurulu-yok", "galeri", resolve("VIEW", "image", emptyList(), null)),
        Ask("tek-kurulu", "galeri", resolve("VIEW", "image", listOf(galeri), null)),
        Ask("iki-kurulu", "galeri", resolve("VIEW", "image", listOf(galeri, duzenleyici), null)),
    )
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(asks.map { it.fulfillment })
}
```

```text
kurulu-yok: istenen=galeri | neden=eslesme-yok -> karsiliksiz
tek-kurulu: istenen=galeri | verilen=galeri -> tam
iki-kurulu: istenen=galeri | bekledigi=kullanici-secimi -> bekleyen
tam: 1
ikame: 0
bekleyen: 1
karsiliksiz: 1
[karsiliksiz, tam, bekleyen]
```

Üç istek, üç farklı karşılama. Bu dersin karşılaması **bekleyen**dir, çünkü örtük intent'in sonucu intent'ten değil, aygıttaki kurulu uygulamalardan çıkar. Gönderen kod hangi sonucun geleceğini önceden bilemez; tek güvenilir yol `resolveActivity` ile bakmak ve her üç sonuca da hazır olmaktır.

**UB5.** Model uygulamaları aygıtta kurulu bir küme olarak kabul eder. Çok kullanıcılı profiller, iş profili ve uygulamanın etkin ya da devre dışı olması ölçülmedi.

#### Özet

- Bu dersin ölçütü: **karşılama**. Açık intent hedefini adıyla verir, örtük intent eylem, kategori ve veriyle eşleşme arar. Bu dersin karşılaması: **bekleyen**; örtük intent'in sonucu kurulu uygulamalara bağlıdır.
- `startActivity` örtük intent'e `DEFAULT` kategorisini ekler; süzgeçte bu kategori yoksa eşleşme olmaz: karşılıksız.
- Birden çok uygulama eşleşirse karşılama bekleyen kalır ve koşulu `kullanici-secimi`dir. Varsayılan seçiliyse tam.
- Varsayılan uygulama kaldırılınca tek kalan uygulama ikame, iki kalan bekleyen sonucu verir.
- Aynı intent kurulu uygulama kümesine göre üç farklı karşılama alır: karşılıksız, tam, bekleyen.

#### Sonraki Adım

Intent ile bir activity başlatılınca platform onu bir göreve yerleştirir. Sıradaki ders, `Görevler ve Geri Yığını`, başlatma kiplerinin yeni bir örnek mi oluşturacağını yoksa var olanı mı kullanacağını ve geri yığınının her adımda nasıl göründüğünü ölçer.

### Görevler ve Geri Yığını

Source: https://academia.sh/tr/kurslar/android-platformu/gorevler-ve-geri-yigini

Görevin ve geri yığınının yapısı, başlatma kiplerinin yeni örnek mi var olan örnek mi kullanacağı, aynı kipin yığıtın durumuna göre iki ayrı karşılama alması ve CLEAR_TOP bayrağının kenar durumu.

Intent ile bir activity başlatılınca platform onu bir yere yerleştirir. Önceki derste bir intent'in hangi uygulamaya ulaştığını ölçtünüz; bu derste ulaştıktan sonra ne olduğuna bakılır: yeni bir ekran mı açılır, var olan ekran mı öne gelir, ve geri tuşu sonra nereye götürür.

Platformda activity'ler **görev** (task) içinde toplanır. Görev, kullanıcının bir işi yaparken sırayla gezdiği activity'lerin topluluğudur: bir e-posta yazarken gönderen seçme ekranı, ek dosya ekranı ve yazı ekranı aynı görevdedir. Görevin içindeki activity'ler bir **geri yığınında** (back stack) sıralanır. Yeni açılan activity yığının tepesine konur; geri tuşu tepedeki activity'yi yığından çıkarır ve bir alttakini öne getirir. Yığının en altındaki activity çıkarılınca görev biter.

Yığın bir **yığıt** (stack) gibi çalışır: son giren ilk çıkar. Bu derste yığıtı bir liste olarak göstereceksiniz ve sırası basılacak. Sol uç en alttaki activity'dir, sağ uç tepedir.

Dersin sorusu: aynı activity'yi yeniden açma isteği geldiğinde platform isteği nasıl **karşılıyor**? İstek her zaman "bu activity'nin yeni bir örneğini aç"tır. Platform bu isteği başlatma kipine göre ya yeni örnekle, ya da var olan örneği kullanarak yerine getirir. Birincisi tam, ikincisi ikame karşılamadır.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

#### Başlatma Kipi

Başlatma kipi (`launchMode`), bir activity'nin başlatılma isteğinde yeni örnek mi açılacağını yoksa var olan örneğin mi kullanılacağını belirler. Kip bildirim dosyasında activity için bildirilir.

```xml
<activity android:name=".DetailActivity" android:launchMode="singleTop" />
```

Dört kip vardır.

- **standard**: varsayılan kiptir. Her istek, activity'nin yeni bir örneğini görevin tepesine koyar. Aynı activity yığında birden çok kez bulunabilir.
- **singleTop**: hedef activity zaten yığının tepesindeyse yeni örnek açılmaz, var olan örneğe `onNewIntent` geri çağrısıyla yeni intent iletilir. Tepede değilse standard gibi yeni örnek açılır.
- **singleTask**: hedef activity görevde bir yerde varsa yeni örnek açılmaz. Var olan örnek öne getirilir ve onun üstündeki bütün activity'ler yığından temizlenir. Yoksa yeni örnek açılır.
- **singleInstance**: singleTask gibidir, ama activity kendi görevinde tek başına çalışır ve o görevde başka activity barındırmaz. Bu ders bu kipi ölçmez.

Kip yalnız bildirim dosyasında değil, intent bayraklarıyla da belirlenir. `FLAG_ACTIVITY_CLEAR_TOP` bayrağı hedef activity yığındaysa onun üstündekileri temizler. `FLAG_ACTIVITY_NEW_TASK` ise activity'yi yeni bir göreve koyar.

```kotlin
// Platform kodu: bayrakla baslatma
import android.content.Intent

val intent = Intent(this, DetailActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(intent)
```

#### Yığıt Modeli

Model yığıtı bir liste olarak tutar. Yardımcı dosya başlatma kipini tek bir fonksiyonda kurar: yığıt, hedef ve kip verilir; yeni yığıt ve ne olduğu döner. Ne olduğu iki değerden biridir: `yeni-ornek` ya da `var-olan-ornek`. İstenen her zaman `yeni-ornek`tir; çünkü çağıran kod activity'yi açmak istemektedir.

```kotlin
// Tasks.kt — baslatma kiplerini ve ardisik istekleri kurar
class Launch(val stack: List<String>, val how: String)

fun launchActivity(stack: List<String>, target: String, mode: String): Launch {
    val at = stack.lastIndexOf(target)
    return when (mode) {
        "standard" -> Launch(stack + target, "yeni-ornek")
        "singleTop" ->
            if (stack.lastOrNull() == target) Launch(stack, "var-olan-ornek")
            else Launch(stack + target, "yeni-ornek")
        "singleTask" ->
            if (at >= 0) Launch(stack.take(at + 1), "var-olan-ornek")
            else Launch(stack + target, "yeni-ornek")
        "standard-clearTop" ->
            if (at >= 0) Launch(stack.take(at) + target, "yeni-ornek")
            else Launch(stack + target, "yeni-ornek")
        "singleTop-clearTop" ->
            if (at >= 0) Launch(stack.take(at + 1), "var-olan-ornek")
            else Launch(stack + target, "yeni-ornek")
        else -> Launch(stack, "tanimsiz-kip")
    }
}

fun runLaunches(start: List<String>, targets: List<String>, mode: String): Pair<List<Ask>, List<String>> {
    var stack = start
    val asks = mutableListOf<Ask>()
    for ((index, target) in targets.withIndex()) {
        val launch = launchActivity(stack, target, mode)
        asks.add(Ask("${index + 1}-$mode-$target", "yeni-ornek", Outcome.Given(launch.how)))
        stack = launch.stack
    }
    return Pair(asks, stack)
}
```

`runLaunches` ardışık istekleri sırayla uygular ve her isteği bir `Ask` olarak toplar. İstek etiketi adım numarasını, kipi ve hedefi taşır. Yığıt her adımdan sonra güncellenir.

**UB1.** Model görev başına tek yığın varsayar ve `NEW_TASK` bayrağını, `taskAffinity` özniteliğini ve çok görevli durumları ölçmez. Yığındaki activity'ler yalnız birer ad olarak tutulur; durumları, yaşam döngüsü geri çağrıları ve yok edilmeleri modellenmez.

#### standard Kipi

`standard` kipinde her istek yeni örnek açar. A açıktır; B, sonra A, sonra yine A başlatılır. A'nın yığında üç kez bulunması geçerlidir.

```kotlin
// Main.kt — standard kip
fun main() {
    val (asks, stack) = runLaunches(listOf("A"), listOf("B", "A", "A"), "standard")
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(stack.joinToString(" > "))
}
```

```text
1-standard-B: istenen=yeni-ornek | verilen=yeni-ornek -> tam
2-standard-A: istenen=yeni-ornek | verilen=yeni-ornek -> tam
3-standard-A: istenen=yeni-ornek | verilen=yeni-ornek -> tam
tam: 3
ikame: 0
bekleyen: 0
karsiliksiz: 0
A > B > A > A
```

Üç istek de tam karşılandı: her biri yeni örnek açtı. Yığın dört elemana çıktı ve A üç kez bulunuyor. Kullanıcı bu yığında ilk A'ya geri tuşuna üç kez basarak ulaşır ve dört elemanın her biri ayrı bir örnektir. Bu, standard kipin getirdiği bedeldir: aynı ekranı tekrar tekrar açan kod yığını sessizce büyütür.

#### singleTop Kipi

`singleTop` hedef zaten tepedeyse yeni örnek açmaz. Detay ekranından kendine bağlantı veren bir akış ya da bir kullanıcı bildirimine arka arkaya iki kez dokunmak bu duruma örnektir. Yığın A, B ile başlar; B'ye iki, C'ye iki istek gelir.

```kotlin
// Main.kt — singleTop kip
fun main() {
    val (asks, stack) = runLaunches(listOf("A", "B"), listOf("B", "C", "C"), "singleTop")
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(stack.joinToString(" > "))
}
```

```text
1-singleTop-B: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame
2-singleTop-C: istenen=yeni-ornek | verilen=yeni-ornek -> tam
3-singleTop-C: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame
tam: 1
ikame: 2
bekleyen: 0
karsiliksiz: 0
A > B > C
```

B zaten tepedeydi; ilk istek var olan örneği kullandı ve karşılama ikame oldu. C tepede değildi, yeni örnek açıldı ve tam karşılandı. Üçüncü istekte C artık tepededir, bu kez ikame. Yığın A, B, C olarak kaldı; ikame edilen iki istek yığını büyütmedi. Çağıran taraf yeni intent'ini `onNewIntent` geri çağrısında alır ve ekranını güncellemelidir. Yeni örnek açıldığını varsayan bir kod burada yanlış çalışır.

**UB2.** Modelde `onNewIntent`'in çağrılıp çağrılmadığı ölçülmez; yalnız yığına ne olduğu ölçülür. Var olan örneğe intent'in nasıl iletildiği ve ekranın güncellenip güncellenmediği ölçülmedi.

#### singleTask Kipi

`singleTask` hedef yığında herhangi bir yerdeyse onu öne getirir ve üstündekileri temizler. A, B, C yığınında B'ye bir istek gelirse C yığından çıkar; B tepe olur. Sonra D açılır, sonra yine B istenir: D de temizlenir.

```kotlin
// Main.kt — singleTask kip
fun main() {
    val (asks, stack) = runLaunches(listOf("A", "B", "C"), listOf("B", "D", "B"), "singleTask")
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(stack.joinToString(" > "))
}
```

```text
1-singleTask-B: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame
2-singleTask-D: istenen=yeni-ornek | verilen=yeni-ornek -> tam
3-singleTask-B: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame
tam: 1
ikame: 2
bekleyen: 0
karsiliksiz: 0
A > B
```

İlk ve üçüncü istek var olan B'yi kullandı ve ikame oldu; ikincisi D yığında olmadığı için yeni örnekle tam karşılandı. Yığın sonunda A, B. İstekler yığına C ve D'yi eklemiş, sonra silmiştir: `singleTask` yalnız yeni örnek açmamakla kalmaz, kullanıcının B'ye dönmeden önce gezdiği ekranları da atar. Bu, ana ekran ya da giriş noktası gibi görevin kökünde durması gereken ekranlar için tasarlanmıştır.

#### CLEAR_TOP Bayrağının Kenar Durumu

`FLAG_ACTIVITY_CLEAR_TOP` bayrağı singleTask'ın yığın temizleme davranışını bir bayrağa taşır, ama var olan örneğe ne olacağı kipe bağlıdır. Hedef `standard` kipindeyse platform var olan örneği de yok edip yeni bir örnek oluşturur. Hedef `singleTop` kipindeyse ya da bayrakla birlikte `FLAG_ACTIVITY_SINGLE_TOP` verilmişse var olan örnek korunur ve `onNewIntent` çağrılır.

İki istek aynı yığına aynı hedefi gönderir: A, B, C yığınında B.

```kotlin
// Main.kt — CLEAR_TOP ve kip
fun main() {
    val standardClear = runLaunches(listOf("A", "B", "C"), listOf("B"), "standard-clearTop")
    val singleTopClear = runLaunches(listOf("A", "B", "C"), listOf("B"), "singleTop-clearTop")
    val asks = standardClear.first + singleTopClear.first
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(standardClear.second.joinToString(" > "))
    println(singleTopClear.second.joinToString(" > "))
}
```

```text
1-standard-clearTop-B: istenen=yeni-ornek | verilen=yeni-ornek -> tam
1-singleTop-clearTop-B: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 0
A > B
A > B
```

İki yığın aynı: A, B. Karşılamalar farklı: standard ile yeni örnek açıldı ve tam, singleTop ile var olan örnek korundu ve ikame. Yığıtın dışarıdan görünen hâli iki durumda da aynıdır; fark örneğin kimliğinde ve onun durumunda yatar. Standard kipte ekranın içindeki kaydedilmemiş durum, örnek yenilendiği için kaybolur.

Bu ince farkın nedeni yığıtın değil, örneğin ölçülmesidir. Karşılama kategorisi "yığın nasıl görünüyor" sorusuna değil, "istek nasıl karşılandı" sorusuna bakar ve ikisi birbirinden bağımsız çıkar.

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

Aynı kip, aynı hedef; yalnız yığının durumu değişir. `singleTop` kipinde B hedeftir. Birinde B tepededir, ötekinde C'nin altında kalmıştır. `singleTask` ile karşılaştırma için üçüncü bir durum eklenir: kip singleTask, B yine C'nin altındadır.

```kotlin
// Main.kt — ayni kip, iki yigit durumu
fun main() {
    val cases = listOf(
        Triple("singleTop-tepede", listOf("A", "B"), "singleTop"),
        Triple("singleTop-tepede-degil", listOf("A", "B", "C"), "singleTop"),
        Triple("singleTask-tepede-degil", listOf("A", "B", "C"), "singleTask"),
    )
    val asks = mutableListOf<Ask>()
    for ((label, stack, mode) in cases) {
        val launch = launchActivity(stack, "B", mode)
        asks.add(Ask(label, "yeni-ornek", Outcome.Given(launch.how)))
        println("$label yigit: ${launch.stack.joinToString(" > ")}")
    }
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(asks.map { it.fulfillment }.distinct().sorted())
}
```

```text
singleTop-tepede yigit: A > B
singleTop-tepede-degil yigit: A > B > C > B
singleTask-tepede-degil yigit: A > B
singleTop-tepede: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame
singleTop-tepede-degil: istenen=yeni-ornek | verilen=yeni-ornek -> tam
singleTask-tepede-degil: istenen=yeni-ornek | verilen=var-olan-ornek -> ikame
tam: 1
ikame: 2
bekleyen: 0
karsiliksiz: 0
[ikame, tam]
```

`singleTop` kipi iki yığıtta iki ayrı karşılama aldı. B tepedeyse ikame: yığın değişmedi. B tepede değilse tam: yığın A, B, C, B oldu ve B iki örnekle yığındadır. `singleTask` ise aynı yığında B'yi öne getirdi ve C'yi attı: ikame.

Bu ölçümün söylediği şudur: kip tek başına karşılamayı belirlemez. Karşılama, kip ile yığıtın o anki durumunun birlikte işleyen kuralından çıkar. Aynı kodu iki farklı anda çalıştıran bir uygulama, kullanıcının nereden geldiğine göre iki farklı davranış görür. Bu dersin karşılaması **ikame**dir: aynı activity'yi açma isteği, platformca çoğu zaman var olan örnekle yerine getirilir.

**UB3.** Model hedef örneğin yığıtta yalnız bir kez bulunduğu durumları ölçer. Hedefin iki örneği yığındaysa `singleTop` ve `CLEAR_TOP` kurallarının hangi örneği seçtiği, ve görevler arası geçişler (örneğin `singleInstance`) ölçülmedi.

#### Özet

- Bu dersin ölçütü: **karşılama**. Activity'ler görevde, görev de geri yığınında toplanır. Başlatma kipi, aynı activity'yi açma isteğinin yeni örnekle mi var olan örnekle mi karşılanacağını belirler. Bu dersin karşılaması: **ikame**; aynı activity'yi açma isteği çoğu zaman var olan örnekle yerine getirilir.
- `standard` her istekte yeni örnek açar: 3 tam. `singleTop` hedef tepedeyse var olan örneği kullanır: ikame.
- `singleTask` hedef yığındaysa onu öne getirir ve üstündekileri temizler: ikame; yığında yoksa yeni örnek, tam.
- `CLEAR_TOP` ile `standard` kipinde var olan örnek de yenilenir (tam), `singleTop` kipinde korunur (ikame). Yığıtın dışarıdan görünüşü aynıdır.
- Aynı kip yığıtın durumuna göre iki karşılama alır: `singleTop` tepede ikame, tepede değilse tam.

#### Sonraki Adım

Bir ekranın hangi göreve ve hangi sıraya yerleştiğini ölçtünüz. O ekran, bir uygulama bileşeni olarak platforma önceden tanıtılmadıkça hiç başlatılamaz. Bu tanıtmanın yeri bildirim dosyasıdır; sıradaki ders, `Bildirim Dosyası`, orada ne yazdığını ve birden çok kaynaktan gelen dosyaların nasıl birleştiğini ölçer.

## Proje Yapısı ve Araçlar

Bileşen ve izin bildiren bildirim dosyası, niteleyicili kaynaklar ve yapılandırma değişiklikleri, derleme betikleri, bağımlılıklar ve derleme türleri, ürün çeşitleri ve derleme varyantları, emülatör, aygıt ve profil araçlarıyla geliştirme ortamı, kurulum ve çalışma zamanı izinleri.

### Bildirim Dosyası

Source: https://academia.sh/tr/kurslar/android-platformu/bildirim-dosyasi

Bildirim dosyasının uygulamayı platforma tanıtma işlevinin ve birleştirmenin her öğe için istenenin korunup korunmadığı açısından modeli.

Önceki ders olan Görevler ve Geri Yığını, bir ekranın hangi göreve ve hangi sıraya yerleştiğini ölçtü. O ekran, bir uygulama bileşeni olarak platforma önceden tanıtılmadıkça hiç başlatılamaz. Bu tanıtmanın yeri bildirim dosyasıdır. Bu ders bildirim dosyasında ne yazdığını ve birden çok kaynaktan gelen bildirim dosyalarının tek dosyada nasıl birleştiğini ölçer.

Kursun ölçü ekseni açılış dersindeki gibidir: bu isteği platform nasıl karşılıyor. Karşılama dörttür: tam, ikame, bekleyen ve karşılıksız. Karşılamayı etiket değil kural verir: bekleyen ve karşılıksız doğrudan sonuçtan, verilen değer istenene eşitse tam, eşit değilse ikame çıkar. Aşağıdaki dosya o kuralı taşır ve her derste aynen yinelenir. Açılış dersindeki bağlayıcı sayı olan 3/2/2/1 burada değiştirilmez, yalnız atıfla anılır.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

#### Bildirim Dosyasının İşlevi

Bildirim dosyası (manifest) uygulamanın platforma verdiği yazılı beyandır. Platform uygulamayı çalıştırmadan önce bu dosyaya bakar ve dört şeyi öğrenir. Birincisi uygulama kimliğidir: uygulamayı aygıtta tekil kılan paket adı. İkincisi uygulama bileşenleridir: ekranlar, arka plan işleri, yayın alıcıları ve içerik sağlayıcılar burada bildirilmeyen bir bileşeni platform tanımaz. Üçüncüsü uygulamanın istediği izinlerdir. Dördüncüsü uygulamanın gerek duyduğu donanım özellikleridir; örneğin kamera `uses-feature` öğesiyle zorunlu ya da isteğe bağlı bildirilir. Aşağıdaki parça bu dört şeyi taşıyan küçük bir dosyadır.

```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-feature android:name="android.hardware.camera" android:required="true" />
    <uses-permission android:name="android.permission.CAMERA" />

    <application android:label="Notlar" android:allowBackup="true">
        <activity android:name=".NoteListActivity" android:exported="true" />
    </application>
</manifest>
```

Bu parça platform biçimindedir ve çalıştırılmaz. Bundan sonraki ölçümler dosyanın kendisini değil, birleştirme kuralını modeller. Model, platformun bu kuralını kurar: platformun gerçek birleştirme aracı değildir.

#### Birleştirmenin Modeli

Bir uygulama tek bildirim dosyasıyla gelmez. Uygulama modülünün dosyası vardır; eklenen her kitaplık kendi dosyasını getirir; derleme varyantına özgü dosyalar da olabilir. Derleme adımı bunları tek dosyada birleştirir. Çakışma olmadığında öğeler olduğu gibi bir araya gelir. Aynı öğenin aynı özniteliği iki dosyada farklı değerdeyse birleştirme çözümü kendiliğinden bulamaz: yazar, uygulama dosyasında öğeyi `tools:replace` ile işaretleyip üstüne yazacağını bildirmedikçe birleştirme hatayla durur.

Model bunu öğe başına bir istek olarak kurar. İstenen, kitaplığın öğe için yazdığı değerdir. Uygulama o öğeye dokunmamışsa ya da aynı değeri yazmışsa kitaplığın değeri korunur. Uygulama farklı bir değer yazmış ve öğeyi `tools:replace` ile işaretlemişse verilen değer uygulamanınkidir. İşaret yoksa çakışma kalır ve değer verilmez. `mergeManifest` bunu uygular; tek katman olarak yalnız uygulama ile kitaplık alınır.

```kotlin
// Merge.kt — bildirim dosyasi birlestirmesi modeli
class Layer(val values: Map<String, String>, val replaced: Set<String> = emptySet())

fun mergeManifest(app: Layer, library: Layer): List<Ask> =
    library.values.toSortedMap().map { (key, wanted) ->
        val own = app.values[key]
        val outcome = when {
            own == null || own == wanted -> Outcome.Given(wanted)
            key in app.replaced -> Outcome.Given(own)
            else -> Outcome.Refused("isaretsiz-cakisma")
        }
        Ask(key, wanted, outcome)
    }
```

Anahtarlar `öğe.öznitelik` biçimindedir ve basılmadan önce sıralanır. İlk ölçümde kitaplık iki öğe getirir: kamerayı zorunlu bildiren donanım özelliği ve sağdan sola yazımı destekleyen bir uygulama özniteliği. Uygulama bu ikisine dokunmaz, yalnız kendi etiketini ve kendi ekranını yazar.

```kotlin
// Main.kt — cakismasiz birlestirme
fun main() {
    val app = Layer(mapOf(
        "application.label" to "Notlar",
        "activity.NoteListActivity" to "exported",
    ))
    val library = Layer(mapOf(
        "uses-feature.camera" to "required",
        "application.supportsRtl" to "true",
    ))
    val asks = mergeManifest(app, library)
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
application.supportsRtl: istenen=true | verilen=true -> tam
uses-feature.camera: istenen=required | verilen=required -> tam
tam: 2
ikame: 0
bekleyen: 0
karsiliksiz: 0
```

İki öğe de istenen gibi korundu. Ölçü yalnız `tam` sayısını değil, her öğenin kendi satırını verir: kitaplığın istediği bir değerin sessizce kaybolmadığı satırda görülür.

**AP1.** İlk iki ölçümde model yalnız iki katmanı, uygulama ve tek bir kitaplığı, birleştirir; üç katman sonraki bölümde ayrıca ölçülür. Birden çok kitaplık ya da derleme varyantı dosyası olduğunda öncelik sırasının nasıl işlediği bu ölçümlerde yoktur.

**AP2.** Her öğe tek bir anahtar ve tek bir değerle temsil edilir. Gerçek bildirim dosyasında bir öğenin birden çok özniteliği vardır; model bunları ayrı anahtarlar sayar.

#### Üstüne Yazma ve Çakışma

İkinci ölçüm çakışmaları içerir. Kitaplık üç öğe getirir. `application.label` için uygulama aynı değeri yazmıştır. `application.allowBackup` için kitaplık `true` ister, uygulama `false` yazmıştır ve öğeyi `tools:replace` ile işaretlemiştir. `application.theme` için de değerler farklıdır ama uygulama öğeyi işaretlememiştir.

```kotlin
// Main.kt — ustune yazma ve cakisma
fun main() {
    val app = Layer(
        values = mapOf(
            "application.label" to "Notlar",
            "application.allowBackup" to "false",
            "application.theme" to "Koyu",
        ),
        replaced = setOf("application.allowBackup"),
    )
    val library = Layer(mapOf(
        "application.label" to "Notlar",
        "application.allowBackup" to "true",
        "application.theme" to "Acik",
    ))
    val asks = mergeManifest(app, library)
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
application.allowBackup: istenen=true | verilen=false -> ikame
application.label: istenen=Notlar | verilen=Notlar -> tam
application.theme: istenen=Acik | neden=isaretsiz-cakisma -> karsiliksiz
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

Üç öğe üç ayrı karşılamaya düştü. `allowBackup` ikamedir: bir değer verildi, ama kitaplığın istediği değil. Kural bunu `true` ile `false` farkından görür, yazarın bilerek yaptığını bilmez. `theme` karşılıksızdır: değer verilmedi, çünkü işaretsiz çakışma birleştirmeyi durdurur. Kuralın gördüğü yalnız sonuç ve eşitliktir; `tools:replace`'in orada olup olmadığı karşılamayı doğrudan değil, verilen değer üzerinden etkiler.

M30/K02'de iki tanım buluşunca nasıl çözüldüğü sorulmuş, o kursun izin dersleri istenen ile verilen kümelerin buluşmasını ölçmüştü. Bu derste ölçü o değildir: iki kümenin buluşması değil, her öğenin ayrı bir istek olarak istenenin korunup korunmadığı ölçülür. Aynı iki dosya aynı anahtar kümesine sahip olabilir ve yine de öğelerin bir kısmı tam, bir kısmı ikame olur.

#### Birleştirme Önceliği

Bildirim dosyası tek bir dosya değildir. Derleme sırasında uygulamanın ana dosyası, derleme varyantının dosyaları ve her kitaplığın kendi dosyası birleştirilir. Öncelik sırası kitaplıktan varyanta doğru artar: kitaplık en düşük önceliktedir, uygulamanın ana dosyası ortada durur ve derleme varyantının dosyaları en yüksek önceliktedir. Düşük öncelikli katmanın yazdığı değeri yüksek öncelikli katman aynen bırakabilir, aynı değerle yineleyebilir ya da farklı bir değerle işaretleyerek ezebilir. Aşağıdaki parça iki işareti gösterir: `tools:replace` öznitelik değerini ezer, `tools:node="remove"` öğeyi birleşimden çıkarır.

```xml
<application
    android:label="@string/app_name"
    tools:replace="android:label">
    <activity android:name=".LegacyScreen" tools:node="remove" />
</application>
```

Model bu önceliği üç katmanlı bir zincir olarak kurar. İstenen, en düşük katmanın yani kitaplığın yazdığı değerdir. Sonraki her katman değeri korur, aynı değeri yineler ya da farklı bir değer yazar. Farklı değer yalnız o katman öğeyi işaretlemişse geçer; işaretsiz fark çakışmadır ve zincir orada durur. Kitaplıkta hiç bulunmayan bir öğe için istenecek bir değer olmadığından model onu karşılıksız verir.

```kotlin
// Chain.kt — katmanli birlestirme modeli
fun mergeChain(key: String, layers: List<Layer>): Ask {
    val wanted = layers.first().values[key] ?: return Ask(key, "-", Outcome.Refused("kitaplikta-yok"))
    var current = wanted
    for (layer in layers.drop(1)) {
        val value = layer.values[key] ?: continue
        if (value == current) continue
        if (key in layer.replaced) current = value
        else return Ask(key, wanted, Outcome.Refused("isaretsiz-cakisma"))
    }
    return Ask(key, wanted, Outcome.Given(current))
}
```

İlk ölçümde üç katman vardır ve sıra kitaplık, ana dosya, varyanttır. Kitaplık üç öğe yazar. Uygulamanın ana dosyası bir öğeyi aynı değerle yineler ve bir öğeyi işaretsiz farklı bir değerle yazar. Varyant bir öğeyi işaretleyerek ezer.

```kotlin
// Main.kt — uc katman
fun main() {
    val library = Layer(mapOf("etiket" to "kitapligin-adi", "yon" to "sagdan-sola", "kamera" to "zorunlu"))
    val variant = Layer(mapOf("etiket" to "varyantin-adi"), setOf("etiket"))
    val app = Layer(mapOf("yon" to "sagdan-sola", "kamera" to "istege-bagli"))
    val asks = listOf("etiket", "yon", "kamera").map { mergeChain(it, listOf(library, app, variant)) }
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
etiket: istenen=kitapligin-adi | verilen=varyantin-adi -> ikame
yon: istenen=sagdan-sola | verilen=sagdan-sola -> tam
kamera: istenen=zorunlu | neden=isaretsiz-cakisma -> karsiliksiz
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

Öğe `etiket` varyant tarafından ezilince verilen, istenenden farklı olduğu için ikamedir. Öğe `yon` hiç değişmedi ve tamdır. Öğe `kamera` işaretsiz bir farkla karşılaştı: uygulama kitaplığın zorunlu dediği donanımı isteğe bağlı yazdı ama öğeyi işaretlemedi, bu yüzden değer verilmedi.

**AP3.** Model katman sırasını sabit alır: kitaplık, uygulamanın ana dosyası, varyant. Gerçek birleştirmede birden çok kitaplığın ve birden çok varyant dosyasının kendi arasındaki sırası ayrıca belirlenir; ölçüm bunu kapsamaz.

#### Çakışmanın Çözülmesi

İşaretsiz bir çakışma iki biçimde çözülür: ya düşük öncelikli katman değerini bırakır ya da yüksek öncelikli katman öğeyi işaretler. Kitaplık dosyasını değiştirmek çoğu zaman mümkün olmadığından çözüm uygulama tarafındadır. İşaretin yerine göre sonuç değişir: işaret ezen katmanda durursa değer ezilir, işaret araya başka bir katman girdiğinde yalnız o katmana kadar etki eder. Aşağıdaki ölçüm iki ayrı durumu karşılaştırır. Birincisinde uygulama kamera öğesini işaretleyerek ezer. İkincisinde işareti uygulamanın ana dosyası koymuştur ve varyant dosyası sonradan farklı bir değer yazar.

```kotlin
// Main.kt — isaretin yeri
fun main() {
    val library = Layer(mapOf("kamera" to "zorunlu"))
    val marked = Layer(mapOf("kamera" to "istege-bagli"), setOf("kamera"))
    val appMarked = Layer(mapOf("kamera" to "istege-bagli"), setOf("kamera"))
    val variantOther = Layer(mapOf("kamera" to "yasak"))
    val asks = listOf(
        mergeChain("kamera", listOf(library, marked)).let { Ask("uygulama-isaretli", it.wanted, it.outcome) },
        mergeChain("kamera", listOf(library, appMarked, variantOther)).let { Ask("ana-dosya-isaretli", it.wanted, it.outcome) },
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
uygulama-isaretli: istenen=zorunlu | verilen=istege-bagli -> ikame
ana-dosya-isaretli: istenen=zorunlu | neden=isaretsiz-cakisma -> karsiliksiz
tam: 0
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

İşaret yalnız onu koyan katmanın değerini korur; sonraki katmanın farklı değeri yeniden işaret ister. Kural bunu ayırt eder: iki istek aynı öğeye bakar ama ilkinde zincirin son halkası işaretlidir, ikincisinde değildir.

**AP4.** Model işareti öğe düzeyinde tutar. Gerçek dosyada işaret öznitelik düzeyinde de verilebilir ve birden çok özniteliği aynı anda kapsayabilir; ölçüm bunu ayırmaz.

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

Modelin ölçemediği bir yan var: değer bildirim dosyasında yazılı olmayabilir. Kitaplık ya da uygulama dosyası değeri `${appLabel}` gibi bir yer tutucuyla (placeholder) bırakır; gerçek değeri derleme varyantının ayarı verir. Yer tutucu çözülmeden birleştirme sonucu belli olmaz. `resolvePlaceholder` aynı öğeyi iki kez sorar: önce varyant değeri vermemişken, sonra vermişken.

```kotlin
// Placeholder.kt — yer tutucunun cozulmesi
fun resolvePlaceholder(label: String, wanted: String, token: String, provided: Map<String, String>): Ask {
    val value = provided[token]
    val outcome = if (value == null) Outcome.Waiting("derleme-varyanti") else Outcome.Given(value)
    return Ask(label, wanted, outcome)
}
```

```kotlin
// Main.kt — ayni oge, iki an
fun main() {
    val before = resolvePlaceholder("application.label", "Notlar", "appLabel", emptyMap())
    val after = resolvePlaceholder("application.label", "Notlar", "appLabel", mapOf("appLabel" to "Notlar"))
    val asks = listOf(before, after)
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
application.label: istenen=Notlar | bekledigi=derleme-varyanti -> bekleyen
application.label: istenen=Notlar | verilen=Notlar -> tam
tam: 1
ikame: 0
bekleyen: 1
karsiliksiz: 0
```

Aynı öğe önce bekleyen, sonra tam oldu. Bekleyen karşılama kalıcı bir sonuç değil, bir andır: kuralın bekleyen dediği şey, bekleneni veren gelince başka bir sonuca döner. Bu yüzden iki satır aynı etiketi taşır ve sayım iki ayrı istek gibi görür. Ölçü, yer tutucunun değerinin yanlış olup olmadığını da söylemez; yalnız verilenin istenene eşit olup olmadığına bakar.

**AP5.** Model yer tutucu çözülmediğinde derlemenin gerçekte ne ürettiğini, yani hata mı verdiğini yoksa boş bir değerle mi sürdüğünü iddia etmez. Bekleyen karşılama yalnız modelin sınıflandırmasıdır.

#### Özet

- Bu dersin ölçütü: **karşılama**; birleştirmede her öğe için istenenin korunup korunmadığı, küme değil öğe başına istek. Bu dersin karşılaması: **tam**.
- Bildirim dosyası uygulama kimliğini, uygulama bileşenlerini, izinleri ve donanım özelliklerini platforma bildirir.
- Birleştirmede kitaplığın değeri uygulama dokunmadığı ya da aynısını yazdığı sürece tam korunur.
- `tools:replace` ile üstüne yazılan öğe ikamedir: verilen değer istenenden farklıdır. İşaretsiz çakışma karşılıksızdır.
- Yer tutucu, değerini derleme varyantı verene kadar bekleyendir; verdikten sonra aynı öğe tam olur.

#### Sonraki Adım

Bildirim dosyası uygulamanın ne olduğunu bildirir; ekranlarda gösterilen metin, görsel ve düzen ise kaynaklarda durur. Sonraki ders, kaynak sisteminin istenen yapılandırma için kaynak seçerken tam eşleşme bulunamadığında ne verdiğini ölçer.

### Kaynak Sistemi

Source: https://academia.sh/tr/kurslar/android-platformu/kaynak-sistemi

Kaynak kimliğinin yapılandırmaya göre niteleyicili dizinlerden seçilmesinin, tam eşleşmenin, varsayılana düşmenin ve niteleyici önceliğinin modeli.

Önceki ders bildirim dosyasının birleştirmesini, her öğeyi ayrı bir istek sayarak ölçtü. Bildirim dosyası uygulamanın ne olduğunu bildirir; ekranda görünen metin, görsel ve düzen ise kaynaklarda durur. Kaynaklar kodun içine gömülmez, ayrı dosyalarda tutulur, çünkü aynı kaynak aygıtın diline, ekran boyutuna ve gece ya da gündüz görünümüne göre farklı bir dosyadan gelmelidir. Bu ders kaynak sisteminin istenen yapılandırma için bir kaynak seçerken ne verdiğini ölçer.

Kursun ölçü ekseni değişmez: bu isteği platform nasıl karşılıyor. Karşılama dörttür: tam, ikame, bekleyen ve karşılıksız. Karşılamayı etiket değil kural verir: verilen değer istenene eşitse tam, eşit değilse ikame; bekleyen ve karşılıksız doğrudan sonuçtan çıkar. Aşağıdaki dosya o kuralı taşır ve her derste aynen yinelenir. Açılış dersindeki 3/2/2/1 sayısı burada değiştirilmez.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

#### Kaynak Türleri ve Kaynak Kimliği

Kaynaklar `res` dizininin altında türe göre ayrılmış alt dizinlerde durur. Metinler `values` dizinindeki XML dosyalarındadır; çizilebilir görseller `drawable` dizinindedir; ekran düzenleri `layout` dizinindedir. Her kaynağın bir adı vardır ve derleme adımı bu adlardan kaynak kimliği (resource ID) üretir: koddan `R.string.title` ya da `R.drawable.logo` yazılarak ulaşılır. Kod hangi dosyadan okunacağını bilmez, yalnız kimliği bilir; dosyayı seçmek sistemin işidir.

```xml
<resources>
    <string name="title">Notes</string>
    <string name="subtitle">Everything in one place</string>
</resources>
```

Bu parça varsayılan `values` dizinindeki metin dosyasıdır. Almanca için aynı adlarla ayrı bir dosya `values-de` dizinine konabilir ve yalnız `title` kimliğini içerebilir. Dizin adındaki `-de` kısmına niteleyici (qualifier) denir. Aşağıdaki parça koddan kimlikle okumayı gösterir.

```kotlin
// Platform kodu: kimlikle metin okuma
val heading = getString(R.string.title)
```

Platform kodudur ve çalıştırılmaz. Sistem bu satırda, aygıtın o andaki yapılandırmasına uyan dizini seçer; model, platformun bu kuralını kurar. Niteleyiciler dil, bölge, gece görünümü ve en küçük ekran genişliği gibi bir yapılandırma özelliğini adlandırır: `values-de`, `values-de-rAT`, `values-night`, `layout-sw600dp`.

#### Seçim Modeli

Model beş şey bilir: kaynak anahtarı, istenen dizin adı, aygıtın yapılandırması ve dizinler. İstenen, yapılandırmanın tam adlandırdığı dizindir. Bir dizin yalnız anahtarı içeriyorsa ve niteleyicilerinin hepsi yapılandırmada varsa aday olur. Adaylar arasından önceliği en yüksek niteleyiciye sahip olan seçilir; öncelik sırası dil, bölge, ekran genişliği, gece görünümüdür. Varsayılan dizinin niteleyicisi yoktur, bu yüzden anahtarı içeriyorsa her zaman adaydır. Aday yoksa kaynak bulunamaz.

```kotlin
// Resources.kt — kaynak secimi modeli
class ResourceDir(val name: String, val keys: Set<String>) {
    val tokens: List<String> = name.split("-").drop(1)
}

fun qualifierWeight(token: String): Int = when {
    token == "night" -> 1
    token.startsWith("sw") -> 2
    token.length == 3 && token.startsWith("r") -> 4
    else -> 8
}

fun resolveResource(key: String, wanted: String, config: Set<String>, dirs: List<ResourceDir>): Ask {
    val candidates = dirs.filter { key in it.keys && it.tokens.all { token -> token in config } }
    val best = candidates.maxByOrNull { dir -> dir.tokens.sumOf { qualifierWeight(it) } }
    val outcome = if (best == null) Outcome.Refused("varsayilan-yok") else Outcome.Given(best.name)
    return Ask(key, wanted, outcome)
}
```

Ağırlıklar öncelik sırasını kurar: dil ağırlığı sekiz, bölge dört, genişlik iki, gece birdir. Bir üst niteleyicinin ağırlığı altındakilerin toplamından büyüktür; bu yüzden dil, ne kadar genişlik ve gece niteleyicisi olursa olsun önce gelir. İlk ölçüm üç kaynak ister. Metin anahtarı Almanca dizinde vardır ve yapılandırma Almancadır. Görsel gece dizininde vardır ve yapılandırma geceyi içerir. Alt başlık metni ise yalnız varsayılan dizinde bulunur; Almanca dizin onu içermez.

```kotlin
// Main.kt — tam eslesme ve varsayilana dusme
fun main() {
    val dirs = listOf(
        ResourceDir("values", setOf("string/title", "string/subtitle")),
        ResourceDir("values-de", setOf("string/title")),
        ResourceDir("drawable", setOf("drawable/logo")),
        ResourceDir("drawable-night", setOf("drawable/logo")),
    )
    val config = setOf("de", "night")
    val asks = listOf(
        resolveResource("string/title", "values-de", config, dirs),
        resolveResource("drawable/logo", "drawable-night", config, dirs),
        resolveResource("string/subtitle", "values-de", config, dirs),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
string/title: istenen=values-de | verilen=values-de -> tam
drawable/logo: istenen=drawable-night | verilen=drawable-night -> tam
string/subtitle: istenen=values-de | verilen=values -> ikame
tam: 2
ikame: 1
bekleyen: 0
karsiliksiz: 0
```

İlk iki kimlik tam eşleşen dizinden geldi. Alt başlık için istenen dizin Almancaydı ama orada yoktu; sistem varsayılan dizindeki metni verdi. Bu ikamedir ve uygulama çökmedi: varsayılan dizin bir güvenlik ağıdır.

**AP1.** Model dört niteleyici türünü bilir ve her dizinde bu türlerden en çok birer tane bekler. Gerçek niteleyici kümesi çok daha geniştir ve her türün kendi eşleşme kuralı vardır.

**AP2.** Model, bir niteleyicinin yapılandırmada bulunmasını yeterli sayar. Ekran genişliği gibi sayısal niteleyicilerde gerçek sistem eşitlik değil eşik karşılaştırır; bu ölçümde `sw600dp` yalnız bir ad olarak ele alınır.

#### Yapılandırma Değişikliği

Aygıt döndürüldüğünde ya da gece görünümü açıldığında yapılandırma değişir ve activity yeniden kurulur; kaynaklar yeni yapılandırmaya göre yeniden seçilir. Aşağıdaki ölçüm aynı anahtarı iki yapılandırmada sorar. Dizinler `values`, `values-de` ve `values-night`'tır. Almanca yapılandırmada `values-de` seçilir. Gece eklenince hem `values-de` hem `values-night` aday olur; istenen ise ikisinin birleşimini adlandıran `values-de-night`'tır. Üçüncü satır yalnız Almanca dizinde olan bir anahtarı Fransızca yapılandırmada sorar.

```kotlin
// Main.kt — yapilandirma degisince yeniden secim
fun main() {
    val dirs = listOf(
        ResourceDir("values", setOf("string/title")),
        ResourceDir("values-de", setOf("string/title", "string/promo")),
        ResourceDir("values-night", setOf("string/title")),
    )
    val asks = listOf(
        resolveResource("string/title", "values-de", setOf("de"), dirs),
        resolveResource("string/title", "values-de-night", setOf("de", "night"), dirs),
        resolveResource("string/promo", "values-fr", setOf("fr"), dirs),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
string/title: istenen=values-de | verilen=values-de -> tam
string/title: istenen=values-de-night | verilen=values-de -> ikame
string/promo: istenen=values-fr | neden=varsayilan-yok -> karsiliksiz
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

Aynı kimlik yapılandırma değişince tamdan ikameye geçti: ikinci yapılandırmanın adlandırdığı birleşik dizin yoktu. Üçüncü kimlik karşılıksızdır, çünkü `string/promo` yalnız Almanca dizinde vardı ve varsayılan dizinde bulunmuyordu; Fransızca yapılandırmada aday kalmadı. Gerçek sistemin bu durumda tam olarak ne yaptığı bu derste iddia edilmez; model yalnız karşılığın verilmediğini sınıflar.

Yapılandırma ölçünün girdisidir; ama sonucu belirleyen girdi değildir. Aynı yapılandırma bir kimlikte tam, ötekinde ikame verir. Sonucu belirleyen, verilen dizinin istenen dizine eşit olup olmadığıdır.

#### Tür Dizinleri ve Ekran Genişliği

Kaynak dizinleri türe göre adlandırılır. `values` metin, renk ve ölçü gibi basit değerleri, `drawable` görselleri, `layout` ekran düzenlerini taşır. Her türün dizini kendi niteleyicilerini alabilir. Ekran genişliği niteleyicisi `sw600dp` gibi yazılır ve en küçük kenar uzunluğunun o değerden az olmadığı aygıtlarda geçerlidir. Aşağıdaki parça geniş ekranlar için ayrı bir düzen dizinini gösterir.

```xml
<!-- layout-sw600dp/main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
```

Aynı ad `layout` dizininde de bulunabilir; geniş ekranda `layout-sw600dp` dizinindeki seçilir, dar ekranda varsayılan dizindeki. İstenen yine yapılandırmanın tam adlandırdığı dizindir. Ölçümde yapılandırma bir tablet gibi geniş ve Almanca. Üç tür ister: düzen, metin ve görsel. Düzen yalnız geniş ekran dizininde ve varsayılanda vardır. Metin yalnız Almanca dizinde ve varsayılanda vardır. Görsel yalnız varsayılan dizindedir.

```kotlin
// Main.kt — kaynak turleri
fun main() {
    val dirs = listOf(
        ResourceDir("layout", setOf("layout/main")),
        ResourceDir("layout-sw600dp", setOf("layout/main")),
        ResourceDir("values", setOf("string/title")),
        ResourceDir("values-de", setOf("string/title")),
        ResourceDir("drawable", setOf("drawable/logo")),
    )
    val config = setOf("de", "sw600dp")
    val asks = listOf(
        resolveResource("layout/main", "layout-sw600dp", config, dirs),
        resolveResource("string/title", "values-de", config, dirs),
        resolveResource("drawable/logo", "drawable-sw600dp", config, dirs),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
layout/main: istenen=layout-sw600dp | verilen=layout-sw600dp -> tam
string/title: istenen=values-de | verilen=values-de -> tam
drawable/logo: istenen=drawable-sw600dp | verilen=drawable -> ikame
tam: 2
ikame: 1
bekleyen: 0
karsiliksiz: 0
```

İlk iki istek yapılandırmanın adlandırdığı dizinden karşılandı. Üçüncüsü geniş ekrana özel bir görsel istedi ama yalnız varsayılan görsel vardı; varsayılan verildi ve ikame oldu.

**AP3.** Model yalnız `de`, `rAT`, `night` ve `sw` niteleyicilerini tanır. Gerçek sistemde yoğunluk, yön ve platform sürümü gibi başka niteleyiciler de vardır; ölçüm bunları kapsamaz.

#### Elemenin Adımları

Seçim iki adımda yapılır. Önce yapılandırmayla çelişen dizinler elenir: bir niteleyicisi yapılandırmada olmayan dizin aday olamaz. Sonra kalan adaylar arasından niteleyici öncelik sırasına göre en iyisi seçilir. Birinci adımda `values-night` gündüz yapılandırmasında elenir, `values-de` ise Almanca yapılandırmada kalır. Aday listesi boşsa kaynak bulunamaz. Varsayılan dizinde de yoksa bu, kaynağın hiçbir yerde tanımlanmadığı anlamına gelir. Ölçümde yapılandırma gündüz ve Almanca. Gece dizini ve Almanca dizini vardır; ayrıca hiçbir dizinde olmayan bir anahtar sorulur.

```kotlin
// Main.kt — elenen dizin ve eksik anahtar
fun main() {
    val dirs = listOf(
        ResourceDir("values", setOf("string/title")),
        ResourceDir("values-night", setOf("string/title")),
        ResourceDir("values-de", setOf("string/title")),
    )
    val config = setOf("de")
    val asks = listOf(
        resolveResource("string/title", "values-night", config, dirs),
        resolveResource("string/title", "values-de", config, dirs),
        resolveResource("string/missing", "values", config, dirs),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
string/title: istenen=values-night | verilen=values-de -> ikame
string/title: istenen=values-de | verilen=values-de -> tam
string/missing: istenen=values | neden=varsayilan-yok -> karsiliksiz
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

Gece dizini istendiği halde gündüz yapılandırmasında elendi ve Almanca dizin verildi: ikame. Hiçbir dizinde olmayan anahtar karşılıksız kaldı; neden `varsayilan-yok` olarak adlandırılır, çünkü model varsayılan dizinde de bulamamıştır.

**AP4.** Model elemeyi niteleyicinin yapılandırmada bulunup bulunmadığına bakarak yapar. Gerçek sistem niteleyicileri öncelik sırasıyla tek tek eleyerek ilerler; ikisi çoğu durumda aynı sonucu verir ama ölçüm bunu karşılaştırmaz.

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

Model niteleyicileri tek tek eşleştirir; bir niteleyicinin yakın bir başkasıyla örtüşmesini bilmez. Almanca konuşulan her bölge için ayrı bir `values-de-rAT` gibi dizin yazılabilir. Bölge dizini yoksa ve yalnız `values-de` varsa, Avusturya bölgesi yapılandırması Almanca dizinden okur: model bunu ikame olarak sınıflar. İkinci ve üçüncü satır iki niteleyicinin çakışmasını sınar. Dizinler `values-de` ve `values-night`'tır, yapılandırma ikisini de içerir. Önce ikisi de varken, sonra Almanca dizin çıkarılınca sorulur.

```kotlin
// Main.kt — bolge ve oncelik
fun main() {
    val region = listOf(
        ResourceDir("values", setOf("string/title")),
        ResourceDir("values-de", setOf("string/title")),
    )
    val both = listOf(
        ResourceDir("values-de", setOf("string/title")),
        ResourceDir("values-night", setOf("string/title")),
        ResourceDir("values", setOf("string/title")),
    )
    val nightOnly = both.filter { it.name != "values-de" }
    val config = setOf("de", "rAT", "night")
    val asks = listOf(
        resolveResource("string/title", "values-de-rAT", config, region),
        resolveResource("string/title", "values-de-night", config, both),
        resolveResource("string/title", "values-de-night", config, nightOnly),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
string/title: istenen=values-de-rAT | verilen=values-de -> ikame
string/title: istenen=values-de-night | verilen=values-de -> ikame
string/title: istenen=values-de-night | verilen=values-night -> ikame
tam: 0
ikame: 3
bekleyen: 0
karsiliksiz: 0
```

Üç satır da ikamedir ve kural aralarını ayırmaz: üçünde de verilen istenenden farklıdır. Ayrımı verilen sütunu gösterir. İkinci satırda iki aday varken dil kazandı. Üçüncü satırda dil dizini kalmadığı için gece dizini seçildi. Öncelik sırasını model kurar; kural yalnız sonucu görür, o sonucun neden seçildiğini görmez. Sayım üç ikameyi sayar ve önceliğin işlediğini göstermez.

**AP5.** Model önceliği dil, bölge, genişlik, gece biçiminde kurar; bu dört niteleyici platformun listesinde de bu sıradadır. Platformun tam öncelik listesi daha uzundur ve ağırlık toplamı kullanmaz, niteleyicileri sırayla eler. Ölçüm yalnız bu dört niteleyici için sıranın çalıştığını gösterir.

#### Özet

- Bu dersin ölçütü: **karşılama**; verilen dizinin istenen dizine eşit olup olmadığı, yapılandırmanın kendisi değil. Bu dersin karşılaması: **ikame**.
- Kaynaklar `res` altında türe göre dizinlerde durur; koddan kaynak kimliğiyle ulaşılır ve dosyayı sistem seçer.
- Niteleyiciler dizin adının sonundaki yapılandırma özellikleridir: dil, bölge, gece görünümü, ekran genişliği.
- Yapılandırmanın adlandırdığı dizin varsa kaynak tam, yoksa varsayılan dizinden gelen kaynak ikamedir.
- Varsayılan dizinde de bulunmayan kaynak karşılıksızdır.
- İki niteleyici çakışınca öncelik sırası sonucu belirler; kural bu nedeni görmez, yalnız verilenle istenenin eşitliğini görür.

#### Sonraki Adım

Kaynaklar ve bildirim dosyası bir araya gelip çalıştırılabilir bir uygulamaya dönüşmek için derlenmelidir. Sonraki ders derleme sistemini ele alır ve bağımlılıkların sürümleri çakıştığında hangi sürümün verildiğini ölçer.

### Derleme Sistemi

Source: https://academia.sh/tr/kurslar/android-platformu/derleme-sistemi

Derleme sisteminin bağımlılıkları ve sürüm kataloğunu nasıl okuduğunun, geçişli bağımlılıkta en yüksek sürümün seçilmesinin ve katı sürüm kısıtının modeli.

Önceki ders kaynakların yapılandırmaya göre nasıl seçildiğini ölçtü. Bildirim dosyası, kaynaklar ve kod tek başına çalışan bir uygulama değildir; bunları bir araya getirip kurulabilir bir pakete dönüştüren, derleme sistemidir. Bu ders derleme sisteminin ana parçalarını tanıtır ve bir modülün istediği bağımlılık sürümünün, başka modüllerin istekleriyle çakıştığında ne olduğunu ölçer.

Kursun ölçü ekseni değişmez: bu isteği platform nasıl karşılıyor. Karşılama dörttür: tam, ikame, bekleyen ve karşılıksız. Karşılamayı etiket değil kural verir: verilen değer istenene eşitse tam, eşit değilse ikame; bekleyen ve karşılıksız doğrudan sonuçtan çıkar. Aşağıdaki dosya o kuralı taşır ve her derste aynen yinelenir. Açılış dersindeki 3/2/2/1 sayısı burada değiştirilmez.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

#### Derleme Sisteminin Parçaları

Bir proje üç ayrı yazılı belge ile kurulur. Proje ayar betiği projenin adını ve hangi modüllerden oluştuğunu söyler. Her modülün kendi modül derleme betiği vardır: modülün derleme API düzeyini, en düşük ve hedef API düzeyini, derleme türlerini ve bağımlılıklarını tanımlar. Sürüm kataloğu ise bağımlılıkların adlarını ve sürümlerini tek yerde toplar. Aşağıdaki iki parça proje ayar betiğini ve bir modül derleme betiğini gösterir; ikisi de platform araçlarının okuduğu betiklerdir ve çalıştırılmaz.

```kotlin
// proje ayar betigi
rootProject.name = "Notes"
include(":app", ":data")
```

```kotlin
// modul derleme betigi
android {
    namespace = "com.example.notes"
    compileSdk = 34
    defaultConfig {
        minSdk = 24
        targetSdk = 34
    }
    buildTypes {
        release { isMinifyEnabled = true }
    }
}

dependencies {
    implementation(project(":data"))
    implementation(libs.notesCore)
}
```

Betik iki derleme türü (build type) bilir: geliştirme sırasında kullanılan `debug` ve dağıtım için hazırlanan `release`. Her tür kendi ayarlarını taşır; burada `release` için kod küçültme açılmıştır. Bağımlılık bildirimi iki biçimde yazılabilir: başka bir modülü `project(":data")` ile ya da sürüm kataloğundaki bir kitaplığı `libs.notesCore` ile. Sürüm kataloğu ayrı bir dosyadır.

```toml
[versions]
notesCore = "1.4"

[libraries]
notesCore = { module = "com.example:notes-core", version.ref = "notesCore" }
```

Derleme sistemi her adımı bir derleme adımı (build task) olarak çalıştırır: kaynakları işler, kodu derler, paketler. Adımlar birbirine bağımlıdır ve sistem sırayı bağımlılıklardan çıkarır. Bu derste ölçülen derleme adımlarının sırası değil, bağımlılık sürümlerinin çözümlenmesidir.

#### Geçişli Bağımlılıkta Sürüm Seçimi

Bir modül bir kitaplığı doğrudan ister; o kitaplık başka kitaplıklara bağlıdır. Bunlara geçişli bağımlılık (transitive dependency) denir. Aynı kitaplık yolda birden çok kez, farklı sürümlerle istenebilir. Derleme sistemi tek bir sürüm seçer ve model bu seçimi en yüksek sürüm olarak kurar. Her istek ayrı bir sorudur: istenen, modülün yazdığı sürümdür; verilen, çözümlemenin seçtiği sürümdür.

```kotlin
// Resolver.kt — surum cozumlemesi modeli
class Request(val from: String, val library: String, val version: String, val strict: Boolean = false)

fun compareVersions(a: String, b: String): Int {
    val left = a.split(".").map { it.toInt() }
    val right = b.split(".").map { it.toInt() }
    for (i in 0 until maxOf(left.size, right.size)) {
        val diff = left.getOrElse(i) { 0 } - right.getOrElse(i) { 0 }
        if (diff != 0) return diff
    }
    return 0
}

fun signOf(n: Int): Int = if (n < 0) -1 else if (n > 0) 1 else 0

fun resolveDependency(requests: List<Request>): List<Ask> {
    val strictVersions = requests.filter { it.strict }.map { it.version }.toSet()
    val highest = requests.map { it.version }.reduce { best, next ->
        if (compareVersions(next, best) > 0) next else best
    }
    return requests.map { request ->
        val outcome = when {
            strictVersions.size > 1 -> Outcome.Refused("kati-surum-uyusmazligi")
            strictVersions.size == 1 -> Outcome.Given(strictVersions.single())
            else -> Outcome.Given(highest)
        }
        Ask("${request.from}>${request.library}", request.version, outcome)
    }
}
```

İlk ölçümde iki kitaplık vardır. `notes-core` kitaplığını uygulama modülü 1.2 sürümüyle, veri modülü 1.4 sürümüyle ister. `net-client` kitaplığını yalnız uygulama modülü ister.

```kotlin
// Main.kt — en yuksek surum secilir
fun main() {
    val core = resolveDependency(listOf(
        Request("app", "notes-core", "1.2"),
        Request("data", "notes-core", "1.4"),
    ))
    val net = resolveDependency(listOf(Request("app", "net-client", "3.0")))
    val asks = core + net
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
app>notes-core: istenen=1.2 | verilen=1.4 -> ikame
data>notes-core: istenen=1.4 | verilen=1.4 -> tam
app>net-client: istenen=3.0 | verilen=3.0 -> tam
tam: 2
ikame: 1
bekleyen: 0
karsiliksiz: 0
```

Veri modülü istediği sürümü aldı. Uygulama modülü 1.2 istedi ve 1.4 aldı: yüksek sürüm bir ikamedir, çünkü verilen istenenden farklıdır. Kural bu sürümün daha iyi ya da kötü olduğunu söylemez; yalnız eşit olmadığını söyler. Tek isteği olan kitaplıkta çözümlenecek bir şey yoktur ve tam çıkar.

**AP1.** Model yalnız en yüksek sürümü seçme kuralını kurar. Gerçek çözümleyici aralık sürümleri, sürüm ağırlıkları ve dışlamalar gibi başka girdileri de okur.

**AP2.** Sürüm dizgileri yalnız noktayla ayrılmış sayılardan oluşur. Ön sürüm ekleri ve harf içeren sürümler modelin dışındadır.

#### Sürüm Kataloğu

Sürüm kataloğunun amacı, aynı kitaplığın her modülde ayrı ayrı yazılmasını önlemektir. Modüller sürümü kendileri yazmaz; katalogdaki adı kullanır ve sürüm tek yerden gelir. Aşağıdaki ölçüm bunu iki durumda sorar. Katalogla üç modül de aynı sürümü ister. Katalog olmadan her modül elle bir sürüm yazmış ve üç sürüm birbirinden ayrışmıştır.

```kotlin
// Main.kt — katalogla ve katalogsuz
fun main() {
    val withCatalog = resolveDependency(listOf(
        Request("app", "ui-kit", "2.0"),
        Request("data", "ui-kit", "2.0"),
        Request("sync", "ui-kit", "2.0"),
    ))
    val withoutCatalog = resolveDependency(listOf(
        Request("app", "ui-kit", "1.8"),
        Request("data", "ui-kit", "1.9"),
        Request("sync", "ui-kit", "2.0"),
    ))
    val asks = withCatalog + withoutCatalog
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
app>ui-kit: istenen=2.0 | verilen=2.0 -> tam
data>ui-kit: istenen=2.0 | verilen=2.0 -> tam
sync>ui-kit: istenen=2.0 | verilen=2.0 -> tam
app>ui-kit: istenen=1.8 | verilen=2.0 -> ikame
data>ui-kit: istenen=1.9 | verilen=2.0 -> ikame
sync>ui-kit: istenen=2.0 | verilen=2.0 -> tam
tam: 4
ikame: 2
bekleyen: 0
karsiliksiz: 0
```

Katalogla üç istek de tam çıktı. Katalogsuz durumda en yüksek sürümü isteyen modül tam, ötekiler ikame olur. Sürümü katalogdan okumanın sonucu, ayrışma ihtimalini ortadan kaldırmasıdır; ölçü yine yalnız verilenle istenenin eşitliğine bakar.

#### Proje ve Modül Ayrımı

Proje ayar betiği ile modül derleme betiği iki farklı düzeyde çalışır. Proje düzeyinde bağımlılıkların nereden indirileceği ve hangi modüllerin projeye girdiği belirlenir. Modül düzeyinde o modülün neye bağlı olduğu ve nasıl derleneceği belirlenir. Aşağıdaki parça proje düzeyinde bir bağımlılık deposu bildirimini gösterir.

```kotlin
// proje ayar betigi
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}
```

Depo bildirimi proje düzeyinde olduğu için her modül aynı yerlerden bağımlılık çözer. Bir modülün betiği ayrıca kendi derleme türlerini, `debug` ve `release`, tanımlayabilir. Derleme türü kod küçültme, hata ayıklanabilirlik ve imzalama gibi ayarları taşır. Çeşit boyutu yoksa derleme varyantı derleme türüyle aynı adı taşır; sonraki ders çeşitlerin bu çarpıma girdiğini ölçer.

#### Bağımlılık Yapılandırmaları

Bir bağımlılık hangi sınıf yolunda görüneceğiyle birlikte bildirilir. Bildirimin ilk sözcüğü yapılandırmadır (configuration). `implementation` bağımlılığı modülün derlemesine ve çalışmasına katar ama modülü kullanan başka modüllere göstermez. `api` aynı bağımlılığı kullanan modüllerin derleme sınıf yoluna da sokar. `compileOnly` yalnız derlemeye girer, çalışan uygulamaya girmez. `runtimeOnly` yalnız çalışmaya girer. `testImplementation` yalnız test derlemesine girer. Model her yapılandırmaya dört sınıf yolundan hangilerine ulaştığını söyleyen bir küme bağlar: derleme, çalışma, tüketici ve test.

```kotlin
// Config.kt — bagimlilik yapilandirmalari modeli
val reach: Map<String, Set<String>> = mapOf(
    "implementation" to setOf("derleme", "calisma"),
    "api" to setOf("derleme", "calisma", "tuketici"),
    "compileOnly" to setOf("derleme"),
    "runtimeOnly" to setOf("calisma"),
    "testImplementation" to setOf("test"),
)

fun resolveConfiguration(configuration: String, classpath: String): Ask {
    val paths = reach[configuration]
    val outcome = when {
        paths == null -> Outcome.Refused("yapilandirma-yok")
        classpath in paths -> Outcome.Given(classpath)
        else -> Outcome.Refused("sinif-yoluna-girmez")
    }
    return Ask(configuration, classpath, outcome)
}
```

İlk ölçümde aynı modüle ait bir kitaplığın çalışma anında bulunması istenir. Üç yapılandırma sorulur.

```kotlin
// Main.kt — calisma sinif yolu
fun main() {
    val asks = listOf("implementation", "runtimeOnly", "compileOnly").map { resolveConfiguration(it, "calisma") }
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
implementation: istenen=calisma | verilen=calisma -> tam
runtimeOnly: istenen=calisma | verilen=calisma -> tam
compileOnly: istenen=calisma | neden=sinif-yoluna-girmez -> karsiliksiz
tam: 2
ikame: 0
bekleyen: 0
karsiliksiz: 1
```

Derleme zamanında bir arayüz kitaplığı gerekiyor ama çalışma anında başka bir paket onu sağlıyorsa `compileOnly` doğru seçimdir. Yanlış seçim çalışma anında sınıfın bulunamamasına yol açar. Model bunu karşılıksız olarak gösterir. İkinci ölçüm kenar durumlarını sorar: tüketici modülün derleme sınıf yolunda görünme, test sınıf yolunda görünme ve hiç tanımlanmamış bir yapılandırma.

```kotlin
// Main.kt — tuketici, test ve bilinmeyen
fun main() {
    val asks = listOf(
        resolveConfiguration("api", "tuketici"),
        resolveConfiguration("implementation", "tuketici"),
        resolveConfiguration("testImplementation", "derleme"),
        resolveConfiguration("debugImplementation", "calisma"),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
api: istenen=tuketici | verilen=tuketici -> tam
implementation: istenen=tuketici | neden=sinif-yoluna-girmez -> karsiliksiz
testImplementation: istenen=derleme | neden=sinif-yoluna-girmez -> karsiliksiz
debugImplementation: istenen=calisma | neden=yapilandirma-yok -> karsiliksiz
tam: 1
ikame: 0
bekleyen: 0
karsiliksiz: 3
```

`api` ile `implementation` aynı kitaplığı aynı modülde derler ama tüketicilere görünürlükleri ayrılır. Bu ayrım uzun vadede önemlidir: gereksiz `api` kullanımı bir modüldeki değişikliğin onu kullanan her modülün yeniden derlenmesine yol açabilir. Model bu maliyeti ölçmez, yalnız erişimi ölçer.

**AP3.** Model yapılandırma adını küçük bir sabit tablodan okur. Gerçek derleme sisteminde yapılandırmalar derleme türüne göre çoğalır (örneğin yalnız bir derleme türüne özel olanlar) ve modül kendi yapılandırmasını tanımlayabilir.

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

Model iki yerde sınırlıdır. Birincisi sürümün sıralanmasıdır. `1.10` sürümü `1.9` sürümünden yüksektir, çünkü sürüm sayısal parçalarla karşılaştırılır. Aynı iki dizgi alfabetik sıralanırsa sonuç tersine döner. `compareVersions` sayısal sıralamayı, dizgi `compareTo` ise alfabetik sıralamayı uygular; ilk iki satır ikisinin işaretini basar. İkincisi katı sürüm kısıtıdır (`strictly`): iki modül aynı kitaplık için farklı katı sürüm yazdıysa çözümleme bir sürüm seçemez.

```kotlin
// Main.kt — siralama ve kati kisit
fun main() {
    println("sayisal: ${signOf(compareVersions("1.10", "1.9"))}")
    println("dizgi: ${signOf("1.10".compareTo("1.9"))}")
    val ordering = resolveDependency(listOf(
        Request("app", "log-tools", "1.9"),
        Request("data", "log-tools", "1.10"),
    ))
    val strict = resolveDependency(listOf(
        Request("app", "crypto-lib", "2.0", strict = true),
        Request("sync", "crypto-lib", "2.1", strict = true),
    ))
    val asks = ordering + strict
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
sayisal: 1
dizgi: -1
app>log-tools: istenen=1.9 | verilen=1.10 -> ikame
data>log-tools: istenen=1.10 | verilen=1.10 -> tam
app>crypto-lib: istenen=2.0 | neden=kati-surum-uyusmazligi -> karsiliksiz
sync>crypto-lib: istenen=2.1 | neden=kati-surum-uyusmazligi -> karsiliksiz
tam: 1
ikame: 1
bekleyen: 0
karsiliksiz: 2
```

Sayısal sıralamada işaret pozitiftir: `1.10` yüksektir. Dizgi sıralamasında işaret negatiftir: aynı iki dizgi için tersi çıkar. Bu yüzden `app>log-tools` isteği ikame oldu ve `data>log-tools` tam kaldı. Model sayısal sıralamayı kurar; ölçüm yalnız modelin bu sıralamayı uyguladığını gösterir. Katı kısıtta iki katı sürüm farklı olduğundan çözümleme hiçbir sürüm vermedi ve her iki istek de karşılıksız çıktı. Kural uyuşmazlığın nedenini görmez; nedeni modelin taşıdığı `kati-surum-uyusmazligi` dizgisi söyler.

**AP4.** Model katı kısıtı yalnız iki katı sürüm birbirinden farklıyken karşılıksız sayar. Tek bir katı sürüm olduğunda o sürümü verir; katı sürümün başka bir isteği düşürüp düşüremeyeceği gibi ayrıntılar ölçülmez.

#### Özet

- Bu dersin ölçütü: **karşılama**; çözümlemenin verdiği sürümün modülün istediği sürüme eşit olup olmadığı. Bu dersin karşılaması: **ikame**.
- Proje ayar betiği modülleri sayar, modül derleme betiği bir modülün ayarlarını ve bağımlılıklarını tanımlar, sürüm kataloğu adları ve sürümleri tek yerde toplar.
- `debug` ve `release` derleme türleridir; her türün kendi ayarları vardır.
- Geçişli bağımlılıkta model en yüksek sürümü seçer: düşük sürümü isteyen ikame, yükseği isteyen tam karşılanır.
- Sürüm kataloğu tüm modüllere aynı sürümü verdiği için ayrışmayı önler.
- Sürüm sıralaması sayısaldır; katı sürüm kısıtı uyuşmazlığı karşılıksızdır.

#### Sonraki Adım

Derleme sistemi aynı kaynaktan birden çok uygulama üretebilir: ücretsiz ve ücretli sürümler, ya da farklı pazarlar için paketler. Sonraki ders ürün çeşitlerini ve bunların derleme türleriyle çarpımından doğan derleme varyantlarını ölçer.

### Ürün Çeşitleri

Source: https://academia.sh/tr/kurslar/android-platformu/urun-cesitleri

Ürün çeşidi ile derleme türünün çarpımından doğan derleme varyantlarının, varyant süzgecinin ve kod kümesi önceliğinin modeli.

Önceki ders derleme sisteminin bağımlılık sürümlerini nasıl çözdüğünü ölçtü. Derleme sistemi aynı kaynaktan tek bir uygulama üretmek zorunda değildir. Ücretsiz ve ücretli sürümler, farklı pazarlar için paketler ya da farklı sunucuya bağlanan derlemeler aynı kod tabanından çıkar. Bunu ürün çeşitleri sağlar. Bu ders hangi derleme varyantlarının üretildiğini ve bir varyant istendiğinde platformun ne verdiğini ölçer.

Kursun ölçü ekseni değişmez: bu isteği platform nasıl karşılıyor. Karşılama dörttür: tam, ikame, bekleyen ve karşılıksız. Karşılamayı etiket değil kural verir: verilen değer istenene eşitse tam, eşit değilse ikame; bekleyen ve karşılıksız doğrudan sonuçtan çıkar. Aşağıdaki dosya o kuralı taşır ve her derste aynen yinelenir. Açılış dersindeki 3/2/2/1 sayısı burada değiştirilmez.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

#### Çeşitler ve Varyantlar

Ürün çeşidi (product flavor) bir uygulamanın aynı kod tabanından türeyen adlandırılmış bir sürümüdür. Çeşitler bir çeşit boyutuna (flavor dimension) bağlanır: boyut, çeşitlerin hangi eksende ayrıştığını söyler. `free` ve `paid` çeşitleri bir ücret boyutunda durur. Derleme türleri `debug` ve `release` ise başka bir eksendir. Derleme sistemi bu eksenlerin çarpımını alır; her bileşime derleme varyantı (build variant) denir. İki çeşit ve iki derleme türü dört varyant verir: `freeDebug`, `freeRelease`, `paidDebug`, `paidRelease`. Aşağıdaki parça bu tanımı ve bir varyantı üretimden çıkaran süzgeci gösterir.

```kotlin
// modul derleme betigi
android {
    flavorDimensions += "tier"
    productFlavors {
        create("free") { dimension = "tier" }
        create("paid") { dimension = "tier" }
    }
}

androidComponents {
    beforeVariants { variant ->
        if (variant.name == "paidDebug") variant.enable = false
    }
}
```

Betik platform araçlarının okuduğu bir betiktir ve çalıştırılmaz. Süzgeç, adı verilen varyantı üretimden çıkarır; o varyant derlenemez ve seçilemez. Model, platformun bu kuralını kurar. Bu parçada süzgeç `paidDebug` varyantını dışarıda bırakır.

#### Varyant Kümesi

Model varyant adını çeşit adı ile derleme türü adının birleşiminden kurar: çeşit olduğu gibi, derleme türü baş harfi büyük. Birden çok boyut varsa çeşitler boyutların sırasıyla birleşir. İstenen varyant kümede varsa ve süzgeçle dışlanmamışsa verilen, istenenin aynısıdır. Süzgeçle dışlanmışsa ya da kümede yoksa varyant verilmez.

```kotlin
// Variants.kt — varyant kumesi modeli
fun variantNames(dimensions: List<List<String>>, buildTypes: List<String>): List<String> {
    var combos = listOf("")
    for (dimension in dimensions) {
        combos = combos.flatMap { prefix ->
            dimension.map { flavor ->
                if (prefix.isEmpty()) flavor else prefix + flavor.replaceFirstChar { it.uppercase() }
            }
        }
    }
    return combos.flatMap { combo -> buildTypes.map { type -> combo + type.replaceFirstChar { it.uppercase() } } }
}

fun resolveVariant(label: String, wanted: String, variants: List<String>, filtered: Set<String>): Ask {
    val outcome = when {
        wanted in filtered -> Outcome.Refused("suzgecle-dislandi")
        wanted in variants -> Outcome.Given(wanted)
        else -> Outcome.Refused("varyant-yok")
    }
    return Ask(label, wanted, outcome)
}
```

İlk ölçüm tek boyutlu iki çeşit ve iki derleme türü kurar ve `paidDebug` süzgeçle dışlanmıştır. Dört istek sorulur: geliştirme için bir varyant, dağıtım için bir varyant, süzgeçle dışlanan varyant ve hiç tanımlanmamış bir derleme türü.

```kotlin
// Main.kt — varyant kumesi ve suzgec
fun main() {
    val variants = variantNames(listOf(listOf("free", "paid")), listOf("debug", "release"))
    println(variants.joinToString())
    val filtered = setOf("paidDebug")
    val asks = listOf(
        resolveVariant("calistir", "freeDebug", variants, filtered),
        resolveVariant("yayin", "paidRelease", variants, filtered),
        resolveVariant("suzulmus", "paidDebug", variants, filtered),
        resolveVariant("tanimsiz", "freeStaging", variants, filtered),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
freeDebug, freeRelease, paidDebug, paidRelease
calistir: istenen=freeDebug | verilen=freeDebug -> tam
yayin: istenen=paidRelease | verilen=paidRelease -> tam
suzulmus: istenen=paidDebug | neden=suzgecle-dislandi -> karsiliksiz
tanimsiz: istenen=freeStaging | neden=varyant-yok -> karsiliksiz
tam: 2
ikame: 0
bekleyen: 0
karsiliksiz: 2
```

Varyant adı kümede olduğu gibi yazılınca verilir; olmayan ad yerine başka bir varyant verilmez. Bu yüzden varyant ölçümünde ikame çıkmaz: varyant ya vardır ya yoktur. `paidDebug` kümede `variants` listesinde durur ama süzgeç onu dışlar; kural iki karşılıksızın nedenini ayırmaz, nedeni modelin taşıdığı dizgi söyler.

**AP1.** Model varyant adını çeşit ve derleme türünden birleştirir. Gerçek derleme sisteminin adlandırmasında ve boyut önceliğinde başka ayrıntılar vardır; ölçüm bunları kapsamaz.

**AP2.** Süzgeç yalnız varyantın adına bakar. Gerçek süzgeç varyantın başka özelliklerini de okuyabilir.

#### Birden Çok Boyut

İkinci ölçümde iki boyut vardır: ücret boyutu (`free`, `paid`) ve pazar boyutu (`global`, `local`). Derleme türleriyle çarpım sekiz varyant verir. Çeşitler boyut sırasıyla birleşir: ücret önce, pazar sonra. Üç istek sorulur: sıraya uyan bir ad, sıra ters çevrilmiş bir ad ve süzgeçle dışlanan bir ad.

```kotlin
// Main.kt — iki boyut
fun main() {
    val variants = variantNames(
        listOf(listOf("free", "paid"), listOf("global", "local")),
        listOf("debug", "release"),
    )
    println(variants.size)
    val filtered = setOf("paidLocalDebug")
    val asks = listOf(
        resolveVariant("sirali", "freeLocalDebug", variants, filtered),
        resolveVariant("ters", "localFreeDebug", variants, filtered),
        resolveVariant("suzulmus", "paidLocalDebug", variants, filtered),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
8
sirali: istenen=freeLocalDebug | verilen=freeLocalDebug -> tam
ters: istenen=localFreeDebug | neden=varyant-yok -> karsiliksiz
suzulmus: istenen=paidLocalDebug | neden=suzgecle-dislandi -> karsiliksiz
tam: 1
ikame: 0
bekleyen: 0
karsiliksiz: 2
```

Aynı iki çeşit ters sırayla yazılınca varyant bulunamadı: ad boyut sırasının sonucudur. Kümenin boyutu çeşit sayılarının ve derleme türü sayısının çarpımıdır: 2 × 2 × 2.

#### Boyut Sırası ve Çarpım

Çeşit boyutlarının sırası iki yerde işe yarar. Birincisi varyant adıdır: ad boyut sırasıyla kurulur, yukarıdaki ölçüm bunu gösterdi. İkincisi çakışmadır: iki boyuttaki çeşit aynı ayarı tanımlarsa hangisinin üstün geleceği boyut sırasından çıkar. Bu ders ikinci kuralı ölçmez; yalnız birincisini ölçer. Aşağıdaki betik parçası iki boyutu sıralı olarak bildirir.

```kotlin
// modul derleme betigi
android {
    flavorDimensions += listOf("tier", "market")
    productFlavors {
        create("free") { dimension = "tier" }
        create("paid") { dimension = "tier" }
        create("global") { dimension = "market" }
        create("local") { dimension = "market" }
    }
}
```

Varyant sayısı boyutlardaki çeşit sayılarının ve derleme türü sayısının çarpımıdır. Üçüncü bir boyut eklemek sayıyı katlar: iki çeşitli üç boyut ve iki derleme türü on altı varyant verir. Her varyant ayrı derlenir, ayrı test edilir ve ayrı imzalanır; bu yüzden çarpım hesabı tasarım aşamasında yapılır. Süzgeç gereksiz bileşimleri baştan ayıklamanın yoludur. Platformun çeşit tanımında bir çeşidin bir boyuta bağlanmasının zorunlu olup olmadığı gibi ayrıntılar bu modelin dışındadır.

**AP4.** Model boyut sırasını çeşit listesinin sırasından alır; derleme ayarındaki boyut bildirimini okumaz.

#### Kod Kümesi Öncelik Sırası

Bir varyant derlenirken ona giren kod kümeleri bir sırayla dizilir. Model sırayı en düşük öncelikten en yükseğe kurar: önce ana küme, sonra çeşitlerin kümeleri, sonra derleme türünün kümesi, en sonda bileşik varyantın kümesi. Çeşitler arasında önce bildirilen boyutun çeşidi daha yüksek önceliktedir, bu yüzden listeye ters sırayla girer. Bir dosya birden çok kümede bulunuyorsa sonuncu küme kazanır. `layersFor` sırayı kurar, `resolveLayered` dosyayı sondan başa doğru arar.

```kotlin
// Layers.kt — kod kumesi sirasi modeli
fun layersFor(flavors: List<String>, buildType: String): List<String> {
    val variant = flavors.joinToString("") { it.replaceFirstChar { c -> c.uppercase() } }
        .replaceFirstChar { it.lowercase() } + buildType.replaceFirstChar { it.uppercase() }
    return listOf("main") + flavors.reversed() + listOf(buildType, variant)
}

fun resolveLayered(path: String, wanted: String, order: List<String>, sets: Map<String, Set<String>>): Ask {
    val owner = order.reversed().firstOrNull { path in (sets[it] ?: emptySet()) }
    val outcome = if (owner == null) Outcome.Refused("dosya-yok") else Outcome.Given(owner)
    return Ask(path, wanted, outcome)
}
```

İlk ölçümde `free` çeşidi ve `release` derleme türü var. Sıra beş kümeden kurulur ve üç dosya sorulur: yalnız ana kümede olan, hem ana hem çeşit kümesinde olan ve hem ana hem bileşik varyant kümesinde olan.

```kotlin
// Main.kt — kod kumesi sirasi
fun main() {
    val order = layersFor(listOf("free"), "release")
    println(order.joinToString())
    val sets = mapOf(
        "main" to setOf("res/logo.xml", "res/title.xml", "res/banner.xml"),
        "free" to setOf("res/title.xml"),
        "freeRelease" to setOf("res/banner.xml"),
    )
    val asks = listOf(
        resolveLayered("res/logo.xml", "main", order, sets),
        resolveLayered("res/title.xml", "main", order, sets),
        resolveLayered("res/banner.xml", "main", order, sets),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
main, free, release, freeRelease
res/logo.xml: istenen=main | verilen=main -> tam
res/title.xml: istenen=main | verilen=free -> ikame
res/banner.xml: istenen=main | verilen=freeRelease -> ikame
tam: 1
ikame: 2
bekleyen: 0
karsiliksiz: 0
```

Dosya hangi kümede bulunursa bulunsun, sonuç yalnız dosyayı taşıyan en öncelikli kümenin adıdır. Bu yüzden ana kümeden istenen dosya başka kümeden verilince ikame sayılır. İkinci ölçüm bir kenar durumudur: hiçbir kümede bulunmayan dosya ve iki çeşit kümesinde bulunan dosya.

```kotlin
// Main.kt — kenar durumlari
fun main() {
    val order = layersFor(listOf("free", "local"), "debug")
    println(order.joinToString())
    val sets = mapOf(
        "free" to setOf("res/promo.xml"),
        "local" to setOf("res/promo.xml"),
    )
    val asks = listOf(
        resolveLayered("res/promo.xml", "local", order, sets),
        resolveLayered("res/missing.xml", "main", order, sets),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
main, local, free, debug, freeLocalDebug
res/promo.xml: istenen=local | verilen=free -> ikame
res/missing.xml: istenen=main | neden=dosya-yok -> karsiliksiz
tam: 0
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

Aynı dosya iki çeşit kümesinde bulunduğunda model önce bildirilen boyutun çeşidini seçti. `local` kümesinden istenen dosya `free` kümesinden verildi: ikame. Platform da bu durumda boyutların bildirim sırasına bakar ve önce bildirilen boyutun çeşidini kazandırır; iki çeşit kümesinde bulunan kaynak dosyası bu yüzden çakışma sayılmaz.

**AP5.** Model platformun dört basamaklı sırasını kurar: varyant, derleme türü, çeşitler, ana küme. Kaynakların birleştirilmesindeki ayrıntılı kurallar ölçülmedi; ölçüm yalnız modelin bu sırayı uyguladığını gösterir.

#### Kod Kümeleri

Her çeşit, kendi kaynak kodunu ve kaynaklarını taşıyan bir kod kümesine (source set) sahiptir. Dizinler çeşit ve varyant adlarıyla kurulur:

- `src/main` her varyanta girer.
- `src/free` ve `src/paid` ilgili çeşitlerde girer.
- `src/paidRelease` yalnız o varyanta girer.

Aynı varyantın kod kümeleri derlemede birleşir ve bir dosya birden çok kümede bulunursa çeşit kümesi ana kümeye üstün gelir. Kaynak dosyalar için bu kural böyle işler. Kaynak kod sınıfları için ise iki kümede aynı sınıf bulunursa sonuç çakışmadır ve derleme durur. Model bu iki ayrımı tek işlevde kurar.

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

`resolveSourceFile` bir dosya yolunu ana küme ve çeşit kümesiyle karşılaştırır. Yalnız çeşit kümesinde bulunan dosya çeşitten, yalnız ana kümede bulunan ana kümeden gelir. İkisinde de bulunan kaynak dosyasında çeşit kazanır; ikisinde de bulunan `.kt` sınıfı çakışmadır.

```kotlin
// Sources.kt — kod kumesi onceligi modeli
class SourceSet(val name: String, val files: Set<String>)

fun resolveSourceFile(path: String, wanted: String, main: SourceSet, flavor: SourceSet): Ask {
    val inMain = path in main.files
    val inFlavor = path in flavor.files
    val outcome = when {
        inMain && inFlavor && path.endsWith(".kt") -> Outcome.Refused("sinif-cakismasi")
        inFlavor -> Outcome.Given(flavor.name)
        inMain -> Outcome.Given(main.name)
        else -> Outcome.Refused("dosya-yok")
    }
    return Ask(path, wanted, outcome)
}
```

Ölçümde dört dosya vardır. Yalnız çeşit kümesinde olan reklam sınıfı, yalnız ana kümede olan ortak görsel, iki kümede bulunan metin kaynağı ve iki kümede bulunan ödeme sınıfı.

```kotlin
// Main.kt — kod kumesi onceligi
fun main() {
    val main = SourceSet("main", setOf("Billing.kt", "res/logo.xml", "res/strings.xml"))
    val free = SourceSet("free", setOf("Ads.kt", "Billing.kt", "res/strings.xml"))
    val asks = listOf(
        resolveSourceFile("Ads.kt", "free", main, free),
        resolveSourceFile("res/logo.xml", "main", main, free),
        resolveSourceFile("res/strings.xml", "main", main, free),
        resolveSourceFile("Billing.kt", "main", main, free),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
Ads.kt: istenen=free | verilen=free -> tam
res/logo.xml: istenen=main | verilen=main -> tam
res/strings.xml: istenen=main | verilen=free -> ikame
Billing.kt: istenen=main | neden=sinif-cakismasi -> karsiliksiz
tam: 2
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

Metin kaynağı ana kümeden istendi ve çeşit kümesinden verildi: ikame. Aynı çift için `Billing.kt` hiçbir şey verilmeden çakışma olarak kaldı. Kural iki sonucu yalnız verilen değerden ayırır; ikamenin gerçek derlemede kasıtlı bir üstüne yazma, çakışmanın ise bir hata olduğunu kural bilmez.

**AP3.** Model yalnız bir ana küme ile tek bir çeşit kümesini karşılaştırır. Varyant ve derleme türü kümelerinin birbirine göre önceliği ölçülmedi.

#### Özet

- Bu dersin ölçütü: **karşılama**; istenen varyantın ya da dosyanın olduğu gibi verilip verilmediği, girdinin değişmesi değil. Bu dersin karşılaması: **ikame**.
- Ürün çeşidi aynı kod tabanından türeyen adlandırılmış bir sürümdür; çeşit boyutu çeşitlerin ayrıştığı eksendir.
- Derleme varyantı çeşitler ile derleme türlerinin çarpımıdır; ad boyut sırasıyla kurulur.
- Kümede bulunan varyant tam verilir; kümede olmayan ya da süzgeçle dışlanan varyant karşılıksızdır.
- Çeşit kod kümesi ana kümeye üstün gelir; kaynak dosyada bu ikame, aynı kaynak kod sınıfında çakışmadır.

#### Sonraki Adım

Derleme varyantları hazır olunca uygulamanın çalıştırılıp denenmesi gerekir. Sonraki ders geliştirme ortamını, emülatörü, aygıt bağlantısını ve hata ayıklama araçlarını ele alır.

### Geliştirme Ortamı

Source: https://academia.sh/tr/kurslar/android-platformu/gelistirme-ortami

Geliştirme ortamında uygulamanın aygıta kurulması isteğinin API düzeyi, hata ayıklama yetkisi ve işlemci mimarisi karşısında modeli.

Önceki ders aynı kaynaktan hangi derleme varyantlarının çıktığını ölçtü. Bir varyant derlenince onu çalıştıracak bir yer gerekir. Geliştirme ortamı (development environment) kaynak kodun yazıldığı, derlendiği, aygıta kurulduğu ve izlendiği araç bütünüdür. Bu ders, bir kurulum isteğine aygıtın ne verdiğini ölçer.

Kursun ölçü ekseni değişmez: bu isteği platform nasıl karşılıyor. Karşılama dörttür: tam, ikame, bekleyen ve karşılıksız. Karşılamayı etiket değil kural verir: verilen değer istenene eşitse tam, eşit değilse ikame; bekleyen ve karşılıksız doğrudan sonuçtan çıkar. Aşağıdaki dosya o kuralı taşır ve her derste aynen yinelenir. Açılış dersindeki 3/2/2/1 sayısı burada değiştirilmez.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

#### Aygıtlar ve Araçlar

Uygulama iki yerde çalıştırılır. Emülatör (emulator) bilgisayarda bir aygıtı taklit eden sanal aygıttır. Fiziksel aygıt ise gerçek bir donanımdır ve bilgisayara bağlanır. İkisi de araçlar açısından aynı arayüzü sunar. Araçlar dört işi yapar:

- Aygıt bağlantı aracı aygıtı bulur, uygulamayı kurar ve aygıtla bilgisayar arasında köprü kurar.
- Günlük (log) aracı aygıtın yazdığı günlük satırlarını gösterir.
- Hata ayıklayıcı (debugger) çalışan uygulamayı durdurup değişkenlerini inceler.
- Profil aracı (profiler) işlemci, bellek ve ağ kullanımını zaman içinde ölçer.

Aşağıdaki parça uygulamanın en düşük API düzeyini bildiren derleme ayarını gösterir. Platform araçlarının okuduğu bir betiktir ve çalıştırılmaz.

```kotlin
// modul derleme betigi
android {
    defaultConfig {
        minSdk = 26
    }
}
```

Bu ayar uygulamanın hangi aygıtlara kurulabileceğini sınırlar. Aygıtın API düzeyi (API level) bu değerin altındaysa kurulum yapılmaz. Model, platformun bu kuralını kurar.

#### Kurulum İsteği

Model bir aygıtı adı, API düzeyi, işlemci mimarisi ve hata ayıklama yetkisinin onaylanıp onaylanmadığıyla tanımlar. Kurulum isteğinde üç durum ayrılır:

- Aygıtın API düzeyi en düşük API düzeyinin altındaysa kurulum verilmez: karşılıksız, neden `api-duzeyi`.
- API düzeyi yeterli ama aygıt hata ayıklama yetkisini henüz onaylamamışsa kurulum bekler: bekleyen, bekledigi `kullanici-onayi`.
- Her iki koşul sağlanıyorsa istenen uygulama olduğu gibi kurulur: tam.

```kotlin
// Install.kt — kurulum istegi modeli
class Device(val name: String, val apiLevel: Int, val abi: String, val debugApproved: Boolean)

fun resolveInstall(wanted: String, minApi: Int, device: Device): Ask {
    val outcome = when {
        device.apiLevel < minApi -> Outcome.Refused("api-duzeyi")
        !device.debugApproved -> Outcome.Waiting("kullanici-onayi")
        else -> Outcome.Given(wanted)
    }
    return Ask(device.name, wanted, outcome)
}
```

Ölçümde en düşük API düzeyi 26'dır. Dört aygıt vardır: güncel bir emülatör, onayı verilmiş bir fiziksel aygıt, onay ekranını henüz geçmemiş bir fiziksel aygıt ve eski bir aygıt.

```kotlin
// Main.kt — kurulum istegi
fun main() {
    val devices = listOf(
        Device("emulator", 34, "arm64", true),
        Device("telefon-onayli", 30, "arm64", true),
        Device("telefon-onaysiz", 30, "arm64", false),
        Device("eski-telefon", 23, "arm64", true),
    )
    val asks = devices.map { resolveInstall("uygulama", 26, it) }
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
emulator: istenen=uygulama | verilen=uygulama -> tam
telefon-onayli: istenen=uygulama | verilen=uygulama -> tam
telefon-onaysiz: istenen=uygulama | bekledigi=kullanici-onayi -> bekleyen
eski-telefon: istenen=uygulama | neden=api-duzeyi -> karsiliksiz
tam: 2
ikame: 0
bekleyen: 1
karsiliksiz: 1
```

İki aygıt tam, bir aygıt bekleyen, bir aygıt karşılıksız aldı. Onaysız telefon reddedilmedi, API düzeyi yetmeyen telefon reddedildi.

**AP1.** Model hata ayıklama yetkisini tek bir mantıksal değer olarak kurar. Gerçek aygıtta bu onay kullanıcı etkileşimiyle verilir ve aygıt başına ayrı tutulur; ölçüm bunu taklit etmez.

**AP2.** Kurulum modeli emülatörle fiziksel aygıt arasında fark kurmaz; fark yalnız sensör ölçümünde vardır. Gerçekte performans ve ağ farkları da vardır.

#### Onayın Gelmesi

Bekleyen bir istek onay gelince tam olur. Bunu ölçmek için aynı aygıt iki kez sorulur: önce onaysız, sonra onaylı.

```kotlin
// Main.kt — onay once ve sonra
fun main() {
    val before = Device("telefon", 30, "arm64", false)
    val after = Device("telefon", 30, "arm64", true)
    val asks = listOf(
        resolveInstall("uygulama", 26, before),
        resolveInstall("uygulama", 26, after),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
telefon: istenen=uygulama | bekledigi=kullanici-onayi -> bekleyen
telefon: istenen=uygulama | verilen=uygulama -> tam
tam: 1
ikame: 0
bekleyen: 1
karsiliksiz: 0
```

Aynı istek bekleyen iken ve onaydan sonra farklı karşılandı. Bekleyen sonuç bir kullanıcı eylemini adlandırır; eylem gelince aynı istek tam olur.

#### Hata Ayıklama ve Profil Araçları

Üç araç aynı çalışan uygulamaya bağlanır ama farklı bir iş görür. Günlük aracı uygulamanın ve sistemin yazdığı satırları akıtır; uygulamayı durdurmaz. Hata ayıklayıcı uygulamayı kaynak kodda belirlenen noktada durdurur, değişkenleri okur ve adım adım ilerletir. Profil aracı uygulamayı durdurmadan işlemci, bellek ve ağ kullanımını zaman çizelgesi olarak kaydeder. Hata ayıklayıcı uygulamanın çalışma biçimine müdahale ettiği için derleme türünün buna izin vermesi gerekir. Aşağıdaki betik parçası bir dağıtım derlemesinin hata ayıklamaya kapalı olduğunu bildirir.

```kotlin
// modul derleme betigi
android {
    buildTypes {
        getByName("release") { isDebuggable = false }
        getByName("debug") { isDebuggable = true }
    }
}
```

Model bu ayara göre üç aracı sınar. Günlük aracı her derlemede tam verilir. Hata ayıklayıcı yalnız hata ayıklamaya açık derlemede tam verilir; kapalıysa karşılıksız, neden `hata-ayiklanabilir-degil`. Profil aracı kapalı derlemede kısıtlı bir ölçüm verir; model bunu ikame sayar.

```kotlin
// Tools.kt — arac baglanma modeli
fun attachTool(tool: String, debuggable: Boolean): Ask {
    val outcome = when (tool) {
        "gunluk-araci" -> Outcome.Given(tool)
        "hata-ayiklayici" ->
            if (debuggable) Outcome.Given(tool) else Outcome.Refused("hata-ayiklanabilir-degil")
        "profil-araci" ->
            if (debuggable) Outcome.Given(tool) else Outcome.Given("kisitli-profil-araci")
        else -> Outcome.Refused("arac-yok")
    }
    return Ask("$tool/${if (debuggable) "debug" else "release"}", tool, outcome)
}
```

Ölçümde üç aracın her biri iki derlemede sorulur ve bilinmeyen bir araç adı eklenir.

```kotlin
// Main.kt — arac baglanma
fun main() {
    val asks = listOf(
        attachTool("gunluk-araci", true),
        attachTool("hata-ayiklayici", true),
        attachTool("profil-araci", true),
        attachTool("gunluk-araci", false),
        attachTool("hata-ayiklayici", false),
        attachTool("profil-araci", false),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
gunluk-araci/debug: istenen=gunluk-araci | verilen=gunluk-araci -> tam
hata-ayiklayici/debug: istenen=hata-ayiklayici | verilen=hata-ayiklayici -> tam
profil-araci/debug: istenen=profil-araci | verilen=profil-araci -> tam
gunluk-araci/release: istenen=gunluk-araci | verilen=gunluk-araci -> tam
hata-ayiklayici/release: istenen=hata-ayiklayici | neden=hata-ayiklanabilir-degil -> karsiliksiz
profil-araci/release: istenen=profil-araci | verilen=kisitli-profil-araci -> ikame
tam: 4
ikame: 1
bekleyen: 0
karsiliksiz: 1
```

Aynı üç araç iki derlemede farklı karşılandı: fark araçtan değil derleme türünden geldi. Hata ayıklayıcı dağıtım derlemesinde hiç verilmedi, profil aracı ise başka bir şey olarak verildi.

**AP3.** Model profil aracının kapalı derlemedeki davranışını kısıtlı ölçüm olarak kurar. Gerçek platformda hangi ölçümlerin kaldığı araca ve sürüme bağlıdır; ölçüm bunu doğrulamaz.

#### Emülatör ile Fiziksel Aygıt

Emülatör sensör verisini taklit eder: konum, ivme ve kamera için sanal değerler verir. Fiziksel aygıtta bu değerler gerçek donanımdan gelir. Aygıt bağlı değilse hiçbir şey verilemez, ama bağlantı kurulunca istek yeniden sorulabilir. Model üç durumu ayırır: bağlı olmayan aygıt bekleyen, bağlı emülatör ikame, bağlı fiziksel aygıt tam.

```kotlin
// Sensor.kt — sensor istegi modeli
class Target(val name: String, val connected: Boolean, val emulated: Boolean)

fun resolveSensor(wanted: String, target: Target): Ask {
    val outcome = when {
        !target.connected -> Outcome.Waiting("aygit-baglantisi")
        target.emulated -> Outcome.Given("sanal-$wanted")
        else -> Outcome.Given(wanted)
    }
    return Ask(target.name, wanted, outcome)
}
```

Ölçümde üç hedef vardır: kablosu takılı bir telefon, çalışan bir emülatör ve kablosu çıkarılmış bir telefon.

```kotlin
// Main.kt — sensor istegi
fun main() {
    val targets = listOf(
        Target("telefon", true, false),
        Target("emulator", true, true),
        Target("telefon-kablosuz", false, false),
    )
    val asks = targets.map { resolveSensor("konum", it) }
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
telefon: istenen=konum | verilen=konum -> tam
emulator: istenen=konum | verilen=sanal-konum -> ikame
telefon-kablosuz: istenen=konum | bekledigi=aygit-baglantisi -> bekleyen
tam: 1
ikame: 1
bekleyen: 1
karsiliksiz: 0
```

Emülatörün verdiği değer, istenen değerin sanal bir sürümüdür; bu yüzden ikamedir. Bağlantı kesik telefon ise reddedilmedi, bekliyor.

**AP4.** Model sanal değeri adıyla ayırır. Gerçek emülatörde sanal konumun nasıl verildiği ve sensör türlerinin hangisinin taklit edildiği ölçülmedi.

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

Yerel kod (native code) belirli bir işlemci mimarisi için derlenir. API düzeyi yeterli olan bir aygıt da mimari uyuşmazsa uygulamanın yerel kısmını çalıştıramaz. Model hangi alanı karşılaştırdığını adıyla söyler: önce API düzeyi, sonra işlemci mimarisi.

```kotlin
// Abi.kt — mimari karsilastirmasi
fun resolveNative(wanted: String, minApi: Int, builtFor: Set<String>, device: Device): Ask {
    val outcome = when {
        device.apiLevel < minApi -> Outcome.Refused("api-duzeyi")
        device.abi !in builtFor -> Outcome.Refused("islemci-mimarisi")
        else -> Outcome.Given(wanted)
    }
    return Ask(device.name, wanted, outcome)
}
```

Yerel kod yalnız `arm64` için derlenmiştir. Emülatör API düzeyi yeterli olsa da `x86` mimarisindedir; ikinci bir emülatör `arm64` mimarisindedir.

```kotlin
// Main.kt — mimari uyusmazligi
fun main() {
    val builtFor = setOf("arm64")
    val devices = listOf(
        Device("emulator-x86", 34, "x86", true),
        Device("emulator-arm", 34, "arm64", true),
        Device("eski-emulator", 23, "x86", true),
    )
    val asks = devices.map { resolveNative("yerel-kod", 26, builtFor, it) }
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
emulator-x86: istenen=yerel-kod | neden=islemci-mimarisi -> karsiliksiz
emulator-arm: istenen=yerel-kod | verilen=yerel-kod -> tam
eski-emulator: istenen=yerel-kod | neden=api-duzeyi -> karsiliksiz
tam: 1
ikame: 0
bekleyen: 0
karsiliksiz: 2
```

İlk emülatör aynı API düzeyindeki ikincisiyle yalnız mimaride ayrılır ve sonuç değişir. Üçüncü aygıtta iki koşul da sağlanmaz; model karşılaştırmayı sırayla yapar ve ilk uyuşmayan alanın adını verir, bu yüzden neden `api-duzeyi` olur.

**AP5.** Model yalnız tam eşleşmeyi kabul eder. Gerçek platformda bir mimari başka bir mimariyi çeviri katmanıyla çalıştırabilir; bu ölçülmedi.

#### Özet

- Bu dersin ölçütü: **karşılama**; kurulan şeyin istenen olup olmadığı, aygıtın nerede çalıştığı değil. Bu dersin karşılaması: **bekleyen**.
- Geliştirme ortamı yazma, derleme, kurma ve izleme araçlarının bütünüdür; emülatör ve fiziksel aygıt aynı kurulum arayüzünü kullanır.
- Aygıtın API düzeyi en düşük API düzeyinin altındaysa kurulum karşılıksızdır.
- Hata ayıklama yetkisi onaylanmadıysa kurulum bekleyendir; onay gelince tam olur.
- Yerel kodun işlemci mimarisi aygıtınkiyle uyuşmazsa API düzeyi yeterli olsa bile sonuç karşılıksızdır.

#### Sonraki Adım

Uygulama bir aygıta kurulunca kullanıcıdan hassas verilere ve donanıma erişim istemesi gerekir. Sonraki ders izin modelini ele alır.

### İzin Modeli

Source: https://academia.sh/tr/kurslar/android-platformu/izin-modeli

Kurulum izni ile çalışma zamanı izninin, normal ve tehlikeli iznin, kesin ve yaklaşık konumun, reddin ve bir daha sorma durumunun karşılama modeli ile kursun kapanış tablosu.

Önceki ders uygulamanın aygıta kurulmasını ölçtü. Kurulum bir kez yapılır ve uygulama aygıtta durur. Sonra uygulama kullanıcının kamerasını, konumunu ya da rehberini ister. İzin (permission) uygulamanın korunan bir veriye ya da donanıma erişim hakkıdır. Bu ders, bir izin isteğine platformun ne verdiğini ölçer ve kursu kapatır.

Kursun ölçü ekseni değişmez: bu isteği platform nasıl karşılıyor. Karşılama dörttür: tam, ikame, bekleyen ve karşılıksız. Karşılamayı etiket değil kural verir: verilen değer istenene eşitse tam, eşit değilse ikame; bekleyen ve karşılıksız doğrudan sonuçtan çıkar. Aşağıdaki dosya o kuralı taşır ve her derste aynen yinelenir.

```kotlin
// Fulfillment.kt — bir istegin platformca nasil karsilandigini siniflandirir
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "bekleyen"
        is Outcome.Refused -> "karsiliksiz"
        is Outcome.Given -> if (outcome.value == wanted) "tam" else "ikame"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "verilen=${outcome.value}"
            is Outcome.Waiting -> "bekledigi=${outcome.on}"
            is Outcome.Refused -> "neden=${outcome.reason}"
        }
        println("$label: istenen=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("tam", "ikame", "bekleyen", "karsiliksiz")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

M30/K02'deki izin dersleri, istenen izin kümesiyle verilen kümenin buluşmasını ölçmüştü. Burada her izin ayrı bir istektir ve kullanıcının henüz yanıtlamadığı bir izin, kendi başına bir kategoridir: bekleyen. M12/K03'te karar kullanıcıya aitti, ama kategori kararın sahibini adlandırmıyordu; karşılama aynı kalıyordu ve yalnız `neden=` alanı sahibi adlandırabiliyordu. Burada da öyledir.

#### Kurulum İzni ve Çalışma Zamanı İzni

İzinler iki anda verilir. Kurulum izni (install-time permission) uygulama kurulurken, kullanıcıya soru sorulmadan verilir. Çalışma zamanı izni (runtime permission) uygulama çalışırken, kullanıcıya bir iletişim kutusu gösterilerek istenir. Hangisinin hangi yoldan geçeceğini izin türü belirler:

- Normal izin (normal permission) kullanıcının gizliliğine az dokunur, örneğin ağa erişim. Bildirim dosyasında bildirilmesi yeterlidir ve kurulumda verilir.
- Tehlikeli izin (dangerous permission) kullanıcının verisine ya da donanımına dokunur, örneğin kamera, ses kaydı ve konum. Bildirim dosyasında bildirilmesi gerekir, ama kullanıcı çalışma zamanında onaylamadıkça verilmez.

Bildirim dosyasındaki bildirim şuna benzer. Platform araçlarının okuduğu bir dosyadır ve çalıştırılmaz.

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

Model bir izni adı, tehlikeli olup olmadığı ve bildirim dosyasında bildirilip bildirilmediğiyle tanımlar. Kullanıcının yanıtı ayrı bir girdidir: yanıt yoksa `null`, onay `ver`, ret `reddet`. Başka bir metin, kullanıcının istenenden farklı bir şey verdiğini gösterir.

```kotlin
// Permission.kt — izin istegi modeli
class Perm(val name: String, val dangerous: Boolean, val declared: Boolean)

fun resolvePermission(label: String, perm: Perm, reply: String?): Ask {
    val outcome = when {
        !perm.declared -> Outcome.Refused("bildirilmedi")
        !perm.dangerous -> Outcome.Given(perm.name)
        reply == null -> Outcome.Waiting("kullanici-yaniti")
        reply == "reddet" -> Outcome.Refused("kullanici-reddetti")
        reply == "ver" -> Outcome.Given(perm.name)
        else -> Outcome.Given(reply)
    }
    return Ask(label, perm.name, outcome)
}
```

Ölçümde üç izin vardır. Normal bir izin ile iki tehlikeli izin sorulur ve kullanıcı hiçbirini henüz yanıtlamamıştır.

```kotlin
// Main.kt — normal ve tehlikeli izin
fun main() {
    val asks = listOf(
        resolvePermission("ag", Perm("internet", false, true), null),
        resolvePermission("kamera", Perm("kamera", true, true), null),
        resolvePermission("mikrofon", Perm("ses-kaydi", true, true), null),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
ag: istenen=internet | verilen=internet -> tam
kamera: istenen=kamera | bekledigi=kullanici-yaniti -> bekleyen
mikrofon: istenen=ses-kaydi | bekledigi=kullanici-yaniti -> bekleyen
tam: 1
ikame: 0
bekleyen: 2
karsiliksiz: 0
```

Normal izin hiçbir yanıt beklemeden tam oldu. Tehlikeli iki izin reddedilmedi, bekliyor ve beklediği şeyin adı `kullanici-yaniti`. Bu dersin karşılaması bekleyendir.

**AP6.** Model yanıtı tek bir metin olarak kurar. Gerçek platformda yanıt, iletişim kutusunun sonucudur ve iznin hangi seçenekle verildiğine göre ayrılır; ölçüm iletişim kutusunu taklit etmez.

**AP7.** Model normal ile tehlikeli izin ayrımını izin başına bir mantıksal değer olarak alır. Gerçek platformda bir iznin koruma düzeyini sürüm belirler; hangi iznin hangi düzeyde olduğu ölçülmedi.

#### İzin İsteme Akışı

Çalışma zamanı izni kod içinde bir sözleşmeyle istenir. `ActivityResultContracts.RequestPermission` iznin adını alır ve kullanıcının onay verip vermediğini bir geri çağrıyla bildirir. Aşağıdaki parça akışın şeklini gösterir. Platform API'si kullanır, bu yüzden ölçülmez ve çıktısı iddia edilmez.

```kotlin
val launcher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { granted ->
    if (granted) openCamera() else showFallback()
}

launcher.launch(Manifest.permission.CAMERA)
```

Akışta iki geri dönüş yolu vardır ve model ikisini de ayırır. İletişim kutusu açıldığı anda uygulama yanıt beklemektedir: bekleyen. Kullanıcı yanıt verince aynı istek ya tam ya karşılıksız olur. Bunu ölçmek için aynı izin üç kullanıcı durumunda sorulur.

```kotlin
// Main.kt — yanit once, onay ve ret
fun main() {
    val camera = Perm("kamera", true, true)
    val asks = listOf(
        resolvePermission("yanit-yok", camera, null),
        resolvePermission("onay", camera, "ver"),
        resolvePermission("ret", camera, "reddet"),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
yanit-yok: istenen=kamera | bekledigi=kullanici-yaniti -> bekleyen
onay: istenen=kamera | verilen=kamera -> tam
ret: istenen=kamera | neden=kullanici-reddetti -> karsiliksiz
tam: 1
ikame: 0
bekleyen: 1
karsiliksiz: 1
```

Aynı istek üç karşılama aldı ve fark izinden gelmedi, kullanıcının yanıtından geldi. Karşılama kategorisi sahibi söylemez; yalnız `neden=kullanici-reddetti` kullanıcının kararını adlandırır.

#### Kesin ve Yaklaşık Konum

Konum izni iki ayrıntıda istenir. Kesin konum (precise location) aygıtın bulunduğu yeri ayrıntılı verir. Yaklaşık konum (approximate location) yalnız çevreyi verir. Uygulama ikisini birlikte isteyebilir ve kullanıcı yaklaşık olanı seçerse uygulama kesin konum olmadan çalışmak zorunda kalır. Model bunu ikame sayar: istenen kesin konumdur, verilen yaklaşık konumdur.

```kotlin
// Main.kt — kesin konum istendi, kullanici ne verdi
fun main() {
    val location = Perm("kesin-konum", true, true)
    val asks = listOf(
        resolvePermission("kesin-ver", location, "ver"),
        resolvePermission("yaklasik-ver", location, "yaklasik-konum"),
        resolvePermission("yanit-yok", location, null),
        resolvePermission("ret", location, "reddet"),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
kesin-ver: istenen=kesin-konum | verilen=kesin-konum -> tam
yaklasik-ver: istenen=kesin-konum | verilen=yaklasik-konum -> ikame
yanit-yok: istenen=kesin-konum | bekledigi=kullanici-yaniti -> bekleyen
ret: istenen=kesin-konum | neden=kullanici-reddetti -> karsiliksiz
tam: 1
ikame: 1
bekleyen: 1
karsiliksiz: 1
```

Dört durumun dördü ayrı bir kategoriye düştü. Kullanıcı yaklaşık konumu verince uygulama reddedilmedi, istediğinden azıyla çalışıyor.

**AP8.** Model yaklaşık konumu kesin konumun ikamesi olarak adlandırır. Gerçek platformda yaklaşık konumun doğruluğu aygıta ve ayara göre değişir; ölçüm bu doğruluğu bir sayıyla vermez.

#### Ret, Bir Daha Sorma ve Gerekçe

Kullanıcı bir isteği reddedince uygulama aynı iznin iletişim kutusunu hemen tekrar gösteremeyebilir. Platform iki durumu ayırır. İlk retten sonra sistem, iznin neden gerektiğini açıklamak için gerekçe göstermeyi önerir: `shouldShowRequestPermissionRationale` değeri `true` döner. Gerekçe (rationale) uygulamanın kendi ekranıdır, kullanıcıya iznin neye yaradığını söyler. Kullanıcı isteği "bir daha sorma" ile reddettiğinde ise sistem iletişim kutusunu hiç göstermez ve istek doğrudan reddedilmiş döner.

Gerekçe karşılamayı değiştirmez. Değişen, kullanıcının vereceği yanıtın olasılığıdır. Karşılamayı değiştiren, kullanıcıya ulaşılıp ulaşılamadığıdır. Model bunu tek bir girdiyle kurar: iletişim kutusu engellenmiş mi.

```kotlin
// Denial.kt — ret sonrasi istek modeli
fun askAfterDenial(label: String, perm: Perm, blocked: Boolean, reply: String?): Ask {
    val outcome = when {
        blocked -> Outcome.Refused("bir-daha-sorma")
        reply == null -> Outcome.Waiting("kullanici-yaniti")
        reply == "reddet" -> Outcome.Refused("kullanici-reddetti")
        else -> Outcome.Given(perm.name)
    }
    return Ask(label, perm.name, outcome)
}
```

Ölçümde kullanıcı kamerayı bir kez reddetmiştir. İlk iki satırda iletişim kutusu açıktır ve gerekçeden önce ve sonra sorulur. Son iki satırda kullanıcı "bir daha sorma" demiştir: biri yanıtsız, biri onaylı sorulur.

```kotlin
// Main.kt — gerekce ve bir daha sorma
fun main() {
    val camera = Perm("kamera", true, true)
    val asks = listOf(
        askAfterDenial("gerekce-oncesi", camera, false, null),
        askAfterDenial("gerekce-sonrasi", camera, false, "ver"),
        askAfterDenial("bir-daha-sorma", camera, true, null),
        askAfterDenial("bir-daha-sorma-onay", camera, true, "ver"),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
gerekce-oncesi: istenen=kamera | bekledigi=kullanici-yaniti -> bekleyen
gerekce-sonrasi: istenen=kamera | verilen=kamera -> tam
bir-daha-sorma: istenen=kamera | neden=bir-daha-sorma -> karsiliksiz
bir-daha-sorma-onay: istenen=kamera | neden=bir-daha-sorma -> karsiliksiz
tam: 1
ikame: 0
bekleyen: 1
karsiliksiz: 2
```

Engellenmiş iletişim kutusunda onay yanıtı bile sonucu değiştirmedi, çünkü yanıt kullanıcıya hiç ulaşmadı. Bu durumdan çıkış uygulamanın içinde değildir: kullanıcı iznin kendisini sistem ayarlarından açar.

**AP9.** Model "bir daha sorma" durumunu tek bir mantıksal değer olarak alır. Gerçek platformda bu durumun hangi reddin kaç kez tekrarlanmasıyla oluştuğu sürüme göre değişir; ölçüm bunu belirlemez.

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

Karşılama kuralı sonucu ve eşitliği görür, nedeni görmez. İzinde bu sınırın bir yüzü vardır: bildirilmemiş bir izin hiçbir kullanıcı yanıtıyla verilemez. Model karşılaştırmayı sırayla yapar. Önce izin bildirim dosyasında var mı, sonra tehlikeli mi, sonra kullanıcı ne dedi. İlk uyuşmayan alan sonucu belirler ve adını verir.

Ölçümde aynı kamera izni bildirimde yazılı olmadan ve kullanıcı onay vermişken sorulur. Karşılaştırma için normal bir izin de bildirilmeden sorulur ve bildirilmiş hali eklenir.

```kotlin
// Main.kt — bildirilmemis izin
fun main() {
    val asks = listOf(
        resolvePermission("bildirimsiz-kamera", Perm("kamera", true, false), "ver"),
        resolvePermission("bildirimsiz-ag", Perm("internet", false, false), null),
        resolvePermission("bildirimli-kamera", Perm("kamera", true, true), "ver"),
    )
    for (ask in asks) ask.report()
    tally(asks)
}
```

```text
bildirimsiz-kamera: istenen=kamera | neden=bildirilmedi -> karsiliksiz
bildirimsiz-ag: istenen=internet | neden=bildirilmedi -> karsiliksiz
bildirimli-kamera: istenen=kamera | verilen=kamera -> tam
tam: 1
ikame: 0
bekleyen: 0
karsiliksiz: 2
```

Kullanıcının onayı bildirilmemiş kamera iznini kurtarmadı. Normal izin de bildirilmeden verilmedi: kurulum izni de bildirim dosyasından başlar.

#### Kapanış Tablosu

Kurs on beş ders boyunca aynı soruyu sordu. Aşağıdaki tablo her dersin ölçtüğü isteği, o dersin karşılamasını ve dersin sınırlayıcı ölçümünün gösterdiğini bir arada verir. Karşılama hücresi her dersin Özet'indeki "Bu dersin karşılaması" maddesinden alınmıştır. Seçim kuralı tektir: dersin karşılaması, "Ölçülen istek" sütunundaki odak isteğin karşılamasıdır; dersteki ölçümlerin çoğunluğu değildir.

| Ders | Ölçülen istek | Karşılama | Sınırlayıcı ölçüm |
|---|---|---|---|
| Android Mimari Katmanları | bildirilen activity, normal izin | tam | aynı tam iki ayrı nedenden gelir, kural nedeni görmez; uyumluluk karşılığı ikame |
| Activity'ler | bildirilmemiş ya da dışa kapalı activity | karşılıksız | `android:exported`: aynı activity çağırana göre tam ya da karşılıksız |
| Activity Yaşam Döngüsü | yapılandırma değişikliğinde durum | ikame | durdurulmuş activity'ye arayüz güncellemesi bekler, yok edilmişe karşılıksız |
| Fragment'lar | ev sahibi durmuşken fragment işlemi | bekleyen | durum kaydedildikten sonra `commit` karşılıksız, `commitAllowingStateLoss` ikame |
| Servisler | arka plandan servis başlatma | karşılıksız | ön plan servisi ikame, koşullu zamanlanmış iş bekleyen |
| Yayın Alıcıları | örtük sistem yayını | karşılıksız | aynı alıcı sınıfı üç yolda iki sonuç |
| İçerik Sağlayıcılar | sağlayıcıdan satır sorgusu | tam | izinsiz sorgu karşılıksız, URI izni yalnız tek satıra |
| Intent'ler ve Intent Süzgeçleri | örtük intent | bekleyen | kurulu uygulama sayısı: sıfır karşılıksız, bir tam, iki bekleyen |
| Görevler ve Geri Yığını | yeni örnek isteği | ikame | aynı kip, yığıtın durumuna göre iki sonuç |
| Bildirim Dosyası | birleştirilen bildirim öğesi | tam | yer tutucu çözülmeden sonuç belli değil, bekleyen |
| Kaynak Sistemi | niteleyicili kaynak seçimi | ikame | bölge dizini yoksa dil dizini; niteleyici çakışması |
| Derleme Sistemi | geçişli bağımlılık sürümü | ikame | `1.10` sürümü `1.9` sürümünden yüksek; katı sürüm çakışması |
| Ürün Çeşitleri | kod kümesinden istenen dosya | ikame | aynı iki kümede bulunan `.kt` sınıfı çakışır, karşılıksız |
| Geliştirme Ortamı | uygulamanın aygıta kurulması | bekleyen | işlemci mimarisi uyuşmazlığı API düzeyi yeterli olsa da karşılıksız |
| İzin Modeli | tehlikeli çalışma zamanı izni | bekleyen | bildirilmemiş izin hiçbir yanıtla verilmez |

Dağılım: tam 3, ikame 5, bekleyen 4, karşılıksız 3; toplam 15. Açılış dersindeki çekirdek 3/2/2/1 dağılımını verdi. İki dağılım aynı şey değildir. Çekirdek sekiz isteğin tek bir ölçümüydü; tablo on beş dersin her birinin kendi karşılamasını sayar. Aralarında bir tahmin köprüsü kurulmaz: biri bir modelin çıktısı, öteki on beş ayrı modelin tek tek seçilmiş satırıdır.

#### Özet

- Bu dersin ölçütü: **karşılama**; iznin kullanıcıdan yanıt bekleyip beklemediği, kimin karar verdiği değil. Bu dersin karşılaması: **bekleyen**.
- Kurulum izni kurulumda, kullanıcıya sorulmadan verilir; çalışma zamanı izni çalışırken, kullanıcıya sorularak verilir.
- Normal izin bildirilince tam olur; tehlikeli izin kullanıcı yanıtlayana kadar bekleyendir, ret karşılıksızdır.
- Kesin konum istenip yaklaşık konum verilirse sonuç ikamedir.
- Gerekçe karşılamayı değiştirmez; "bir daha sorma" durumunda sistem kullanıcıya ulaşmaz ve istek karşılıksız döner.
- Bildirim dosyasında bildirilmemiş izin, kullanıcı onay verse de verilmez.

#### Sonraki Adım

Kurs burada kapanıyor. Sıradaki kurs arayüzün bildirimsel biçimde kurulmasını ele alır.

