APIレベルを更新したらパフォーマンスが低下した話

この記事はトリスタinsideで書かれた記事です。
現在トリスタinsideはBOOK☆WALKER Tech Blogに統合されました。

f:id:bookwalker_developers:20210930191918p:plain 年末(執筆時20191225)ということで混みいったタスクは来年に先送りということもあり、時間も出来たので今この記事を書いてます。今回もAndroidの話で、targetSdkVersion(以下APIレベル)を更新したらパフォーマンスが低下した、という内容となります。

はじめに

2018年から義務付けられたAPIレベルの指定ですが、今年(2019年11月から)はAPIレベル28以上を指定することが必須となりました1。ニコニコ漫画・読書メーターの両アプリケーション共に条件を満たしていなかったため更新の必要があり、読書メーターは特に大きな問題はありませんでしたが、ニコニコ漫画についてはいくつかの機能に影響がありました。その1つがコメント出力時のパフォーマンス低下です。この記事では遭遇した問題とそれらの解決方法について紹介していきます。

APIレベルの選定

更新前アプリのtargetSdkVersionは27であり、28又は29へ更新するという選択肢が存在しました。それぞれの更新^2にかかるコストを比べた時、どちらを選択してもほとんど違いが無かったためその時点で最新であるAPIレベル29を採用することに決まりました2

APIレベル変更による影響

APIレベル更新によって影響の出たニコニコ漫画アプリの機能は次の通りです。

  • Widget
  • ローカルへのユーザーデータ保存
  • コメント出力

パフォーマンス低下の影響が出たのはコメント出力となりますが、ここでは発生した全ての影響について具体的な内容とその対策について記載していきます。

Widget

ニコニコ漫画アプリではWidgetをサポートしています。

f:id:bookwalker_developers:20210930191937p:plain

ランキングデータを同期するためにサービスを利用しています。API26にてバックグランドサービスには制限が追加され[^4]、API28から更にフォアグラウンドサービスの利用に新たな権限が必要となりました。

フォアグラウンド サービス

Android 9 以降をターゲットとするアプリは、フォアグラウンド サービスを使用する場合、FOREGROUND_SERVICE 権限をリクエストする必要があります。

これは公式の案内通りに権限をリクエストすることのみで対応は完了となります。

ローカルへのユーザーデータ保存

ニコニコ漫画アプリでは一部のデータをローカルで保持しています。それらを暗号化するためにキーとしてシリアル番号を使用していましたが、API28からユーザーのプライバシー保護を理由としてシリアル番号の取得に制限が入りました。

ビルドシリアル番号のサポートの終了

Android 9 では、ユーザーのプライバシーを保護するために、Build.SERIAL が常に "UNKNOWN" に設定されます。

アプリでデバイスのハードウェア シリアル番号を使用する必要がある場合は、READ_PHONE_STATE 権限をリクエストしてから getSerial()) を呼び出してください。

READ_PHONE_STATE権限はDangerousパーミッションであるため、ユーザーがアプリに明示的にパーミッションを付与する必要があります。APIレベル22以前はインストール時に、23以降は実行時に求められます。表示されるメッセージは次の通りです。

f:id:bookwalker_developers:20210930191733p:plain

f:id:bookwalker_developers:20210930191926p:plain

漫画アプリという性質から考えると、直感的には受け入れにくい権限である為、既存のデータ保持の仕組みを再構築することにしました。告知の通り、状況次第ではユーザーに影響のある変更ではありましたが、年初よりデータマイグレーションを進めていたこともあって現時点ではこの問題に関しての問い合わせはありません。

コメント出力

APIレベル更新後、特定端末においてコメントの出力がカクツクようになりました。公式が用意しているマイグレーション手順には移行によって発生し得る問題点が掲示されていますが、それらの中に本現象に関連している項目は存在せず、また環境(OSやメーカー等)に偏りは見られませんでした。APIレベルを戻すと問題は発生しないことからAPIレベルの更新に問題があることは明白だった為、関係ありそうな変更履歴を確認していた所、次の情報を見つけました。

https://developer.android.com/guide/topics/graphics/hardware-accel#unsupported

要約するとHALが有効であると2Dレンダリングがサポートされるとあります。注目したのがPaintのAPI一覧です。

Paint First supported API level
setAntiAlias() (for text) 18
setAntiAlias() (for lines) 16
setFilterBitmap() 17
setLinearText()
setMaskFilter()
setPathEffect() (for lines) 28
setShadowLayer() (other than text) 28

ニコニコ漫画アプリではコメントの影の表示にPaintのsetShadowLayerを利用していました。表に記載されている通り、このAPIはAPIレベル28よりHALのサポートが有効になるようです。試しにLayerTypeをSOFTWAREにしてみた所、問題の発生していた端末は期待通りに動作するようになりました。しかし一方で問題のなかった一部端末においてパフォーマンスが低下するという状況に直面しました。ドライバ関連の相性的な問題の可能性があり、その場合根本的な解決には時間を要することが想像できたので、現在はsetShadowLayerの利用を諦めLayerを重ねて表示することで影を表現するようにしています。実際のコードではありませんが次のような修正を行いました。

-    paint.setShadowLayer(1f, 0f, 0f, Color.BLACK)
+    paint.strokeWidth = 8F
+    paint.alpha = 128

この対応により、変更以前と比べると影の表現に多少の違いがありますが、パフォーマンスに関しては同等以上という結果になりました。

終わりに

今回、APIレベルの変更によってパフォーマンスが低下するという公式から提示されていない問題に遭遇しました。義務化以前から多くのアプリケーションでAPIレベルの更新は行なって来ましたが、この様なケースは初めてとなるので良い経験となりました。これまでは公式からの案内を盲目的に踏襲してきただけという面が強いので今回の件を教訓に考えを改めて行ければと思います。

それでは2020年も改めてよろしくお願いします。