İçeriğe geç
academia.sh

Ders 06 / 15

Yayın Alıcıları

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ü.

Yayın Güncelleme

Bu derste

  • Yayın alıcısının bildirim dosyasında ve çalışma zamanında iki yoldan kaydedildiğini ayırır
  • Örtük sistem yayınının bildirilen alıcıya ulaşmadığını, istisna listesindekinin ulaştığını ölçer
  • Çalışma zamanı kaydının ömrüne göre karşılamanın değiştiğini gösterir
  • Aynı yayının alıcı sınıfına üç ayrı yoldan iki farklı karşılamayla ulaştığını doğrular
İçindekiler

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.

// 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.
<application>
    <receiver android:name=".ScreenReceiver" android:exported="false">
        <intent-filter>
            <action android:name="android.intent.action.SCREEN_ON" />
        </intent-filter>
    </receiver>
</application>
// 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.

// 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.

// 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)
}
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ızdı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.

// 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)
}
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.

// 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.

// 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)
}
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.

// 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.

// 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)
}
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.

// 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())
}
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.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat