目次

目次

【Android】Bitmapでアプリがクラッシュした時の話

アバター画像
寺島広
アバター画像
寺島広
最終更新日2023/05/19 投稿日2023/05/19

はじめに

NX開発推進部Androidアプリ開発グループの寺島です。 今回はAndroidアプリ開発をしていて起こった事をもとに自分が学んだ内容をまとめました。 いろいろなアプリで起こり得る事象だったので、ぜひ参考にしてみてください。

アプリクラッシュ発生

事象発覚

ある日、いつものように開発を進めていると1件のSlackが飛んできました。

【確認環境】---------
確認端末は社用携帯のarrowsF51B
最新アプリver
-------------
ご確認お願いできますでしょうか。
(確認する限り音源は大丈夫でビデオと画像をアルバムタブ内で押下すると落ちる。)
---------------------------------------------------------
DL完了後、ライブラリから再生をしようとする
↓
ライブラリの楽曲からだと再生可能にみえるがすぐ落ちる
↓
再起動
↓
アルバムタブを開くとジャケ写が表示されない商品がある状態&特定商品押下でアプリが落ちる

アプリ上に楽曲が表示されるが、タップする時にアプリがクラッシュしてしまうという報告でした。私は報告にあった端末 arrowsF51B を所有していたため、楽曲の再生を試したのですが、再現せずでした。そのため、特定商品の押下でクラッシュするのは間違いなさそうでした。

報告いただいた方にクラッシュの詳細を聞いていきました。

  • Q: 特定商品以外は再生できるか?
    • A: 再生できる
  • Q: クラッシュが起きる特定商品はどのような商品か?
    • A: その日新しく追加された商品
  • Q: 今までに起きた事のある事象か?
    • A: 今日初めて起きた事象
  • Q: いつクラッシュが起きているか?
    • A: 特定の商品のジャケットが表示されるタイミング

以上の情報をもとに、画像処理に問題がありそうだという事にもあたりをつけながら、より詳しい原因の究明をしていくことにしました。

原因は何か?

クラッシュを解消するために、チーム内でより詳しい原因を調べました。調べていくうちに、Firebase Crashlyticsのログにたどりつきました。

java.lang.RuntimeException
Canvas: trying to draw too large(144000000bytes) bitmap.

上記は、Firebase Crashlyticsでのクラッシュレポートに関する報告です。 Firebase Crachlyticsは、アプリを使用しているユーザのクラッシュを集計することができます。

該当のクラッシュを調べるには、

  • クラッシュの発生日時
  • 端末の型番
  • 端末のOSver

上記のような要素から絞り込む必要があります。

今回の場合、以下の要素でクラッシュを特定することができました。

  • F51Bという端末でクラッシュが起きていた
  • クラッシュの発生タイミング

その際に、表示されていたクラッシュの詳細が上記の内容でした。Firebase Crashlyticsでエラーの詳細を確認できたことで、早急に大まかな原因を突き止めることができました。

内容を読むと、Bitmapの表示時にクラッシュしていることが分かりました。

Bitmapの表示エラーについて

Bitmapについて

Bitmapは、ラスタ画像と呼ばれていて、画像をピクセルと呼ばれる複数のドットで表現する画像形式です。

ラスタ画像の図(引用: ピクセル密度をサポートする)

図を見た時に画像が格子状になっていることが分かると思います。このように画像は複数の四角から形成されていて、そのうちの1つをピクセルと言います。

ピクセルは、必ず1色を示しています。この1色を表す仕組みは様々ありますが、今回はRGBAを取り上げます。その他の色の仕組みは調べてみてください。

RGBAは、Red(赤)・Green(緑)・Blue(青)・Alpha(透明度)を表しています。これらの値は0~255の値で度合いが示されます。例えば、下の色は以下のように示します。

Red: 255, Green: 91, Blue: 154, Alpha: 255 (今回は透明度は無しとしてます。) このようにピクセル1つの色が決められます。

0~255の値は、 8 bit で表すことができます。また、 8 bit1 byte です。 さらにARGBの4つの値が必要になるので、 4 byte で、1つのピクセルを表せます。

横16ピクセルで、縦31ピクセルなので、上の図の画像は、496ピクセルの画像になります。 16 × 31 = 496 1つのピクセルが 4 byte で表せるので、上の図の場合のデータサイズを計算する時は以下のようになります。 496 × 4 = 1984

よって、上の図の画像の場合は、 1984 byte になります。

Bitmapの場合は、上記のようにしてデータサイズを計算していくことができます。

改めて今回のアプリクラッシュ原因

今回、報告のあったアプリクラッシュの原因は、Bitmapのデータサイズが大きすぎるという点でした。

使われていた画像は、 横6000ピクセル・縦6000ピクセル の画像でした。 6000 × 6000 = 36000000 つまり、 36000000ピクセルの画像になります。

1ピクセルは、4 byteなので、データサイズは以下のようになります。 36000000 × 4 = 144000000 結果、 144000000 byte になります。

また、 1024 byte は 1 KB1024 KB は 1 MB と示せます。 (今回は簡略化のため、 1000 byte は 1 KB 1000 KB は 1 MB で計算します。)

