Android Bitmap.createBitmap で OutOfMemory
しかし、Android カメラ周りの挙動はわかりにくい。
自作のアプリをカメラを使用したら強制終了することがあることは承知していたのだが、なにぶん所持している実機で再現しないので、怪しいところを try ~ catch するぐらいしか対処方法がない。
そんな折、知人が、MEDIAS を入手したので、試させてもらったところ、めでたく(?) エラー が発生したので、レポートを入手できた。
- java.lang.OutOfMemoryError: bitmap size exceeds VM budget
- at android.graphics.Bitmap.nativeCreate(Native Method)
- at android.graphics.Bitmap.createBitmap(Bitmap.java:468)
- at info.typea.shujiroid.free.ShujiActivity.onActivityResult(ShujiActivity.java:303)
- at android.app.Activity.dispatchActivityResult(Activity.java:3890)
- at android.app.ActivityThread.deliverResults(ActivityThread.java:3511)
- at android.app.ActivityThread.handleSendResult(ActivityThread.java:3557)
- at android.app.ActivityThread.access$2800(ActivityThread.java:125)
- at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2063)
- at android.os.Handler.dispatchMessage(Handler.java:99)
- at android.os.Looper.loop(Looper.java:123)
- at android.app.ActivityThread.main(ActivityThread.java:4627)
- at java.lang.reflect.Method.invokeNative(Native Method)
- at java.lang.reflect.Method.invoke(Method.java:521)
- at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:858)
- at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
- at dalvik.system.NativeStart.main(Native Method)
ほーー OutOfMemory ねーそりゃだめだわ。
で、原因を探るが、ネット上では、Bitmap を recycle しろ~ 的なアドバイスが多いのだが、自分のソースではインスタンスを使い回していないし、いきなり発生するんだよなー
ということで、よくよくコードを見ると、別にカメラで撮影したビットマップをどうこうする以前に、編集領域としてビットマップを取得するところで既に落ちている。
- //pic は撮影した画像のBitmap
- Bitmap bmpBase = Bitmap.createBitmap(pic.getWidth(), pic.getHeight(), Bitmap.Config.ARGB_8888);
ということは、撮影した画像のサイズ云々ではなくて、Bitmap の領域の確保ができていないということか。
確保するサイズは、ARGB_8888 なので、 1px あたり、32bit × width × height / 8 (MB) って感じかな?
実機 X06HT で確認したところ、20,000,000 を超えるサイズを確保使用とすると OutOfMemory が発生した。
うーむ。
面倒なので、閾値を 10MB ほどにとって、それを超えたら画質を落とすか。
- Bitmap.Config config = Bitmap.Config.ARGB_8888;
- if (pic.getWidth() * pic.getHeight() * 4.0d > 10000000.0d) { // 閾値 X06HT 実測 20MB の半分程度にしとく
- config = Bitmap.Config.ARGB_4444;
- }
- Bitmap bmpBase = Bitmap.createBitmap(pic.getWidth(), pic.getHeight(), config);
てな、感じで、まぁ何とか動くのだが・・・
なんか違う。
あーーー、そもそも、画面に表示するのに、カメラで撮影した、画像のサイズまるまる領域確保する必要がないのではないか。自分のアプリの場合。
View のサイズ分、createBitmap してあげて(これならOutOfMemoryは起きない)そこに、縮小した画像をはめ込めばいいじゃん。
- // カメラアクティビティから、結果をIntentで返す。画像はbyte[]として
- Bundle bundle = data.getExtras();
- byte[] picdata = bundle.getByteArray(CameraActivity.KEY_CAMERA_DATA);
- Bitmap pic = BitmapFactory.decodeByteArray(picdata, 0, picdata.length);
- // View のサイズ にあわせて編集領域となる Bitmapを生成
- float viewWidth = (float)shujiView.getWidth();
- float viewHeight = (float)shujiView.getHeight();
- Bitmap bmpBase = Bitmap.createBitmap((int)viewWidth, (int)viewHeight, Bitmap.Config.ARGB_8888);
- // 画像を編集領域にあわせて縮小
- Matrix matrix = new Matrix();
- float ratio = viewWidth / (float)pic.getWidth();
- matrix.postScale(ratio,ratio);
- pic = Bitmap.createBitmap(pic, 0, 0, pic.getWidth(), pic.getHeight(), matrix, true);
- // キャンバスに編集領域 Bitmap を設定
- Canvas canvas = new Canvas(bmpBase);
- // 縮小した画像を貼り付け
- canvas.drawBitmap(pic, 0f, 0f, null);
- // キャンバスにお絵かき
- Paint paint = new Paint();
- paint.setTextSize(30);
- paint.setColor(Color.parseColor("#E6E6E6"));
- paint.setStyle(Style.STROKE);
- paint.setAntiAlias(true);
- Date d = new Date();
- String timestamp = d.toLocaleString();
- float tw = paint.measureText(timestamp);
- float th = paint.getTextSize();
- canvas.drawText(timestamp, viewWidth - (tw + 10f) , viewHeight - (th + 5f), paint);
- // View の背景に設定
- shujiView.setBackgroundBitmap(bmpBase);
と、Android のカメラの挙動がなどとほざいていたが、ただ単に自分が未熟なだけでした。。。
また一歩野望に近づいた。