Android13でイルーナ戦記がクラッシュする件

Android13でイルーナ戦記がクラッシュする件

※ 今回の記事は専門的な内容を多分に含みます。

Androidでイルーナ戦記をプレイされている皆さん、私もその一人ですが、不具合報告にある通りAndroid13では頻繁にアプリがクラッシュします。

https://android.iruna.jp/information

これが非常に厄介で、特に前触れもなくアプリがクラッシュするため、ボーダーレイド等のランキングイベント等で悲しい思いをします( ノД`)シクシク…

アプリがクラッシュしたために街に戻されることも多々あり、方舟等の課金アイテムも無駄になってしまっているので運営には補償してもらいたいくらいですが…

愚痴はここまでにして、何故Android12まで問題なかったのにAndroid13から不具合が発生するのか、外部から観測できる範囲で調査してみました。

目次

  1. ログの確認
  2. 該当ソースコードの確認
  3. MotionEventについて
  4. 再現確認
  5. まとめ

ログの確認

まずはアプリクラッシュ時のログから有用な情報が得られないかを探ってみます。

複数回分のクラッシュ時のログを確認したところ、共通の出力が確認されました。

02-05 21:10:31.100 F/Input   (26033): Input.cpp:576] getHistoricalRawPointerCoords: Invalid array index 1 for MotionEvent { action=MOVE, id[0]=0, x[0]=243.732, y[0]=681, historySize=1, eventTime=136523609785332, downTime=136523589646000, deviceId=6, source=TOUCHSCREEN, displayId=0, eventId=265601249}

########################################## 中略 ##########################################

02-05 21:10:31.230 F/sobimo.iruna_gc(26033): runtime.cc:691]   native: #07 pc 00016648  /system/lib64/libbase.so (android::base::LogMessage::~LogMessage+356) (BuildId: ef369bfbad96b532c6d8e0b144a68b96)
02-05 21:10:31.230 F/sobimo.iruna_gc(26033): runtime.cc:691]   native: #08 pc 00029a38  /system/lib64/libinput.so (android::MotionEvent::getHistoricalRawPointerCoords const+672) (BuildId: 81acf8f1b067f9cf61b5e58535e6282f)
02-05 21:10:31.230 F/sobimo.iruna_gc(26033): runtime.cc:691]   native: #09 pc 00029728  /system/lib64/libinput.so (android::MotionEvent::getAxisValue const+52) (BuildId: 81acf8f1b067f9cf61b5e58535e6282f)
02-05 21:10:31.230 F/sobimo.iruna_gc(26033): runtime.cc:691]   at android.view.MotionEvent.nativeGetAxisValue(Native method)
02-05 21:10:31.230 F/sobimo.iruna_gc(26033): runtime.cc:691]   at android.view.MotionEvent.getY(MotionEvent.java:2482)
02-05 21:10:31.230 F/sobimo.iruna_gc(26033): runtime.cc:691]   at f0.f.a(unavailable:94)
02-05 21:10:31.230 F/sobimo.iruna_gc(26033): runtime.cc:691]   at u0.j.g(unavailable:0)
02-05 21:10:31.230 F/sobimo.iruna_gc(26033): runtime.cc:691]   at com.asobimo.iruna_alpha.c.o(unavailable:11)
02-05 21:10:31.230 F/sobimo.iruna_gc(26033): runtime.cc:691]   - locked <0x0093501d> (a com.asobimo.iruna_alpha.c)
02-05 21:10:31.230 F/sobimo.iruna_gc(26033): runtime.cc:691]   at a0.j$a.run(unavailable:6)
02-05 21:10:31.230 F/sobimo.iruna_gc(26033): runtime.cc:691]   at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1502)
02-05 21:10:31.230 F/sobimo.iruna_gc(26033): runtime.cc:691]   at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1272)

注目すべきは一行目の内容です。

出力元がInput.cppとなっていることから、イルーナ戦記側ではなく共有ライブラリ側がログを出力していることがわかります。

該当ソースコードの確認

Androidはオープンソースですので該当箇所のソースコードの確認が可能です。

ログのスタックトレースをもとに呼び出し順に並べてみました。

また、Android12との差異を確認するためそれぞれの内容を横並びにしています。

https://android.googlesource.com/platform/frameworks/native/+/refs/heads/android12-release/libs/input/Input.cpp https://android.googlesource.com/platform/frameworks/native/+/refs/heads/android12-release/include/android/input.h https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android12-release/core/jni/android_view_MotionEvent.cpp https://android.googlesource.com/platform/frameworks/native/+/refs/heads/android13-release/libs/input/Input.cpp https://android.googlesource.com/platform/frameworks/native/+/refs/heads/android13-release/include/android/input.h https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android13-release/core/jni/android_view_MotionEvent.cpp

Android13ではInput.cpp575行目のアサーションに失敗した場合、576行目にてログレベルFATALでログ出力を行っています。

ログの内容と合致していますね。

FATALなログ出力はアプリのクラッシュを引き起こします。

https://source.android.com/docs/core/tests/debug/native-crash?hl=ja#abort

Android12では該当のアサーションを行っていないためクラッシュは発生しないと推測されます。

MotionEventについて

共有ライブラリまで深堀しましたが、一旦Java側の処理を確認します。

ログのスタックトレースから、イルーナ戦記側のMotionEvent.getYの呼び出しが起点となってネイティブメソッドが呼び出されていることがわかります。

そもそもMotionEventとは、マウス操作やタッチ操作のイベントを取得するために使用されるオブジェクトでMotionEvent.getYはイベントが発生したY座標を取得するためのメソッドです。

詳細な説明は割愛しますが、getYは引数としてpointerIndexを渡すことができます。

クラッシュの原因の一つとして引数の不正が考えられますが、バリデーションによりIllegalArgumentException(Java側で捕捉可能)が発生するため、今回のクラッシュの原因からは除外されます。

引数不正時のログ

W  java.lang.IllegalArgumentException: invalid pointerIndex 1 for MotionEvent { action=UP, id[0]=2, x[0]=285, y[0]=787, eventTime=435946212141000, downTime=435946089783000, deviceId=6, source=TOUCHSCREEN, displayId=0, eventId=297823237}
W  	at android.view.MotionEvent.nativeGetAxisValue(Native Method)
W  	at android.view.MotionEvent.getX(MotionEvent.java:2460)

また、MotionEventの特徴としてオブジェクトが再利用(Recycle)されます。

この点を考慮して改めてInput.cppの処理を確認すると、567行目および570行目のアサーションをクリアしているのにも関わらず575行目のアサーションに失敗していることから、イベントの処理中に該当のオブジェクトが再利用された可能性が考えられます。

再現確認

仮説として挙げたイベント処理中のオブジェクト再利用について、実際にAndroidアプリを作成して再現確認を行ってみます。

onTouchEventで捕捉したMotionEventを別スレッド内で処理しています。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Tap!")
        }
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        val mWorker = Thread {
            try {
                val pointerCnt: Int = event.pointerCount
                Thread.sleep(500)
                for (i in 0 until pointerCnt) {
                    println(event.getX(i))
                    println(event.getY(i))
                }
            } catch (e: IllegalArgumentException) {
                e.printStackTrace()
            }
        }
        mWorker.start()
        return false
    }
}

別スレッドで処理中のMotionEventが再利用されれば、アプリのクラッシュが再現するはずです。

Android13とAndroid11の実機でそれぞれアプリを起動し、画面をひらすらタップし続けるとAndroid13のみアプリのクラッシュが発生し、イルーナ戦記と同様のログが出力されました。

Input.cpp:576] getHistoricalRawPointerCoords: Invalid array index 12 for MotionEvent { action=MOVE, id[0]=1, x[0]=667.5, y[0]=1142.93, id[1]=2, x[1]=476.072, y[1]=648.072, id[2]=3, x[2]=407.857, y[2]=1743.79, id[3]=4, x[3]=171.786, y[3]=1878.86, id[4]=5, x[4]=785.143, y[4]=1636.5, id[5]=6, x[5]=322.571, y[5]=2280.36, id[6]=7, x[6]=261.214, y[6]=1402.43, id[7]=8, x[7]=305.072, y[7]=513.001, pointerCount=8, historySize=1, eventTime=440007691861448, downTime=439973561154000, deviceId=6, source=TOUCHSCREEN, displayId=0, eventId=980161488}

まとめ

検証結果より、Android13でクラッシュが頻発する原因は処理中のMotionEventが再利用されることである可能性が高いです。

一方でイルーナ戦記のアプリ自体の実装を確認しているわけでないので、意図せずMotionEventが再利用されている原因がアプリの実装にあるのか、Android側の不具合なのかは不明です。

運営側の調査が難航していることを考えると、後者の可能性もありますね…

おそらく、Android12以前でも意図しないMotionEventの再利用は発生していたと思われますが、Input.cppのアサーションの追加により問題が顕在化した形と思われます。

参考ですが該当のアサーションは下記のコミットにて追加されているようです。

https://android.googlesource.com/platform/frameworks/native/+/4ded0b06035d9e69ef88c5ccd0a794eb3104ad5c%5E%21/#F1

Android14で改善されるかについてですが、最新(2024/3/11現在)のソースコードを確認する限りアサーションは残されたままのため、改善されない可能性も高いと思われます😢

マニアックな内容なのでここまで読んでくださった方は少ないと思いますが、ここまでお付き合いいただいた方、どうもありがとうございます!!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です