Android Uygulamalarını Optimize Etmek İçin İpuçları ve Araçlar
Yayınlanan: 2022-03-11Android cihazların çok sayıda çekirdeği vardır, bu nedenle sorunsuz uygulamalar yazmak herkes için basit bir iştir, değil mi? Yanlış. Android'deki her şey birçok farklı şekilde yapılabildiğinden, en iyi seçeneği seçmek zor olabilir. En verimli yöntemi seçmek istiyorsanız, kaputun altında neler olduğunu bilmek zorundasınız. Neyse ki, neler olup bittiğini ölçerek ve açıklayarak darboğazları bulmanıza yardımcı olabilecek birçok araç olduğundan, hislerinize veya koku duyunuza güvenmek zorunda değilsiniz. Uygun şekilde optimize edilmiş ve sorunsuz uygulamalar, kullanıcı deneyimini büyük ölçüde iyileştirir ve ayrıca daha az pil tüketir.
Optimizasyonun gerçekten ne kadar önemli olduğunu düşünmek için önce bazı sayıları görelim. Bir Nimbledroid gönderisine göre, kullanıcıların %86'sı (ben dahil), düşük performans nedeniyle uygulamaları yalnızca bir kez kullandıktan sonra kaldırmış. Bir içerik yüklüyorsanız, bunu kullanıcıya göstermek için 11 saniyeden az süreniz var. Sadece her üç kullanıcıdan biri size daha fazla zaman verecektir. Ayrıca bu nedenle Google Play'de çok sayıda kötü yorum alabilirsiniz.
Her kullanıcının tekrar tekrar fark ettiği ilk şey, uygulamanın başlama zamanıdır. Başka bir Nimbledroid gönderisine göre, en iyi 100 uygulamadan 40'ı 2 saniyenin altında ve 70'i 3 saniyenin altında başlıyor. Bu nedenle, mümkünse, genellikle bazı içerikleri mümkün olan en kısa sürede görüntülemeli ve arka plan kontrollerini ve güncellemeleri biraz geciktirmelisiniz.
Her zaman unutmayın, erken optimizasyon tüm kötülüklerin köküdür. Ayrıca mikro optimizasyon ile çok fazla zaman kaybetmemelisiniz. Sık çalışan kodu optimize etmenin en büyük faydasını göreceksiniz. Örneğin, bu, her kareyi ideal olarak saniyede 60 kez çalıştıran onDraw()
işlevini içerir. Çizim, oradaki en yavaş işlemdir, bu nedenle yalnızca yapmanız gerekenleri yeniden çizmeyi deneyin. Bununla ilgili daha fazla bilgi daha sonra gelecek.
Performans İpuçları
Yeterli teori, performans sizin için önemliyse göz önünde bulundurmanız gereken bazı şeylerin bir listesi.
1. String ve StringBuilder
Diyelim ki bir Dizeniz var ve nedense ona 10 bin kez daha fazla Dize eklemek istiyorsunuz. Kod şöyle bir şeye benzeyebilir.
String string = "hello"; for (int i = 0; i < 10000; i++) { string += " world"; }
Android Studio Monitörlerinde bazı String birleştirmelerin ne kadar verimsiz olabileceğini görebilirsiniz. Devam eden tonlarca Çöp Toplama (GC) var.
Oldukça iyi durumda olan Android 5.1.1'e sahip cihazımda bu işlem yaklaşık 8 saniye sürüyor. Aynı hedefe ulaşmanın daha verimli yolu, bunun gibi bir StringBuilder kullanmaktır.
StringBuilder sb = new StringBuilder("hello"); for (int i = 0; i < 10000; i++) { sb.append(" world"); } String string = sb.toString();
Aynı cihazda bu, neredeyse anında, 5 ms'den daha kısa sürede gerçekleşir. CPU ve Bellek görselleştirmeleri neredeyse tamamen düz, yani bu gelişmenin ne kadar büyük olduğunu hayal edebilirsiniz. Yine de, bu farkı elde etmek için, muhtemelen sık sık yapmadığınız 10 bin Strings eklemek zorunda kaldığımıza dikkat edin. Bu nedenle, bir kez sadece birkaç Dize ekliyorsanız, herhangi bir gelişme görmeyeceksiniz. Bu arada, yaparsanız:
String string = "hello" + " world";
Dahili olarak bir StringBuilder'a dönüştürülür, bu nedenle gayet iyi çalışır.
Merak ediyor olabilirsiniz, neden Dizeleri birleştirmek ilk yol bu kadar yavaş? Dizelerin değişmez olmasından kaynaklanır, bu nedenle bir kez oluşturulduktan sonra değiştirilemezler. Bir String'in değerini değiştirdiğinizi düşünseniz bile, aslında yeni değerle yeni bir String yaratıyorsunuz. Aşağıdaki gibi bir örnekte:
String myString = "hello"; myString += " world";
Hafızaya alacağınız şey 1 String “merhaba dünya” değil, aslında 2 String. String myString, beklediğiniz gibi "merhaba dünya" içerecektir. Ancak, “merhaba” değerine sahip orijinal String, herhangi bir referansı olmadan hala hayatta, çöp toplanmayı bekliyor. Bu aynı zamanda parolaları String yerine char dizisinde saklamanızın nedenidir. Bir parolayı Dize olarak saklarsanız, bir sonraki GC'ye kadar tahmin edilemeyen bir süre boyunca insan tarafından okunabilir biçimde bellekte kalır. Yukarıda açıklanan değişmezliğe geri dönersek, String, kullandıktan sonra başka bir değer atasanız bile bellekte kalacaktır. Ancak, şifreyi kullandıktan sonra char dizisini boşaltırsanız, her yerden kaybolacaktır.
2. Doğru Veri Türünü Seçme
Kod yazmaya başlamadan önce koleksiyonunuz için hangi veri türlerini kullanacağınıza karar vermelisiniz. Örneğin, bir Vector
mü yoksa ArrayList
mi kullanmalısınız? Peki, kullanım durumunuza bağlı. Aynı anda yalnızca bir iş parçacığının çalışmasına izin verecek iş parçacığı güvenli bir koleksiyona ihtiyacınız varsa, senkronize olduğu için bir Vector
seçmelisiniz. Diğer durumlarda, vektörleri kullanmak için gerçekten belirli bir nedeniniz yoksa, muhtemelen bir ArrayList
bağlı kalmalısınız.
Benzersiz nesneler içeren bir koleksiyon istediğinizde durum nasıl? Muhtemelen bir Set
seçmelisiniz. Tasarım gereği kopyaları içeremezler, bu nedenle kendiniz halletmek zorunda kalmayacaksınız. Birden çok set türü vardır, bu nedenle kullanım durumunuza uyan birini seçin. Basit bir benzersiz öğe grubu için bir HashSet
kullanabilirsiniz. Eklendiği öğelerin sırasını korumak istiyorsanız, bir LinkedHashSet
seçin. Bir TreeSet
, öğeleri otomatik olarak sıralar, böylece üzerinde herhangi bir sıralama yöntemini çağırmanız gerekmez. Ayrıca, sıralama algoritmalarını düşünmenize gerek kalmadan öğeleri verimli bir şekilde sıralamalıdır.
— Rob Pike'ın 5 Programlama Kuralı
Tamsayıları veya dizeleri sıralamak oldukça basittir. Ancak, bir sınıfı bazı özelliklere göre sıralamak isterseniz ne olur? Diyelim ki yediğiniz yemeklerin bir listesini yazıyorsunuz ve adlarını ve zaman damgalarını saklıyorsunuz. Yemekleri zaman damgasına göre en düşükten en yükseğe nasıl sıralarsınız? Neyse ki, oldukça basit. Comparable
arabirimini Meal
sınıfında uygulamak ve compareTo()
işlevini geçersiz kılmak yeterlidir. Yemekleri en düşük zaman damgasına göre en yükseğe sıralamak için şöyle bir şey yazabiliriz.
@Override public int compareTo(Object object) { Meal meal = (Meal) object; if (this.timestamp < meal.getTimestamp()) { return -1; } else if (this.timestamp > meal.getTimestamp()) { return 1; } return 0; }
3. Konum Güncellemeleri
Kullanıcının konumunu toplayan birçok uygulama var. Bunun için pek çok faydalı işlev içeren Google Konum Servisleri API'sini kullanmalısınız. Kullanımıyla ilgili ayrı bir makale var, bu yüzden tekrar etmeyeceğim.
Performans açısından bazı önemli noktaları vurgulamak istiyorum.
Her şeyden önce, yalnızca ihtiyacınız olan en hassas konumu kullanın. Örneğin, hava tahmini yapıyorsanız, en doğru konuma ihtiyacınız yoktur. Ağa dayalı olarak yalnızca çok kaba bir alan elde etmek daha hızlıdır ve pil açısından daha verimlidir. Önceliği LocationRequest.PRIORITY_LOW_POWER
olarak ayarlayarak bunu başarabilirsiniz.
setSmallestDisplacement()
adlı LocationRequest
işlevini de kullanabilirsiniz. Bunu metre cinsinden ayarlamak, uygulamanızın belirtilen değerden küçük olması durumunda konum değişikliği konusunda bilgilendirilmemesine neden olur. Örneğin, yakınınızdaki restoranların bulunduğu bir haritanız varsa ve en küçük yer değiştirmeyi 20 metreye ayarlarsanız, kullanıcı bir odada dolaşırken uygulama restoranları kontrol etmek için istekte bulunmayacaktır. Zaten yakınlarda yeni bir restoran olmayacağı için istekler işe yaramazdı.
İkinci kural, konum güncellemelerini yalnızca ihtiyaç duyduğunuz sıklıkta talep etmektir. Bu oldukça açıklayıcı. Bu hava tahmini uygulamasını gerçekten oluşturuyorsanız, muhtemelen bu kadar kesin tahminlere sahip olmadığınız için konumu birkaç saniyede bir istemeniz gerekmez (varsa benimle iletişime geçin). Cihazın konum hakkında uygulamanızı güncelleyeceği gerekli aralığı ayarlamak için setInterval()
işlevini kullanabilirsiniz. Birden fazla uygulama kullanıcının konumunu istemeye devam ederse, daha yüksek bir setInterval()
olsa bile her yeni konum güncellemesinde her uygulama bilgilendirilecektir. Uygulamanızın çok sık bildirilmesini önlemek için setFastestInterval()
ile her zaman en hızlı güncelleme aralığını ayarladığınızdan emin olun.
Ve son olarak, üçüncü kural, yalnızca ihtiyacınız olduğunda konum güncellemelerini talep etmektir. Her x saniyede bir haritada yakındaki bazı nesneleri görüntülüyorsanız ve uygulama arka planda gidiyorsa, yeni konumu bilmenize gerek yoktur. Kullanıcı yine de göremiyorsa, haritayı güncellemek için hiçbir neden yoktur. Uygun olduğunda, tercihen onPause()
'da konum güncellemelerini dinlemeyi bıraktığınızdan emin olun. Daha sonra güncellemeleri onResume()
içinde sürdürebilirsiniz.
4. Ağ İstekleri
Uygulamanızın interneti veri indirmek veya yüklemek için kullanıyor olma olasılığı yüksektir. Öyleyse, ağ isteklerini işlemeye dikkat etmek için birkaç nedeniniz var. Bunlardan biri de mobil data ki bu çok fazla kişiyle sınırlı ve boşa harcamamanız gerekiyor.
İkincisi ise pil. Hem WiFi hem de mobil ağlar, çok fazla kullanılırlarsa oldukça fazla tüketebilirler. Diyelim ki 1 kb indirmek istiyorsunuz. Bir ağ isteğinde bulunmak için hücresel veya WiFi radyoyu uyandırmanız gerekir, ardından verilerinizi indirebilirsiniz. Ancak telsiz operasyondan hemen sonra uykuya dalmayacak. Cihazınıza ve operatörünüze bağlı olarak, yaklaşık 20-40 saniye daha oldukça aktif durumda kalacaktır.
Peki, bu konuda ne yapabilirsiniz? Grup. Telsizi birkaç saniyede bir uyandırmaktan kaçınmak için, kullanıcının önümüzdeki dakikalarda ihtiyaç duyabileceği şeyleri önceden getirin. Doğru gruplama yöntemi, uygulamanıza bağlı olarak oldukça dinamiktir, ancak mümkünse, kullanıcının ihtiyaç duyabileceği verileri önümüzdeki 3-4 dakika içinde indirmelisiniz. Kullanıcının internet türüne veya şarj durumuna göre toplu iş parametreleri de düzenlenebilir. Örneğin, kullanıcı şarj olurken WiFi'deyse, kullanıcının düşük pil ile mobil internette olduğundan çok daha fazla veriyi önceden getirebilirsiniz. Tüm bu değişkenleri hesaba katmak, yalnızca birkaç kişinin yapabileceği zor bir şey olabilir. Neyse ki, kurtarmaya GCM Ağ Yöneticisi var!
GCM Network Manager, birçok özelleştirilebilir özelliğe sahip gerçekten yararlı bir sınıftır. Hem yinelenen hem de bir kerelik görevleri kolayca zamanlayabilirsiniz. Tekrarlanan görevlerde en düşük ve en yüksek tekrar aralığını ayarlayabilirsiniz. Bu, yalnızca isteklerinizi değil, aynı zamanda diğer uygulamalardan gelen istekleri de gruplandırmaya izin verecektir. Telsizin belirli periyotlarda yalnızca bir kez uyandırılması gerekir ve açıkken, sıradaki tüm uygulamalar gerekenleri indirip yükler. Bu Yönetici ayrıca aygıtın ağ türü ve şarj durumunun da farkındadır, böylece buna göre ayar yapabilirsiniz. Bu yazıda daha fazla detay ve örnek bulabilirsiniz, incelemenizi tavsiye ederim. Örnek bir görev şöyle görünür:
Task task = new OneoffTask.Builder() .setService(CustomService.class) .setExecutionWindow(0, 30) .setTag(LogService.TAG_TASK_ONEOFF_LOG) .setUpdateCurrent(false) .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED) .setRequiresCharging(false) .build();
Bu arada, Android 3.0'dan beri, ana iş parçacığında bir ağ isteği yaparsanız, bir NetworkOnMainThreadException
. Bu kesinlikle sizi bunu bir daha yapmamanız konusunda uyaracaktır.
5. Yansıma
Yansıma, sınıfların ve nesnelerin kendi kurucularını, alanlarını, yöntemlerini vb. inceleme yeteneğidir. Belirli bir işletim sistemi sürümü için belirli bir yöntemin mevcut olup olmadığını kontrol etmek için genellikle geriye dönük uyumluluk için kullanılır. Bu amaçla yansıma kullanmanız gerekiyorsa, yansımayı kullanmak oldukça yavaş olduğundan yanıtı önbelleğe aldığınızdan emin olun. Yaygın olarak kullanılan bazı kitaplıklar, bağımlılık enjeksiyonu için Roboguice gibi Reflection'ı da kullanır. Bu yüzden Dagger 2'yi tercih etmelisiniz. Yansıma hakkında daha fazla detay için ayrı bir gönderiye göz atabilirsiniz.
6. Otomatik Kutulama
Otomatik kutulama ve kutudan çıkarma, ilkel bir türü Nesne türüne veya tam tersine dönüştürme işlemleridir. Pratikte, bir int'yi bir Tamsayıya dönüştürmek anlamına gelir. Bunu başarmak için derleyici dahili olarak Integer.valueOf()
işlevini kullanır. Dönüştürme sadece yavaş olmakla kalmaz, aynı zamanda Nesneler de ilkel eşdeğerlerinden çok daha fazla bellek alır. Biraz koda bakalım.
Integer total = 0; for (int i = 0; i < 1000000; i++) { total += i; }
Bu ortalama 500 ms sürse de, otomatik kutulamayı önlemek için yeniden yazmak onu büyük ölçüde hızlandıracaktır.
int total = 0; for (int i = 0; i < 1000000; i++) { total += i; }
Bu çözüm, 25 kat daha hızlı olan yaklaşık 2 ms'de çalışır. Bana inanmıyorsanız, test edin. Rakamlar cihaza göre açıkça farklı olacak, ancak yine de çok daha hızlı olması gerekiyor. Ayrıca optimize etmek için gerçekten basit bir adım.
Tamam, muhtemelen bunun gibi Tamsayı türünde bir değişken oluşturmuyorsunuz. Peki ya kaçınmanın daha zor olduğu durumlar? Map<Integer, Integer>
gibi Objects kullanmanız gereken bir haritada olduğu gibi? Birçok kişinin kullandığı çözüme bakın.
Map<Integer, Integer> myMap = new HashMap<>(); for (int i = 0; i < 100000; i++) { myMap.put(i, random.nextInt()); }
Haritaya 100 bin rastgele giriş eklemek, çalıştırmak için yaklaşık 250 ms sürer. Şimdi SparseIntArray ile çözüme bakın.
SparseIntArray myArray = new SparseIntArray(); for (int i = 0; i < 100000; i++) { myArray.put(i, random.nextInt()); }
Bu çok daha az, kabaca 50 ms sürer. Ayrıca, karmaşık bir şey yapılması gerekmediğinden ve kod da okunabilir durumda kaldığından, performansı artırmanın daha kolay yöntemlerinden biridir. İlk çözümle net bir uygulama çalıştırırken hafızamın 13MB'sini aldı, ilkel int'leri kullanmak 7MB'nin altında bir şey aldı, yani sadece yarısı.
SparseIntArray, otomatik kutulamadan kaçınmanıza yardımcı olabilecek harika koleksiyonlardan yalnızca biridir. Map<Integer, Long>
gibi bir harita, haritanın değeri Long
türünde olduğundan SparseLongArray
ile değiştirilebilir. SparseLongArray
kaynak koduna bakarsanız oldukça ilginç bir şey göreceksiniz. Kaputun altında, temelde sadece bir çift dizi var. Benzer şekilde bir SparseBooleanArray
de kullanabilirsiniz.
Kaynak kodunu okursanız, SparseIntArray
HashMap
öğesinden daha yavaş olabileceğini söyleyen bir not fark etmiş olabilirsiniz. Çok fazla deney yapıyorum ama benim için SparseIntArray
hem bellek hem de performans açısından her zaman daha iyiydi. Sanırım hangisini seçeceğiniz hala size kalmış, kullanım durumlarınızı deneyin ve hangisinin size en uygun olduğunu görün. Haritaları kullanırken kesinlikle SparseArrays
bulundurun.
7. Çizimde
Yukarıda söylediğim gibi, performansı optimize ederken, muhtemelen en çok faydayı sık çalışan kodu optimize etmede göreceksiniz. Çok çalışan fonksiyonlardan biri onDraw()
. Ekrandaki görünümleri çizmekten sorumlu olması sizi şaşırtmayabilir. Cihazlar genellikle 60 fps hızında çalıştığı için fonksiyon saniyede 60 kez çalıştırılır. Hazırlanması ve çizilmesi de dahil olmak üzere her karenin tam olarak işlenmesi için 16 ms vardır, bu nedenle yavaş işlevlerden gerçekten kaçınmalısınız. Ekranda sadece ana iplik çekilebilir, bu yüzden üzerinde pahalı işlemler yapmaktan kaçınmalısınız. Ana iş parçacığını birkaç saniye dondurursanız, kötü şöhretli Uygulama Yanıt Vermiyor (ANR) iletişim kutusuyla karşılaşabilirsiniz. Görüntüleri yeniden boyutlandırmak, veritabanı çalışması vb. için bir arka plan iş parçacığı kullanın.
Bazı insanların kodlarını kısaltmaya çalıştığını ve bu şekilde daha verimli olacağını düşünerek gördüm. Bu kesinlikle gidilecek yol değil, çünkü daha kısa kod tamamen daha hızlı kod anlamına gelmez. Hiçbir koşulda kodun kalitesini satır sayısıyla ölçmemelisiniz.

