Ders 05 / 15
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ü.
Yayın Güncelleme
Bu derste
- Başlatılan servis, bağlı servis ve ön plan servisini birbirinden ayırır
- Servisin kendi iş parçacığını getirmediğini ölçer
- Arka plandan servis başlatma isteğinin API düzeyine göre karşılıksız kaldığını gösterir
- Koşullu zamanlanmış işin bekleyen kaldığını ve koşulunu adıyla taşıdığını doğrular
İçindekiler
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.
// 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
startServiceile başlatır; servis iş bitene kadar çalışır ve kendi kendini durdurur. - Bağlı servis: bir istemci
bindServiceile 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.
<application> <service android:name=".SyncService" android:exported="false" /> </application>
// 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ış.
// 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)
}
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.
// 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)
}
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ızdı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;onStartCommandboş (null) bir intent’le çağrılır.START_REDELIVER_INTENT: servis yeniden başlatılır ve son teslim edilen intent, işstopSelfile tamamlanana kadar yeniden teslim edilir.
// 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.
// 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)
}
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.
// 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.
// 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)
}
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.
// 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 })
}
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,sarjya daag,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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.