Kilitlenmeler

İşlenmemiş bir istisna veya sinyalden kaynaklanan beklenmedik bir çıkış olduğunda Android uygulaması kilitlenir. Java veya Kotlin kullanılarak yazılan bir uygulama, Throwable sınıfıyla temsil edilen işlenmemiş bir istisna oluşturduğunda çöker. Makine kodu veya C++ kullanılarak yazılan bir uygulama, yürütme sırasında SIGSEGV gibi işlenmemiş bir sinyal olduğunda kilitlenir.

Bir uygulama kilitlendiğinde, Android uygulama işlemini 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örüntüler.

Android cihazdaki uygulama kilitlenmesi

Şekil 1. Android cihazdaki 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 de dahil olmak üzere herhangi bir uygulama bileşeni uygulamanın kilitlenmesine neden olabilir. Bu kilitlenmeler uygulamanızla aktif bir şekilde 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 kılavuzu kullanabilirsiniz.

Sorunu tespit edin

Kullanıcılarınızın uygulamanızı kullanırken kilitlenmelerle karşılaştığını her zaman anlayamayabilirsiniz. Uygulamanızı daha önce 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ız arasında, 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ı aktif olarak kullandıkları sırada en az bir kilitlenme yaşayanların yüzdesidir (kullanıcı tarafından algılanan kilitlenme). Herhangi bir etkinlik görüntüleyen veya ön plan hizmeti yürüten uygulamalar etkin kullanımda olarak kabul edilir.

  • Birden fazla 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ı tek bir gün içinde tek bir cihazda ve potansiyel olarak birden fazla oturumda kullanan tekil bir kullanıcıdır. Bir kullanıcı aynı 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 bu, 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 metrikte iki kötü davranış eşiği belirlemiştir:

  • 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: Tek bir cihaz modeli için günlük etkin kullanıcıların en az% 8'i, 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 bulunabilirliği azalabilir. Uygulamanız cihaz başına kötü davranış eşiğini bazı cihazlarda aşarsa bu cihazlarda bulunabilirliği azalabilir 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 belgelerine bakın.

Kilitlenmeleri teşhis edin

Uygulamanızın kilitlenmeler bildirdiğini belirledikten bir sonraki adım bunları teşhis etmektir. Kilitlenmeleri çözmek zor olabilir. Ancak, kilitlenmenin temel nedenini belirleyebilirseniz büyük olasılıkla bir çözüm bulabilirsiniz.

Uygulamanızda kilitlenmeye neden olabilecek pek çok durum vardır. Boş değer veya boş dize olup olmadığını kontrol etmek gibi bazı nedenler barizdir. Diğerleri ise bir API'ye geçersiz bağımsız değişkenler ve hatta karmaşık, çok iş parçacıklı etkileşimler gibi daha belirgindir.

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. Kilitlenme yığın izlemelerini Android vitals'da görüntüleyebilirsiniz.

Yığın izleme (stack trace) nasıl okunur?

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 kilitlenmeyi yerel olarak yeniden oluşturup logcat'i kullanarak 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 nelerin ters 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 gidip kodu inceleyerek yanlış değer ileten bir yer görebilirsiniz. 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 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ı teyit ederek ne olduğunu anlayabilirsiniz.

C ve C++ kodu olan uygulamaların yığın izlemeleri (stack trace) 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üzeyi bilgileri görmüyorsanız yerel hata ayıklama simgeleri dosyası oluşturup bu dosyayı Google Play Console'a yüklemeniz gerekebilir. Daha fazla bilgi için Çökmeyle sonuçlanan yığın izlemenin kodunu 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.

Kilitlenmenin yeniden oluşturulması için ipuçları

Yalnızca 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 tercih edilir. 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 korelasyon bulun.

Bellek hataları

OutOfMemoryError kullanıyorsanız test yapmak 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österilmiştir.

