Yavaş oluşturma

Kullanıcı arayüzü oluşturma, uygulamanızdan bir çerçeve oluşturup ekranda görüntüleme işlemidir. Kullanıcının uygulamanızla etkileşiminin sorunsuz olmasını sağlamak için uygulamanızın saniyede 60 kare (fps) hızına ulaşmak üzere kareleri 16 ms'den kısa sürede oluşturması gerekir. 60 FPS'nin neden tercih edildiğini anlamak için Android Performance Patterns: Why 60fps? (Android Performans Kalıpları: Neden 60 FPS?) başlıklı makaleyi inceleyin. 90 FPS elde etmeye çalışıyorsanız bu pencere 11 ms'ye, 120 FPS için ise 8 ms'ye düşer.

Bu pencereyi 1 ms aşarsanız kare 1 ms geç gösterilmez, Choreographer kare tamamen bırakılır. Uygulamanızda kullanıcı arayüzü yavaş oluşturuluyorsa sistem kareleri atlamak zorunda kalır ve kullanıcı uygulamanızda takılma olduğunu fark eder. Bu duruma jank adı verilir. Bu sayfada, duraklama sorununu nasıl teşhis edip düzelteceğiniz gösterilmektedir.

View sistemini kullanmayan oyunlar geliştiriyorsanız Choreographer'ı atlayabilirsiniz. Bu durumda Frame Pacing Library, OpenGL ve Vulkan oyunlarının Android'de sorunsuz oluşturma ve doğru kare aralama elde etmesine yardımcı olur.

Duraklamayı tanımlama

Uygulamanızda duraklamaya neden olan kodu bulmak zor olabilir. Bu bölümde, duraklamayı tanımlamak için kullanılan üç yöntem açıklanmaktadır:

Görsel inceleme, uygulamanızdaki tüm kullanım alanlarını birkaç dakika içinde incelemenizi sağlar ancak Systrace kadar ayrıntılı bilgi vermez. Systrace daha fazla ayrıntı sağlar ancak uygulamanızdaki tüm kullanım alanları için Systrace'i çalıştırırsanız analiz edilmesi zor olabilecek çok fazla veriyle karşılaşabilirsiniz. Hem görsel inceleme hem de Systrace, yerel cihazınızdaki takılmaları tespit eder. Yerel cihazlarda duraklama sorununu yeniden oluşturamıyorsanız uygulamanızın belirli bölümlerini sahada çalışan cihazlarda ölçmek için özel performans izleme oluşturabilirsiniz.

Görsel inceleme

Görsel inceleme, duraklamaya neden olan kullanım alanlarını belirlemenize yardımcı olur. Görsel inceleme yapmak için uygulamanızı açın ve uygulamanızın farklı bölümlerini manuel olarak inceleyip kullanıcı arayüzünüzde duraklama olup olmadığını kontrol edin.

Görsel inceleme yapmayla ilgili bazı ipuçları:

  • Uygulamanızın yayınlanmış veya en azından hata ayıklanamayan bir sürümünü çalıştırın. ART çalışma zamanı, hata ayıklama özelliklerini desteklemek için çeşitli önemli optimizasyonları devre dışı bırakır. Bu nedenle, kullanıcının gördüğüne benzer bir şeyi incelediğinizden emin olun.
  • Profil GPU oluşturmayı etkinleştirin. Profil GPU oluşturma, ekranda çubuklar göstererek bir kullanıcı arayüzü penceresinin karelerinin oluşturulmasının ne kadar sürdüğünü kare başına 16 ms ölçütüne göre görsel olarak gösterir. Her çubukta, oluşturma ardışık düzenindeki bir aşamayla eşlenen renkli bileşenler bulunur. Böylece, hangi bölümün en uzun sürdüğünü görebilirsiniz. Örneğin, kare girişi işlemek için çok fazla zaman harcıyorsa uygulamanızın kullanıcı girişini işleyen koduna bakın.
  • Jank'ın yaygın kaynakları olan RecyclerView gibi bileşenleri inceleyin.
  • Uygulamayı sıfırdan başlatın.
  • Sorunu daha da kötüleştirmek için uygulamanızı daha yavaş bir cihazda çalıştırın.

Duraklamaya neden olan kullanım alanlarını bulduğunuzda, uygulamanızda duraklamaya neyin neden olduğu hakkında iyi bir fikriniz olabilir. Daha fazla bilgiye ihtiyacınız varsa nedenini daha ayrıntılı olarak incelemek için Systrace'i kullanabilirsiniz.

Systrace

Systrace, cihazın tamamının ne yaptığını gösteren bir araç olsa da uygulamanızdaki takılmaları belirlemek için yararlı olabilir. Systrace'in sistem yükü minimum düzeydedir. Bu nedenle, araçlandırma sırasında gerçekçi takılma deneyimi yaşayabilirsiniz.

Cihazınızda takılma sorununa neden olan kullanım alanını gerçekleştirirken Systrace ile izleme kaydı alın. Systrace'i kullanma talimatları için Komut satırında sistem izi yakalama başlıklı makaleye bakın. Systrace, işlemler ve iş parçacıklarına göre ayrılır. Systrace'te uygulamanızın sürecini bulun. Bu süreç, Şekil 1'deki gibi görünür.

Systrace örneği
Şekil 1. Systrace örneği.

Şekil 1'deki Systrace örneğinde, duraklamayı tanımlamak için aşağıdaki bilgiler yer alıyor:

  1. Systrace, her karenin ne zaman çizildiğini gösterir ve yavaş oluşturma sürelerini vurgulamak için her kareyi renk kodlarıyla işaretler. Bu sayede, görsel incelemeye kıyasla tek tek titrek kareleri daha doğru bir şekilde bulabilirsiniz. Daha fazla bilgi için Kullanıcı arayüzü çerçevelerini ve uyarılarını inceleme başlıklı makaleyi inceleyin.
  2. Systrace, uygulamanızdaki sorunları tespit eder ve hem tek tek karelerde hem de uyarılar panelinde uyarılar gösterir. Uyarıdaki talimatları uygulamanız önerilir.
  3. Android çerçevesinin ve kitaplıklarının bazı bölümleri (ör. RecyclerView) iz işaretçileri içerir. Bu nedenle, systrace zaman çizelgesi bu yöntemlerin kullanıcı arayüzü iş parçacığında ne zaman yürütüldüğünü ve yürütülmelerinin ne kadar sürdüğünü gösterir.

Systrace çıktısına baktıktan sonra uygulamanızda duraklamaya neden olduğundan şüphelendiğiniz yöntemler olabilir. Örneğin, zaman çizelgesi yavaş bir karenin RecyclerView işleminin uzun sürmesinden kaynaklandığını gösteriyorsa ilgili koda özel izleme etkinlikleri ekleyebilir ve daha fazla bilgi için Systrace'i yeniden çalıştırabilirsiniz. Yeni Systrace'te zaman çizelgesi, uygulamanızın yöntemlerinin ne zaman çağrıldığını ve yürütülmesinin ne kadar sürdüğünü gösterir.

Systrace, kullanıcı arayüzü iş parçacığı çalışmasının neden uzun sürdüğüne dair ayrıntıları göstermiyorsa örneklenmiş veya araçlandırılmış bir yöntem izi kaydetmek için Android CPU Profiler'ı kullanın. Genel olarak, yöntem izleri, ağır ek yük nedeniyle yanlış pozitif duraklamalar oluşturduğundan ve iş parçacıklarının ne zaman çalıştığını, ne zaman engellendiğini göremediğinden duraklamaları belirlemek için uygun değildir. Ancak yöntem izleri, uygulamanızda en çok zaman alan yöntemleri belirlemenize yardımcı olabilir. Bu yöntemleri belirledikten sonra iz işaretçileri ekleyin ve bu yöntemlerin duraklamaya neden olup olmadığını görmek için Systrace'i yeniden çalıştırın.

Daha fazla bilgi için Systrace'i anlama başlıklı makaleyi inceleyin.

Özel performans izleme

Yerel bir cihazda duraklama sorununu yeniden oluşturamıyorsanız sahadaki cihazlarda duraklamanın kaynağını belirlemenize yardımcı olması için uygulamanıza özel performans izleme işlevi ekleyebilirsiniz.

Bunu yapmak için FrameMetricsAggregator ile uygulamanızın belirli kısımlarından kare oluşturma sürelerini toplayın ve Firebase Performance Monitoring'i kullanarak verileri kaydedip analiz edin.

Daha fazla bilgi için Android için Performance Monitoring'i kullanmaya başlama başlıklı makaleyi inceleyin.

Donmuş kare

Donmuş kareler, oluşturulması 700 ms'den uzun süren kullanıcı arayüzü kareleridir. Bu durum, uygulamanızın takılmış gibi görünmesi ve kare oluşturulurken yaklaşık bir saniye boyunca kullanıcı girişine yanıt vermemesi nedeniyle sorun teşkil ediyor. Sorunsuz bir kullanıcı arayüzü için uygulamaları 16 ms içinde bir kare oluşturacak şekilde optimize etmenizi öneririz. Ancak uygulama başlatılırken veya farklı bir ekrana geçiş yapılırken, uygulamanızın görünümleri genişletmesi, ekranı yerleştirmesi ve ilk çizimi sıfırdan yapması gerektiğinden ilk karenin çizilmesi 16 ms'den uzun sürebilir. Bu nedenle Android, donmuş kareleri yavaş oluşturmadan ayrı olarak izler. Uygulamanızdaki hiçbir karenin oluşturulması 700 ms'den uzun sürmemelidir.

Donan kareler, yavaş oluşturmanın uç bir şeklidir. Bu nedenle, sorunu teşhis etme ve düzeltme prosedürü aynıdır.

Duraklamayı izleme

Perfetto'daki FrameTimeline, yavaş veya donmuş kareleri izlemeye yardımcı olabilir.

Yavaş kareler, donmuş kareler ve ANR'ler arasındaki ilişki

