AccessibilityService olmadan Android uygulama engelleyici yapmak

Yayındaki bir uygulamadan UsageStatsManager, bir ön plan hizmeti ve bir overlay. Yazan: Thomas Willheim · 9 Kasım 2026

Ekran süresini şınav çekerek kazandığın bir uygulama engelleyici olan HonorFit'i yapıyorum (onur sistemi, kamera yok). İlk Android sürümü uygulamaları çoğu Android engelleyicisinin yaptığı gibi engelliyordu: bir AccessibilityService ile. Ağustosta bunu söküp attım ve yerine UsageStatsManager, bir ön plan hizmeti (foreground service) ve bir overlay penceresi koydum. Engelleme biraz yumuşadı, ilk kurulum çok daha az korkutucu oldu ve Play incelemesi çok daha basitleşti.

Bu yazı o mimarinin neye benzediğini ve yol boyunca başımı ağrıtan şeyleri anlatıyor. Kodun tamamı yerel bir Expo modülünün içinde Kotlin, ama buradaki hiçbir şey React Native'e özgü değil.

Accessibility API'den neden vazgeçmeli

Erişilebilirlik hizmeti, Android'de eline geçebilecek en güçlü araç. Her pencere değişikliğinden anında haberin olur ve performGlobalAction(GLOBAL_ACTION_HOME) kullanıcıyı engelli bir uygulamadan o anda dışarı atar. Bu kadar çok engelleyicinin onu kullanmasının sebebi bu. Bedeli ise:

O tek bilgi başka bir yerden de alınabiliyor: Dijital Denge'nin (Digital Wellbeing) dayandığı kullanım olaylarından.

Genel yapı

Üç izin; hepsini kullanıcı Ayarlar'dan veriyor:

İzinNe için
PACKAGE_USAGE_STATS (“Kullanım erişimi”)Önde hangi uygulamanın olduğunu öğrenmek için UsageEvents okumak. Asla çalışma zamanında verilmez; kullanıcıyı Ayarlar ekranına gönderir ve app-op'u kontrol edersin.
SYSTEM_ALERT_WINDOW (“Diğer uygulamaların üzerinde göster”)Kilit ekranını engelli bir uygulamanın üstüne çizmek.
FOREGROUND_SERVICE_SPECIAL_USEİzleme hizmeti. Uygulama engelleme, listelenen ön plan hizmeti türlerinin hiçbirine uymuyor, bu yüzden nedenini açıklayan bir alt tür metniyle specialUse kullanılıyor.

Parçalar:

Hizmetin manifest girdisi:

<service
  android:name=".ForegroundMonitorService"
  android:exported="false"
  android:foregroundServiceType="specialUse">
  <property
    android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
    android:value="User-configured app blocking for digital wellbeing: monitors
      which app is in the foreground (UsageStats) so apps the user chose to block
      can be locked when their earned screen time runs out." />
</service>

specialUse'u ayrıca Play Console'daki ön plan hizmeti beyanında da gerekçelendiriyorsun. İki yerde de aynı hikâyeyi anlatmak işe yarıyor.

UsageStats'i yoklamak (ve yazma gecikmesi)

