4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Androidアプリのクラッシュ対策にやったこと

Posted at

先日GooglePlayにAndroidアプリをリリースしたのですが、1週間でおよそ20件のクラッシュレポートが上がってきました
レポートは20件ほどありましたが、およそ2件の対策をしましたのでメモしておきます

解決の保証はできませんので悪しからず

やったこと

TextInputEditTextの実装修正

  • noExcludeDescendantsを追加
  • hint属性はTextinputLayoutにつける
  • setOnEditorActionListenerを削除

MapViewのnullチェックを追加

MapActivity.kt
override fun onDestroy() {
        try { 
            binder?.mapView?.onDestroy()
        } catch (e : NullPointerException) {
            // Send report exception..
        }
        super.onDestroy()
    }

クラッシュレポート

GooglePlayConsoleのサイドバー「障害およびANR」より、クラッシュレポートが確認できます
こんな感じで、かなりの数が報告されています

スクリーンショット 2021-03-27 15.58.54.png

たくさん出ていて目眩がしそうですが、対処できそうなのはおおよそ2種類に収まりました

TextInputEditText関連

setHint周りの例外

java.lang.NullPointerException: 
  at android.widget.Editor.stopTextActionMode (Editor.java:2687)
  at android.widget.Editor.prepareCursorControllers (Editor.java:822)
  at android.widget.TextView.nullLayouts (TextView.java:9153)
  at android.widget.TextView.checkForRelayout (TextView.java:10101)
  at android.widget.TextView.setHintInternal (TextView.java:6534)
  at android.widget.TextView.setHint (TextView.java:6523)
  at com.google.android.material.textfield.TextInputLayout.dispatchProvideAutofillStructure (TextInputLayout.java:1280)
  at android.view.ViewGroup.dispatchProvideAutofillStructure (ViewGroup.java:3774)
  at android.view.ViewGroup.dispatchProvideAutofillStructure (ViewGroup.java:3774)
  at android.app.assist.AssistStructure$WindowNode.<init> (AssistStructure.java:517)
  at android.app.assist.AssistStructure.<init> (AssistStructure.java:2047)
  at android.app.ActivityThread.handleRequestAssistContextExtras (ActivityThread.java:3386)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1899)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loop (Looper.java:214)
  at android.app.ActivityThread.main (ActivityThread.java:7050)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:965)

Samsung端末固有のフォーム長押し問題かと思いましたが、微妙に違うように見えます
(performLongClickとかが絡んでいない)
とはいえ今回クラッシュしたのもSamsung系端末でした

AssistStructure$WindowNodeの例外

java.lang.NullPointerException: 
  at android.app.assist.AssistStructure$WindowNode.<init> (AssistStructure.java:484)
  at android.app.assist.AssistStructure.<init> (AssistStructure.java:1908)
  at android.app.ActivityThread.handleRequestAssistContextExtras (ActivityThread.java:3040)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1812)
  at android.os.Handler.dispatchMessage (Handler.java:105)
  at android.os.Looper.loop (Looper.java:251)
  at android.app.ActivityThread.main (ActivityThread.java:6563)
  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:767)

こちらはAndroid8系の問題のようで、実際Android8の3デバイスで発生していました
リンク先の記事を追うとわかりますが、こちらもTextInputEditText関連です

処置

こちらのブログ記事や上記StackOverFlowでも言及されていますが、

  • フォームの自動入力Autofillを無効化(noExcludeDescendants)
  • hintを挿入する位置を TextInputEditText -> TextInputLayout に入れ替える

を実施しました。入力フォーム周りはちょっと不安定なんですかねー

加えて、あまり関係ないかもですがsetOnEditorActionListenerを無効化しました
ユーザ情報入力フォームが今回問題だったのですが、キーボードのエンターボタンが使えなくともUXに大きな影響はないと判断しました

MapView関連

