こんにちは。App SDK Unitでエンジニアをやっている大島です。Reproでは多くのプラットフォームに対応したSDKを提供しており、それらの開発・保守を担当しています。
今回は、プラットフォームの1つであるFlutterにて発生したクラッシュと、その原因にたどり着いて対策するまでの事例を紹介します。
始まりはお客様からの「アプリが時々クラッシュする」というお問い合わせでした。詳しくヒアリングと調査を進めると、ある特定の条件下で発生することが分かってきました。
アプリ起動と戻るボタンを連続で繰り返すとクラッシュする
Flutter Androidの環境下で、「アプリ起動→OSの戻るボタンをタップ→ランチャーに戻る」を連続で繰り返すと、稀にアプリがクラッシュするという報告を受けました。
エラーログを確認すると、 onDetachedFromActivity 内で NullPointerException が投げられていました。社内での検証の結果、特定のタイミングで操作を繰り返すことで事象を再現できました。
調査して判明したこと:想定外のライフサイクル
原因を調査したところ、Android Viewがウィンドウから取り除かれる際のリソース解放処理において、すでにリソースが解放されている(あるいは未生成の)オブジェクトにアクセスしようとしていることが分かりました。
問題となった当時のソースコード(抜粋)は以下の通りです。
@Override public void onDetachedFromActivity() { // ここで channel が null になっているケースがある channel.setMethodCallHandler(null); channel = null; }
通常、この channel はアプリ起動時の onAttachedToEngine でインスタンス化され、戻るボタンをタップした時に onDetachedFromActivity が呼ばれる時点ではインスタンスが存在していることが期待されます。しかし、高速な起動・終了操作によって、Flutterエンジンのアタッチ/デタッチとActivityのライフサイクルが反転、あるいは重複して走るような「想定外の挙動」が起きていました。
これはRepro Packageのバグといえばバグなのですが、Flutterのライフサイクルを見てもここで channel がnullになることは想定しづらいものでした。
2021年から報告されていたIssue
なぜ channel がnullになるのか、Flutterを調査してみると同様のIssueがすでに登録されていました。
https://github.com/flutter/flutter/issues/76526
2021年に報告されて以来、今日までCloseされず、根本的な修正が行われていない根深い問題でした。当初は「フレームワーク側の修正を待つべきか」という議論もありましたが、複数のお客様からお問い合わせをいただいている現状を鑑み、SDK側でのワークアラウンド(回避策)の実施を決定しました。
修正にあたっては、多くの開発者に利用されている他社SDKの事例も参考にしました。 結論として、channel が常に存在するという前提を捨て、以下のような「防衛的なnullチェック」を追加しました。
@Override public void onDetachedFromActivity() { if (channel != null) { channel.setMethodCallHandler(null); channel = null; } }
この修正をリリースして以降、当該のクラッシュ報告は収まりました。プラットフォーム側の挙動を完全に制御することは難しくても、SDK側で不整合を吸収することで、アプリ全体の安定性を守ることができました。
最後に
今回の件を通して、改めて「プラットフォームの境界線」で起きる問題の難しさと面白さを実感しました。
プラットフォームを使っていることは、そのプラットフォームに挙動を依存することになりますが、依存しているからといって修正されることを待つのではなく、時には完璧な方法でなくても修正をする必要があるのだと思いました。
App SDK Unitでは、安心してSDKを導入してもらえることを責務として日々の業務に取り組んでいます。
WE ARE HIRING!
Reproではエンジニアを募集しています。App SDK UnitではFlutterの他にReact Native/Unity/Cordova/Cocos2d-x向けのSDKを提供しています。iOS/Androidに加えて多様な言語で開発をすることに興味がありましたら、ぜひお話させてください。ご連絡をお待ちしております。
