ANR'ler (Görüntülemeler)

Kavramlar ve Jetpack Compose uygulaması

Bir Android uygulamasının kullanıcı arayüzü iş parçacığı çok uzun süre engellendiğinde "Uygulama Yanıt Vermiyor" (ANR) hatası tetiklenir. Uygulama ön plandaysa sistem, Şekil 1'de gösterildiği gibi kullanıcıya bir iletişim kutusu gösterir. ANR iletişim kutusu, kullanıcıya uygulamayı kapanmaya zorlama fırsatı verir.

Kullanıcıya gösterilen ANR iletişim kutusu.
Şekil 1. Kullanıcıya gösterilen ANR iletişim kutusu

ANR'ler, uygulamanın kullanıcı arayüzünü güncellemekten sorumlu ana iş parçacığının kullanıcı girişi etkinliklerini işleyememesi veya çizememesi nedeniyle kullanıcıda hayal kırıklığına yol açan bir sorundur. Uygulamanın ana iş parçacığı hakkında daha fazla bilgi için İşlemlere ve iş parçacıklarına genel bakış başlıklı makaleyi inceleyin.

Aşağıdaki durumlardan biri gerçekleştiğinde uygulamanız için bir ANR tetiklenir:

  • Giriş gönderme zaman aşımına uğradı: Uygulamanız bir giriş etkinliğine (ör. tuşa basma veya ekrana dokunma) 5 saniye içinde yanıt vermediyse.
  • Hizmet yürütme: Uygulamanız tarafından beyan edilen bir hizmet, birkaç saniye içinde Service.onCreate() ve Service.onStartCommand()/Service.onBind() yürütmeyi tamamlayamazsa.
  • **Service.startForeground() çağrılmadı**: Uygulamanız ön planda yeni bir hizmet başlatmak için Context.startForegroundService() kullanıyor ancak hizmet 5 saniye içinde startForeground() çağrısı yapmıyor.
  • Amaç yayını: Bir BroadcastReceiver, belirlenen süre içinde yürütmeyi tamamlamadıysa. Uygulamanın ön planda herhangi bir etkinliği varsa bu zaman aşımı 10 saniyedir.
  • **JobScheduler etkileşimleri**: Bir JobService, birkaç saniye içinde JobService.onStartJob() veya JobService.onStopJob()'ten dönmezse ya da kullanıcı tarafından başlatılan bir iş başlarsa ve uygulamanız, JobService.onStartJob() çağrıldıktan sonra birkaç saniye içinde JobService.setNotification()'i çağırmazsa. Android 13 ve önceki sürümleri hedefleyen uygulamalarda ANR'ler sessizdir ve uygulamaya bildirilmez. Android 14 ve sonraki sürümleri hedefleyen uygulamalarda ANR'ler açıktır ve uygulamaya bildirilir.

Uygulamanızda ANR'ler yaşanıyorsa sorunu teşhis edip düzeltmek için bu makaledeki yönergeleri kullanabilirsiniz.

Sorunları düzeltme

Sorunu belirledikten sonra, sık karşılaşılan sorunları düzeltmek için bu bölümdeki ipuçlarını kullanabilirsiniz.

Ana iş parçacığında yavaş kod

Kodunuzda, uygulamanın ana iş parçacığının 5 saniyeden uzun süre meşgul olduğu yerleri belirleyin. Uygulamanızda şüpheli kullanım alanlarını arayın ve ANR'yi yeniden oluşturmaya çalışın.

Örneğin, Şekil 2'de ana iş parçacığının 5 saniyeden uzun süre meşgul olduğu bir Traceview zaman çizelgesi gösterilmektedir.

Şekil 2. Meşgul bir ana ileti dizisini gösteren Traceview zaman çizelgesi

Şekil 2. Meşgul bir ana ileti dizisini gösteren Traceview zaman çizelgesi

Şekil 2, sorunlu kodun çoğunun aşağıdaki kod örneğinde gösterildiği gibi onClick(View) işleyicisinde olduğunu gösterir:

Kotlin

override fun onClick(v: View) {
    // This task runs on the main thread.
    BubbleSort.sort(data)
}

Java

@Override
public void onClick(View view) {
    // This task runs on the main thread.
    BubbleSort.sort(data);
}

