ANDROİD PERFORMANS: ANR, MEMORY LEAK VE ANDROİD STUDİO PROFİLER KULLANIMI
Bir Android uygulaması “çalışıyor” gibi görünürken kullanıcıların sessizce terk etmesinin en yaygın sebeplerinden biri performanstır: donmalar, geciken dokunuşlar, birden büyüyen bellek kullanımı ve nihayetinde ANR ekranı. Üstelik bu problemler çoğu zaman sadece “zayıf cihazlarda” değil; animasyonlu bir liste, yanlış yerde yapılan bir sorgu veya döngüye giren bir gözlemciyle en güçlü cihazlarda bile ortaya çıkabilir.
Bu yazıda ANR (Application Not Responding) ve memory leak gibi performans sorunlarının temel mekaniklerini netleştirip, Android Studio Profiler ile bunları nasıl yakalayacağınızı ve düzeltme adımlarını pratik bir akışta ele alacağız. Amaç; “Profiler’a baktım, bir şeyler var” seviyesinden çıkıp, bulguyu aksiyona dönüştüren bir teşhis yaklaşımı kazanmanız.
Ek olarak, daha sistematik bir ilerleme için eğitim programındaki ilgili modüllere Android Kotlin Eğitimi sayfasından ulaşabilirsiniz. Profiler kullanımı, eşzamanlılık, bellek yönetimi ve performans tasarımı birlikte ele alındığında, sorunlar daha geliştirme aşamasında görünür hale gelir.
Android ANR nedir ve uygulamayı neden “kilitler”?
ANR; uygulamanın ana iş parçacığında (main thread) belirli süre boyunca giriş olaylarını (dokunma, tuş) işleyememesi veya belirli broadcast/servis işlemlerini tamamlayamaması durumunda sistemin kullanıcıya “Uygulama yanıt vermiyor” uyarısı göstermesidir. Bu, bir “çökme” değildir; uygulama hayattadır ama UI tarafı cevap veremez.
ANR’nin tipik tetikleyicileri
- UI thread üzerinde yoğun hesaplama, büyük JSON parse işlemleri veya senkron disk okuma/yazma
- Ağ çağrılarının ana thread üzerinde yapılması (bloklayan IO)
- Yanlış kilit (lock) kullanımıyla oluşan deadlock veya uzun süren synchronized bloklar
- Bitmap decode, büyük liste diff işlemleri, gereksiz layout geçişleri
ANR raporlarında ne aramalısınız?
ANR oluştuğunda “main” thread’in hangi noktada takıldığını gösteren stack trace çok değerlidir. Genellikle problem; bir bloklayan çağrı, uzun süren bir döngü veya kilit beklemesi olarak görünür. Teşhis akışınız, “main thread niye bekliyor?” sorusunu adım adım yanıtlamalıdır.