Yavaş kareler, donmuş kareler ve ANR'ler, uygulamanızın karşılaşabileceği farklı jank biçimleridir. Farkı anlamak için aşağıdaki tabloya bakın.

Yavaş kare sayısı Donmuş kare ANR'ler
Oluşturma süresi 16 ms ile 700 ms arasında 700 ms ile 5 saniye arasında 5 saniyeden fazla
Görünür kullanıcı etkisi alanı
  • RecyclerView Kaydırma işleminin aniden durması
  • Karmaşık animasyonların düzgün şekilde animasyon oluşturulmadığı ekranlar
  • Uygulama başlatılırken
  • Bir ekrandan diğerine geçiş (ör. ekran geçişi)
  • Etkinliğiniz ön plandayken uygulamanız, beş saniye içinde bir giriş etkinliğine veya BroadcastReceiver (ör. tuşa basma ya da ekrana dokunma etkinlikleri) yanıt vermedi.
  • Ön planda etkinliğiniz yokken BroadcastReceiver, önemli bir süre içinde yürütmeyi tamamlamadı.

Yavaş kareleri ve donmuş kareleri ayrı ayrı izleme

Uygulama başlatılırken veya farklı bir ekrana geçiş yapılırken, uygulamanın görünümleri genişletmesi, ekranı yerleştirmesi ve ilk çizimi sıfırdan yapması gerektiğinden ilk karenin çizilmesi 16 ms'den uzun sürebilir.

Jank'i önceliklendirme ve çözme ile ilgili en iyi uygulamalar

Uygulamanızdaki duraklama sorununu çözmek için aşağıdaki en iyi uygulamaları göz önünde bulundurun:

  • Jank'ın en kolay yeniden üretilebilen örneklerini belirleyin ve çözün.
  • ANR'lere öncelik verin. Yavaş veya donmuş kareler, uygulamanın yavaş görünmesine neden olabilir ancak ANR'ler uygulamanın yanıt vermeyi durdurmasına neden olur.
  • Yavaş oluşturma sorununu yeniden üretmek zordur ancak 700 ms boyunca donmuş kareleri sonlandırarak başlayabilirsiniz. Bu durum en çok uygulama başlatılırken veya ekranlar değiştirilirken görülür.

Duraklamayı düzeltme

Jank sorununu düzeltmek için 16 ms içinde tamamlanmayan kareleri inceleyin ve sorunun ne olduğunu bulun. Bazı karelerde Record View#draw veya Layout öğesinin anormal derecede uzun sürüp sürmediğini kontrol edin. Bu ve diğer sorunlar için Duraklamanın yaygın kaynakları başlıklı makaleye bakın.

Duraklamayı önlemek için uzun süren görevleri kullanıcı arayüzü iş parçacığının dışında eşzamansız olarak çalıştırın. Kodunuzun hangi iş parçacığında çalıştığının her zaman farkında olun ve önemli olmayan görevleri ana iş parçacığına gönderirken dikkatli olun.

Uygulamanız için karmaşık ve önemli bir birincil kullanıcı arayüzünüz (ör. merkezi kaydırma listesi) varsa yavaş oluşturma sürelerini otomatik olarak algılayabilen ve gerilemeleri önlemek için testleri sık sık çalıştırabilen enstrümantasyon testleri yazmayı düşünebilirsiniz.

Sık karşılaşılan jank kaynakları

Aşağıdaki bölümlerde, View sistemini kullanan uygulamalarda duraklamanın yaygın kaynakları ve bunları ele almak için en iyi uygulamalar açıklanmaktadır. Jetpack Compose ile ilgili performans sorunlarını düzeltme hakkında bilgi için Jetpack Compose performansı başlıklı makaleyi inceleyin.

Kaydırılabilir listeler

ListView ve özellikle RecyclerView, en çok takılmaya yatkın olan karmaşık kaydırma listeleri için yaygın olarak kullanılır. Her ikisi de Systrace işaretçileri içerdiğinden, uygulamanızda duraklamaya katkıda bulunup bulunmadıklarını görmek için Systrace'i kullanabilirsiniz. İzleme bölümlerinin RecyclerView içinde görünmesinin yanı sıra eklediğiniz izleme işaretçilerinin de görünmesi için komut satırı bağımsız değişkeni -a <your-package-name>'yı iletin. Kullanılabiliyorsa Systrace çıktısında oluşturulan uyarıların yönlendirmelerini uygulayın. Systrace'te, RecyclerView-izlenen bölümleri tıklayarak RecyclerView'nın yaptığı çalışmayla ilgili bir açıklama görebilirsiniz.

RecyclerView: notifyDataSetChanged()

RecyclerView içindeki her öğenin yeniden bağlanıp tek bir karede yeniden düzenlendiğini ve yeniden çizildiğini görüyorsanız küçük güncellemeler için notifyDataSetChanged(), setAdapter(Adapter) veya swapAdapter(Adapter, boolean)'yi çağırmadığınızdan emin olun. Bu yöntemler, liste içeriğinin tamamında değişiklik olduğunu belirtir ve Systrace'te RV FullInvalidate olarak görünür. Bunun yerine, içerik değiştirildiğinde veya eklendiğinde minimum güncellemeler oluşturmak için SortedList veya DiffUtil kullanın.

