Androidで安全にブラーをかける

背景

ブラー効果の流行

最近はアプリでブラー効果をよく見ます。

YouTube Music アプリでのブラー効果

ブラー効果を使う理由は、次のようなものだと思います。

  • 背景をぼかすことで、その上の文字や画像に注目させたい
  • 没入感を高める(奥行きを出す)

また注目の対象が画像の場合、それにブラーをかけたものを背景にするのをよく見ます。

アプリでブラーをかける懸念

一般的に画像処理は重いものです。
そのためブラー処理をアプリで実装すると以下ような懸念があります。

  • 端末のメモリ不足
  • パフォーマンス低下

古い端末だとメモリ不足で処理ができないケースがあります。
サイズが大きい画像の処理ではそれだけメモリが必要になりますが、メモリ不足になるとアプリがクラッシュします。

またメモリ不足にならなくても、処理に時間がかかります。
それがメインスレッドで処理された場合はアプリが固まり、ユーザにストレスを与えてしまいます。

安全にブラーをかける方法を考えよう

Androidの端末はピンキリなので、上記の懸念に向き合う必要があります。

ただ大変なので、アプリでブラー処理をしないアプローチもあります。(後述)
でも安全なブラー処理ができるなら、UIが良くなり、アプリの競争力向上につながるはずです。

ブラーをかける方法

Compose

ライブラリ

Android API

  • Modifier.blur()Android 12 以上)

AndroidView

ライブラリ

Android API

パフォーマンス計測

Jetpack MacrobenchmarkFrameTimingMetric を計測する。

テストコード: BlurBenchmark.kt

  • frameDurationCpuMs
    • フレームの生成にかかった時間
    • フレーム間の時間は60fps → 16.67ms、90fps → 11.12msなので、これより短くなければフレーム落ちする。
  • frameOverrunMs (API 29以上)
    • 正の値 → どれくらいの時間フレームの生成が遅れたか
    • 負の値 → どれくらいの時間フレームの生成が早かったか

計測結果

テスト環境

  • Pixel 7
    • Android 13
    • RAM 8 GB
    • 解像度 2400 × 1080
    • 最大 90 fps
    • 私物の実機
  • Galaxy J5
    • Android 6.0.1
    • RAM 2 GB
    • 解像度 1280 × 720
    • 最大 60 fps
    • 私物の実機

計測結果の見方

  • frameDurationCpuMsのP95の列で降順ソート
  • frameDurationCpuMs
    • 16.67ms以上なら赤色
    • 11.11ms以上なら黄色
  • frameOverrunMs
    • 負の値なら青色

Pixel 7 の計測結果

計測結果

Galaxy J5 の計測結果

Galaxy J5 の計測結果

比較検討

全体的にAndroidViewが早いので、フルComposeでなければAndroidViewを選択するのが良さそうです。

Composeが良ければ、glide-transformations を使うのが、 Modifier.blur() より良さそうです。

さらに選択肢を精査するためには、以下の観点で比較します。

  • メンテナンスのしやすさ
  • パフォーマンス

メンテナンスのしやすさ

メンテナンスしづらくなる要素には以下のようなものがあります。

  • ライブラリがメンテナンスされていない
  • ライブラリが古いAPIや非推奨APIを利用している

それぞれ比較してみたのが以下の表です。

メンテナンス観点での比較

ここであげたライブラリは全て非推奨のRenderScriptを利用しています。
そのため将来的にRenderScriptから移行が必要になった時に、ライブラリの更新を待つか、自前で実装したものに置き換える必要が出てきます。

パフォーマンス

非推奨のRenderScriptを利用しない方法は以下に絞られました。

  • AndroidView | renderscript-intrinsics-replacement-toolkit
  • AndroidView | RenderEffect(Android 12以上)
  • Compose | Modifier.blur()(Android 12以上)

Modifier.blur() はフレーム落ちが多いので、あまり使いたくありません。

AndroidViewの2つの方法は、どちらも同等のパフォーマンスです。
ちなみに公式ドキュメントには以下の記載がありました。

Android 12(API レベル 31)以降を対象としている場合は、Toolkit.blur() ではなく RenderEffect クラスを使用することをおすすめします。

https://developer.android.com/guide/topics/renderscript/migrate?hl=ja

どの方法を使うか

ここまでの比較で、以下の方法が良いと思いました。

  • Android 12 以上
    • AndroidView + RenderEffect で自前実装
  • Android 12 未満
    • AndroidView + renderscript-intrinsics-replacement-toolkit で自前実装

パフォーマンスと安全性を上げる

TODO

参考