性能改善のために,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が大幅に減り,十分な性能改善が得られました.
kamolandをフォローしましょう


© 2017 KMIソフトウェア