Örneğin, bir sunucudan yeni bir haber içeriği listesi sürümü alan bir uygulamayı ele alalım. Bu bilgileri bağdaştırıcıya gönderdiğinizde, aşağıdaki örnekte gösterildiği gibi notifyDataSetChanged() işlevini çağırmak mümkündür:

Kotlin

fun onNewDataArrived(news: List<News>) {
    myAdapter.news = news
    myAdapter.notifyDataSetChanged()
}

Java

void onNewDataArrived(List<News> news) {
    myAdapter.setNews(news);
    myAdapter.notifyDataSetChanged();
}

Bunun dezavantajı, tek bir öğenin en üste eklenmesi gibi küçük bir değişiklik olduğunda RecyclerView'nın bunu fark etmemesidir. Bu nedenle, tüm önbelleğe alınmış öğe durumunu bırakması ve her şeyi yeniden bağlaması gerektiği söylenir.

Sizin için minimum güncellemeleri hesaplayıp gönderen DiffUtil aracını kullanmanızı öneririz:

Kotlin

fun onNewDataArrived(news: List<News>) {
    val oldNews = myAdapter.items
    val result = DiffUtil.calculateDiff(MyCallback(oldNews, news))
    myAdapter.news = news
    result.dispatchUpdatesTo(myAdapter)
}

Java

void onNewDataArrived(List<News> news) {
    List<News> oldNews = myAdapter.getItems();
    DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news));
    myAdapter.setNews(news);
    result.dispatchUpdatesTo(myAdapter);
}

DiffUtil'ya listelerinizi nasıl inceleyeceğini bildirmek için MyCallback'nizi Callback uygulaması olarak tanımlayın.

RecyclerView: İç içe yerleştirilmiş RecyclerView'lar

RecyclerView öğesinin birden fazla örneğini iç içe yerleştirmek yaygın bir uygulamadır. Özellikle yatay kaydırma listelerinin dikey bir listesi söz konusu olduğunda bu durum geçerlidir. Buna örnek olarak Play Store ana sayfasındaki uygulama ızgaraları verilebilir. Bu yöntem çok iyi sonuçlar verebilir ancak çok fazla görünümün taşınması gerekir.

Sayfada ilk kez aşağı kaydırdığınızda çok sayıda iç öğenin şiştiğini görüyorsanız RecyclerView öğesinin iç (yatay) örnekleri arasında RecyclerView.RecycledViewPool paylaştığınızı kontrol etmeniz gerekebilir. Varsayılan olarak her birinin RecyclerView kendi öğe grubu vardır. Ancak, ekranda aynı anda bir düzine itemViews olması durumunda, tüm satırlar benzer türde görünümler gösteriyorsa itemViews farklı yatay listeler tarafından paylaşılamadığında sorun yaşanır.

Kotlin

class OuterAdapter : RecyclerView.Adapter<OuterAdapter.ViewHolder>() {

    ...

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        // Inflate inner item, find innerRecyclerView by ID.
        val innerLLM = LinearLayoutManager(parent.context, LinearLayoutManager.HORIZONTAL, false)
        innerRv.apply {
            layoutManager = innerLLM
            recycledViewPool = sharedPool
        }
        return OuterAdapter.ViewHolder(innerRv)
    }
    ...

Java

class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
    RecyclerView.RecycledViewPool sharedPool = new RecyclerView.RecycledViewPool();

    ...

    @Override
    public void onCreateViewHolder(ViewGroup parent, int viewType) {
        // Inflate inner item, find innerRecyclerView by ID.
        LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(),
                LinearLayoutManager.HORIZONTAL);
        innerRv.setLayoutManager(innerLLM);
        innerRv.setRecycledViewPool(sharedPool);
        return new OuterAdapter.ViewHolder(innerRv);

    }
    ...

Daha fazla optimizasyon yapmak isterseniz içteki RecyclerView öğesinin LinearLayoutManager üzerinde setInitialPrefetchItemCount(int) öğesini de çağırabilirsiniz. Örneğin, satırda her zaman 3,5 öğe görünüyorsa innerLLM.setInitialItemPrefetchCount(4) çağrısı yapın. Bu, RecyclerView'ya yatay bir satır ekrana gelmek üzereyken kullanıcı arayüzü iş parçacığında boş zaman varsa içindeki öğeleri önceden getirmeye çalışması gerektiğini bildirir.

RecyclerView: Çok fazla şişirme veya oluşturma işlemi çok uzun sürüyor

