性能改善のために,AndroidでBitmapを無圧縮でファイル読み書きした話
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が大幅に減り,十分な性能改善が得られました.