Bu durumda, ana iş parçacığında çalışan işi bir çalışan iş parçacığına taşımanız gerekir. Android çerçevesi, görevi çalışan iş parçacığına taşımaya yardımcı olabilecek sınıflar içerir. Daha fazla bilgi için Çalışan iş parçacıkları bölümüne bakın.

Ana iş parçacığında G/Ç

Ana iş parçacığında G/Ç işlemlerinin yürütülmesi, ana iş parçacığındaki işlemlerin yavaşlamasının yaygın bir nedenidir ve ANR'lere yol açabilir. Oluşturma'da geliştiriciler, ilk durumu türetmeye çalışırken genellikle yanlışlıkla disk okuma işlemlerini (SharedPreferences veya veritabanı çağrıları gibi) tetikler.

Uzun süren G/Ç işlemlerini kullanıcı arayüzü katmanından uzakta yürütün. withContext(Dispatchers.IO)'yi ViewModel içinde kullanın veya daha da iyisi, veri katmanında bir depo kullanın. Önceki bölümde gösterildiği gibi, tüm G/Ç işlemlerini bir çalışan iş parçacığına taşımanız önerilir.

G/Ç işlemlerine örnek olarak ağ ve depolama işlemleri verilebilir. Daha fazla bilgi için Ağ işlemleri gerçekleştirme ve Veri kaydetme başlıklı makaleleri inceleyin.

Kilit anlaşmazlığı

Bazı senaryolarda ANR'ye neden olan çalışma doğrudan uygulamanın ana iş parçacığında yürütülmez. Çalışan iş parçacığı, ana iş parçacığının çalışmasını tamamlaması için gereken bir kaynakta kilit tutuyorsa ANR oluşabilir.

Örneğin, Şekil 3'te, işlerin çoğunun bir çalışan iş parçacığında gerçekleştirildiği bir Traceview zaman çizelgesi gösterilmektedir.

Şekil 3. Çalışan iş parçacığında yürütülen çalışmayı gösteren Traceview zaman çizelgesi

Şekil 3. Çalışan iş parçacığında yürütülen çalışmayı gösteren Traceview zaman çizelgesi

Ancak kullanıcılarınız hâlâ ANR'lerle karşılaşıyorsa Android Device Monitor'da ana iş parçacığının durumuna bakmanız gerekir. Genellikle ana iş parçacığı, kullanıcı arayüzünü güncellemeye hazırsa RUNNABLE durumundadır ve genellikle yanıt verir.

Ancak ana iş parçacığı yürütmeye devam edemezse BLOCKED durumundadır ve etkinliklere yanıt veremez. Durum, Şekil 5'te gösterildiği gibi Android cihaz Monitörü'nde Monitor (İzle) veya Wait (Bekle) olarak gösterilir.

Şekil 4. İzleme durumunda ana iş parçacığı

4. şekil. Ana iş parçacığı "İzleme" durumunda

Aşağıdaki iz, bir kaynağı beklerken engellenen bir uygulamanın ana iş parçacığını göstermektedir:

...
AsyncTask #2" prio=5 tid=18 Runnable
  | group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
  | sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
  | state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
  | stack=0x94a7e000-0x94a80000 stackSize=1038KB
  | held mutexes= "mutator lock"(shared held)
  at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
  at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)
  - locked <0x083105ee> (a java.lang.Boolean)
  at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135)
  at android.os.AsyncTask$2.call(AsyncTask.java:305)
  at java.util.concurrent.FutureTask.run(FutureTask.java:237)
  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
  at java.lang.Thread.run(Thread.java:761)
...

İzlemeyi incelemek, ana iş parçacığını engelleyen kodu bulmanıza yardımcı olabilir. Aşağıdaki kod, önceki izlemede ana iş parçacığını engelleyen kilidi tutmaktan sorumludur:

Kotlin

override fun onClick(v: View) {
    // The worker thread holds a lock on lockedResource
    LockTask().execute(data)

    synchronized(lockedResource) {
        // The main thread requires lockedResource here
        // but it has to wait until LockTask finishes using it.
    }
}

class LockTask : AsyncTask<Array<Int>, Int, Long>() {
    override fun doInBackground(vararg params: Array<Int>): Long? =
            synchronized(lockedResource) {
                // This is a long-running operation, which makes
                // the lock last for a long time
                BubbleSort.sort(params[0])
            }
}

