この記事はAndroid Advent Calendar 2017 - Adventarの16日目です。
知見にならないものもありますが、Android開発を続けてきた中で特に印象が残っているバグについて記録しておこうと思います。本当は10個くらい書きたかったのですが案外よく覚えている不具合というのは少ないもので、今回思い出したのは3つだけでした。
メインスレッド上でアニメーションを行っている
とあるアプリの起動がとても遅いということで、原因を調べた際に遭遇したものです。開発経験のある方ほど表題の意味が理解できないと思います。
それは確かこのようなコードでした。
var tick = 100 //数字は超適当
while (tick == 20000) {
view.x = tick.toFloat() / 100
tick += 1
}
こういうものがActivityのonCreate()で実行されていたため、描画前にメインスレッドがブロックされていたのです。他にもツッコミどころはあるのですが、まずAndroidではViewのアニメーションを簡単に実行するする方法がたくさん用意されています。フレームワークのAPIを使えば、安全にライフサイクルに沿った描画を行ってくれます(きっと)。アニメーションに限らず、自力で実装する前には標準APIで同じような機能がないか公式リファレンスを読んでおきましょう。この実装はなかなかにびっくりしたので、未だに印象に残っています。
Rippleアニメーションが止まる
LollipopからはMaterial Designを取り入れたコンポーネントが増えました。
ほとんどのユーザーにとって、クリッカブル領域のタップを視覚的にフィードバックさせるRippleエフェクトはお馴染みのものでしょう。
あるアプリでそのエフェクトが途中でフリーズしてしまう、という不思議な現象が発生していました。
原因はとあるデバッグ用外部ライブラリのスクリーンショット撮影機能でした。詳細は追っていないのですが描画への割り込みをしていたのでしょうか?
この原因を突き止めた時には、まず問題のプロジェクトでボタンのみのシンプルなActivityを作成し、特定のActivityのみで発生する問題ではないことを確認しました。その後、エフェクトが正常に動作する別プロジェクトを作成し、gradleの設定などを問題プロジェクトのものに近付けていきました。結局特定のライブラリのセットアップを実行するか否かで挙動が変わることがわかったため原因が特定できました。
標準の環境では起きえないけど原因が不明なバグというのはしばしばあるもので、その時には同じように正常なものと不具合が起きる最小構成のものを用意して差分を確認するようにしています。これを説明すると嫌な顔をされることもあるのですが問題の整理もできたりついでに別の問題も発見できたり、何より確実なのでしっかりやっていきたいです。
TransactionTooLargeException
開発者の方であれば、一度は見たことがあるのではないでしょうか(遭遇したことがないという方は、しっかりガイドラインに沿った設計を心がけていると思います)。MarshmallowからはonSaveInstanceState()で保存可能なデータに上限が設定されるようになったため、何も考えずにデータを保存していると死にます。
当時携わっていたプロジェクトでもこの問題に遭遇しました。該当するActivityの実装はこのようなものでした。
override fun onSaveInstanceState(outState: Bundle?, outPersistentState: PersistableBundle?) {
outState?.putString("key", "短い文字列")
super.onSaveInstanceState(outState, outPersistentState)
}
おや?文字列しか保存してませんね…。
そう、犯人はFragmentのonSaveInstanceStateです。
Activityでデータを保存していないように見えたとしても、その上で表示されているFragmentで巨大なデータを保存していればこの例外に引っかかってしまいます。例外が発生するのはActivityなのでやっかいです。こういう状況では混乱しますが、落ち着いてフレームワークの実装を読むことでクラッシュの原因がわかると思います。
以上3つだけですが、簡単にまとめました。
みなさま、今年もAndroidと共に良いクリスマスをお過ごしください。