性能改善のために,AndroidでBitmapを無圧縮でファイル読み書きした話
拙作のAndroidアプリ 地図ロイド の3D描画処理のパフォーマンスチューニングをしていたのですが,
多数のBitmapをファイル読み書きする処理があり,そこで繰り返し実行されている
-BitmapFactory.decodeByteArray() – byte[]からBitmapを読み込む
-Bitmap#compress() – BitmapをOutputStreamに書き出す
の性能がボトルネックになっていました.
これらを使わない方法でBitmapをファイル読み書きできないかと
Bitmapクラスのリファレンス
を見たところ,
Bufferとの入出力メソッド
-Bitmap#copyPixelsFromBuffer() – BufferからBitmapを読み込む
-Bitmap#copyPixelsToBuffer() – BufferにBitmapを書き出す
がありましたので,これを使うことにしました.
Bitmapの無圧縮読み書き 第1弾
public static void saveBitmapNoCompress(Bitmap bmp, File saveFile) throws IOException {
int width = bmp.getWidth();
int height = bmp.getHeight();
// RGB_565:2バイト,ARGB_8888:4バイト
int bytesPerPixel = (bmp.getConfig() == Bitmap.Config.RGB_565? 2: 4);
FileChannel fileChan = null;
try {
ByteBuffer buf = ByteBuffer.allocate(width * height * bytesPerPixel);
bmp.copyPixelsToBuffer(buf);
fileChan = new FileOutputStream(saveFile).getChannel();
buf.flip();
fileChan.write(buf, 0);
} finally {
if (fileChan != null) {
try {
fileChan.close();
} catch (IOException e) {
}
}
}
}
public static Bitmap loadBitmapNoCompress(File file,
int width, int height, Bitmap.Config conf) throws IOException {
Bitmap bmp = Bitmap.createBitmap(width, height, conf);
// RGB_565:2バイト,ARGB_8888:4バイト
int bytesPerPixel = (conf == Bitmap.Config.RGB_565? 2: 4);
FileChannel fileChan = null;
try {
ByteBuffer buf = ByteBuffer.allocate(width * height * bytesPerPixel);
fileChan = new FileInputStream(file).getChannel();
fileChan.read(buf);
buf.flip();
bmp.copyPixelsFromBuffer(buf);
return bmp;
} finally {
if (fileChan != null) {
try {
fileChan.close();
} catch (IOException e) {
}
}
}
}
ByteBufferを生成して,それとBitmapとでやりとりし,ByteBufferはFileChannelでファイルとやりとりしています.
しかしこの方法では,速度が速くならず,むしろ遅くなってしまいました...
ByteBufferを使い回す
動作を調べたところ,読み書きのたびにByteBufferを生成しているのがまずいようでした.
Bitmapの無圧縮読み書き 改訂版
~呼びだし元で ByteBuffer.allocate()をしておき,以下のメソッドにはその結果を渡す~
public static void saveBitmapNoCompress(Bitmap bmp, ByteBuffer buf, File saveFile) throws IOException {
FileChannel fileChan = null;
try {
// 使い回すのでクリアが必要
buf.clear();
bmp.copyPixelsToBuffer(buf);
fileChan = new FileOutputStream(saveFile).getChannel();
buf.flip();
fileChan.write(buf, 0);
} finally {
if (fileChan != null) {
try {
fileChan.close();
} catch (IOException e) {
}
}
}
}
public static Bitmap loadBitmapNoCompress(File file, ByteBuffer buf,
int width, int height, Bitmap.Config conf) throws IOException {
Bitmap bmp = Bitmap.createBitmap(width, height, conf);
FileChannel fileChan = null;
try {
fileChan = new FileInputStream(file).getChannel();
// 使い回すのでクリアが必要
buf.clear();
fileChan.read(buf);
buf.flip();
bmp.copyPixelsFromBuffer(buf);
return bmp;
} finally {
if (fileChan != null) {
try {
fileChan.close();
} catch (IOException e) {
}
}
}
}
呼びだし元で1度だけByteBufferを生成して,それをこんな感じで繰り返し実行の毎回使い回すようにすれば,
元のdecodeByteArray(),compress()使用時よりGCが大幅に減り,十分な性能改善が得られました.