Java

@Override
public void onClick(View v) {
    // The worker thread holds a lock on lockedResource
  new LockTask().execute(data);

  synchronized (lockedResource) {
      // The main thread requires lockedResource here
      // but it has to wait until LockTask finishes using it.
  }
}

public class LockTask extends AsyncTask<Integer[], Integer, Long> {
  @Override
  protected Long doInBackground(Integer[]... params) {
      synchronized (lockedResource) {
          // This is a long-running operation, which makes
          // the lock last for a long time
          BubbleSort.sort(params[0]);
      }
  }
}

Başka bir örnek de aşağıdaki kodda gösterildiği gibi, bir çalışan iş parçacığından sonuç bekleyen bir uygulamanın ana iş parçacığıdır. wait() ve notify() kullanımı, eşzamanlılığı yönetmek için kendi mekanizmalarına sahip olan Kotlin'de önerilen bir kalıp değildir. Kotlin kullanırken mümkünse Kotlin'e özgü mekanizmaları kullanmanız gerekir.

Kotlin

fun onClick(v: View) {
    val lock = java.lang.Object()
    val waitTask = WaitTask(lock)
    synchronized(lock) {
        try {
            waitTask.execute(data)
            // Wait for this worker thread's notification
            lock.wait()
        } catch (e: InterruptedException) {
        }
    }
}

internal class WaitTask(private val lock: java.lang.Object) : AsyncTask<Array<Int>, Int, Long>() {
    override fun doInBackground(vararg params: Array<Int>): Long? {
        synchronized(lock) {
            BubbleSort.sort(params[0])
            // Finished, notify the main thread
            lock.notify()
        }
    }
}

Java

public void onClick(View v) {
  WaitTask waitTask = new WaitTask();
  synchronized (waitTask) {
      try {
          waitTask.execute(data);
          // Wait for this worker thread’s notification
          waitTask.wait();
      } catch (InterruptedException e) {}
  }
}

class WaitTask extends AsyncTask<Integer[], Integer, Long> {
  @Override
  protected Long doInBackground(Integer[]... params) {
      synchronized (this) {
          BubbleSort.sort(params[0]);
          // Finished, notify the main thread
          notify();
      }
  }
}

Lock, Semaphore kullanan iş parçacıklarının yanı sıra bir kaynak havuzu (ör. veritabanı bağlantıları havuzu) veya diğer karşılıklı dışlama (mutex) mekanizmaları dahil olmak üzere ana iş parçacığını engelleyebilecek başka durumlar da vardır.

Uygulamanızın genel olarak kaynaklarda tuttuğu kilitleri değerlendirmeniz gerekir ancak ANR'leri önlemek istiyorsanız ana iş parçacığının ihtiyaç duyduğu kaynaklar için tutulan kilitlere bakmanız gerekir.

Kilitlerin mümkün olan en kısa süre boyunca tutulduğundan emin olun. Hatta uygulamanın kilidi tutması gerekip gerekmediğini değerlendirin. Kullanıcı arayüzünün ne zaman güncelleneceğini çalışan iş parçacığının işlenmesine göre belirlemek için kilidi kullanıyorsanız çalışan iş parçacığı ile ana iş parçacığı arasında iletişim kurmak için onProgressUpdate() ve onPostExecute() gibi mekanizmalar kullanın.

Yavaş yayın alıcıları

Uygulamalar, yayın alıcıları aracılığıyla uçak modunu etkinleştirme veya devre dışı bırakma ya da bağlantı durumundaki bir değişiklik gibi yayın mesajlarına yanıt verebilir. Uygulamanın, yayın mesajını işlemesi çok uzun sürdüğünde ANR meydana gelir.

ANR aşağıdaki durumlarda gerçekleşir:

  • Bir yayın alıcısı, makul bir süre içinde onReceive yöntemini yürütmeyi tamamlamadı.
  • Yayın alıcısı, PendingResult nesnesinde goAsync yöntemini çağırıyor ancak finish yöntemini çağıramıyor.

Uygulamanız, bir BroadcastReceiver öğesinin onReceive yönteminde yalnızca kısa işlemler gerçekleştirmelidir. Ancak uygulamanız, yayın mesajı sonucunda daha karmaşık bir işlem gerektiriyorsa görevi ViewModel (Kotlin eş yordamlarının, kapsamlarının ve dağıtıcılarının gücünden yararlanarak) en fazla birkaç saniye sürmesi beklenen görevler için herhangi bir durum bilgisi depolayıcıya veya birkaç saniyeden uzun sürmesi beklenen görevler için WorkManager'e ertelemeniz gerekir.

