Android: 画面に合わせて画像を縮小して読み込む

Nexus OneやDesireではエラーが起きなくなったのに、HT-03AではOut of Memoryによる強制終了が頻発。これをどうやったら解決できるのか?色々と考えたけれど、DDMSを使ってHEAPのメモリ使用量を見たら、圧倒的に画像が占めているぽかったので、Bitmap自体のメモリ使用率を下げること以外に方法はないのだろうと。じゃあ、どうすればいいか?読み込む画像サイズを、BitmapFactiory.decodeStream()で読み込むタイミングで大きすぎる画像は小さくして読み込んでやれば、使用するメモリ量は少なくて済むだろうと。なんでも、Xperiaで取った写真を読み込んだだけでOut of Memoryが発生したりするから、サイズを最適化したらいいというのを見て、ネット上の画像でもできるだろうと判断。通信は複数回になっているのかもしれんが、まぁわからん。

しかし、やってみたんだが、思ったよりもいい結果が得られなかった。

// AsyncTask.doInBackgroundの中。引数はurls

HttpGet httpRequest = new HttpGet(urls[0]); 
HttpClient httpclient = new DefaultHttpClient();
HttpResponse response = (HttpResponse) httpclient.execute(httpRequest); 
HttpEntity entity = response.getEntity();
BufferedHttpEntity bufHttpEntity = new BufferedHttpEntity(entity);
InputStream is = bufHttpEntity.getContent();

BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
// 画像の大きさだけ取得
image = BitmapFactory.decodeStream(is, null, opts);
is.close();

Log.d(TAG, "before opts.outWidth = " + String.valueOf(opts.outWidth));
Log.d(TAG, "before opts.outHeight = " + String.valueOf(opts.outHeight));

// DisplayMetricsをActivity側でキャッシュしておいたと仮定
DisplayMetrics metrics = MetrixCache.get();
if (metrics != null) {
	Log.d(TAG, "run metrics.widthPixels = " + String.valueOf(metrics.widthPixels));
	Log.d(TAG, "run metrics.heightPixels = " + String.valueOf(metrics.heightPixels));
	int zoomWidth = (int)Math.floor(opts.outWidth / metrics.widthPixels);
	int zoomHeight = (int)Math.floor(opts.outHeight / metrics.heightPixels);
	opts.inSampleSize = Math.max(zoomWidth, zoomHeight);
}
// 今度は画像を読み込む
opts.inJustDecodeBounds = false;
				
is = bufHttpEntity.getContent();
// 最適な画像サイズで読み込む
image = BitmapFactory.decodeStream(is, null, opts);
is.close();
return image;

opts.inSampleSizeはintで指定するため、値が2になった途端に、縦横比は半分に(画像サイズは1/4)になるので、やけに荒くなる。しかもぎりぎりで2に届かないものはオリジナルで読み込まれるので、メモリ消費もでかくて役にたたんと判断。判断というか、HT-03Aの強制終了が起きたから、しゃーない。あと、inputStreamを2度使うから、やっぱり2回通信が発生しているのかもしれない。確かめてないけど。とにかく、別の手段を考える。

いったん画像を読み込んで、機種の解像度に合わせて画像をコピーし直す方式にしてみた。

HttpGet httpRequest = new HttpGet(urls[0]); 
HttpClient httpclient = new DefaultHttpClient();
HttpResponse response = (HttpResponse) httpclient.execute(httpRequest); 
HttpEntity entity = response.getEntity();
BufferedHttpEntity bufHttpEntity = new BufferedHttpEntity(entity);
InputStream is = bufHttpEntity.getContent();

image = BitmapFactory.decodeStream(is);
is.close();
DisplayMetrics metrics = MetrixCache.get();
if (metrics != null) {
	// 画像の大きさを最適化する
	Log.d(TAG, "before image.getWidth() = " + String.valueOf(image.getWidth()));
	Log.d(TAG, "before image.getHeight() = " + String.valueOf(image.getHeight()));
	float s_x = (float)image.getWidth() / (float)metrics.widthPixels;
	float s_y = (float)image.getHeight() / (float)metrics.heightPixels;
	float scale = Math.max(s_x, s_y);
	if (scale > 1){
		int new_x = (int)(image.getWidth() / scale);
		int new_y = (int)(image.getHeight() / scale);
		Log.d(TAG, "new_x = " + String.valueOf(new_x));
		Log.d(TAG, "new_y = " + String.valueOf(new_y));
		image = Bitmap.createScaledBitmap(
				image,
				new_x,
				new_y,
				false);
		Log.d(TAG, "after image.getWidth() = " + String.valueOf(image.getWidth()));
		Log.d(TAG, "after image.getHeight() = " + String.valueOf(image.getHeight()));
	}
}
return image;

あれだけ頻発していたHT-03AでのOut of Memoryが影を潜めた。さらに、画面の解像度に合わせて画像サイズを最適化するため、そんなに画像も汚くはない。この方式ならば、フォトフレームなどで使うにしても、きれいな画像が表示できそうだ。今のところは、これが一番よさそうなので、採用!!


カテゴリー Android, Java | タグ   | パーマリンク

コメント・トラックバック一覧

  1. sn says:

    参考にさせていただきました。
    コピペで動作もしてとても良かったです

  2. まき says:

    とても参考になりました。

    scaleの判定はトリミングが必要なのかどうかを判定しているんですね。

  3. nanbudiver says:

    同じ問題に遭遇。

    InSampleSizeはローカルディスクにある画像を読込むには適しているけど、
    ネット上にある画像は、教えてもらった方法がいいですね。

    参考になりました。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です