İçeriğe geç
academia.sh

Ders 01 / 14

Bildirimler

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

Yayın Güncelleme

Bu derste

  • Bildirim kanalı, önem düzeyi, eylem ve çalışma zamanı izninin kullanıcı bildirimindeki görevlerini ayırır
  • Gönderilen bir olayın alıcıya kaç kopya olarak ulaştığını birebir, yinelenen, ezilen ve kayıp olarak sınıflandırır
  • Kapalı kanalın ve verilmemiş iznin gönderilen olayı kayıp yaptığını ölçer
  • Aynı kimlikle gönderilen ikinci kullanıcı bildirimi birincinin yerini aldığında ilkinin ezilen olduğunu gösterir
İçindekiler

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

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

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

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

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

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

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

Kullanıcı Bildiriminin Parçaları

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

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

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

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

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

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

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

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

Sekiz Olayın Dört Teslimi

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

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

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

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

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

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
ulasan siralar: [3, 5, 5, 6, 8]
olay-1: anahtar=mesaj.deniz | kopya=0 -> ezilen
olay-2: anahtar=siparis.42 | kopya=0 -> ezilen
olay-3: anahtar=mesaj.deniz | kopya=1 -> birebir
olay-4: anahtar=kampanya.indirim | kopya=0 -> kayip
olay-5: anahtar=odeme.onay | kopya=2 -> yinelenen
olay-6: anahtar=siparis.42 | kopya=1 -> birebir
olay-7: anahtar=kampanya.yeni | kopya=0 -> kayip
olay-8: anahtar=hatirlatma.ilac | kopya=1 -> birebir
birebir: 3
yinelenen: 1
ezilen: 2
kayip: 2

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

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

Kapalı Kanal ve Verilmemiş İzin

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Kapıları Kim Kapatır

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

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

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

Yaygın Yanılgılar

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

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

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

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

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

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

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

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

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
ulasan siralar: [18, 19, 20]
olay-17: anahtar=siparis.42 | kopya=0 -> ezilen
olay-18: anahtar=mesaj.deniz | kopya=1 -> birebir
olay-19: anahtar=siparis.42 | kopya=1 -> birebir
olay-20: anahtar=hatirlatma.ilac | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 1
kayip: 0

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

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

Özet

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

Sonraki Adım

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

İ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