Yayın alıcınızın uygulamanın ana iş parçacığında uzun süreli işlemler yürütüp yürütmediğini belirlemek için Traceview gibi araçları kullanabilirsiniz. Örneğin, Şekil 6'da ana iş parçacığında yaklaşık 100 saniye boyunca bir mesajı işleyen bir yayın alıcısının zaman çizelgesi gösterilmektedir.

Şekil 5. Ana iş parçacığında `BroadcastReceiver` çalışmasını gösteren Traceview zaman çizelgesi

Şekil 5. Ana iş parçacığında BroadcastReceiver çalışmasını gösteren Traceview zaman çizelgesi

Bu davranış, aşağıdaki örnekte gösterildiği gibi BroadcastReceiver sınıfının onReceive() yönteminde uzun süreli işlemler yürütülmesinden kaynaklanabilir:

Kotlin

override fun onReceive(context: Context, intent: Intent) {
    // This is a long-running operation
    BubbleSort.sort(data)
}

Java

@Override
public void onReceive(Context context, Intent intent) {
    // This is a long-running operation
    BubbleSort.sort(data);
}

Bu gibi durumlarda, uzun süreli işlemi bir IntentService'e taşımanız önerilir. Çünkü bu işlem, işini yürütmek için çalışan iş parçacığı kullanır. Aşağıdaki kodda, uzun süreli bir işlemi işlemek için IntentService'nin nasıl kullanılacağı gösterilmektedir:

Kotlin

override fun onReceive(context: Context, intent: Intent) {
    Intent(context, MyIntentService::class.java).also { intentService ->
        // The task now runs on a worker thread.
        context.startService(intentService)
    }
}

class MyIntentService : IntentService("MyIntentService") {
    override fun onHandleIntent(intent: Intent?) {
        BubbleSort.sort(data)
    }
}

Java

@Override
public void onReceive(Context context, Intent intent) {
    // The task now runs on a worker thread.
    Intent intentService = new Intent(context, MyIntentService.class);
    context.startService(intentService);
}

public class MyIntentService extends IntentService {
  @Override
  protected void onHandleIntent(@Nullable Intent intent) {
      BubbleSort.sort(data);
  }
}

IntentService kullanıldığında uzun süreli işlem, ana iş parçacığı yerine çalışan iş parçacığında yürütülür. Şekil 7'de, Traceview zaman çizelgesinde çalışan iş parçacığına ertelenen çalışma gösterilmektedir.

Şekil 6. Çalışan iş parçacığında işlenen yayın mesajını gösteren Traceview zaman çizelgesi

6. Şekil. Çalışan iş parçacığında işlenen yayın mesajını gösteren Traceview zaman çizelgesi

Yayın alıcınız, mesajı işlemek için daha fazla zamana ihtiyacı olduğunu sisteme bildirmek için goAsync() yöntemini kullanabilir. Ancak PendingResult nesnesinde finish() yöntemini çağırmanız gerekir. Aşağıdaki örnekte, sistemin yayın alıcıyı geri dönüştürmesine izin vermek ve ANR'yi önlemek için finish() yönteminin nasıl çağrılacağı gösterilmektedir:

Kotlin

val pendingResult = goAsync()

object : AsyncTask<Array<Int>, Int, Long>() {
    override fun doInBackground(vararg params: Array<Int>): Long? {
        // This is a long-running operation
        BubbleSort.sort(params[0])
        pendingResult.finish()
        return 0L
    }
}.execute(data)

Java

final PendingResult pendingResult = goAsync();
new AsyncTask<Integer[], Integer, Long>() {
  @Override
  protected Long doInBackground(Integer[]... params) {
      // This is a long-running operation
      BubbleSort.sort(params[0]);
      pendingResult.finish();
  }
}.execute(data);

Ancak yayının arka planda olması durumunda kodu yavaş bir yayın alıcıdan başka bir iş parçacığına taşımak ve goAsync() kullanmak ANR'yi düzeltmez. ANR zaman aşımı hâlâ geçerlidir.