java.lang.NullPointerException: 
  at com.google.maps.api.android.lib6.impl.cy.f (com.google.android.gms.dynamite_mapsdynamite@210214081@21.02.14 (120400-0):1)
  at com.google.android.gms.maps.internal.q.aX (com.google.android.gms.dynamite_mapsdynamite@210214081@21.02.14 (120400-0):12)
  at dt.onTransact (com.google.android.gms.dynamite_mapsdynamite@210214081@21.02.14 (120400-0):4)
  at android.os.Binder.transact (Binder.java:914)
  at com.google.android.gms.internal.maps.zza.zzb (Unknown Source:20)
  at com.google.android.gms.maps.internal.zzk.onDestroy (Unknown Source:26)
  at com.google.android.gms.maps.MapView$zza.onDestroy (Unknown Source:34)
  at com.google.android.gms.dynamic.DeferredLifecycleHelper.onDestroy (Unknown Source:71)
  at com.google.android.gms.maps.MapView.onDestroy (Unknown Source:40)
  at com.example.android.fragment.ExmapleFragment.onLowMemory (LastTripFragment.kt:231)
  at androidx.fragment.app.Fragment.performLowMemory (Fragment.java:3069)
  at androidx.fragment.app.FragmentManager.dispatchLowMemory (FragmentManager.java:3152)
  at androidx.fragment.app.Fragment.performLowMemory (Fragment.java:3070)
  at androidx.fragment.app.FragmentManager.dispatchLowMemory (FragmentManager.java:3152)
  at androidx.fragment.app.FragmentController.dispatchLowMemory (FragmentController.java:379)
  at androidx.fragment.app.FragmentActivity.onLowMemory (FragmentActivity.java:332)
  at android.app.ActivityThread.handleLowMemory (ActivityThread.java:6002)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1921)
  at android.os.Handler.dispatchMessage (Handler.java:107)
  at android.os.Looper.loop (Looper.java:359)
  at android.app.ActivityThread.main (ActivityThread.java:7418)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:492)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:935)

GoogleMapを表示するMapViewと、それに関連するFragmentのライフサイクル系で多発しております
この例ではonLowMemoryですが、onStart, onPause, onDestoryなどでも同様の例外が出ておりました

MapViewの公式ドキュメントで言われているように、ライフサイクルとMapViewを紐付けしていましたが、そのときにMapオブジェクトが存在しないようです

API を完全なインタラクティブ モードで使用している場合は、MapView クラスで、アクティビティ ライフサイクル メソッド(onCreate()、onStart()、onResume()、onPause()、onStop()、onDestroy()、onSaveInstanceState()、onLowMemory())を MapView クラスの対応するメソッドに転送する必要があります。

ExampleFragment.kt
override fun onDestroy() {
    binder.mapView.onDestroy()  // ←ここでmapViewがnullになってる
    super.onDestroy()
}

処置

こちらに関しては明確な解決策が見つけられず、とりあえず例外をキャッチすることにしました↓

ExampleFragment.kt
override fun onDestroy() {
        try {
            binder.mapView.onDestroy()
        } catch(e: Exception) {
            /* do nothing */
        }
        super.onDestroy()
    }

これでまずは様子を見てみます

tgkill

backtrace:
  #00  pc 000000000006b970  /system/lib64/libc.so (tgkill+8)
  #00  pc 0000000000068df4  /system/lib64/libc.so (pthread_kill+64)
  #00  pc 0000000000024290  /system/lib64/libc.so (raise+24)
  #00  pc 000000000001ccac  /system/lib64/libc.so (abort+52)
  #00  pc 000000000042cca8  /system/lib64/libart.so (art::Runtime::Abort()+352)
  #00  pc 00000000000e4c24  /system/lib64/libart.so (art::LogMessage::~LogMessage()+1204)
  #00  pc 000000000024ab24  /system/lib64/libart.so (art::IndirectReferenceTable::Add(unsigned int, art::mirror::Object*)+308)
  #00  pc 0000000000324684  /system/lib64/libart.so (art::JNI::FindClass(_JNIEnv*, char const*)+2824)

クラッシュレポートの中でもひときわ異彩を放っているのがこちらで、linuxのシステムコールかな?と思っています
これに関してはスタックトレースがないので原因特定が難しく、またかなり限られたデバイスでしか出ていないので対応を見送っています
(Android7系の古めのデバイスで出るようです)

まとめ

ということで、Androidのクラッシュレポートに対して自分なりに対策を入れてみました
入力フォームやマップあたりがやや不安定な印象ではありますね
一旦これで様子をみてみようかと思います

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?