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, 1. şekilde 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 ANR iletişim kutusu gösterildiğinde.
Şekil 1. Kullanıcıya gösterilen ANR iletişim kutusu

ANR'ler sorun yaratır. Çünkü kullanıcı arayüzünü güncellemekten sorumlu olan uygulamanın ana iş parçacığı, kullanıcı giriş etkinliklerini işleyemez veya çizim yapamaz. Bu durum, kullanıcıların hayal kırıklığına uğramasına neden olur. 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 koşullardan biri gerçekleştiğinde uygulamanız için ANR tetiklenir:

  • Giriş gönderme zaman aşımına uğradı: Uygulamanız 5 saniye içinde bir giriş etkinliğine (ör. tuşa basma veya ekrana dokunma) yanıt vermediyse.
  • Hizmet yürütme: Uygulamanız tarafından bildirilen bir hizmet birkaç saniye içinde Service.onCreate() ve Service.onStartCommand()/Service.onBind() yürütmeyi tamamlayamıyorsa.
  • **Service.startForeground() çağrılmadı**: Uygulamanız, ön planda yeni bir hizmet başlatmak için Context.startForegroundService() kullanıyorsa ancak hizmet 5 saniye içinde startForeground() işlevini çağırmıyorsa.
  • Niyet yayını: Bir BroadcastReceiver belirli bir süre içinde yürütülmeyi tamamlamadıysa. Uygulama ön planda herhangi bir etkinlik gösteriyorsa bu zaman aşımı 5 saniyedir.
  • **JobScheduler etkileşimleri**: Bir JobService birkaç saniye içinde JobService.onStartJob() veya JobService.onStopJob()'dan 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()'u ç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 ise ANR'ler açıkça belirtilir ve uygulamaya bildirilir.

Uygulamanızda ANR'ler yaşanıyorsa sorunu teşhis etmek ve 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ından yararlanabilirsiniz.

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

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

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

Şekil 2. Yoğun bir ana ileti dizisini gösteren Traceview zaman çizelgesi

Şekil 2. Yoğun bir ana ileti dizisini gösteren Traceview zaman çizelgesi

Şekil 2'de, aşağıdaki kod örneğinde gösterildiği gibi, ihlale neden olan kodun çoğunun onClick(View) işleyicisinde gerçekleştiği gösterilmektedir:

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 Framework, görevi bir çalışan iş parçacığına taşımaya yardımcı olabilecek sınıflar içerir. Daha fazla bilgi için Worker iş parçacıkları bölümüne bakın.

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

Ana iş parçacığında G/Ç işlemleri yürütmek, ana iş parçacığında yavaş işlemlere yol açan yaygın bir nedendir ve bu durum ANR'lere neden olabilir. Geliştiriciler, Compose'da ilk durumu elde etmeye çalışırken genellikle yanlışlıkla disk okuma işlemlerini (ör. SharedPreferences veya veritabanı çağrıları) tetikler.

Uzun süren G/Ç işlemlerini kullanıcı arayüzü katmanından uzakta yürütün. withContext(Dispatchers.IO) içinde ViewModel 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 Verileri kaydetme başlıklı makaleleri inceleyin.

Kilit anlaşmazlığı

Bazı senaryolarda ANR'ye neden olan işlem, uygulamanın ana iş parçacığında doğrudan yürütülmez. Bir çalışan iş parçacığı, ana iş parçacığının işini tamamlamak için ihtiyaç duyduğu bir kaynakta kilit tutuyorsa ANR oluşabilir.

Örneğin, Şekil 3'te, çalışmanın büyük bölümünün bir çalışan iş parçacığında yapıldığı bir Traceview zaman çizelgesi gösterilmektedir.

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

Şekil 3. Bir çalışan iş parçacığında yürütülen işi 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 ve genel olarak yanıt veriyorsa RUNNABLE durumundadır.

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

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

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

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

...
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 inceleyerek ana iş parçacığını engelleyen kodu bulabilirsiniz. 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 ise aşağıdaki kodda gösterildiği gibi, bir uygulamanın ana iş parçacığının bir çalışan iş parçacığından sonuç beklemesidir. Eşzamanlılığı işlemek için kendi mekanizmalarına sahip olan Kotlin'de wait() ve notify() kullanmanın önerilen bir yöntem olmadığını unutmayın. 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ı, kaynak havuzu (ör. veritabanı bağlantıları havuzu) veya diğer karşılıklı dışlama (mutex) mekanizmaları gibi ana iş parçacığını engelleyebilecek başka durumlar da vardır.

Uygulamanızın kaynaklar üzerinde tuttuğu kilitleri genel olarak değerlendirmeniz gerekir. Ancak ANR'leri önlemek istiyorsanız ana iş parçacığı tarafından gereken kaynaklar için tutulan kilitlere bakmanız gerekir.

Kilitlerin en kısa süre boyunca tutulduğundan emin olun. Daha da iyisi, uygulamanın kilidi tutmaya ihtiyacı olup olmadığını değerlendirin. Bir çalışan iş parçacığının işlenmesine göre kullanıcı arayüzünün ne zaman güncelleneceğini belirlemek için kilidi kullanıyorsanız çalışan ve ana iş parçacıkları 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 yayın mesajlarına yanıt verebilir. Örneğin, uçak modunu etkinleştirebilir veya devre dışı bırakabilir ya da bağlantı durumundaki bir değişikliği bildirebilir. Bir uygulama yayın mesajını işlemek için çok uzun süre beklediğinde ANR oluşur.

ANR, aşağıdaki durumlarda oluşur:

  • Bir yayın alıcısı, onReceive yöntemini önemli bir süre içinde yürütmeyi tamamlamadı.
  • Bir yayın alıcısı goAsync işlevini çağırır ve PendingResult nesnesinde finish işlevini çağıramaz.

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

Yayın alıcınızın uygulamanın ana iş parçacığında uzun süren işlemler gerçekleştirip gerçekleştirmediğini belirlemek için Traceview gibi araçları kullanabilirsiniz. Örneğin, Şekil 6'da bir mesajı yaklaşık 100 saniye boyunca ana iş parçacığında 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. BroadcastReceiver ana iş parçacığında yapılan çalışmayı gösteren Traceview zaman çizelgesi

Bu davranış, aşağıdaki örnekte gösterildiği gibi BroadcastReceiver onReceive() yönteminde uzun süren işlemlerin 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üren işlemin IntentService'e taşınması önerilir. Çünkü bu işlem, işini yürütmek için bir çalışan iş parçacığı kullanır. Aşağıdaki kodda, uzun süren bir işlemi işlemek için IntentService 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üren işlem, ana iş parçacığı yerine bir çalışan iş parçacığında yürütülür. Şekil 7'de, Traceview zaman çizelgesinde çalışan iş parçacığına ertelenen iş gösterilmektedir.

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

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

Yayın alıcınız, goAsync() kullanarak sisteme mesajı işlemesi için daha fazla zamana ihtiyacı olduğunu bildirebilir. Ancak PendingResult nesnesinde finish() işlevini çağırmanız gerekir. Aşağıdaki örnekte, sistemin yayın alıcısını geri dönüştürmesine ve ANR'yi önlemesine izin vermek için finish()'nın 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 kodu yavaş bir yayın alıcısından başka bir işleme taşımak ve goAsync() kullanmak, yayın arka planda olduğunda ANR'yi düzeltmez. ANR zaman aşımı yine geçerlidir.