Android Studio Profiler’a hızlı başlangıç: doğru ölçüm için doğru kurulum
Profiler’dan anlamlı veri almak için ölçüm şartlarını sabitlemek gerekir. Aksi halde “bugün CPU yüksek” gibi sonuçlar rastgele olabilir. Önce hedef senaryoyu belirleyin: açılış süresi mi, liste kaydırma mı, bir ekranın veri yüklemesi mi, arka planda çalışan bir servis mi?
Debug mı Release mı? Veriyi nasıl yorumlamalı?
Debug build; ek izleme maliyetleri ve farklı optimizasyon seviyeleri nedeniyle ölçümleri çarpıtabilir. Yine de ilk teşhis için yeterlidir. Nihai doğrulama, mümkünse release benzeri bir derlemede yapılmalıdır. Profiler verisini okurken “ölçümün kendisi de maliyet üretir” ilkesini aklınızda tutun.
Ölçüm senaryosu tanımı: tekrar edilebilirlik
Her performans çalışması; tekrarlanabilir bir akışa dayanmalı: uygulamayı soğuk başlat, aynı ekrana git, aynı listeyi kaydır, aynı aramayı yap. Bu yaklaşım, yaptığınız değişikliklerin etkisini net biçimde görmenizi sağlar.
CPU Profiler ile donma ve gecikmelerin izini sürmek
CPU Profiler; hangi metotların ne kadar süre çalıştığını, hangi thread’in ne zaman aktif olduğunu ve UI jank (takılma) ile CPU yükü arasındaki ilişkiyi görmenizi sağlar. Özellikle “ekran açılıyor ama birkaç saniye dokunamıyorum” gibi şikayetlerde CPU tarafı ilk bakılacak yerdir.
Main thread üzerinde pahalı iş yapan örnek (ANR’a giden yol)
Aşağıdaki örnek; UI thread’i bilinçli olarak bloklar. Gerçek hayatta bu durum, büyük bir listeyi filtrelemek veya büyük bir dosyayı okumak gibi masum görünen işlemlerle oluşabilir. Buradaki amaç; Profiler’da hangi izleri bırakacağını netleştirmektir.
class SearchActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search)
findViewById<Button>(R.id.btnSearch).setOnClickListener {
// Kötü örnek: UI thread üzerinde ağır iş
val result = heavyFilter(loadBigList())
showResult(result)
}
}
private fun heavyFilter(list: List<String>): List<String> {
// Uzun süren CPU işi: örnek amaçlı
val out = ArrayList<String>()
for (item in list) {
repeat(30_000) { _ -> item.hashCode() }
if (item.contains("android", ignoreCase = true)) out.add(item)
}
return out
}
}CPU Profiler’da bu senaryoda genellikle “main” thread üzerinde yoğun süre tüketen metotları görürsünüz. Eğer tıklama sonrası UI “donuyor” hissi veriyorsa, zaman çizelgesinde uzun bloklar ve giriş olaylarının gecikmesi dikkat çeker.
Çözüm yaklaşımı: işi arka plana taşıma ve sonuçları güvenle UI’a alma
Örnek çözüm; hesaplamayı arka thread’de çalıştırıp sadece sonuç render’ını UI thread’de yapmaktır. Burada coroutines ile basit, okunabilir bir akış kurabilir ve iptal edilebilirlik kazanırsınız.
class SearchActivity : AppCompatActivity() {
private val scope = MainScope()
override fun onDestroy() {
super.onDestroy()
scope.cancel()
}
private fun onSearchClicked() {
scope.launch {
val result = withContext(Dispatchers.Default) {
heavyFilter(loadBigList())
}
showResult(result) // UI thread
}
}
}Bu değişiklikten sonra CPU Profiler’da ağır hesaplamanın “DefaultDispatcher-worker” gibi bir thread’e kaydığını, main thread’in daha kısa sürelerle çalıştığını ve etkileşimin akıcı hale geldiğini gözlemlemelisiniz. Aynı zamanda iptal senaryoları (ekrandan çıkma) daha güvenli yönetilir.
Memory Profiler ile bellek artışı, GC baskısı ve memory leak tespiti
Memory Profiler, uygulamanın heap kullanımını, nesne sayısını, GC (garbage collection) davranışını ve “zamanla artan bellek” gibi klasik problem desenlerini görmenizi sağlar. Burada hedef; sadece “bellek yüksek” demek değil, hangi nesneler tutuluyor sorusunu cevaplamaktır.
Memory leak nedir ve neden sinsi ilerler?
Memory leak; kullanılmayan bir nesnenin, yanlış referanslar yüzünden hâlâ erişilebilir durumda kalmasıdır. Android’de en sık senaryo; Activity/Fragment’in yaşam döngüsü bitmişken, bir singleton, static alan, callback veya uzun yaşayan coroutine/handler üzerinden hâlâ tutulmasıdır. Sonuç; bellek artışı, daha sık GC ve sonunda OOM riskidir.
Heap dump ve allocation tracking ne zaman kullanılmalı?
Bir ekrana girip çıkınca bellek sürekli artıyorsa, önce davranışı doğrulayın: ekranı 10 kez aç-kapa yapın ve heap trendini izleyin. Artış kalıcıysa heap dump alıp, şüpheli sınıfların retained size değerlerini inceleyin. Allocation tracking ise “bu nesne nerede oluşturuldu?” sorusu için idealdir; özellikle kısa sürede çok fazla nesne yaratılıyorsa ipucu verir.