Çoğu durumda, RecyclerView önceden getirme özelliği, kullanıcı arayüzü iş parçacığı boşta kalırken işi önceden yaparak enflasyonun maliyetini azaltmaya yardımcı olabilir. Bir kare sırasında ve RV Prefetch etiketli bir bölümde değil de şişme görüyorsanız desteklenen bir cihazda test yaptığınızdan ve Destek Kitaplığı'nın son sürümünü kullandığınızdan emin olun. Önceden getirme yalnızca Android 5.0 API düzeyi 21 ve sonraki sürümlerde desteklenir.

Yeni öğeler ekrana geldiğinde sıklıkla şişirme nedeniyle takılma görüyorsanız ihtiyacınızdan fazla görünüm türü kullanmadığınızı doğrulayın. Bir RecyclerView içeriğindeki görünüm türleri ne kadar az olursa yeni öğe türleri ekrana geldiğinde o kadar az şişirme yapılması gerekir. Mümkünse görünüm türlerini makul olan yerlerde birleştirin. Türler arasında yalnızca simge, renk veya metin parçası değişiyorsa bu değişikliği bağlama sırasında yapabilir ve aynı zamanda uygulamanızın bellek ayak izini azaltan şişirmeyi önleyebilirsiniz.

Görünüm türleriniz iyi görünüyorsa enflasyon maliyetinizi düşürmeyi deneyin. Gereksiz kapsayıcı ve yapısal görünümleri azaltmak yardımcı olabilir. ConstraintLayout ile itemViews oluşturmayı deneyin. Bu, yapısal görünümlerin azaltılmasına yardımcı olabilir.

Performans için daha fazla optimizasyon yapmak istiyorsanız ve öğe hiyerarşileriniz basitse, karmaşık tema ve stil özelliklerine ihtiyacınız yoksa oluşturucuları kendiniz çağırmayı düşünebilirsiniz. Ancak bu, XML'in basitliğini ve özelliklerini kaybetme pahasına elde edilecek bir avantaj değildir.

RecyclerView: Bağlama çok uzun sürüyor

Bağlama (onBindViewHolder(VH, int)) işlemi basit olmalı ve en karmaşık öğeler hariç tüm öğeler için bir milisaniyeden çok daha kısa sürmelidir. Adaptörünüzün dahili ürün verilerinden düz eski Java nesnesi (POJO) öğeleri almalı ve ViewHolder içindeki görünümlerde belirleyicileri çağırmalıdır. RV OnBindView uzun sürüyorsa bağlama kodunuzda minimum düzeyde işlem yaptığınızı doğrulayın.

Adaptörünüzde verileri tutmak için temel POJO nesnelerini kullanıyorsanız onBindViewHolder içinde bağlama kodu yazmaktan tamamen kaçınmak için Data Binding Library'yi kullanabilirsiniz.

RecyclerView veya ListView: Düzen oluşturma ya da çizim çok uzun sürüyor

Çizim ve düzenle ilgili sorunlar için Düzen performansı ve Oluşturma performansı bölümlerine bakın.

ListView: Inflation

Dikkatli olmazsanız ListView bölümünde geri dönüşümü yanlışlıkla devre dışı bırakabilirsiniz. Bir öğe ekrana her geldiğinde şişirme görüyorsanız Adapter.getView() uygulamanızın convertView parametresini kullandığından, yeniden bağladığından ve döndürdüğünden emin olun. getView() uygulamanız her zaman şişiyorsa uygulamanız ListView'deki geri dönüşüm avantajlarından yararlanamaz. getView() yapınız neredeyse her zaman aşağıdaki uygulamaya benzer olmalıdır:

Kotlin

fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    return (convertView ?: layoutInflater.inflate(R.layout.my_layout, parent, false)).apply {
        // Bind content from position to convertView.
    }
}

Java

View getView(int position, View convertView, ViewGroup parent) {

    if (convertView == null) {
        // Only inflate if no convertView passed.
        convertView = layoutInflater.inflate(R.layout.my_layout, parent, false)
    }
    // Bind content from position to convertView.
    return convertView;
}

Düzen performansı

Systrace, Choreographer#doFrame Layout (Düzen) segmentinin çok fazla veya çok sık çalıştığını gösteriyorsa bu, düzen performansıyla ilgili sorunlar yaşadığınız anlamına gelir. Uygulamanızın düzen performansı, görünüm hiyerarşisinin hangi bölümünde değişen düzen parametreleri veya girişler olduğuna bağlıdır.

Düzen performansı: Maliyet

Segmentler birkaç milisaniyeden uzunsa RelativeLayouts veya weighted-LinearLayouts için en kötü iç içe yerleştirme performansıyla karşılaşıyor olabilirsiniz. Bu düzenlerin her biri, alt öğelerinin birden fazla ölçü ve düzen geçişini tetikleyebilir. Bu nedenle, bunları iç içe yerleştirmek, iç içe yerleştirme derinliğinde O(n^2) davranışına yol açabilir.

Hiyerarşinin en düşük yaprak düğümleri hariç tüm düğümlerinde RelativeLayout veya LinearLayout ağırlık özelliğini kullanmaktan kaçının. Bunu yapmanın yolları:

  • Yapısal görünümlerinizi yeniden düzenleyin.
  • Özel düzen mantığı tanımlayın. Belirli bir örnek için Düzen hiyerarşilerini optimize etme bölümüne bakın. Performansla ilgili dezavantajlar olmadan benzer özellikler sunan ConstraintLayout'a dönüştürmeyi deneyebilirsiniz.

