Bir Android uygulaması, işlenmeyen bir istisna veya sinyalden kaynaklanan beklenmedik bir çıkış
olduğunda kilitlenir. Java veya Kotlin kullanılarak yazılan, Throwable
sınıfıyla temsil edilen işlenmemiş bir istisna oluşturan bir uygulama. Makine kodu veya C++ kullanılarak yazılan bir uygulama, yürütme sırasında SIGSEGV
gibi işlenmemiş bir sinyal varsa kilitlenir.
Bir uygulama kilitlendiğinde, Android uygulama işlemini sonlandırır ve kullanıcıya uygulamanın durduğunu bildiren bir iletişim kutusu gösterir (Şekil 1'de gösterildiği gibi).
1. Şekil. Android cihazda uygulama kilitlenmesi
Bir uygulamanın kilitlenmesi için ön planda çalışıyor olması gerekmez. Arka planda çalışan yayın alıcıları veya içerik sağlayıcılar gibi bileşenler dahil olmak üzere, herhangi bir uygulama bileşeni uygulamanın kilitlenmesine neden olabilir. Bu kilitlenmeler, uygulamanızla aktif olarak etkileşimde bulunmadıkları için genellikle kullanıcılar için kafa karıştırıcıdır.
Uygulamanız kilitlenmelerle karşılaşıyorsa sorunu teşhis edip düzeltmek için bu sayfadaki bilgilerden yararlanabilirsiniz.
Sorunu tespit edin
Kullanıcılarınızın uygulamanızı kullanırken çökme yaşadığını her zaman anlayamayabilirsiniz. Uygulamanızı zaten yayınladıysanız uygulamanızın kilitlenme oranlarını görmek için Android vitals'ı kullanabilirsiniz.
Android vitals
Android vitals, uygulamanızın kilitlenme oranını izlemenize ve iyileştirmenize yardımcı olabilir. Android vitals çeşitli kilitlenme oranlarını ölçer:
- Kilitlenme oranı: Günlük etkin kullanıcılarınızdan herhangi bir türde kilitlenme yaşayanların yüzdesidir.
Kullanıcı tarafından algılanan kilitlenme oranı: Günlük etkin kullanıcılarınız arasında, uygulamanızı etkin olarak kullandıkları sırada en az bir kilitlenme (kullanıcı tarafından algılanan kilitlenme) yaşayanların yüzdesidir. Herhangi bir etkinlik görüntüleyen veya ön plan hizmetlerini yürüten uygulamalar etkin kullanımda olarak kabul edilir.
Çoklu kilitlenme oranı: Günlük etkin kullanıcılarınız arasında, en az iki kilitlenme yaşayanların yüzdesidir.
Günlük etkin kullanıcı, uygulamanızı bir gün içinde tek bir cihazda veya potansiyel olarak birden fazla oturumda kullanan benzersiz bir kullanıcıdır. Bir kullanıcı tek bir günde uygulamanızı birden fazla cihazda kullanırsa her cihaz o günün etkin kullanıcı sayısına eklenir. Birden fazla kullanıcı tek bir gün içinde aynı cihazı kullanırsa bunlar tek bir etkin kullanıcı olarak sayılır.
Kullanıcı tarafından algılanan kilitlenme oranı önemli bir metriktir. Yani Google Play'de uygulamanızın bulunabilirliğini etkiler. Bu metriğin saydığı kilitlenmelerin her zaman kullanıcılar uygulamayla etkileşim halindeyken gerçekleşerek çok fazla aksamaya yol açması, bu metriği önemli kılar.
Play bu metrik için iki kötü davranış eşiği tanımlamıştır:
- Genel kötü davranış eşiği: Tüm cihaz modellerinde, günlük etkin kullanıcıların en az% 1,09'u, kullanıcı tarafından algılanan kilitlenme yaşamıştır.
- Cihaz bazında kötü davranış eşiği: Günlük etkin kullanıcıların en az% 8'i tek bir cihaz modeli için kullanıcı tarafından algılanan kilitlenme yaşamıştır.
Genel kötü davranış eşiğini aşan uygulamaların tüm cihazlarda daha az bulunabilir hale gelmesi olasıdır. Uygulamanız cihaz başına kötü davranış eşiğini aşıyorsa bazı cihazlarda büyük olasılıkla daha az bulunabilir olur ve mağaza girişinizde bir uyarı gösterilebilir.
Uygulamanız çok fazla kilitlenme gösterdiğinde Android vitals sizi Play Console aracılığıyla uyarabilir.
Google Play'in Android vitals verilerini nasıl topladığı hakkında bilgi için Play Console belgelerini inceleyin.
Kilitlenmeleri teşhis etme
Uygulamanızın kilitlenmeleri rapor ettiğini belirledikten sonra, bunları teşhis etmeniz gerekir. Kilitlenmeleri çözmek zor olabilir. Bununla birlikte, kilitlenmenin temel nedenini belirleyebilirseniz muhtemelen buna bir çözüm bulabilirsiniz.
Uygulamanızda çökmeye neden olabilecek pek çok durum vardır. Null değerin veya boş dizenin kontrol edilmesi gibi bazı nedenler barizdir. Diğerleri ise bir API'ye geçersiz bağımsız değişkenler veya hatta karmaşık çok iş parçacıklı etkileşimler gibi daha karmaşıktır.
Android'deki kilitlenmeler bir yığın izleme (stack trace) oluşturur. Bu, programınızda çağrılan iç içe işlev dizisinin kilitlenme anına kadarki adım sırasının anlık görüntüsüdür. Çökmeyle sonuçlanan yığın izlemeleri Android vitals'da görüntüleyebilirsiniz.
Yığın izleme (stack trace) okuma
Bir kilitlenmeyi düzeltmenin ilk adımı, kilitlenmenin meydana geldiği yeri belirlemektir. Play Console'u veya logcat aracının çıkışını kullanıyorsanız rapor ayrıntılarındaki yığın izlemeyi kullanabilirsiniz. Yığın izleme (stack trace) yoksa uygulamayı manuel olarak test ederek veya etkilenen kullanıcılara ulaşarak ve logcat'i kullanarak yeniden oluşturarak kilitlenmeyi yerel olarak yeniden oluşturmanız gerekir.
Aşağıdaki izde, Java programlama dili kullanılarak yazılmış bir uygulamadaki kilitlenme örneği gösterilmektedir:
--------- beginning of crash
AndroidRuntime: FATAL EXCEPTION: main
Process: com.android.developer.crashsample, PID: 3686
java.lang.NullPointerException: crash sample
at com.android.developer.crashsample.MainActivity$1.onClick(MainActivity.java:27)
at android.view.View.performClick(View.java:6134)
at android.view.View$PerformClick.run(View.java:23965)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6440)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:746)
--------- beginning of system
Yığın izleme, bir kilitlenmede hata ayıklamak için kritik önem taşıyan iki bilgiyi gösterir:
- Atılan istisna türü.
- Kodda istisnanın atıldığı bölüm.
Atılan istisna türü, genellikle neyin yanlış gittiğine dair çok güçlü bir ipucudur. IOException
, OutOfMemoryError
veya başka bir öğe olup olmadığına bakın ve istisna sınıfıyla ilgili dokümanları bulun.
Yığın izlemenin ikinci satırında, istisnanın atıldığı kaynak dosyanın sınıfı, yöntemi, dosyası ve satır numarası gösterilir. Çağrılan her işlev için bir başka satır, önceki çağrı sitesini (yığın çerçevesi olarak adlandırılır) gösterir. Yığına geçip kodu inceleyerek yanlış değer ileten bir yer bulabilirsiniz. Kodunuz yığın izlemede görünmüyorsa muhtemelen bir yerde eşzamansız işleme geçersiz bir parametre iletmişsinizdir. Genellikle yığın izlemenin (stack trace) her satırını inceleyerek, kullandığınız API sınıflarını bularak, geçtiğiniz parametrelerin doğru olduğunu ve parametreyi izin verilen bir yerden çağırdığınızı teyit ederek ne olduğunu anlayabilirsiniz.
C ve C++ kodu olan uygulamaların yığın izlemeleri hemen hemen aynı şekilde çalışır.
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/foo/bar:10/123.456/78910:user/release-keys'
ABI: 'arm64'
Timestamp: 2020-02-16 11:16:31+0100
pid: 8288, tid: 8288, name: com.example.testapp >>> com.example.testapp <<<
uid: 1010332
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Cause: null pointer dereference
x0 0000007da81396c0 x1 0000007fc91522d4 x2 0000000000000001 x3 000000000000206e
x4 0000007da8087000 x5 0000007fc9152310 x6 0000007d209c6c68 x7 0000007da8087000
x8 0000000000000000 x9 0000007cba01b660 x10 0000000000430000 x11 0000007d80000000
x12 0000000000000060 x13 0000000023fafc10 x14 0000000000000006 x15 ffffffffffffffff
x16 0000007cba01b618 x17 0000007da44c88c0 x18 0000007da943c000 x19 0000007da8087000
x20 0000000000000000 x21 0000007da8087000 x22 0000007fc9152540 x23 0000007d17982d6b
x24 0000000000000004 x25 0000007da823c020 x26 0000007da80870b0 x27 0000000000000001
x28 0000007fc91522d0 x29 0000007fc91522a0
sp 0000007fc9152290 lr 0000007d22d4e354 pc 0000007cba01b640
backtrace:
#00 pc 0000000000042f89 /data/app/com.example.testapp/lib/arm64/libexample.so (com::example::Crasher::crash() const)
#01 pc 0000000000000640 /data/app/com.example.testapp/lib/arm64/libexample.so (com::example::runCrashThread())
#02 pc 0000000000065a3b /system/lib/libc.so (__pthread_start(void*))
#03 pc 000000000001e4fd /system/lib/libc.so (__start_thread)
Yerel yığın izlemelerde sınıf ve işlev düzeyinde bilgiler görmüyorsanız yerel hata ayıklama simgeleri dosyası oluşturup Google Play Console'a yüklemeniz gerekebilir. Daha fazla bilgi için Çökmeyle sonuçlanan yığın izlemenin kodlarını gösterme bölümüne bakın. Yerel kilitlenmeler hakkında genel bilgi için Yerel kilitlenmeleri teşhis etme bölümüne bakın.
Kilitlenmeleri yeniden oluşturmaya yönelik ipuçları
Sadece bir emülatörü başlatarak veya cihazınızı bilgisayarınıza bağlayarak sorunu yeniden oluşturamayabilirsiniz. Geliştirme ortamlarında bant genişliği, bellek ve depolama gibi daha fazla kaynak bulunur. Az bulunan kaynağın ne olabileceğini belirlemek için istisna türünü kullanın veya Android sürümü, cihaz türü ya da uygulamanızın sürümü arasında bir ilişki bulun.
Bellek hataları
OutOfMemoryError
kullanıyorsanız test için düşük bellek kapasitesine sahip bir emülatör oluşturabilirsiniz. Şekil 2'de, cihazdaki bellek miktarını kontrol edebileceğiniz AVD yöneticisi ayarları gösterilmektedir.
2. Şekil. AVD yöneticisindeki bellek ayarı
Ağ iletişimi istisnaları
Kullanıcılar sık sık mobil veya kablosuz ağ kapsamına girdiklerinden ve kapsam dışında olduklarından, bir uygulama ağı istisnalarında genellikle hata olarak değil, beklenmedik bir şekilde gerçekleşen normal çalışma koşulları olarak ele alınmalıdır.
UnknownHostException
gibi bir ağ istisnası yeniden oluşturmanız gerekiyorsa uygulamanız ağı kullanmaya çalışırken uçak modunu açmayı deneyin.
Diğer bir seçenek de ağ hızı emülasyonu ve/veya ağ gecikmesi seçerek emülatörde ağ kalitesini azaltmaktır. AVD yöneticisinde Hız ve Gecikme ayarlarını kullanabilir veya aşağıdaki komut satırı örneğinde gösterildiği gibi emülatörü -netdelay
ve -netspeed
işaretleriyle başlatabilirsiniz:
emulator -avd [your-avd-image] -netdelay 20000 -netspeed gsm
Bu örnekte, tüm ağ isteklerinde 20 saniyelik bir gecikme ve 14,4 Kb/sn'lik bir yükleme ve indirme hızı ayarlanmaktadır. Emülatörün komut satırı seçenekleri hakkında daha fazla bilgi için Emülatörü komut satırından başlatma bölümüne bakın.
Logcat ile okuma
Kilitlenmeyi yeniden oluşturma adımlarını öğrendiğinizde, daha fazla bilgi edinmek için logcat
gibi bir araç kullanabilirsiniz.
Logcat çıktısı, sistemdeki diğer günlük iletileriyle birlikte başka hangi günlük iletilerini yazdırdığınızı gösterir. Eklediğiniz ekstra Log
ifadelerini devre dışı bırakmayı unutmayın. Bu ifadelerin yazdırılması, uygulamanız çalışırken CPU ve pili boşa harcar.
Boş işaretçi istisnalarından kaynaklanan kilitlenmeleri önleyin
Boş işaretçi istisnaları (NullPointerException
çalışma zamanı hata türüyle tanımlanır), genellikle yöntemlerini çağırarak veya üyelerine erişerek boş olan bir nesneye erişmeye çalıştığınızda ortaya çıkar. Boş işaretçi istisnaları, Google Play'de uygulama kilitlenmelerinin en büyük nedenidir. Null'un amacı, nesnenin eksik olduğunu, örneğin henüz oluşturulmadığını veya atanmadığını belirtmektir. Boş işaretçi istisnalarını önlemek için, üzerinde çalıştığınız nesne referanslarının üzerinde yöntem çağırmadan veya üyelerine erişmeye çalışmadan önce bu referansların boş olmadığından emin olmanız gerekir. Nesne referansı boşsa bu durumu iyi ele alın (örneğin, nesne referansında herhangi bir işlem gerçekleştirmeden önce bir yöntemden çıkın ve hata ayıklama günlüğüne bilgi yazın).
Çağrılan her yöntemin her parametresi için null kontrolü yapmak istemediğinizden, null değeri belirtmek için IDE'ye veya nesnenin türüne güvenebilirsiniz.
Java programlama dili
Aşağıdaki bölümler Java programlama dili için geçerlidir.
Zaman uyarılarını derleyin
IDE'den derleme süresi uyarıları almak için yöntemlerinizin parametrelerine ve değerlerine @Nullable
ve @NonNull
ile ek açıklama ekleyin. Bu uyarılar, boş değerli bir nesne beklemenizi ister:
Bu null denetimler, boş olabileceğini bildiğiniz nesneler içindir. Bir @NonNull
nesnesindeki istisna, kodunuzda düzeltilmesi gereken bir hatanın göstergesidir.
Derleme zaman hataları
Nullability anlamlı olması gerektiğinden, null için derleme zaman denetimi olması amacıyla bunu kullandığınız türlere yerleştirebilirsiniz. Bir nesnenin null olabileceğini ve null özelliğinin işlenmesi gerektiğini biliyorsanız bu nesneyi Optional
gibi bir nesne içine sarmalayabilirsiniz.
Her zaman null değer ileten türleri tercih etmelisiniz.
Kotlin
Kotlin'de nullability, tür sisteminin bir parçasıdır. Örneğin, bir değişkenin en başından itibaren null veya null olamaz olarak tanımlanması gerekir. Boş değer atanabilir türler ?
ile işaretlenir:
// non-null
var s: String = "Hello"
// null
var s: String? = "Hello"
Null yapılamayan değişkenlere null değer atanamaz ve null yapabilen değişkenler null olarak kullanılmadan önce null özellik açısından kontrol edilmelidir.
Null değerini açık bir şekilde kontrol etmek istemiyorsanız ?.
güvenli çağrı operatörünü kullanabilirsiniz:
val length: Int? = string?.length // length is a nullable int
// if string is null, then length is null
En iyi uygulama olarak, null özellikli bir nesnenin null değerini ele aldığınızdan emin olun. Aksi takdirde uygulamanız beklenmedik durumlara girebilir. Uygulamanız artık NullPointerException
ile çökmezse bu hataların olup olmadığını bilemezsiniz.
Aşağıda null değerini kontrol etmenin bazı yolları verilmiştir:
if
kontrolval length = if(string != null) string.length else 0
Kotlin derleyicisi, akıllı yayın ve null denetimi nedeniyle dize değerinin null olmadığını bilir. Bu sayede, güvenli çağrı operatörüne gerek kalmadan referansı doğrudan kullanabilirsiniz.
-
Bu operatör, "Nesne null değilse nesneyi döndürür; aksi takdirde başka bir şey döndürür" durumunu ifade etmenize olanak tanır.
val length = string?.length ?: 0
Yine de Kotlin'de NullPointerException
alabilirsiniz. En yaygın durumlar şunlardır:
- Açıkça
NullPointerException
atarken. - Boş onay
!!
operatörünü kullandığınızda. Bu operatör, herhangi bir değeri null olmayan bir türe dönüştürür ve değer null olursaNullPointerException
değerini döndürür. - Bir platform türünün boş referansına erişirken.
Platform türleri
Platform türleri, Java'dan gelen nesne bildirimleridir. Bu türler özel olarak işlenir; boş kontroller zorunlu kılınmadığı için null olmayan garanti Java'daki ile aynıdır. Platform türü referansına eriştiğinizde Kotlin derleme zamanı hataları oluşturmaz ancak bu referanslar çalışma zamanı hatalarına neden olabilir. Kotlin belgelerinden aşağıdaki örneğe bakın:
val list = ArrayList<String>() // non-null (constructor result) list.add("Item")
val size = list.size // non-null (primitive int) val item = list[0] // platform
type inferred (ordinary Java object) item.substring(1) // allowed, may throw an
// exception if item == null
Kotlin, bir Kotlin değişkenine platform değeri atandığında tür çıkarımını kullanır veya ne tür bekleyeceğinizi tanımlayabilirsiniz. Java'dan gelen bir referansın null değer alabilme durumunun doğru olmasını sağlamanın en iyi yolu, Java kodunuzda nullability ek açıklamaları (örneğin, @Nullable
) kullanmaktır. Kotlin derleyicisi, bu referansları platform türü olarak değil, null veya null yapılamayan gerçek türler olarak temsil eder.
Java Jetpack API'lerine gerektiğinde @Nullable
veya @NonNull
ek açıklamaları eklenmiştir ve benzer bir yaklaşım Android 11 SDK'sında uygulanmıştır.
Bu SDK'dan gelen ve Kotlin'de kullanılan türler, null yapabilen veya null yapılamayan doğru türler olarak temsil edilir.
Kotlin'in tür sistemi nedeniyle uygulamaların NullPointerException
kilitlenmelerinde büyük bir düşüş gözlemledik. Örneğin, Google Home uygulaması yeni özellik geliştirmesini Kotlin'e taşıdığı yıl boyunca boş işaretçi istisnalarından kaynaklanan kilitlenmelerde %30'luk bir düşüş gördü.