ビットマップのデコードに伴うメモリ割り当ての問題に関する多くの関連記事を読みましたが、公式Webサイトで提供されているコードを使用しても、次の問題の解決策を見つけることができません。
これが私のコードです:
public static Bitmap decodeSampledBitmapFromResource(InputStream inputStream, int reqWidth, int reqHeight) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
baos.write(buffer, 0, len);
}
baos.flush();
InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
InputStream is2 = new ByteArrayInputStream(baos.toByteArray());
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is1, null, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeStream(is2, null, options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
bitmap = decodeSampledBitmapFromResource(inputStream, 600, 600);
この行に「3250016のメモリ不足エラー-バイト割り当て」が表示されます。
return BitmapFactory.decodeStream(is2, null, options);
3.2MBは割り当てるのに十分小さいように思えます。どこが間違っているのですか?どうすればこれを解決できますか?
[〜#〜]編集[〜#〜]
このソリューションを調べた後 [〜#〜] here [〜#〜] by N-Joy必要なサイズ300で正常に動作しますが、必要なサイズは800なので、まだエラーが発生します。
メソッドdecodeSampledBitmapFromResource
は、ByteArrayOutputStream baos、ByteArrayInputStream is1、ByteArrayInputStream is2の3つのストリームを使用するため、メモリ効率が良くありません。それらのそれぞれは、画像の同じストリームデータを格納します(それぞれに1バイトの配列)。
また、デバイス(LGネクサス4)でテストしてSDカード上の2560x1600画像をターゲットサイズ800にデコードすると、次のようになります。
03-13 15:47:52.557: E/DecodeBitmap(11177): dalvikPss (beginning) = 1780
03-13 15:47:53.157: E/DecodeBitmap(11177): dalvikPss (decoding) = 26393
03-13 15:47:53.548: E/DecodeBitmap(11177): dalvikPss (after all) = 30401 time = 999
見ることができます:4096000ピクセルの画像をデコードするためだけに割り当てられたメモリが多すぎます(28.5MB)。
ソリューション:InputStreamを読み取り、データを1つのバイト配列に直接格納し、残りの作業にこのバイト配列を使用します。
サンプルコード:
public Bitmap decodeSampledBitmapFromResourceMemOpt(
InputStream inputStream, int reqWidth, int reqHeight) {
byte[] byteArr = new byte[0];
byte[] buffer = new byte[1024];
int len;
int count = 0;
try {
while ((len = inputStream.read(buffer)) > -1) {
if (len != 0) {
if (count + len > byteArr.length) {
byte[] newbuf = new byte[(count + len) * 2];
System.arraycopy(byteArr, 0, newbuf, 0, count);
byteArr = newbuf;
}
System.arraycopy(buffer, 0, byteArr, count, len);
count += len;
}
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(byteArr, 0, count, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
int[] pids = { Android.os.Process.myPid() };
MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
Log.e(TAG, "dalvikPss (decoding) = " + myMemInfo.dalvikPss);
return BitmapFactory.decodeByteArray(byteArr, 0, count, options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
計算を行う方法:
public void onButtonClicked(View v) {
int[] pids = { Android.os.Process.myPid() };
MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
Log.e(TAG, "dalvikPss (beginning) = " + myMemInfo.dalvikPss);
long startTime = System.currentTimeMillis();
FileInputStream inputStream;
String filePath = Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/test2.png";
File file = new File(filePath);
try {
inputStream = new FileInputStream(file);
// mBitmap = decodeSampledBitmapFromResource(inputStream, 800, 800);
mBitmap = decodeSampledBitmapFromResourceMemOpt(inputStream, 800,
800);
ImageView imageView = (ImageView) findViewById(R.id.image);
imageView.setImageBitmap(mBitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
Log.e(TAG, "dalvikPss (after all) = " + myMemInfo.dalvikPss
+ " time = " + (System.currentTimeMillis() - startTime));
}
そして結果:
03-13 16:02:20.373: E/DecodeBitmap(13663): dalvikPss (beginning) = 1823
03-13 16:02:20.923: E/DecodeBitmap(13663): dalvikPss (decoding) = 18414
03-13 16:02:21.294: E/DecodeBitmap(13663): dalvikPss (after all) = 18414 time = 917
これは、ユーザーが大きなビットマップで遊んでいるときに通常直面する一般的な問題であり、サイトで議論される質問がたくさんあります ここ 、 ここ 、 ここ および ここ およびその他多数。ただし、ユーザーは正確なソリューションを操作できません。
いつか、ビットマップをスムーズに管理するライブラリや、以下にリストした他のリンクに出くわしました。お役に立てれば!
Android-Universal-Image-LoaderOutOfMemoryErrorの解決策:ビットマップサイズがVM予算 を超えています
ARGB_8888
はアルファ色の値を取るため、より多くのメモリを使用するため、前述のようにRGB_565
を使用することをお勧めします [〜#〜] here [〜#〜]
注:品質はARGB_8888
と比較して少し低くなります。
ビットマップのメモリ使用量に関して多くの問題がありました。
結果:
WebViewを使用して、必要な数の画像を動的にロードします。NDK(低レベル)を使用して構築されているため、GDIヒープメモリの制限はありません。スムーズかつ高速に動作します: )
あなたはおそらく以前のビットマップ参照を保持しています。このコードを数回実行していて、bitmap.recycle()
を実行していないと思います。必然的にメモリが不足します。
ビットマップをデコードする際のメモリ不足の問題は、デコードする画像サイズとリンクしていないことがよくあります。もちろん、5000x5000pxの画像を開こうとすると、OutOfMemoryErrorで失敗しますが、800x800pxのサイズでは完全に合理的であり、正常に機能するはずです。
デバイスのメモリが3.2MBの画像で不足している場合は、アプリのどこかでコンテキストがリークしている可能性があります。
この投稿 の最初の部分です:
問題はレイアウトにあるのではなく、コードのどこかにあると思います。そしておそらくどこかでコンテキストをリークしています。
つまり、コンポーネントでアクティビティコンテキストを使用してはならず、ガベージコレクションが行われないようにしているということです。コンポーネントはアクティビティによって保持されることが多いため、これらのアクティビティはGCではなく、Javaヒープは非常に速く成長し、アプリはいつかクラッシュします。
Raghunandanが言ったように、MATを使用して、アクティビティ/コンポーネントが保持されているものを見つけ、コンテキストリークを削除する必要があります。
コンテキストリークを検出するために今のところ見つけた最良の方法は、向きの変更です。たとえば、ActivityMainを複数回ローテーションし、MATを実行して、ActivityMainのインスタンスが1つしかないかどうかを確認します。複数のものがある場合(回転が変化するのと同じくらい)、コンテキストリークがあることを意味します。
私は何年も前に MATの使用に関する良いチュートリアル を見つけました。多分今より良いものがあります。
メモリリークに関するその他の投稿:
このビデオをご覧ください。 http://www.youtube.com/watch?v=_CruQY55HOk 。ビデオで提案されているようにsystem.gc()を使用しないでください。 MATアナライザーを使用して、メモリリークを見つけます。返されたビットマップが大きすぎて、メモリリークが発生していると思います。