AVD yöneticisindeki bellek ayarı

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 veya kapsam dışında olduklarından, uygulama ağı istisnaları genellikle hata olarak değerlendirilmemelidir. Bunun yerine, beklenmedik bir şekilde gerçekleşen normal çalışma koşulları olarak değerlendirilir.

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ğın kalitesini düşürmektir. 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 yükleme ve indirme hızı ayarlanmaktadır. Emülatör için 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şturmak için gereken adımları edindikten sonra, daha fazla bilgi edinmek için logcat gibi bir araç kullanabilirsiniz.

Logcat çıktısı, sistemdeki diğer günlük mesajlarıyla birlikte yazdırdığınız diğer günlük mesajlarını gösterir. Eklediğiniz Log ifadelerinin yazdırılması, uygulamanız çalışırken CPU ve pil kaybına neden olacağından bu ifadeleri kapatmayı unutmayın.

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ş 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 boş olmadığından emin olmanız gerekir. Nesne referansı boşsa bu durumu iyi bir şekilde 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 denetimler kullanmak istemeyeceğinizden, null değerini 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.

Derleme zamanı uyarıları

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ğer atanabilir bir nesne beklemenizi ister:

Boş işaretçi istisna uyarısı

Bu boş denetimler, boş olabileceğini bildiğiniz nesneler içindir. @NonNull nesnesindeki istisna, kodunuzda ele alınması gereken bir hatanın göstergesidir.

Derleme zamanı hataları

Boş değer atanabilirliğinin anlamlı olması gerektiğinden, null için derleme zamanı kontrolü yapılabilmesi 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 aktaran türleri tercih etmelisiniz.

Kotlin

Kotlin'de nullability, tür sisteminin bir parçasıdır. Örneğin, bir değişkenin baştan itibaren null veya null özellikli değil şeklinde bildirilmesi 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 özellikli değişkenlerin, null olarak kullanılmadan önce null özellikli olup olmadıklarının kontrol edilmesi gerekir.

Açık bir şekilde null kontrolü yapmak istemiyorsanız ?. güvenli arama 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 nesne için null değerini ele aldığınızdan emin olun. Aksi takdirde uygulamanız beklenmedik durumlara geçebilir. Uygulamanız artık NullPointerException ile kilitlenmezse bu hataların olup olmadığını bilemezsiniz.

Boş olup olmadığını kontrol etmek için aşağıdaki yöntemlerden yararlanabilirsiniz:

  • if kontrol

    val length = if(string != null) string.length else 0
    

    Akıllı yayın ve null denetimi sayesinde Kotlin derleyicisi, dize değerinin null olmadığını bilir ve bu sayede, güvenli çağrı operatörüne gerek olmadan referansı doğrudan kullanmanıza olanak tanır.

  • ?: Elvis operatörü

    Bu operatör, "nesne null değilse nesneyi döndürün; aksi takdirde başka bir şey döndürün" ifadesini belirtmenize olanak tanır.

    val length = string?.length ?: 0
    

Yine de Kotlin'de NullPointerException kazanabilirsiniz. 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 ise NullPointerException 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ığından boş olmayan garanti, Java'dakiyle 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 belgelerinde yer alan 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

Bir Kotlin değişkenine platform değeri atandığında Kotlin tür çıkarımını kullanır veya ne tür bekleneceğini tanımlayabilirsiniz. Java'dan gelen bir referansın null değer durumunun doğru olmasını sağlamanın en iyi yolu, Java kodunuzda null değer 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çıklaması eklenmiştir. Android 11 SDK'sında da benzer bir yaklaşım uygulanmıştır. Bu SDK'dan gelen ve Kotlin'de kullanılan türler, doğru veya null yapılamayan türler olarak temsil edilir.

Kotlin'in tür sistemi nedeniyle uygulamaların NullPointerException kilitlenmelerinde büyük bir düşüşe sahip olduğunu gördük. Ö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ü.