Bir Android uygulaması, işlenmemiş bir istisna veya sinyal nedeniyle beklenmedik bir çıkış olduğunda kilitlenir. Java veya Kotlin kullanılarak yazılan bir uygulama, Throwable sınıfıyla temsil edilen işlenmemiş bir istisna oluşturursa çöker. Makine dili veya C++ kullanılarak yazılmış bir uygulama, yürütülmesi sırasında SIGSEGV gibi işlenmemiş bir sinyal varsa kilitlenir.
Bir uygulama çöktüğünde Android, uygulamanın sürecini sonlandırır ve kullanıcıya uygulamanın durduğunu bildirmek için Şekil 1'de gösterildiği gibi bir iletişim kutusu gösterir.
Bir uygulamanın kilitlenmesi için ön planda çalışması gerekmez. Arka planda çalışan yayın alıcılar veya içerik sağlayıcılar gibi bileşenler de dahil olmak üzere tüm uygulama bileşenleri, uygulamanın kilitlenmesine neden olabilir. Bu kilitlenmeler, kullanıcılar uygulamanızla aktif olarak etkileşimde bulunmadığı için genellikle kafa karıştırıcıdır.
Uygulamanız kilitleniyorsa sorunu teşhis etmek ve düzeltmek için bu sayfadaki yönergelerden yararlanabilirsiniz.
Sorunu tespit etme
Kullanıcılarınızın uygulamanızı kullanırken kilitlenme sorunu yaşadığını her zaman bilemeyebilirsiniz. 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ı sayınız arasında herhangi bir türde kilitlenme yaşayanların yüzdesi.
Kullanıcı tarafından algılanan kilitlenme oranı: Uygulamanızı etkin olarak kullanırken en az bir kilitlenme (kullanıcı tarafından algılanan kilitlenme) yaşayan günlük etkin kullanıcı sayınızın yüzdesi. Herhangi bir etkinlik görüntüleyen veya ön plan hizmeti yürüten uygulamalar etkin olarak kullanılıyor kabul edilir.
Çoklu kilitlenme oranı: Günlük etkin kullanıcı sayınız arasında en az iki kez kilitlenme yaşayanların yüzdesi.
Günlük etkin kullanıcı, uygulamanızı tek bir günde tek bir cihazda (birden fazla oturumda) kullanan benzersiz bir kullanıcıdır. Tek bir günde uygulamanızı birden çok cihazda kullanan bir kullanıcı, o günün etkin kullanıcı sayısına her cihaz için bir kullanıcı olarak 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 keşfedilebilirliğ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 belirlemiştir:
- Genel olarak kötü davranış eşiği: Tüm cihaz modellerinde, günlük etkin kullanıcı sayısı 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ı sayısının en az% 8'i tek bir cihaz modeliyle ilişkili, kullanıcı tarafından algılanan kilitlenme yaşamıştır.
Uygulamanız genel kötü davranış eşiğini aşarsa tüm cihazlarda bulunabilirliği azalabilir. Uygulamanız bazı cihazlarda cihaz başına kötü davranış eşiğini aşarsa bu cihazlardaki bulunabilirliği azalabilir ve mağaza girişinizde bir uyarı gösterilebilir.
Android vitals, uygulamanız aşırı kilitlenme gösterdiğinde Play Console'da sizi uyarabilir.
Google Play'in Android vitals verilerini nasıl topladığı hakkında bilgi edinmek için Play Console belgelerine bakın.
Kilitlenmeleri teşhis etme
Uygulamanızın kilitlenmeler bildirdiğini belirledikten sonraki adım, bu kilitlenmeleri teşhis etmektir. Kilitlenme sorunlarını çözmek zor olabilir. Ancak kilitlenmenin temel nedenini belirleyebilirseniz büyük olasılıkla soruna çözüm bulabilirsiniz.
Uygulamanızın kilitlenmesine neden olabilecek birçok durum vardır. Bazı nedenler (ör. boş değer veya boş dize kontrolü) açıktır ancak diğerleri (ör. bir API'ye geçersiz bağımsız değişkenler iletme veya karmaşık çok iş parçacıklı etkileşimler) daha az belirgindir.
Android'deki kilitlenmeler, programınızdaki iç içe yerleştirilmiş işlev çağrılarının kilitlenme anına kadarki adım sırasının anlık görüntüsü olan bir yığın izleme (stack trace) oluşturur. Kilitlenme yığın izlemelerini Android vitals'ta görüntüleyebilirsiniz.
Yığın izlemeyi okuma
Kilitlenmeyi düzeltmenin ilk adımı, kilitlenmenin gerçekleştiği yeri belirlemektir. Play Console'u kullanıyorsanız rapor ayrıntılarında bulunan yığın izleme (stack trace) veya Logcat aracının çıkışını kullanabilirsiniz. Yığın iziniz yoksa uygulamayı manuel olarak test ederek veya etkilenen kullanıcılara ulaşarak kilitlenmeyi yerel olarak yeniden oluşturmanız ve logcat'i kullanırken yeniden oluşturmanız gerekir.
Aşağıdaki izleme, Jetpack Compose kullanılarak yazılmış bir uygulamadaki kilitlenme örneğini gösterir:
--------- beginning of crash
AndroidRuntime: FATAL EXCEPTION: main
Process: com.android.developer.crashsample, PID: 3686
java.lang.NullPointerException
at com.android.developer.crashsample.ComposableSingletons$MainActivityKt.lambda$0(MainActivity.kt:27)
at androidx.compose.foundation.ClickableNode.handleUpEvent(Clickable.kt:958)
at androidx.compose.foundation.ClickableNode.onPointerEvent-H0pRuoY(Clickable.kt:895)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:446)
at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges(HitPathTracker.kt:181)
at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog(PointerInputEventProcessor.kt:118)
at androidx.compose.ui.platform.AndroidComposeView.dispatchTouchEvent(AndroidComposeView.android.kt:2650)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2969)
at android.app.Activity.dispatchTouchEvent(Activity.java:4683)
at android.os.Looper.loop(Looper.java:398)
at android.app.ActivityThread.main(ActivityThread.java:9569)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:918)
Yığın izleme, kilitlenmeyle ilgili hataları ayıklamak için kritik öneme sahip iki bilgi gösterir:
- İstisna türü.
- İstisnanın oluşturulduğu kod bölümü.
Oluşan istisnanın türü, genellikle neyin yanlış gittiği konusunda çok güçlü bir ipucu verir. IOException, OutOfMemoryError veya başka bir şey olup olmadığına bakın ve istisna sınıfıyla ilgili dokümanları bulun.
İstisnanın oluşturulduğu kaynak dosyanın sınıfı, yöntemi, dosyası ve satır numarası yığın izinin ikinci satırında gösterilir. Çağrılan her işlev için başka bir satırda önceki çağrı sitesi (yığın çerçevesi olarak adlandırılır) gösterilir.
Yığını inceleyip kodu inceleyerek yanlış değer ileten bir yer bulabilirsiniz. Kodunuz yığın izinde görünmüyorsa muhtemelen bir yerde eşzamansız bir işleme geçersiz bir parametre iletmişsinizdir. Genellikle yığın izinin her satırını inceleyerek, kullandığınız API sınıflarını bularak ve ilettiğiniz parametrelerin doğru olduğunu ve izin verilen bir yerden çağırdığınızı onaylayarak ne olduğunu anlayabilirsiniz.
C ve C++ kodu içeren uygulamalarda yığın izleri de benzer ş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 izlemelerinde sınıf ve işlev düzeyinde bilgiler görmüyorsanız yerel hata simgeselleştirme dosyası oluşturmanız ve bu dosyayı Google Play Console'a yüklemeniz gerekebilir. Daha fazla bilgi için Kilitlenme yığını izlerinin kodunu çözme başlıklı makaleyi inceleyin. Yerel kilitlenmeler hakkında genel bilgi için Yerel kilitlenmeleri teşhis etme başlıklı makaleyi inceleyin.
Kilitlenmeyi yeniden üretmeyle ilgili ipuçları
Yalnızca bir emülatörü başlatarak veya cihazınızı bilgisayarınıza bağlayarak sorunu tam olarak yeniden üretemeyebilirsiniz. Geliştirme ortamları genellikle bant genişliği, bellek ve depolama alanı gibi daha fazla kaynağa sahiptir. Hangi kaynağın kıt 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 korelasyon bulun.
Bellek hataları
OutOfMemoryError cihazınız varsa test etmek 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.
Ağ iletişimi istisnaları
Kullanıcılar mobil veya kablosuz ağ kapsamına sık sık girip çıktığından, uygulama ağındaki istisnalar genellikle hata olarak değil, beklenmedik şekilde gerçekleşen normal çalışma koşulları olarak değerlendirilmelidir.
UnknownHostException gibi bir ağ istisnasını 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, ağ gecikmesi veya her ikisini de seçerek emülatördeki ağ kalitesini düşürmektir. AVD yöneticisindeki Hız ve Gecikme ayarlarını kullanabilir veya aşağıdaki komut satırı örneğinde gösterildiği gibi, -netdelay ve -netspeed işaretleriyle emülatörü başlatabilirsiniz:
emulator -avd [your-avd-image] -netdelay 20000 -netspeed gsm
Bu örnek, tüm ağ isteklerinde 20 saniyelik bir gecikme ve 14,4 Kbps yükleme ve indirme hızı ayarlar. 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 başlıklı makaleyi inceleyin.
logcat ile okuma
Kilitlenmeyi yeniden oluşturabildiğinizde daha fazla bilgi edinmek için logcat gibi bir araç kullanabilirsiniz.
Logcat çıktısı, sistemden gelen diğer mesajlarla birlikte yazdırdığınız diğer günlük mesajlarını gösterir. Uygulamanız çalışırken yazdırılmaları CPU ve pil gücünü boşa harcadığı için eklediğiniz tüm Log
ifadelerini devre dışı bırakmayı unutmayın.
Null işaretçi istisnalarından kaynaklanan çökmeleri önleme
Boş işaretçi istisnaları (çalışma zamanı hata türü NullPointerException ile tanımlanır), genellikle yöntemlerini çağırarak veya üyelerine erişerek boş bir nesneye erişmeye çalıştığınızda oluşur. Google Play'de uygulama çökmelerinin en büyük nedeni boş işaretçi istisnalarıdır. Boş değerin amacı, nesnenin eksik olduğunu belirtmektir. Örneğin, nesne henüz oluşturulmamış veya atanmamıştır.
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 boş olmadığından emin olmanız gerekir. Nesne referansı boşsa (null), bu durumu iyi bir şekilde ele alın (örneğin, nesne referansında herhangi bir işlem yapmadan ö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 denetimleri yapmak istemediğinizden, null değer alabilirliği belirtmek için IDE'ye veya nesnenin türüne güvenebilirsiniz.
Kotlin
Kotlin'de nullability, tür sisteminin bir parçasıdır. Örneğin, bir değişkenin en başından itibaren null değer atanabilir veya null değer atanabilir olmayan olarak tanımlanması gerekir. Null değer atanabilir türler ? ile işaretlenir:
// non-null
var s: String = "Hello"
// null
var s: String? = "Hello"
Boş değer atanamayan değişkenlere boş değer atanamaz ve boş değer atanabilen değişkenlerin null değer alabilme durumu, boş değer atanmayan değişken olarak kullanılmadan önce kontrol edilmelidir.
Boş değerleri açıkça 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, boş değer atanabilir bir nesne için boş durumu ele aldığınızdan emin olun. Aksi takdirde uygulamanız beklenmedik durumlara girebilir. Uygulamanız NullPointerException ile artık kilitlenmiyorsa bu hataların varlığını bilemezsiniz.
Boş değerleri kontrol etmenin bazı yolları şunlardır:
ifkontrolval length = if(string != null) string.length else 0Akıllı yayın ve boşluk kontrolü nedeniyle Kotlin derleyicisi, dize değerinin boş olmadığını bilir. Bu nedenle, güvenli çağrı operatörüne gerek kalmadan referansı doğrudan kullanmanıza izin verir.
-
Bu operatör, "nesne boş değilse nesneyi döndür, aksi takdirde başka bir şey döndür" ifadesini belirtmenize olanak tanır.
val length = string?.length ?: 0
Kotlin'de NullPointerException almaya devam edebilirsiniz. En yaygın durumlar şunlardır:
- Açıkça
NullPointerExceptionatıyorsanız. - Null assertion
!!operatörünü kullanırken. Bu operatör, tüm değerleri null olmayan bir türe dönüştürür. Değer null iseNullPointerExceptionhatası verilir. - 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ş değer kontrolleri Java'daki kadar zorunlu değildir. Bu nedenle, boş olmayan değer garantisi Java'dakiyle aynıdır. Bir platform türü referansına eriştiğinizde Kotlin derleme zamanı hataları oluşturmaz ancak bu referanslar çalışma zamanı hatalarına yol açabilir. Kotlin belgelerindeki 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 platform değeri Kotlin değişkenine atandığında tür çıkarımına dayanır veya beklenen türü tanımlayabilirsiniz. Java'dan gelen bir referansın doğru nullability durumunu sağlamanın en iyi yolu, Java kodunuzda nullability ek açıklamaları (ör. @Nullable) kullanmaktır. Kotlin derleyicisi, bu referansları platform türleri olarak değil, gerçek null değer atanabilir veya atanabilir olmayan türler olarak temsil eder.
Java Jetpack API'leri gerektiği gibi @Nullable veya @NonNull ile açıklama eklenmiş olup Android 11 SDK'sında da benzer bir yaklaşım benimsenmiştir.
Bu SDK'dan gelen ve Kotlin'de kullanılan türler, doğru null değer alabilen veya alamayan türler olarak gösterilir.
Kotlin'in tür sistemi, NullPointerException kilitlenmelerini önemli ölçüde azaltır. Örneğin, Google Home uygulaması yeni özellik geliştirme sürecini Kotlin'e taşıdığı yıl içinde boş işaretçi istisnalarından kaynaklanan kilitlenmelerde% 30 azalma gördü.