ANR teşhis akışı: iz bırakmadan çözüm yok
ANR’ı çözmek, çoğu zaman “kodda şurayı değiştirince geçti” yaklaşımından daha sistematik olmalı. Çünkü aynı semptom, farklı kök nedenlerle ortaya çıkabilir: IO beklemesi, CPU yoğunluğu, kilit çatışması, binder çağrısı gecikmesi, hatta yanlış kurgulanmış bir retry döngüsü.
Trace okuma yaklaşımı: bekleme mi, çalışma mı?
Stack trace’de main thread “RUNNABLE” durumda olup CPU yakıyorsa, işin kendisi ağırdır. “WAITING” veya “BLOCKED” görüyorsanız, bir kilit beklemesi ya da senkron IO olasılığı yükselir. Bu ayrım, çözüm yönünü hemen değiştirir: ya işi bölmek/taşımak gerekir ya da kilit tasarımını düzeltmek.
UI jank ile ANR arasındaki fark
Her takılma ANR değildir. UI jank; frame sürelerinin uzamasıyla oluşur ve kullanıcı kaydırmada “kekemelik” hisseder. ANR ise belirli bir süre boyunca yanıt yokluğudur. CPU Profiler ve frame timeline verilerini birlikte okumak, “önce jank mı var, yoksa doğrudan kilitlenme mi?” sorusunu netleştirir.
LeakCanary ve pratik koruma kalkanları
Profiler teşhiste güçlüdür; ancak üretime yakın koşullarda “sızıntıyı erken yakalamak” için otomatik gözlem araçları da gerekir. LeakCanary gibi araçlar; Activity/Fragment’in GC edilmediği durumlarda referans zincirini raporlayarak sızıntıyı görünür kılar. Bu, “profiler’a bakmadan da alarm” mekanizması oluşturur.
Sık görülen leak kaynakları ve düzeltme desenleri
- Anonymous inner class’larda Activity referansı: callback’leri yaşam döngüsünde temizleme
- Uzun yaşayan coroutine’ler: scope’u yaşam döngüsüne bağlama, iptal etmeyi garanti etme
- Adapter/Listener referansları: view binding ve listener’ları onDestroyView’de serbest bırakma
- Static/singleton cache: context yerine applicationContext kullanma, cache politikası tanımlama
Örneğin Fragment içinde view binding kullanıyorsanız, view yaşam döngüsü bittiğinde binding’i null’lamak kritik bir adımdır. Bu küçük pratik, çok sayıda “ekran kapanınca bellek düşmüyor” şikayetini tek hamlede çözer.
Network Profiler ile yavaşlık algısını azaltma
Kullanıcı için performans, çoğu zaman “gerçek hız” değil, algıdır. Ağ çağrıları yavaşsa, doğru loading durumu, cache stratejisi ve gereksiz tekrarların önlenmesi büyük fark yaratır. Network Profiler; hangi isteğin ne kadar sürdüğünü, payload boyutlarını ve çağrı yoğunluğunu görmenizi sağlar.
Gereksiz istekleri azaltma: tetikleyici noktaları bulma
Bir ekran açılışında aynı endpoint’in art arda çağrıldığını görüyorsanız, sorun genelde UI katmanında yanlış tetikleme veya yaşam döngüsüne bağlı tekrar eden observer kurulumudur. Bu durumda çözüm; tekil tetikleme, debounce veya veri kaynağı tarafında cache’leme olabilir.
Performans iyileştirme checklist’i: teşhisten aksiyona
Analiz sürecini hızlandırmak için aşağıdaki kontrol listesini kullanabilirsiniz. Her madde, Profiler ekranlarında somut bir karşılığa sahiptir ve “neden yavaş?” sorusunu daha küçük parçalara böler.
- Semptomu tanımla: donma mı, jank mı, bellek artışı mı, batarya mı?
- Tekrarlanabilir senaryo oluştur: aynı adımlarla aynı problemi üret
- CPU Profiler: main thread’de uzun süreli metot var mı?
- Memory Profiler: heap trendi yükseliyor mu, GC baskısı artıyor mu?
- Heap dump: retained size yüksek sınıflar ve referans zincirleri
- Network Profiler: yavaş istekler, büyük payload, gereksiz tekrarlar
- Değişiklik sonrası yeniden ölç: aynı senaryoda farkı doğrula

Sonuç: Profiler’ı “grafik” değil “karar” aracı yapmak
Android performans sorunları; tek bir araçla değil, doğru sorularla çözülür. ANR için ana soru “main thread neden bekliyor?”, memory leak için “hangi nesne neden serbest kalmıyor?”, yavaşlık algısı için “hangi adım kullanıcıyı bekletiyor?” olmalıdır. Android Studio Profiler, bu soruların her biri için somut kanıt üretir.
Bu yazıdaki yaklaşımı rutine dönüştürdüğünüzde, performans çalışması bir “yangın söndürme” değil, iteratif bir kalite süreci haline gelir. Üstelik küçük iyileştirmeler bile, kullanıcı memnuniyetinde ve mağaza puanlarında ölçülebilir fark yaratır.


