アプリが落ちた!実例で見るバグ発生からの原因調査方法(Android/Java)


概要

はじめてのAndroidプログラミング第3版(金田浩明 著 SB Creative)の課題をKotlinではなくJavaで書いている。

第6章「体型記録アプリを作ろう」のプログラムが最終形まで組み終わって動作確認を始めたところエラーが発生したため、その原因調査方法を解説する。

対象読者

実務に向けたプログラミングの学習をしている人。または、興味がある人。

症状

身長入力ボタンを押すと、身長入力アクティビティへ移動するはずだが、エラーが発生し、アプリが終了してしまう。

エラーでアプリが落ちる

原因調査手順

まずはログを見る

「ボタンを押した時だから・・・」と当たりをつけてプログラムを見直すのも良いが、もっと効率の良い原因の調べ方がある。

Android Studio下部の「ツールウィンドウ」
デフォルトでは「Build」が選択されているところを「Run」に切り替える。

すると発生したエラーの詳細が記載されている(赤字部分)

D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.mysize, PID: 27237
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.mysize/com.example.mysize.HeightActivity}: android.content.res.Resources$NotFoundException: String resource ID #0xa0
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2778)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
        at android.app.ActivityThread.-wrap11(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
     Caused by: android.content.res.Resources$NotFoundException: String resource ID #0xa0
        at android.content.res.Resources.getText(Resources.java:339)
        at android.widget.TextView.setText(TextView.java:5496)
        at com.example.mysize.HeightActivity.onCreate(HeightActivity.java:43)
        at android.app.Activity.performCreate(Activity.java:7009)
        at android.app.Activity.performCreate(Activity.java:7000)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2731)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856) 
        at android.app.ActivityThread.-wrap11(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6494) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 
I/zygote: Do partial code cache collection, code=121KB, data=96KB
    After code cache collection, code=121KB, data=96KB
    Increasing code cache capacity to 512KB
Application terminated.

長い英文が続き圧倒されるかもしれないが、読む必要がある部分は限られているので安心してほしい。基本的には各ブロックの始まりを見ればいい。

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.mysize/com.example.mysize.HeightActivity}: android.content.res.Resources$NotFoundException: String resource ID #0xa0

多くの場合、ここには最終結果(概要)→過程(詳細)の順に書かれる。
「java.lang.RuntimeException」→「例外が起きた」
「Unable to start activity ComponentInfo」→「アクティビティを開始できない」
「android.content.res.Resources$NotFoundException: String resource ID #0xa0」→「IDが#0xa0のStringリソースが見つからない」

最初を見れば概要がわかり、読み進めていくほど詳細がわかるようになっている。
知りたいところまで読み進めていけばいいということだ。

今回の場合、どうやら「リソースがない」か「リソースとして指定したIDが間違っている」かのどちらかのように見える。

原因の見当が付いたら、次は該当箇所を調べる。
先程の英文の中をよく見ると、青いリンクの文字が書かれている箇所があるのに気づくと思う。

この部分が自分で書いたコード上のエラーの発生個所だ。
親切にリンクになっているため、クリックすれば該当箇所にジャンプできる。

今回の場合は「HightActivity.java」の「43行目」になる。
この行が正しいのかを確認すれば良い。

しかし、どうやら「対象のIDのリソースは定義されているし、IDもあっている」ようだ。

それでも原因がわからなければGoogle検索

こういう時は、エラーメッセージでGoogle検索をする。

なるべく概要ではなく、1番奥のメッセージを使った方がいい。
今回適切と思われるエラーメッセージ(検索キーワード)は「Resources$NotFoundException」だ。

すると、1番上にこんなサイトが見つかる。
「Android 見つけにくいResources$NotFoundExceptionの原因」

ん? あ! setTextなのに、intの数値を指定しているからじゃーん。はっず🤣

final int height = pref.getInt("HEIGHT", 160);
((TextView)findViewById(R.id.height)).setText(height);

よって、下記のように変更したら動作するようになった。

final int height = pref.getInt("HEIGHT", 160);
((TextView)findViewById(R.id.height)).setText(String.valueOf(height));

尚、他にも同じ個所があったため、そちらも併せて直している。
この確認を水平展開というとか言わないとか。