onDraw()
'da kaçınmanız gereken şeylerden biri, Paint gibi nesneleri tahsis etmektir. Her şeyi yapıcıda hazırlayın, böylece çizim yaparken hazır olur. onDraw()
ı optimize etseniz bile, onu yalnızca gerektiği kadar sık çağırmalısınız. Optimize edilmiş bir işlevi çağırmaktan daha iyi ne olabilir? Hiç bir işlevi çağırmamak. Metin çizmek istemeniz durumunda, metin, koordinatlar ve metin rengi gibi şeyleri belirtebileceğiniz drawText()
adında oldukça temiz bir yardımcı fonksiyon vardır.
8. Görünüm Sahipleri
Muhtemelen bunu biliyorsunuzdur ama ben atlayamam. Viewholder tasarım deseni, kaydırma listelerini daha yumuşak hale getirmenin bir yoludur. Bu, findViewById()
çağrılarını ciddi şekilde azaltabilen ve görünümleri depolayarak şişirebilen bir tür önbelleğe almadır. Bunun gibi bir şey görünebilir.
static class ViewHolder { TextView title; TextView text; public ViewHolder(View view) { title = (TextView) view.findViewById(R.id.title); text = (TextView) view.findViewById(R.id.text); } }
Ardından, bağdaştırıcınızın getView()
işlevi içinde kullanılabilir bir görünüme sahip olup olmadığınızı kontrol edebilirsiniz. Değilse, bir tane yaratırsınız.
ViewHolder viewHolder; if (convertView == null) { convertView = inflater.inflate(R.layout.list_item, viewGroup, false); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.title.setText("Hello World");
İnternette bu model hakkında birçok yararlı bilgi bulabilirsiniz. Bazı bölüm başlıkları gibi, liste görünümünüzde birden çok farklı türde öğe bulunduğunda da kullanılabilir.
9. Görüntüleri Yeniden Boyutlandırma
Muhtemelen, uygulamanız bazı resimler içerecektir. Web'den bazı JPG'ler indiriyorsanız, gerçekten çok büyük çözünürlükleri olabilir. Ancak, görüntülenecekleri cihazlar çok daha küçük olacaktır. Cihazınızın kamerasıyla bir fotoğraf çekseniz bile, fotoğraf çözünürlüğü ekranın çözünürlüğünden çok daha büyük olduğu için görüntülenmeden önce küçültülmesi gerekir. Görüntüleri görüntülemeden önce yeniden boyutlandırmak çok önemli bir şeydir. Bunları tam çözünürlükte görüntülemeyi denerseniz, oldukça hızlı bir şekilde belleğiniz tükenir. Android belgelerinde bitmaplerin verimli bir şekilde görüntülenmesi hakkında yazılmış çok şey var, özetlemeye çalışacağım.
Yani bir bitmap'iniz var ama onun hakkında hiçbir şey bilmiyorsunuz. Hizmetinizde, bitmap'in çözünürlüğünü bulmanızı sağlayan, inJustDecodeBounds adlı kullanışlı bir Bitmap bayrağı var. Bitmap'inizin 1024x768 olduğunu ve onu görüntülemek için kullanılan ImageView'in sadece 400x300 olduğunu varsayalım. Hala verilen ImageView'den daha büyük olana kadar bitmap'in çözünürlüğünü 2'ye bölmeye devam etmelisiniz. Bunu yaparsanız, size 512x384 bitmap vererek bitmap'i 2 faktörü ile aşağı örnekleyecektir. Aşağı örneklenmiş bitmap, 4 kat daha az bellek kullanır ve bu, ünlü OutOfMemory hatasından kaçınmanıza çok yardımcı olur.
Artık nasıl yapılacağını bildiğinize göre, yapmamalısınız. … En azından, uygulamanız büyük ölçüde resimlere dayanıyorsa ve bu sadece 1-2 resim değilse. Görüntüleri manuel olarak yeniden boyutlandırmak ve geri dönüştürmek gibi şeylerden kesinlikle kaçının, bunun için bazı üçüncü taraf kitaplıkları kullanın. En popülerleri Picasso by Square, Universal Image Loader, Fresco by Facebook veya benim favorim Glide. Etrafında büyük bir aktif geliştirici topluluğu var, bu nedenle GitHub'daki sorunlar bölümünde de birçok yardımcı insan bulabilirsiniz.
10. Sıkı Mod
Strict Mode, pek çok kişinin bilmediği oldukça kullanışlı bir geliştirici aracıdır. Genellikle ana iş parçacığından ağ isteklerini veya disk erişimlerini algılamak için kullanılır. Katı Mod'un hangi sorunları araması gerektiğini ve hangi cezayı tetiklemesi gerektiğini belirleyebilirsiniz. Bir google örneği şöyle görünür:
public void onCreate() { if (DEVELOPER_MODE) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() .build()); } super.onCreate(); }
Strict Mode'un bulabileceği her sorunu tespit etmek istiyorsanız, detectAll()
de kullanabilirsiniz. Pek çok performans ipucunda olduğu gibi, Katı Modda rapor edilen her şeyi körü körüne düzeltmeye çalışmamalısınız. Sadece araştırın ve bunun bir sorun olmadığından eminseniz, kendi haline bırakın. Ayrıca Katı Mod'u yalnızca hata ayıklama için kullandığınızdan ve üretim yapılarında her zaman devre dışı bıraktığınızdan emin olun.
Hata Ayıklama Performansı: Profesyonel Yöntem
Şimdi darboğazları bulmanıza veya en azından bir şeylerin yanlış olduğunu göstermenize yardımcı olabilecek bazı araçları görelim.
1. Android Monitör
Bu, Android Studio'da yerleşik bir araçtır. Varsayılan olarak, Android Monitor'ü sol alt köşede bulabilir ve orada 2 sekme arasında geçiş yapabilirsiniz. Logcat ve Monitörler. Monitörler bölümü 4 farklı grafik içerir. Ağ, CPU, GPU ve Bellek. Oldukça açıklayıcılar, bu yüzden onları hızlıca geçeceğim. İşte indirilen bazı JSON'ları ayrıştırırken alınan grafiklerin ekran görüntüsü.
Ağ bölümü, gelen ve giden trafiği KB/sn cinsinden gösterir. CPU kısmı, CPU kullanımını yüzde olarak gösterir. GPU monitörü, bir UI penceresinin çerçevelerini oluşturmanın ne kadar sürdüğünü gösterir. Bu, bu 4 monitör arasında en ayrıntılı monitördür, bu yüzden onun hakkında daha fazla ayrıntı istiyorsanız, bunu okuyun.
Son olarak, muhtemelen en çok kullanacağınız Bellek monitörü var. Varsayılan olarak, mevcut Boş ve Ayrılan bellek miktarını gösterir. Kullanılmış bellek miktarının düşüp düşmediğini test etmek için onunla bir Çöp Toplama işlemini de zorlayabilirsiniz. HPROF Viewer ve Analyzer ile açılabilen bir HPROF dosyası oluşturacak olan Dump Java Heap adlı kullanışlı bir özelliği vardır. Bu, kaç nesne ayırdığınızı, neyin ne kadar bellek aldığını ve belki de hangi nesnelerin bellek sızıntılarına neden olduğunu görmenizi sağlar. Bu analizörün nasıl kullanılacağını öğrenmek, oradaki en basit görev değil, ama buna değer. Memory Monitor ile yapabileceğiniz bir sonraki şey, istediğiniz gibi başlatıp durdurabileceğiniz zamanlanmış Tahsis Takibi yapmaktır. Birçok durumda, örneğin cihazı kaydırırken veya döndürürken yararlı olabilir.
2. GPU Aşırı Çekme
Bu, geliştirici modunu etkinleştirdikten sonra Geliştirici Seçenekleri'nde etkinleştirebileceğiniz basit bir yardımcı araçtır. Hata Ayıkla GPU fazla çizimini seçin, “Aşırı çizim alanlarını göster” ve ekranınız bazı garip renkler alacaktır. Sorun değil, olması gereken bu. Renkler, belirli bir alanın kaç kez fazla çizildiğini gösterir. Gerçek renk, fazla çizim olmadığı anlamına gelir, bunu amaçlamalısınız. Mavi bir fazla çekme, yeşil iki, pembe üç, kırmızı dört demektir.
Gerçek rengi görmek en iyisi olsa da, özellikle metinler, gezinme çekmeceleri, diyaloglar ve daha fazlası çevresinde her zaman bazı fazla çizimler göreceksiniz. Bu yüzden ondan tamamen kurtulmaya çalışmayın. Uygulamanız mavimsi veya yeşilimsiyse, bu muhtemelen iyidir. Ancak bazı basit ekranlarda çok fazla kırmızı görüyorsanız, neler olduğunu araştırmalısınız. Değiştirmek yerine eklemeye devam ederseniz, birbiri üzerine yığılmış çok fazla parça olabilir. Yukarıda bahsettiğim gibi, çizim, uygulamaların en yavaş kısmıdır, bu nedenle üzerine 3'ten fazla katman çizilecekse bir şey çizmenin bir anlamı yoktur. En sevdiğiniz uygulamaları onunla kontrol etmekten çekinmeyin. Bir milyardan fazla indirmeye sahip uygulamaların bile kırmızı alanlara sahip olduğunu göreceksiniz, bu yüzden optimize etmeye çalışırken sakin olun.
3. GPU Oluşturma
Bu, Profil GPU oluşturma adı verilen Geliştirici seçeneklerinden başka bir araçtır. Seçtikten sonra, “Çubuklar olarak ekranda” seçeneğini seçin. Ekranınızda bazı renkli çubukların göründüğünü fark edeceksiniz. Her uygulamanın ayrı çubukları olduğundan, garip bir şekilde durum çubuğunun kendi çubukları vardır ve yazılım gezinme düğmeleriniz varsa, onların da kendi çubukları vardır. Her neyse, siz ekranla etkileşime girdikçe çubuklar güncellenir.
Çubuklar 3-4 renkten oluşuyor ve Android belgelerine göre boyutları gerçekten önemli. Ne kadar küçük, o kadar iyi. En altta, Görünümün görüntüleme listelerini oluşturmak ve güncellemek için kullanılan zamanı temsil eden mavi var. Bu kısım çok uzunsa, çok sayıda özel görünüm çizimi olduğu veya onDraw()
işlevlerinde çok fazla iş yapıldığı anlamına gelir. Android 4.0+ sürümüne sahipseniz, mavi olanın üzerinde mor bir çubuk göreceksiniz. Bu, kaynakları işleme iş parçacığına aktarmak için harcanan zamanı temsil eder. Ardından, Android'in 2B oluşturucusunun OpenGL'ye görüntüleme listelerini çizmesi ve yeniden çizmesi için komutlar vermesi tarafından harcanan zamanı temsil eden kırmızı kısım gelir. En üstte, CPU'nun GPU'nun işini bitirmesini beklediği süreyi temsil eden turuncu çubuk bulunur. Çok uzunsa, uygulama GPU üzerinde çok fazla iş yapıyor demektir.
Yeterince iyiysen, portakalın üstünde bir renk daha var. 16 ms eşiğini temsil eden yeşil bir çizgidir. Amacınız uygulamanızı 60 fps'de çalıştırmak olması gerektiğinden, her kareyi çizmek için 16 ms'niz var. Bunu yapmazsanız, bazı kareler atlanabilir, uygulama sarsıntılı hale gelebilir ve kullanıcı kesinlikle fark eder. Animasyonlara ve kaydırmaya özellikle dikkat edin, burada akıcılık en önemli şeydir. Bu araçla bazı atlanan kareleri tespit edebilseniz bile, sorunun tam olarak nerede olduğunu anlamanıza gerçekten yardımcı olmaz.
4. Hiyerarşi Görüntüleyici
Bu, gerçekten güçlü olduğu için en sevdiğim araçlardan biri. Android Studio'dan Araçlar -> Android -> Android Cihaz Monitörü üzerinden başlatabilirsiniz veya ayrıca sdk/tools klasörünüzde “monitör” olarak bulunur. Orada ayrıca yürütülebilir bağımsız bir hierarachyviewer bulabilirsiniz, ancak kullanımdan kaldırıldığı için monitörü açmalısınız. Ancak Android Cihaz Monitörü'nü açarsanız, Hiyerarşi Görüntüleyicisi perspektifine geçin. Cihazınıza atanmış çalışan herhangi bir uygulama görmüyorsanız, düzeltmek için yapabileceğiniz birkaç şey vardır. Ayrıca bu konu başlığına bir göz atmayı deneyin, her türlü sorunu ve her türlü çözümü olan insanlar var. Bir şey senin için de işe yaramalı.
Hiyerarşi Görüntüleyici ile, görünüm hiyerarşilerinize (tabii ki) gerçekten temiz bir genel bakış elde edebilirsiniz. Her düzeni ayrı bir XML'de görürseniz, gereksiz görünümleri kolayca fark edebilirsiniz. Ancak, düzenleri birleştirmeye devam ederseniz, kolayca kafa karıştırıcı olabilir. Bunun gibi bir araç, örneğin yalnızca 1 çocuğu olan bazı RelativeLayout'ları, başka bir RelativeLayout'u tespit etmeyi kolaylaştırır. Bu, birini çıkarılabilir yapar.
Her bir görünümün ne kadar büyük olması gerektiğini bulmak için tüm görünüm hiyerarşisinin geçişine neden olacağından requestLayout()
çağırmaktan kaçının. Ölçümlerle bazı çelişkiler varsa, hiyerarşi birden çok kez geçilebilir; bu, bazı animasyonlar sırasında gerçekleşirse, kesinlikle bazı karelerin atlanmasına neden olur. Android'in görüşlerini nasıl çizdiği hakkında daha fazla bilgi edinmek istiyorsanız, bunu okuyabilirsiniz. Hiyerarşi Görüntüleyici'de görüldüğü gibi bir görünüme bakalım.
Sağ üst köşe, bağımsız bir pencerede belirli bir görünümün önizlemesini en üst düzeye çıkarmak için bir düğme içerir. Bunun altında, uygulamada görünümün gerçek önizlemesini de görebilirsiniz. Sonraki öğe, görünümün kendisi de dahil olmak üzere, verilen görünümün kaç çocuğu olduğunu gösteren bir sayıdır. Bir düğüm seçerseniz (tercihen kök düğüm) ve "Düzen zamanlarını al"a (3 renkli daire) basarsanız, ölçü, düzen ve çizim etiketli renkli dairelerle birlikte 3 değer daha doldurulur. Ölçme aşamasının verilen görüşü ölçmek için geçen süreyi temsil etmesi şaşırtıcı olmayabilir. Çizim aşaması gerçek çizim işlemi iken, yerleşim aşaması render süresi ile ilgilidir. Bu değerler ve renkler birbirine göredir. Yeşil olan, görünümün ağaçtaki tüm görünümlerin ilk %50'sinde işlendiği anlamına gelir. Sarı, ağaçtaki tüm görünümlerin daha yavaş %50'sinde oluşturma anlamına gelir; kırmızı, verilen görünümün en yavaş olanlardan biri olduğu anlamına gelir. Bu değerler göreceli olduğu için her zaman kırmızı olanlar olacaktır. Onlardan kaçınamazsınız.
Değerlerin altında, "TextView", nesnenin dahili görünüm kimliği ve XML dosyalarında ayarladığınız görünümün android:id gibi sınıf adı bulunur. Kodda bunlara atıfta bulunmasanız bile, tüm görünümlere kimlik ekleme alışkanlığı oluşturmanızı rica ediyorum. Hiyerarşi Görüntüleyici'deki görünümleri tanımlamayı gerçekten basitleştirecek ve projenizde otomatik testler olması durumunda, öğeleri hedeflemeyi çok daha hızlı hale getirecektir. Bu, siz ve meslektaşlarınız için bunları yazarken biraz zaman kazandıracaktır. XML dosyalarına eklenen öğelere kimlik eklemek oldukça basittir. Peki ya dinamik olarak eklenen öğeler? Eh, o da gerçekten basit olduğu ortaya çıkıyor. Değerler klasörünüzün içinde bir ids.xml dosyası oluşturmanız ve gerekli alanları yazmanız yeterlidir. Şöyle görünebilir:
<resources> <item name="item_title" type="id"/> <item name="item_body" type="id"/> </resources>
Ardından kodda setId(R.id.item_title)
kullanabilirsiniz. Daha basit olamazdı.
Kullanıcı arayüzünü optimize ederken dikkat edilmesi gereken birkaç şey daha var. Genelde derin hiyerarşilerden kaçınırken sığ, belki geniş hiyerarşileri tercih etmelisiniz. İhtiyacınız olmayan düzenleri kullanmayın. Örneğin, muhtemelen iç içe LinearLayouts
grubunu bir RelativeLayout
veya TableLayout
ile değiştirebilirsiniz. Farklı düzenleri denemekten çekinmeyin, her zaman LinearLayout
ve RelativeLayout
kullanmayın. Ayrıca gerektiğinde bazı özel görünümler oluşturmayı deneyin, iyi yapılırsa performansı önemli ölçüde artırabilir. Örneğin, Instagram'ın yorumları görüntülemek için TextViews kullanmadığını biliyor muydunuz?
Hiyerarşi Görüntüleyici hakkında daha fazla bilgiyi Android Developers sitesinde, Pixel Perfect aracını kullanarak farklı bölmelerin açıklamalarıyla birlikte bulabilirsiniz. Vs. "Pencere katmanlarını yakala" düğmesi. Her görünüm ayrı bir katmanda olacak, bu yüzden Photoshop veya GIMP'de onu gizlemek veya değiştirmek gerçekten çok kolay. Oh, bu, yapabileceğiniz her görünüme bir kimlik eklemek için başka bir neden. Katmanların gerçekten anlamlı olan adlara sahip olmasını sağlayacaktır.
Geliştirici seçeneklerinde çok daha fazla hata ayıklama aracı bulacaksınız, bu yüzden onları etkinleştirmenizi ve ne yaptıklarını görmenizi tavsiye ederim. Ne yanlış gidebilir ki?
Android geliştiricileri sitesi, performans için bir dizi en iyi uygulama içerir. Gerçekten bahsetmediğim bellek yönetimi de dahil olmak üzere birçok farklı alanı kapsıyorlar. Sessizce görmezden geldim, çünkü hafızayı idare etmek ve hafıza sızıntılarını takip etmek tamamen ayrı bir hikaye. Görüntüleri verimli bir şekilde görüntülemek için üçüncü taraf bir kitaplık kullanmak çok yardımcı olacaktır, ancak hala bellek sorunlarınız varsa, Square tarafından yapılan Leak canary'ye bakın veya bunu okuyun.
Toplama
Yani, bu iyi haberdi. Kötü yeni, Android uygulamalarını optimize etmek çok daha karmaşık. Her şeyi yapmanın birçok yolu vardır, bu yüzden bunların artılarını ve eksilerini bilmelisiniz. Genellikle sadece faydaları olan herhangi bir gümüş kurşun çözümü yoktur. Sadece perde arkasında neler olduğunu anlayarak sizin için en iyi olan çözümü seçebileceksiniz. En sevdiğiniz geliştiricinin bir şeyin iyi olduğunu söylemesi sizin için en iyi çözüm olduğu anlamına gelmez. Tartışılacak daha çok alan ve daha gelişmiş profil oluşturma araçları var, bu yüzden bir dahaki sefere onlara ulaşabiliriz.
En iyi geliştiricilerden ve en iyi şirketlerden öğrendiğinizden emin olun. Bu bağlantıda birkaç yüz mühendislik blogu bulabilirsiniz. Açıkçası sadece Android ile ilgili şeyler değil, bu yüzden yalnızca Android ile ilgileniyorsanız, belirli blogu filtrelemeniz gerekir. Facebook ve Instagram bloglarını şiddetle tavsiye ederim. Android'deki Instagram kullanıcı arayüzü şüpheli olsa da, mühendislik bloglarında gerçekten harika makaleler var. Benim için her gün yüz milyonlarca kullanıcıyla ilgilenen şirketlerde işlerin nasıl yapıldığını görmenin bu kadar kolay olması harika, bu yüzden onların bloglarını okumamak çılgınca geliyor. Dünya gerçekten çok hızlı değişiyor, bu nedenle sürekli gelişmeye, başkalarından bir şeyler öğrenmeye ve yeni araçlar kullanmaya çalışmıyorsanız, geride kalacaksınız. Mark Twain'in dediği gibi, okumayan bir kişinin okuyamayan birine göre hiçbir avantajı yoktur.