144000000 ÷ 1000 ÷ 1000 = 144

上記のように計算すると、 データサイズが 144 MB になっていました。クラッシュのレポートにあった内容とも一致して、原因がはっきりと分かりました。

ただ、Bitmapを表示するときに、どのくらいのデータサイズであれば表示が可能かより詳しく調べてみました。

Bitmapのデータサイズの限界

どのくらいのサイズのBitmapならギリギリ表示が可能なのか、気になったので調べてみました。 まず、エラー表示を Android Code Search で調べてみました。 調べてみると、こんな感じで検索に引っ掛かります。

Android_Code_Search1.png

リンク

今回は表示しているBitmapのデータサイズが表示されるようなエラー文だったので、1つ目に引っかかったものが、今回のエラー表示部分になります。詳細を見ます。

Android_Code_Search2.png

リンク

MAX_BITMAP_SIZEとあり、どうやらこれがBitmapの最大値っぽいです。

Android_Code_Search3.png

リンク

さらにこの定数の定義をみると、 100 MBが限界値だと分かりました。

ただ、これはデフォルト値が 100 MB として設定されています。 そのため、端末によっては限界が異なることがありそうです。 今回はPixel 6aを使って試してみます。 (一旦、Pixel 6aの限界が 100 MBだと願いつつ進めます。)

それでは、限界をギリギリ越えるような画像を作ってみることにします。

まず、 100 MBをbyteで計算し直します。 100000000 byteが限界の値になります。

1ピクセル4 byteだとすると、25000000ピクセルの画像ならば表示できそうだと分かります。 表示したい画像を正方形とすると、横・縦それぞれの長さが出せそうです。

√25000000 = 5000 横5000ピクセル・ 縦5000ピクセル の画像だとクラッシュしそうだと分かります。

ただ、実際に試したみたのですが、クラッシュしませんでした。

そこでクラッシュしない原因を考えてみました。

  • 横・縦が 5000ピクセルの画像だとクラッシュしない
  • 横・縦が 6000ピクセルの画像だとクラッシュする つまり、 5000 ~ 6000ピクセルのどこかに境界値があります。

ここで、自分が計算を端折っていることに気づきました。 本来 1 KB は 1024 byte で、1 MB は 1024 KB だということを思い出しました。 改めて、計算してみました。

1024 × 1024 × 100 = 104857600 つまり 104857600 byteが、表示できる画像のデータサイズの限界です。 これを使って、正方形画像の表示限界である横縦のピクセル数を出します。

104857600 ÷ 4 = 26214400 √26214400 = 5120

これで横縦の長さが 5120ピクセルだと分かりました。

実際に試してみると、クラッシュしませんでした!(これは想定通りです。限界値ギリギリなので、クラッシュしないと考えてました。) さらに 横5121ピクセル・縦5121ピクセル の画像を作成し、ImageViewで表示するとクラッシュします!

クラッシュ時の表示はこちらです。

java.lang.RuntimeException: Canvas: trying to draw too large(104898564bytes) bitmap.

若干、上記で求めた 104857600 byteよりも大きいことが分かります。

これでImageViewの表示限界は、 横5120ピクセル・縦5120ピクセル の画像だということが分かりました。 (正確には 26214400ピクセル以上の画像だとクラッシュします。)

ImageViewは画像を表示するときに、必ずBitmapに変換しているようです。 PNG, JPG等の画像形式に関わらず、 26214400ピクセル を越えるとクラッシュするようです。

限界ギリギリを狙いたい場合は、 26214400ピクセルの画像で試してみてください!

原因の究明はここまでにして、事象の解決策を考えていきます!

事象の解決策

今回、事象が起きる商品以外は問題なく表示できていました。したがって、表示できている商品に合わせて、画像のサイズを変更するという解決策を取りました。(出来るだけ早急に対応するための暫定対応です。)

事象の起きた画像は 横6000ピクセル・縦6000ピクセル の画像でしたが、最大でも 横1400ピクセル・縦1400ピクセルで画像をスケーリングして読み込むようにしました。1400ピクセルの数字はどのように決めたかというと、表示できている画像の最大値が 1400ピクセル だったからという理由になります。

しかし、上記で調べたピクセル数を使えば、画像の表示限界も決めていくことができます! 横と縦どちらとも 5120ピクセル を超えないように設定することで、理由のある数値で限界を決めることができます!

まとめ

今回は自分の担当するサービスで、画像を表示するタイミングでクラッシュが発生しました。クラッシュが起きてしまったことは問題ですが、それによりBitmapについて学べて良かったです。

もし、ImageViewで表示する画像で、サイズの限界値が決まってない場合には、 今回の内容を考えつつ、ImageViewに渡す画像のサイズを見直すと良いかもしれないです。 (今回のように限界を超える画像を表示することは、あまりなさそうですが…)

ここまで読んでいただき、ありがとうございました。

アバター画像

寺島広

レコチョクの寺島です。
Android アプリ開発に携わっています。
アニメ、ゲームなどが好きです。

目次