Düzen performansı: Sıklık

Yeni içerik ekrana geldiğinde düzenin yapılması beklenir. Örneğin, RecyclerView uygulamasında yeni bir öğe kaydırılarak görünüme getirildiğinde düzen yapılır. Her karede önemli düzen değişiklikleri yapılıyorsa düzeni animasyon hâline getiriyor olabilirsiniz. Bu durum, karelerin düşmesine neden olabilir.

Genel olarak animasyonlar, aşağıdaki gibi View çizim özelliklerinde çalışmalıdır:

Bunların tümünü, dolgu veya kenar boşlukları gibi düzen özelliklerinden çok daha ucuza değiştirebilirsiniz. Genel olarak, bir görünümün çizim özelliklerini, bir sonraki karede invalidate()'ı tetikleyen bir ayarlayıcıyı çağırmak ve ardından draw(Canvas)'ı çağırmak suretiyle değiştirmek çok daha ucuzdur. Bu işlem, geçersiz kılınan görünüm için çizim işlemlerini yeniden kaydeder ve genellikle düzenden çok daha ucuzdur.

Görüntüleme performansı

Android kullanıcı arayüzü iki aşamada çalışır:

  • Record View#draw, her geçersiz kılınan görünümde draw(Canvas) işlevini çalıştıran ve özel görünümlere veya kodunuza çağrı yapabilen kullanıcı arayüzü iş parçacığında çalışır.
  • RenderThread üzerinde DrawFrame. Bu, yerel RenderThread üzerinde çalışır ancak Record View#draw aşamasında oluşturulan çalışmaya göre işlem yapar.

Oluşturma performansı: kullanıcı arayüzü iş parçacığı

Record View#draw işlemi uzun sürüyorsa genellikle kullanıcı arayüzü iş parçacığına bir bit eşlem çizilir. Bit eşleme üzerinde çizim yapmak için CPU oluşturma kullanılır. Bu nedenle, mümkün olduğunda bundan kaçının. Sorunun bu olup olmadığını görmek için Android CPU Profiler ile yöntem izlemeyi kullanabilirsiniz.

Uygulama, bir bit eşlemi görüntülemeden önce süslemek istediğinde genellikle bit eşleme çizimi yapılır. Bazen yuvarlak köşeler ekleme gibi bir süsleme yapılır:

Kotlin

val paint = Paint().apply {
    isAntiAlias = true
}
Canvas(roundedOutputBitmap).apply {
    // Draw a round rect to define the shape:
    drawRoundRect(
            0f,
            0f,
            roundedOutputBitmap.width.toFloat(),
            roundedOutputBitmap.height.toFloat(),
            20f,
            20f,
            paint
    )
    paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)
    // Multiply content on top to make it rounded.
    drawBitmap(sourceBitmap, 0f, 0f, paint)
    setBitmap(null)
    // Now roundedOutputBitmap has sourceBitmap inside, but as a circle.
}

Java

