İçeriğe geç
academia.sh

Ders 07 / 15

İçerik Sağlayıcılar

İç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 Güncelleme

Bu derste

  • İçerik sağlayıcıyı, içerik URI'sini ve içerik çözümleyiciyi birbirinden ayırır
  • URI eşleyicisinin yolu hangi koda çevirdiğini, eşleşmeyen yolun karşılıksız kaldığını ölçer
  • Olmayan satırın boş imleçle ikame edildiğini, olmayan sütunun karşılıksız kaldığını gösterir
  • Aynı sorgunun çağıran durumuna göre tam ya da karşılıksız karşılandığını doğrular
İçindekiler

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.

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

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

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

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

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

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

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

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

İ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