Ana döngü küçük. Son yoklamadan beri olan olayları tara ve son MOVE_TO_FOREGROUND olayının paketini tut (ACTIVITY_RESUMED ile aynı sabit, API 29'da yeniden adlandırıldı):

val end = System.currentTimeMillis()
val events = usm.queryEvents(windowStart, end)
val event = UsageEvents.Event()
while (events.hasNextEvent()) {
  events.getNextEvent(event)
  if (event.eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
    currentForeground = event.packageName
  }
}
// Re-scan the last 15 s next time: see below.
windowStart = end - REQUERY_OVERLAP_MS
handleForeground(currentForeground)

Erişilebilirlik olaylarına kıyasla hoş bir yan etki: klavye ve bildirim paneli activity değil, bu yüzden asla sahte “kullanıcı uygulamadan çıktı” çıkışları olarak görünmüyorlar.

Tuzak: olaylar geç geliyor

İlk sürümüm pencereyi yalnızca ileriye doğru kaydırıyordu: her yoklama [lastEnd, now] aralığını sorguluyordu. Çoğunlukla işe yaradı, ta ki soğuk başlatılmış bir telefonda engelli bir uygulamanın ilk açılışı hiç görülmeyene kadar. UsageStats olayları deposuna gecikmeyle yazıyor, yani 10:00:00.900 zaman damgalı bir olay, 10:00:01'i zaten kapsamış bir yoklamadan sonra ortaya çıkabiliyor. Yalnızca ileri giden bir pencere onu sonsuza dek atlıyor.

Çözüm: her yoklama geriye doğru bir örtüşme payını yeniden tarıyor (ben 15 saniye kullanıyorum). Aynı olayı iki kez görmek zararsız, çünkü her durumda penceredeki son resume kazanıyor. 15 saniyeden daha geç düşen bir olay yine de kaybolur, ama hiç görmedim.

Tuzak: ilk yoklamanın uzun bir geriye bakışa ihtiyacı var

Hizmet başladığında (bir yeniden başlatmadan sonra ya da ekran açıldığında) “bilinen son uygulama” yok. Kullanıcı 20 dakikadır TikTok'taysa ve ilk yoklama sadece bir dakika geriye bakıyorsa, kullanıcı uygulama değiştirene kadar hiçbir şey görmez. Bu yüzden ilk yoklama son bir saati tarıyor. Bu güvenli, çünkü başlatıcı (launcher) da bir activity: en son resume olan şey gerçekten de önde olan şey.

Tuzak: geri alınan erişim sessizlik gibi görünüyor

Kullanıcı hizmet çalışırken Kullanım erişimini kapatırsa queryEvents hata fırlatmıyor. Sadece hiçbir şey döndürmüyor. Son okuman (“Instagram önde”) bayatlıyor ve bakiye sonsuza dek eriyor. Bu yüzden her yoklama önce app-op'u kontrol ediyor ve izin gittiyse saati durduruyor:

fun hasUsageAccess(context: Context): Boolean {
  val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
  val mode = appOps.checkOpNoThrow(
    AppOpsManager.OPSTR_GET_USAGE_STATS, Process.myUid(), context.packageName)
  return mode == AppOpsManager.MODE_ALLOWED
}

Ekran kapalıysa dur

Ekran kapandığında hizmet açık oturumu bakiyeye işliyor ve yoklamayı tamamen durduruyor. Ekran kapalıyken kimse uygulama kullanmıyor ve yalnızca telefonuna bakarken çalışan 1 Hz'lik bir döngü ucuz. Ekran tekrar açıldıktan sonraki ilk yoklama gün devrini de (gece yarısı sıfırlama, günlük taban) hallediyor; böylece kullanıcı HonorFit'i hiç açmasa bile bu iş, o engelli bir uygulamaya ulaşmadan önce yapılmış oluyor.

Kilit overlay'i

Erişilebilirlik olmadan GLOBAL_ACTION_HOME yok, yani kullanıcıyı bir uygulamadan dışarı atamazsın. Onun yerine üstünü örtüyorsun: “daha fazla süre kazan” düğmesi ve “ana ekrana dön” düğmesi olan bir TYPE_APPLICATION_OVERLAY penceresiyle.

Hizmeti hayatta tutmak

Doğru yapması en uzun süren kısım bu ve çoğunlukla senin kodunla ilgili değil.

START_STICKY yetmiyor

Samsung'un “uyuyan uygulamaları”, MIUI, OnePlus ve benzerleri arka plan hizmetlerini öldürüyor ve sonra sticky yeniden başlatmayı yutuyor. Bir engelleyici bunun için en kötü senaryo: insanlar süreleri varken onu açmıyor, bu yüzden izleyicinin gittiğini kimse fark etmiyor. HonorFit, hizmeti yeniden ayağa kaldıran, 15 dakikada bir tekrarlanan, kesin zamanlı olmayan ve cihazı uyandırmayan bir alarm kuruyor. Telefonu asla uyandırmıyor ve ekran kapalıyken ertelenen alarmlar ekran açıldıktan kısa süre sonra çalışıyor; bu da tam olarak izleyicinin önemli olduğu an.

Android 12+ onu başlatmana her zaman izin vermiyor

Arka plandan (bir alarmdan, sticky yeniden başlatmadan) bir ön plan hizmeti başlatmak, bir muafiyetin yoksa ForegroundServiceStartNotAllowedException fırlatıyor. Burada önemli olan iki muafiyet var: kullanıcı uygulama için pil optimizasyonunu kapatmışsa ya da uygulamanın SYSTEM_ALERT_WINDOW izni varsa (bu sonuncusu sadece Android 15'ten önce işe yarıyor). Yani:

Yeniden başlatmalar ve güncellemeler

BOOT_COMPLETED ve MY_PACKAGE_REPLACED için bir receiver izlemeyi yeniden başlatıyor. İkincisini unutmak kolay: Play'in otomatik güncellemesi sürecini öldürüyor ve hizmetlerini asla yeniden başlatmıyor; yani o olmadan her güncelleme, kullanıcı uygulamayı açana kadar engelleyicini sessizce kapatıyor. İki yayın da arka plandan başlatma kısıtlamasından muaf ve exported="false" olan bir receiver da onları alıyor.

Daha küçük şeyler

Ödünleşimler

Bu yaklaşım erişilebilirlikten daha yumuşak ve bu konuda açık olmak istiyorum:

Onur sistemine dayanan bir engelleyici için bu doğru bir ödünleşim. HonorFit kırılmaz olmaya çalışmıyor. Hile yapmaya kararlı biri zaten yapmadığı tekrarları kaydedebilir. Amaç bir hapishane değil, bir duraklama ve bir bedel. Karşılığında “tam kontrol” yerine “kullanım erişimi” diyen bir izin ekranı, çok daha basit bir Play incelemesi ve tam olarak tek bir şeyi okuyan bir hizmet elde ediyorsun: öndeki uygulamanın paket adı.

iOS için de geliştiriyorsan, orası bambaşka bir dünya. Apple'ın Screen Time belgelerinin söylemediklerini de yazdım.

HonorFit Blocker, iPhone ve Android için ücretsiz bir uygulama engelleyici. Ekran süresini şınavla, squat'la ya da seçtiğin herhangi bir egzersizle kazan. Onur sistemi, kamera yok, hesap yok, hiçbir şey telefonundan çıkmıyor.

Okumaya devam et