Canvas bitmapCanvas = new Canvas(roundedOutputBitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
// Draw a round rect to define the shape:
bitmapCanvas.drawRoundRect(0, 0,
        roundedOutputBitmap.getWidth(), roundedOutputBitmap.getHeight(), 20, 20, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
// Multiply content on top to make it rounded.
bitmapCanvas.drawBitmap(sourceBitmap, 0, 0, paint);
bitmapCanvas.setBitmap(null);
// Now roundedOutputBitmap has sourceBitmap inside, but as a circle.

Kullanıcı arayüzü iş parçacığında bu tür bir çalışma yapıyorsanız bunun yerine arka plandaki kod çözme iş parçacığında yapabilirsiniz. Önceki örnekte olduğu gibi bazı durumlarda, çalışmayı çizim sırasında bile yapabilirsiniz. Bu nedenle, Drawable veya View kodunuz aşağıdaki gibi görünüyorsa:

Kotlin

fun setBitmap(bitmap: Bitmap) {
    mBitmap = bitmap
    invalidate()
}

override fun onDraw(canvas: Canvas) {
    canvas.drawBitmap(mBitmap, null, paint)
}

Java

void setBitmap(Bitmap bitmap) {
    mBitmap = bitmap;
    invalidate();
}

void onDraw(Canvas canvas) {
    canvas.drawBitmap(mBitmap, null, paint);
}

Bunu aşağıdakiyle değiştirebilirsiniz:

Kotlin

fun setBitmap(bitmap: Bitmap) {
    shaderPaint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
    invalidate()
}

override fun onDraw(canvas: Canvas) {
    canvas.drawRoundRect(0f, 0f, width, height, 20f, 20f, shaderPaint)
}

Java

void setBitmap(Bitmap bitmap) {
    shaderPaint.setShader(
            new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP));
    invalidate();
}

void onDraw(Canvas canvas) {
    canvas.drawRoundRect(0, 0, width, height, 20, 20, shaderPaint);
}

Ayrıca, bit eşlemin üzerine gradyan çizme ve ColorMatrixColorFilter ile görüntü filtreleme gibi arka plan koruması için de bu işlemi yapabilirsiniz. Bu işlemler, bit eşlemleri değiştirirken yapılan diğer iki yaygın işlemdir.

Başka bir nedenle (ör. önbellek olarak kullanmak) bit eşleme çiziyorsanız doğrudan View öğenize iletilen donanım hızlandırmalı Canvas veya Drawable öğesine çizmeye çalışın. Gerekirse karmaşık oluşturma çıkışını önbelleğe almak ve GPU oluşturmadan yararlanmaya devam etmek için setLayerType() ile LAYER_TYPE_HARDWARE'ı da arayabilirsiniz.

Oluşturma performansı: RenderThread

Bazı Canvas işlemleri kaydetmek ucuzdur ancak RenderThread üzerinde pahalı bir hesaplama tetikler. Systrace genellikle bunları uyarılarla belirtir.

Büyük yollara animasyon ekleme

Donanım hızlandırmalı Canvas, View'ye iletildiğinde Canvas.drawPath() çağrıldığında Android, bu yolları önce CPU'da çizer ve GPU'ya yükler. Büyük yollarınız varsa bunların kare kare düzenlenmesinden kaçının. Böylece, yollar etkili bir şekilde önbelleğe alınabilir ve çizilebilir. drawPoints(), drawLines() ve drawRect/Circle/Oval/RoundRect(), daha fazla çizim çağrısı kullansanız bile daha verimlidir ve daha iyi kullanılır.

Canvas.clipPath

clipPath(Path) pahalı kırpma davranışını tetikler ve genellikle kaçınılması gerekir. Mümkün olduğunda, dikdörtgen olmayan şekillerde kırpma yerine şekil çizme seçeneğini kullanın. Daha iyi performans gösterir ve kenarları yumuşatma özelliğini destekler. Örneğin, aşağıdaki clipPath çağrısı farklı şekilde ifade edilebilir:

Kotlin

canvas.apply {
    save()
    clipPath(circlePath)
    drawBitmap(bitmap, 0f, 0f, paint)
    restore()
}

Java

canvas.save();
canvas.clipPath(circlePath);
canvas.drawBitmap(bitmap, 0f, 0f, paint);
canvas.restore();

Bunun yerine, önceki örneği aşağıdaki gibi ifade edin:

Kotlin

paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
// At draw time:
canvas.drawPath(circlePath, mPaint)

Java

// One time init:
paint.setShader(new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP));
// At draw time:
canvas.drawPath(circlePath, mPaint);
Bit eşlem yüklemeleri

Android, bit eşlemleri OpenGL dokuları olarak görüntüler ve bir bit eşlem bir karede ilk kez görüntülendiğinde GPU'ya yüklenir. Bunu Systrace'te Texture upload(id) width x height olarak görebilirsiniz. Şekil 2'de gösterildiği gibi bu işlem birkaç milisaniye sürebilir ancak görüntünün GPU ile gösterilmesi için gereklidir.

Bu işlemler uzun sürüyorsa önce izlemedeki genişlik ve yükseklik sayılarını kontrol edin. Görüntülenen bit eşlemin, ekranda gösterildiği alandan önemli ölçüde büyük olmadığından emin olun. Bu durumda, yükleme süresi ve bellek boşa harcanır. Genellikle, bit eşlem yükleme kitaplıkları uygun boyutta bir bit eşlem isteme yöntemi sağlar.

Android 7.0'da, genellikle kitaplıklar tarafından yapılan bit eşlem yükleme kodu, gerekmeden önce erken yüklemeyi tetiklemek için prepareToDraw()'ı çağırabilir. Bu sayede, RenderThread boşta kalırken yükleme işlemi erken gerçekleşir. Bu işlemi, bit eşlemi bildiğiniz sürece kod çözme işleminden sonra veya bit eşlemi bir görünüme bağlarken yapabilirsiniz. İdeal olarak, bit eşlem yükleme kitaplığınız bunu sizin için yapar ancak kendi kitaplığınızı yönetiyorsanız veya daha yeni cihazlarda yükleme sınırına ulaşmadığınızdan emin olmak istiyorsanız kendi kodunuzda prepareToDraw() işlevini çağırabilirsiniz.

Bir uygulama, büyük bir bit eşlemi yüklemek için önemli bir süre harcıyor.
Şekil 2. Bir uygulama, büyük bir bit eşlemi yüklerken bir karede önemli ölçüde zaman harcıyor. Boyutunu küçültün veya prepareToDraw() ile kodunu çözdüğünüzde erken tetikleyin.

İleti dizisi planlama gecikmeleri

İş parçacığı planlayıcı, sistemdeki hangi iş parçacıklarının ne zaman ve ne kadar süreyle çalışması gerektiğine karar vermekten sorumlu olan Android işletim sisteminin bir parçasıdır.

