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が大幅に減り,十分な性能改善が得られました.