Bazen, uygulamanızın kullanıcı arayüzü iş parçacığı engellendiği veya çalışmadığı için jank oluşur. Systrace, bir iş parçacığının uykuda (gri), çalıştırılabilir (mavi: çalıştırılabilir ancak henüz zamanlayıcı tarafından çalıştırılmak üzere seçilmemiştir), etkin olarak çalışıyor (yeşil) veya kesintiye uğratılamayan uyku (kırmızı veya turuncu) durumunda olduğunu belirtmek için Şekil 3'te gösterildiği gibi farklı renkler kullanır. Bu, iş parçacığı planlama gecikmelerinden kaynaklanan takılma sorunlarında hata ayıklamak için son derece yararlıdır.

Kullanıcı arayüzü iş parçacığının uyuduğu bir dönemi vurgular.
Şekil 3. Kullanıcı arayüzü iş parçacığının uyuduğu bir dönemin vurgulanması.

Android'deki süreçler arası iletişim (IPC) mekanizması olan bağlayıcı çağrıları, uygulamanızın yürütülmesinde genellikle uzun duraklamalara neden olur. Android'in sonraki sürümlerinde, kullanıcı arayüzü iş parçacığının çalışmayı durdurmasının en yaygın nedenlerinden biridir. Genellikle düzeltme, bağlayıcı çağrıları yapan işlevleri çağırmaktan kaçınmaktır. Kaçınılmazsa değeri önbelleğe alın veya işi arka plan iş parçacıklarına taşıyın. Kod tabanları büyüdükçe dikkatli olmazsanız bazı düşük düzeyli yöntemleri çağırarak yanlışlıkla bir bağlayıcı çağrısı ekleyebilirsiniz. Ancak izleme ile bu sorunları bulup düzeltebilirsiniz.

Bağlayıcı işlemleriniz varsa aşağıdaki adb komutlarla bunların çağrı yığınlarını yakalayabilirsiniz:

$ adb shell am trace-ipc start
… use the app - scroll/animate ...
$ adb shell am trace-ipc stop --dump-file /data/local/tmp/ipc-trace.txt
$ adb pull /data/local/tmp/ipc-trace.txt

Bazen getRefreshRate() gibi zararsız görünen aramalar, bağlayıcı işlemleri tetikleyebilir ve sık sık arandıklarında büyük sorunlara neden olabilir. Periyodik olarak izleme yapmak, bu sorunları ortaya çıktıkça bulup düzeltmenize yardımcı olabilir.

RV hızlı kaydırmasında binder işlemleri nedeniyle kullanıcı arayüzü iş parçacığının uyuduğunu gösterir. Bağlama mantığınızı odaklanmış tutun ve bağlayıcı çağrıları bulup kaldırmak için trace-ipc'yi kullanın.
Şekil 4. Kullanıcı arayüzü iş parçacığı, RV hızlı kaydırmasındaki binder işlemleri nedeniyle uyuyor. Bağlama mantığınızı basit tutun ve bağlayıcı çağrılarını bulup kaldırmak için trace-ipc kullanın.

Bağlayıcı etkinliği görmüyorsanız ancak kullanıcı arayüzü iş parçacığınızın çalışmadığını fark ediyorsanız başka bir iş parçacığından kilit veya başka bir işlem beklemediğinizden emin olun. Genellikle kullanıcı arayüzü iş parçacığının diğer iş parçacıklarından gelen sonuçları beklemesi gerekmez. Diğer iş parçacıkları, bu iş parçacığına bilgi göndermelidir.

Nesne ayırma ve atık toplama

Android 5.0'da varsayılan çalışma zamanı olarak ART'nin kullanıma sunulmasıyla birlikte nesne ayırma ve çöp toplama (GC) önemli ölçüde daha az sorunlu hale geldi ancak yine de bu ek işlerle iş parçacıklarınızı yavaşlatmanız mümkün. Saniyede birçok kez gerçekleşmeyen nadir bir olaya (ör. kullanıcının bir düğmeye dokunması) yanıt olarak ayırma yapabilirsiniz ancak her ayırmanın bir maliyeti olduğunu unutmayın. Sık sık çağrılan sıkı bir döngüdeyse GC üzerindeki yükü azaltmak için ayırmadan kaçınmayı düşünebilirsiniz.

Systrace, GC'nin sık sık çalışıp çalışmadığını gösterir. Android Memory Profiler ise tahsislerin nereden geldiğini gösterebilir. Mümkün olduğunda, özellikle sıkı döngülerde tahsislerden kaçınırsanız sorun yaşama olasılığınız azalır.

HeapTaskDaemon'da 94 ms GC gösteriliyor
Şekil 5. A 94ms GC on the HeapTaskDaemon thread.

Android'in son sürümlerinde GC genellikle HeapTaskDaemon adlı bir arka plan iş parçacığında çalışır. Önemli miktarda ayırma, Şekil 5'te gösterildiği gibi GC'de daha fazla CPU kaynağı harcanması anlamına gelebilir.