各行にいくつかの画像ボタンがあるリストビューがあります。リストの行をクリックすると、新しいアクティビティが起動されます。カメラのレイアウトに問題があるため、自分でタブを作成する必要がありました。結果に対して起動されるアクティビティはマップです。ボタンをクリックしてイメージプレビューを起動する(SDカードからイメージをロードする)と、アプリケーションはアクティビティからlistview
アクティビティに戻り、結果ウィジェットに戻ります。これは、イメージウィジェット以外の何ものでもありません。
リストビューでの画像プレビューはカーソルとListAdapter
で行われています。これはかなり簡単になりますが、サイズ変更された画像をどのように置くことができるのかわかりません(すなわち、小さいボタンサイズは画像ボタンのsrc
としてピクセルではありません)。
問題は、2番目のアクティビティに戻って再起動しようとすると、メモリ不足エラーが発生することです。
フォーカスの問題でタッチスクリーンで行を選択できないため、各行のウィジェット/要素のプロパティにも変更を加える必要があるので、これは望ましいでしょう。 ( ローラーボールが使えます。 )
リストビューで画像を無効にするとすぐにまたうまくいきました。
参考:これは私がやっていた方法です:
String[] from = new String[] { DBHelper.KEY_BUSINESSNAME,DBHelper.KEY_ADDRESS,DBHelper.KEY_CITY,DBHelper.KEY_GPSLONG,DBHelper.KEY_GPSLAT,DBHelper.KEY_IMAGEFILENAME + ""};
int[] to = new int[] {R.id.businessname,R.id.address,R.id.city,R.id.gpslong,R.id.gpslat,R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);
R.id.imagefilename
はButtonImage
です。
これが私のLogCatです。
01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): Java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.graphics.BitmapFactory.decodeStream(BitmapFactory.Java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.graphics.BitmapFactory.decodeFile(BitmapFactory.Java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.graphics.BitmapFactory.decodeFile(BitmapFactory.Java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.graphics.drawable.Drawable.createFromPath(Drawable.Java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.ImageView.resolveUri(ImageView.Java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.ImageView.setImageURI(ImageView.Java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.Java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.Java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.CursorAdapter.getView(CursorAdapter.Java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.AbsListView.obtainView(AbsListView.Java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.ListView.makeAndAddView(ListView.Java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.ListView.fillSpecific(ListView.Java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.ListView.layoutChildren(ListView.Java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.AbsListView.onLayout(AbsListView.Java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.view.View.layout(View.Java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.LinearLayout.setChildFrame(LinearLayout.Java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.LinearLayout.layoutHorizontal(LinearLayout.Java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.LinearLayout.onLayout(LinearLayout.Java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.view.View.layout(View.Java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.FrameLayout.onLayout(FrameLayout.Java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.view.View.layout(View.Java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.LinearLayout.setChildFrame(LinearLayout.Java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.LinearLayout.layoutVertical(LinearLayout.Java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.LinearLayout.onLayout(LinearLayout.Java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.view.View.layout(View.Java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.widget.FrameLayout.onLayout(FrameLayout.Java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.view.View.layout(View.Java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.view.ViewRoot.performTraversals(ViewRoot.Java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.view.ViewRoot.handleMessage(ViewRoot.Java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.os.Handler.dispatchMessage(Handler.Java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.os.Looper.loop(Looper.Java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Android.app.ActivityThread.main(ActivityThread.Java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at Java.lang.reflect.Method.invoke(Method.Java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed
画像を表示するときにも新しいエラーがあります。
01-25 22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
01-25 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri:
01-25 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
01-25 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
01-25 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
Androidトレーニング クラス、 " ビットマップの効率的な表示 "は、ビットマップを読み込むときの例外Java.lang.OutOfMemoryError: bitmap size exceeds VM budget
の理解と対処のための優れた情報を提供します。
BitmapFactory
クラスは、さまざまなソースからBitmap
を作成するためのいくつかのデコード方法(decodeByteArray()
、decodeFile()
、decodeResource()
など)を提供します。画像データソースに基づいて最も適切なデコード方法を選択してください。これらのメソッドは、構築されたビットマップにメモリを割り当てようとします。そのため、簡単にOutOfMemory
例外が発生する可能性があります。各種類のデコード方法には、BitmapFactory.Options
クラスを使用してデコードオプションを指定できる追加のシグネチャがあります。デコード中にinJustDecodeBounds
プロパティをtrue
に設定すると、メモリ割り当てが回避され、ビットマップオブジェクトにnull
が返されますが、outWidth
、outHeight
およびoutMimeType
は設定されます。この方法では、ビットマップの作成(およびメモリ割り当て)の前に、画像データのサイズと種類を読み取ることができます。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
Java.lang.OutOfMemory
例外を回避するには、利用可能なメモリに収まるように予測可能なサイズの画像データを提供することがソースから絶対に信頼されていない限り、デコードする前にビットマップのサイズを確認してください。
画像の大きさはわかっているので、フル画像をメモリにロードするかどうか、またはサブサンプリングされたバージョンをロードするかどうかを決定するために使用できます。考慮すべき点がいくつかあります。
たとえば、1024×768ピクセルの画像を最終的にImageView
内の128×96ピクセルのサムネイルで表示するのであれば、メモリにロードする価値はありません。
小さいバージョンをメモリにロードしてイメージをサブサンプリングするようにデコーダに指示するには、BitmapFactory.Options
オブジェクトでinSampleSize
をtrue
に設定します。たとえば、4のinSampleSize
でデコードされた解像度2048 x 1536のイメージは、約512 x 384のビットマップを生成します。これをメモリにロードすると、フルイメージに12MBではなく0.75MBが使用されます(ARGB_8888
のビットマップ構成を想定)。ターゲットの幅と高さに基づいて、2のべき乗であるサンプルサイズの値を計算する方法は次のとおりです。
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) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
注 :
inSampleSize
のドキュメントのように、デコーダーは最も近い2の累乗に切り捨てて最終値を使用するため、2の累乗値が計算されます。
このメソッドを使用するには、まずinJustDecodeBounds
をtrue
に設定してデコードし、オプションを渡してから、新しいinSampleSize
の値とinJustDecodeBounds
をfalse
に設定して再度デコードします。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
次のコード例に示すように、このメソッドを使用すると、任意のサイズのビットマップを100×100ピクセルのサムネイルを表示するImageView
に簡単にロードできます。
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
必要に応じて適切なBitmapFactory.decode*
メソッドを使用することで、他のソースからのビットマップをデコードするために同様のプロセスをたどることができます。
OutOfMemoryエラーを修正するには、次のようにします。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);
このinSampleSize
オプションはメモリ消費を減らします。
これが完全な方法です。まず、コンテンツ自体をデコードせずに画像サイズを読み取ります。それからそれは最良のinSampleSize
値を見つけます、それは2のべき乗であるべきです、そして最後に画像はデコードされます。
// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
try {
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
// The new size we want to scale to
final int REQUIRED_SIZE=70;
// Find the correct scale value. It should be the power of 2.
int scale = 1;
while(o.outWidth / scale / 2 >= REQUIRED_SIZE &&
o.outHeight / scale / 2 >= REQUIRED_SIZE) {
scale *= 2;
}
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {}
return null;
}
私はFedorのコードを少し改良しました。基本的には同じですが、(私の考えでは)醜いwhileループがないため、常に2のべき乗になります。オリジナルの解決策を作ったことに対してFedorに敬意を表します、彼が見つかるまで私は立ち往生していました、そしてそれから私はこれを作ることができました:)
private Bitmap decodeFile(File f){
Bitmap b = null;
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);
BitmapFactory.decodeStream(fis, null, o);
fis.close();
int scale = 1;
if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE /
(double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
}
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, o2);
fis.close();
return b;
}
私はiOSの経験から来たもので、画像を読み込んで表示するのと同じくらい基本的なことで問題を発見したことに不満を感じました。結局のところ、この問題を抱えている誰もが適度なサイズの画像を表示しようとしています。とにかく、ここに私の問題を修正した2つの変更点があります(そして私のアプリは非常に敏感になりました)。
1)BitmapFactory.decodeXYZ()
を実行するたびに、inPurgeable
をtrue
に設定して(できればinInputShareable
もtrue
に設定して)BitmapFactory.Options
を必ず渡してください。
2)Bitmap.createBitmap(width, height, Config.ARGB_8888)
を絶対に使用しないでください。私は絶対に意味しません!私はそのことが数回のパスの後にメモリエラーを起こさないようにしたことは一度もありません。 recycle()
、System.gc()
の量は、何でも助けになりました。いつも例外が発生しました。実際に機能するもう1つの方法は、ドロウアブル(または上記の手順1を使用してデコードした別のビットマップ)にダミーイメージを入れ、それを任意の大きさに拡大縮小してからキャンバスに渡すなどの操作で作成することです。もっと楽しく)だから、代わりにあなたが使うべきものはBitmap.createScaledBitmap(srcBitmap, width, height, false)
です。何らかの理由でブルートフォースのcreateメソッドを使用しなければならない場合は、少なくともConfig.ARGB_4444
を渡してください。
これは数日ではないにしてもあなたの時間を節約することがほぼ保証されています。画像の拡大縮小などについて説明していることは、実際にはうまくいきません(サイズが間違っていたり、劣化した画像を解決方法と見なさない限り)。
これは 既知のバグ ですが、これは大きなファイルが原因ではありません。 AndroidはDrawablesをキャッシュしているので、画像をいくつか使用しただけでメモリ不足になります。しかし、私はAndroidのデフォルトのキャッシュシステムをスキップすることで、それに代わる方法を見つけました。
解決策 :画像を "assets"フォルダに移動し、次の関数を使ってBitmapDrawableを取得する:
public static Drawable getAssetImage(Context context, String filename) throws IOException {
AssetManager assets = context.getResources().getAssets();
InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
Bitmap bitmap = BitmapFactory.decodeStream(buffer);
return new BitmapDrawable(context.getResources(), bitmap);
}
私はこれと同じ問題を抱えていて、BitmapFactory.decodeStreamまたはdecodeFile関数を回避することによってそれを解決し、代わりにBitmapFactory.decodeFileDescriptor
を使用しました
decodeFileDescriptor
はdecodeStream/decodeFileとは異なるネイティブメソッドを呼び出すように見えます。
とにかく、うまくいったのはこれでした(上記のようにいくつかのオプションを追加しましたが、違いはありません。重要なのは、decodeStreamではなくBitmapFactory.decodeFileDescriptorの呼び出しです。 _またはdecodeFile):
private void showImage(String path) {
Log.i("showImage","loading:"+path);
BitmapFactory.Options bfOptions=new BitmapFactory.Options();
bfOptions.inDither=false; //Disable Dithering mode
bfOptions.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared
bfOptions.inInputShareable=true; //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
bfOptions.inTempStorage=new byte[32 * 1024];
File file=new File(path);
FileInputStream fs=null;
try {
fs = new FileInputStream(file);
} catch (FileNotFoundException e) {
//TODO do something intelligent
e.printStackTrace();
}
try {
if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
} catch (IOException e) {
//TODO do something intelligent
e.printStackTrace();
} finally{
if(fs!=null) {
try {
fs.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: Java.lang.OutOfMemoryError: bitmap size exceeds VM budget
im.setImageBitmap(bm);
//bm.recycle();
bm=null;
}
DecodeStream/decodeFileで使用されているネイティブ関数に問題があると思います。 decodeFileDescriptorを使用するときに別のネイティブメソッドが呼び出されることを確認しました。また、私が読んだのは、「画像(ビットマップ)は標準的なJavaの方法ではなくネイティブ呼び出しによって割り当てられる;割り当ては仮想ヒープの外側で行われるが、 それに対して数えられる! "
私はOutOfMemoryError
を避けるための最善の方法はそれに直面しそれを理解することです。
意図的にOutOfMemoryError
を発生させ、メモリ使用量を監視するために app を作成しました。
私はこのアプリでたくさんの実験をした後、私は以下の結論を得ました:
私はハニーコームが最初に来る前にSDKのバージョンについて話すつもりです。
ビットマップはネイティブヒープに格納されますが、ガベージコレクトされるため、recycle()を呼び出す必要はありません。
{VMヒープサイズ} + {割り当てられたネイティブヒープメモリ}> = {デバイスのVMヒープサイズ制限}で、ビットマップを作成しようとしている場合、OOMがスローされます。
注意:VM ALLOCATED MEMORYではなく、VM HEAP SIZEがカウントされます。
割り当てられたVMメモリが縮小されても、VMヒープサイズは拡張後に縮小されることはありません。
そのため、VMヒープサイズが大きくなりすぎてビットマップに使用可能なメモリを節約できないようにするには、ピークのVMメモリをできるだけ小さくする必要があります。
System.gc()を手動で呼び出すのは意味がありません。システムはヒープサイズを増やす前に最初にそれを呼び出します。
ネイティブヒープサイズも縮小されることはありませんが、OOMには含まれないため、心配する必要はありません。
それでは、Honey CombのSDK Startについて話しましょう。
ビットマップはVMヒープに格納されます。ネイティブメモリはOOMにはカウントされません。
OOMの条件ははるかに単純です。{VMヒープサイズ}> = {デバイスのVMヒープサイズ制限}。
そのため、同じヒープサイズ制限でビットマップを作成するためのメモリの空き容量が増え、OOMがスローされる可能性が低くなります。
これは、ガベージコレクションとメモリリークについての私の観察のいくつかです。
あなたはアプリでそれを自分で見ることができます。アクティビティが破棄された後もまだ実行されていたAsyncTaskをアクティビティが実行した場合、そのアクティビティはAsyncTaskが終了するまでガベージコレクトされません。
これは、AsyncTaskが匿名の内部クラスのインスタンスであり、Activityの参照を保持しているためです。
タスクがバックグラウンドスレッドのIO操作でブロックされている場合、AsyncTask.cancel(true)を呼び出しても実行は停止しません。
コールバックも匿名の内部クラスなので、プロジェクト内の静的インスタンスがそれらを保持していて解放しないと、メモリがリークします。
Timerなどの繰り返しタスクまたは遅延タスクをスケジュールし、onPause()でcancel()およびpurge()を呼び出さないと、メモリがリークします。
最近、OOMの例外とキャッシュについて多くの質問を受けました。開発者ガイドには 本当に良い記事 がありますが、それを適切な方法で実装できない人もいます。
このため、Android環境でのキャッシングを実演するサンプルアプリケーションを書きました。この実装はまだOOMを取得していません。
ソースコードへのリンクについては、この回答の最後をご覧ください。
ListView
が離れている場合、それは単にその間のビットマップをダウンロードしないでしょう。ダウンロードされている画像はFlickrからの画像(75×75)です。ただし、処理したい画像のURLをすべて指定してください。最大サイズを超えると、アプリケーションで縮小されます。このアプリケーションでは、URLは単にString
配列にあります。
LruCache
はビットマップを扱う良い方法を持っています。ただし、このアプリケーションでは、アプリケーションをより実行可能にするために、作成した別のキャッシュクラス内にLruCache
のインスタンスを配置しています。
Cache.Javaの重要なもの(loadBitmap()
メソッドが最も重要です):
public Cache(int size, int maxWidth, int maxHeight) {
// Into the constructor you add the maximum pixels
// that you want to allow in order to not scale images.
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
mBitmapCache = new LruCache<String, Bitmap>(size) {
protected int sizeOf(String key, Bitmap b) {
// Assuming that one pixel contains four bytes.
return b.getHeight() * b.getWidth() * 4;
}
};
mCurrentTasks = new ArrayList<String>();
}
/**
* Gets a bitmap from cache.
* If it is not in cache, this method will:
*
* 1: check if the bitmap url is currently being processed in the
* BitmapLoaderTask and cancel if it is already in a task (a control to see
* if it's inside the currentTasks list).
*
* 2: check if an internet connection is available and continue if so.
*
* 3: download the bitmap, scale the bitmap if necessary and put it into
* the memory cache.
*
* 4: Remove the bitmap url from the currentTasks list.
*
* 5: Notify the ListAdapter.
*
* @param mainActivity - Reference to activity object, in order to
* call notifyDataSetChanged() on the ListAdapter.
* @param imageKey - The bitmap url (will be the key).
* @param imageView - The ImageView that should get an
* available bitmap or a placeholder image.
* @param isScrolling - If set to true, we skip executing more tasks since
* the user probably has flinged away the view.
*/
public void loadBitmap(MainActivity mainActivity,
String imageKey, ImageView imageView,
boolean isScrolling) {
final Bitmap bitmap = getBitmapFromCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.ic_launcher);
if (!isScrolling && !mCurrentTasks.contains(imageKey) &&
mainActivity.internetIsAvailable()) {
BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
mainActivity.getAdapter());
task.execute();
}
}
}
ディスクキャッシュを実装しないのであれば、Cache.Javaファイルの内容を編集する必要はありません。
MainActivity.Javaの重要なもの:
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (view.getId() == Android.R.id.list) {
// Set scrolling to true only if the user has flinged the
// ListView away, hence we skip downloading a series
// of unnecessary bitmaps that the user probably
// just want to skip anyways. If we scroll slowly it
// will still download bitmaps - that means
// that the application won't wait for the user
// to lift its finger off the screen in order to
// download.
if (scrollState == SCROLL_STATE_FLING) {
mIsScrolling = true;
} else {
mIsScrolling = false;
mListAdapter.notifyDataSetChanged();
}
}
}
// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View row = convertView;
final ViewHolder holder;
if (row == null) {
LayoutInflater inflater = getLayoutInflater();
row = inflater.inflate(R.layout.main_listview_row, parent, false);
holder = new ViewHolder(row);
row.setTag(holder);
} else {
holder = (ViewHolder) row.getTag();
}
final Row rowObject = getItem(position);
// Look at the loadBitmap() method description...
holder.mTextView.setText(rowObject.mText);
mCache.loadBitmap(MainActivity.this,
rowObject.mBitmapUrl, holder.mImageView,
mIsScrolling);
return row;
}
getView()
は頻繁に呼ばれます。行ごとに無限のスレッド数を開始しないことを確認するためのチェックを実装していない場合は、イメージをそこからダウンロードするのは通常お勧めできません。 Cache.Javaは、rowObject.mBitmapUrl
がすでにタスク内にあるかどうかを確認し、ある場合は別のタスクを開始しません。したがって、ほとんどの場合、AsyncTask
プールの作業キュー制限を超えていません。
ソースコードは https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.Zip からダウンロードできます。
私は今数週間これをテストしました、私はまだ単一のOOM例外を得ていません。エミュレータ、Nexus One、およびNexus Sでこれをテストしました。HD品質の画像を含む画像URLをテストしました。唯一のボトルネックは、ダウンロードに時間がかかることです。
OOMが表示されることを想像できるシナリオは1つしかありません。つまり、非常に大きなイメージを多数ダウンロードした場合、それらが拡大縮小されてキャッシュに入れられる前に、同時により多くのメモリを消費してOOMが発生します。しかし、それでも理想的な状況ではなく、より現実的な方法で解決することは不可能でしょう。
コメントにエラーを報告してください。 :-)
私は次のようにして画像を撮り、その場でサイズを変更しました。お役に立てれば
Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);
残念ながら 上記のどれも上手く行かない場合は、これを Manifest ファイルに追加してください。 Inside アプリケーション tag
<application
Android:largeHeap="true"
これは非常に長期にわたる問題であり、さまざまな説明があります。私はここで最も一般的に提示された2つの回答のアドバイスを受けましたが、どちらもバイトに デコード 一部を実行する余裕がないと主張したVMプロセス。少し掘った後、私はここで本当の問題は _ native _ ヒープから奪う復号化プロセスであることを学びました。
こちらを参照してください。 BitmapFactory OOMで私のナッツを動かす
それは私がこの問題へのより多くの解決策を見つけた別の議論スレッドに私を導きました。一つは、画像が表示された後に手動でSystem.gc();
を呼び出すことです。しかし、それは実際にはネイティブヒープを減らすための努力として、あなたのアプリがより多くのメモリを使うようにします。 2.0(ドーナツ)のリリースの時点でより良い解決策はBitmapFactoryオプション "inPurgeable"を使うことです。だから私は単にo2.inPurgeable=true;
の直後にo2.inSampleSize=scale;
を追加しました。
ここでそのトピックの詳細: メモリヒープの制限はわずか6Mですか?
さて、これをすべて言ったので、私はJavaとAndroidにも完全に敬意を表しています。あなたがこれがこの問題を解決するためのひどい方法であると思うなら、あなたはおそらく正しいです。 ;-)しかし、これは私にとっては驚異的なことですが、VMを現在ヒープキャッシュから実行することは不可能であることがわかりました。私が見つけることができる唯一の欠点はあなたがあなたのキャッシュされた描画イメージを捨てているということです。つまり、その画像に戻っても、そのたびに再描画されます。私のアプリケーションがどのように動作するかの場合、それは実際には問題ではありません。あなたのマイレージは異なる場合があります。
これを使うbitmap.recycle();
これは画質の問題なしに役立ちます。
次のようにして同じ問題を解決しました。
Bitmap b = null;
Drawable d;
ImageView i = new ImageView(mContext);
try {
b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);
b.eraseColor(0xFFFFFFFF);
Rect r = new Rect(0, 0,320 , 424);
Canvas c = new Canvas(b);
Paint p = new Paint();
p.setColor(0xFFC0C0C0);
c.drawRect(r, p);
d = mContext.getResources().getDrawable(mImageIds[position]);
d.setBounds(r);
d.draw(c);
/*
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inTempStorage = new byte[128*1024];
b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);
o2.inSampleSize=16;
o2.inPurgeable = true;
*/
} catch (Exception e) {
}
i.setImageBitmap(b);
スケーリングを必要としない、はるかに効果的な解決策があります。ビットマップを一度だけデコードしてから、その名前に対してマップにキャッシュするだけです。それから単純に名前に対してビットマップを取得してImageViewでそれを設定します。やるべきことはこれ以上ありません。
デコードされたビットマップの実際のバイナリデータがdalvik VMヒープ内に格納されていないため、これは機能します。外部に保存されます。そのため、ビットマップをデコードするたびに、VMヒープの外側にメモリが割り当てられ、GCによって回収されることはありません。
あなたがこれをよりよく理解するのを助けるために、あなたがdrawableフォルダにur画像を保存したと想像してください。画像を取得するには、getResources()。getDrwable(R.drawable。)を実行します。これは毎回あなたのイメージをデコードするのではなく、あなたがそれを呼び出すたびに既にデコードされたインスタンスを再利用します。だから本質的にそれはキャッシュされます。
あなたの画像はどこかのファイルに入っているので(あるいは外部サーバーから来ているかもしれません)、それが必要なところで再利用されるためにデコードされたビットマップインスタンスをキャッシュすることはあなたの責任です。
お役に立てれば。
ここに2つの問題があります。
これは私のために働いた!
public Bitmap readAssetsBitmap(String filename) throws IOException {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
if(bitmap == null) {
throw new IOException("File cannot be opened: It's value is null");
} else {
return bitmap;
}
} catch (IOException e) {
throw new IOException("File cannot be opened: " + e.getMessage());
}
}
ここではすばらしい答えがありますが、この問題に対処するために 完全に使用可能なクラス が必要でした。
これが私の BitmapHelperクラス ですOutOfMemoryErrorプルーフです:-)
import Java.io.File;
import Java.io.FileInputStream;
import Android.graphics.Bitmap;
import Android.graphics.Bitmap.Config;
import Android.graphics.BitmapFactory;
import Android.graphics.Canvas;
import Android.graphics.Matrix;
import Android.graphics.drawable.BitmapDrawable;
import Android.graphics.drawable.Drawable;
public class BitmapHelper
{
//decodes image and scales it to reduce memory consumption
public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
{
try
{
//Decode image size
BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
bitmapSizeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);
// load image using inSampleSize adapted to required image size
BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
bitmapDecodeOptions.inPurgeable = true;
bitmapDecodeOptions.inDither = !quickAndDirty;
bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;
Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);
// scale bitmap to mathc required size (and keep aspect ratio)
float srcWidth = (float) bitmapDecodeOptions.outWidth;
float srcHeight = (float) bitmapDecodeOptions.outHeight;
float dstWidth = (float) requiredWidth;
float dstHeight = (float) requiredHeight;
float srcAspectRatio = srcWidth / srcHeight;
float dstAspectRatio = dstWidth / dstHeight;
// recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
// (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
// Java.lang.RuntimeException: Canvas: trying to use a recycled bitmap Android.graphics.Bitmap@416ee7d8
// I do not excatly understand why, but this way it's OK
boolean recycleDecodedBitmap = false;
Bitmap scaledBitmap = decodedBitmap;
if (srcAspectRatio < dstAspectRatio)
{
scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
// will recycle recycleDecodedBitmap
recycleDecodedBitmap = true;
}
else if (srcAspectRatio > dstAspectRatio)
{
scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
recycleDecodedBitmap = true;
}
// crop image to match required image size
int scaledBitmapWidth = scaledBitmap.getWidth();
int scaledBitmapHeight = scaledBitmap.getHeight();
Bitmap croppedBitmap = scaledBitmap;
if (scaledBitmapWidth > requiredWidth)
{
int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
scaledBitmap.recycle();
}
else if (scaledBitmapHeight > requiredHeight)
{
int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
scaledBitmap.recycle();
}
if (recycleDecodedBitmap)
{
decodedBitmap.recycle();
}
decodedBitmap = null;
scaledBitmap = null;
return croppedBitmap;
}
catch (Exception ex)
{
ex.printStackTrace();
}
return null;
}
/**
* compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
*
* @param requiredWidth
* @param requiredHeight
* @param powerOf2
* weither we want a power of 2 sclae or not
* @return
*/
public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
{
int inSampleSize = 1;
// Raw height and width of image
final int srcHeight = options.outHeight;
final int srcWidth = options.outWidth;
if (powerOf2)
{
//Find the correct scale value. It should be the power of 2.
int tmpWidth = srcWidth, tmpHeight = srcHeight;
while (true)
{
if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
break;
tmpWidth /= 2;
tmpHeight /= 2;
inSampleSize *= 2;
}
}
else
{
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);
// 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;
}
public static Bitmap drawableToBitmap(Drawable drawable)
{
if (drawable instanceof BitmapDrawable)
{
return ((BitmapDrawable) drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
{
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// CREATE A MATRIX FOR THE MANIPULATION
Matrix matrix = new Matrix();
// RESIZE THE BIT MAP
matrix.postScale(scaleWidth, scaleHeight);
// RECREATE THE NEW BITMAP
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
return resizedBitmap;
}
}
これは私のために働きます。
Bitmap myBitmap;
BitmapFactory.Options options = new BitmapFactory.Options();
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;
File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);
そしてこれはC#のmonodroidにあります。あなたは簡単に画像のパスを変更することができます。ここで重要なのは設定するオプションです。
上記の答えのどれも私のために働きませんでした、しかし私は問題を解決する恐ろしく醜い回避策を思い付きました。私はプロジェクトに非常に小さい1x1ピクセルの画像をリソースとして追加し、ガベージコレクションを呼び出す前にそれを私のImageViewにロードしました。 ImageViewがBitmapをリリースしていなかったのではないかと思われるので、GCはそれを拾い上げませんでした。それは醜いですが、今のところうまくいっているようです。
if (bitmap != null)
{
bitmap.recycle();
bitmap = null;
}
if (imageView != null)
{
imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.
}
System.gc();
imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.
これはコミュニティと画像をロードして処理するための私のユーティリティクラスを共有するのに適切な場所のように思えます、あなたはそれを使い自由にそれを修正することを歓迎します。
package com.emil;
import Java.io.IOException;
import Java.io.InputStream;
import Android.graphics.Bitmap;
import Android.graphics.BitmapFactory;
/**
* A class to load and process images of various sizes from input streams and file paths.
*
* @author Emil http://stackoverflow.com/users/220710/emil
*
*/
public class ImageProcessing {
public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
if(ImageProcessing.checkDecode(options)){
return bm;
}else{
throw new IOException("Image decoding failed, using stream.");
}
}
public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
if(ImageProcessing.checkDecode(options)){
return bm;
}else{
throw new IOException("Image decoding failed, using file path.");
}
}
public static Dimensions getDimensions(InputStream stream) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
BitmapFactory.decodeStream(stream,null,options);
if(ImageProcessing.checkDecode(options)){
return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
}else{
throw new IOException("Image decoding failed, using stream.");
}
}
public static Dimensions getDimensions(String imgPath) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
BitmapFactory.decodeFile(imgPath,options);
if(ImageProcessing.checkDecode(options)){
return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
}else{
throw new IOException("Image decoding failed, using file path.");
}
}
private static boolean checkDecode(BitmapFactory.Options options){
// Did decode work?
if( options.outWidth<0 || options.outHeight<0 ){
return false;
}else{
return true;
}
}
/**
* Creates a Bitmap that is of the minimum dimensions necessary
* @param bm
* @param min
* @return
*/
public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
int newWidth, newHeight;
switch(min.type){
case WIDTH:
if(bm.getWidth()>min.minWidth){
newWidth=min.minWidth;
newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
break;
case HEIGHT:
if(bm.getHeight()>min.minHeight){
newHeight=min.minHeight;
newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
break;
case BOTH: // minimize to the maximum dimension
case MAX:
if(bm.getHeight()>bm.getWidth()){
// Height needs to minimized
min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
if(bm.getHeight()>min.minDim){
newHeight=min.minDim;
newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
}else{
// Width needs to be minimized
min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
if(bm.getWidth()>min.minDim){
newWidth=min.minDim;
newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
}
break;
default:
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
}
public static int getScaledWidth(int height, Bitmap bm){
return (int)(((double)bm.getWidth()/bm.getHeight())*height);
}
public static int getScaledHeight(int width, Bitmap bm){
return (int)(((double)bm.getHeight()/bm.getWidth())*width);
}
/**
* Get the proper sample size to meet minimization restraints
* @param dim
* @param min
* @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
* @return
*/
public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
switch(min.type){
case WIDTH:
return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
case HEIGHT:
return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
case BOTH:
int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
// Return the smaller of the two
if(widthMaxSampleSize<heightMaxSampleSize){
return widthMaxSampleSize;
}else{
return heightMaxSampleSize;
}
case MAX:
// Find the larger dimension and go bases on that
if(dim.width>dim.height){
return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
}else{
return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
}
}
return 1;
}
public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
int add=multipleOf2 ? 2 : 1;
int size=0;
while(min<(dim/(size+add))){
size+=add;
}
size = size==0 ? 1 : size;
return size;
}
public static class Dimensions {
int width;
int height;
public Dimensions(int width, int height) {
super();
this.width = width;
this.height = height;
}
@Override
public String toString() {
return width+" x "+height;
}
}
public static class Minimize {
public enum Type {
WIDTH,HEIGHT,BOTH,MAX
}
Integer minWidth;
Integer minHeight;
Integer minDim;
Type type;
public Minimize(int min, Type type) {
super();
this.type = type;
switch(type){
case WIDTH:
this.minWidth=min;
break;
case HEIGHT:
this.minHeight=min;
break;
case BOTH:
this.minWidth=min;
this.minHeight=min;
break;
case MAX:
this.minDim=min;
break;
}
}
public Minimize(int minWidth, int minHeight) {
super();
this.type=Type.BOTH;
this.minWidth = minWidth;
this.minHeight = minHeight;
}
}
/**
* Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
* @param width
* @param height
* @param config
* @return
*/
public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
long pixels=width*height;
switch(config){
case ALPHA_8: // 1 byte per pixel
return pixels;
case ARGB_4444: // 2 bytes per pixel, but depreciated
return pixels*2;
case ARGB_8888: // 4 bytes per pixel
return pixels*4;
case RGB_565: // 2 bytes per pixel
return pixels*2;
default:
return pixels;
}
}
private static BitmapFactory.Options getOptionsForDimensions(){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;
return options;
}
private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inDither = false;
options.inSampleSize = sampleSize;
options.inScaled = false;
options.inPreferredConfig = bitmapConfig;
return options;
}
}
私のアプリケーションの一つで私はCamera/Gallery
から写真を撮る必要があります。ユーザーがカメラから画像をクリックした場合(2MP、5MPまたは8MP)、画像サイズはkB
sからMB
sまで異なります。イメージのサイズが上記のコードよりも小さい(または最大1〜2 MB)場合でも、4 MBまたは5 MBを超えるサイズのイメージがある場合は、OOM
がframe :(に入ってきます。
それから私はこの問題を解決するために働いてきました&ついに私はFedorの(そのような素晴らしい解決策を作るためのFedorへのすべての信用)コードに以下の改良をしました:)
private Bitmap decodeFile(String fPath) {
// Decode image size
BitmapFactory.Options opts = new BitmapFactory.Options();
/*
* If set to true, the decoder will return null (no bitmap), but the
* out... fields will still be set, allowing the caller to query the
* bitmap without having to allocate the memory for its pixels.
*/
opts.inJustDecodeBounds = true;
opts.inDither = false; // Disable Dithering mode
opts.inPurgeable = true; // Tell to gc that whether it needs free
// memory, the Bitmap can be cleared
opts.inInputShareable = true; // Which kind of reference will be used to
// recover the Bitmap data after being
// clear, when it will be used in the
// future
BitmapFactory.decodeFile(fPath, opts);
// The new size we want to scale to
final int REQUIRED_SIZE = 70;
// Find the correct scale value.
int scale = 1;
if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) opts.outHeight
/ (float) REQUIRED_SIZE);
final int widthRatio = Math.round((float) opts.outWidth
/ (float) REQUIRED_SIZE);
// 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.
scale = heightRatio < widthRatio ? heightRatio : widthRatio;//
}
// Decode bitmap with inSampleSize set
opts.inJustDecodeBounds = false;
opts.inSampleSize = scale;
Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(
Bitmap.Config.RGB_565, false);
return bm;
}
私はこれが同じ問題に直面している仲間を助けることを願っています!
詳しくは this を参照してください。
数分前にこの問題に遭遇しました。私は私のリストビューアダプタを管理するのにより良い仕事をすることによってそれを解決しました。私はそれが私が使っていた何百もの50x50px画像の問題であると思いました、私は私のカスタム表示を行が表示されているたびに膨らませようとしていたことがわかります。行が膨らんでいるかどうかを確認するためにテストするだけで、このエラーを排除し、何百ものビットマップを使用しています。これは実際にはSpinner用ですが、基本アダプターはListViewでも同じように機能します。この簡単な修正によって、アダプタのパフォーマンスも大幅に向上しました。
@Override
public View getView(final int position, View convertView, final ViewGroup parent) {
if(convertView == null){
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.spinner_row, null);
}
...
この問題はAndroidエミュレータでのみ発生します。私はエミュレータでもこの問題に直面していましたが、デバイスをチェックインすると問題なく動作しました。
デバイスをチェックインしてください。それは装置で動くかもしれません。
私は一日中これらの解決策のテストに費やしました、そして私のために働いた唯一のものはイメージを得て、手動でGCを呼び出すための上記のアプローチです。アプリをアクティビティ間で切り替えて負荷の高いテストを実行したとき。私のアプリのリストビューにサムネイル画像のリストがあり(アクティビティAと言います)、それらの画像の1つをクリックすると、そのアイテムのメイン画像を表示する別のアクティビティ(アクティビティBと言います)に移動します。 2つのアクティビティを切り替えると、最終的にOOMエラーが発生し、アプリケーションが強制終了します。
リストビューの途中まで来たらクラッシュします。
これで、アクティビティBで次のことを実行すると、問題なくリストビュー全体を見て行き来し続けることができます...そしてそれは十分に高速です。
@Override
public void onDestroy()
{
Cleanup();
super.onDestroy();
}
private void Cleanup()
{
bitmap.recycle();
System.gc();
Runtime.getRuntime().gc();
}
sdCardから選択するか、ビットマップオブジェクトを変換するために描画可能なすべての画像にこれらのコードを使用します。
Resources res = getResources();
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
@SuppressWarnings("deprecation")
int width = display.getWidth();
@SuppressWarnings("deprecation")
int height = display.getHeight();
try {
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
System.gc();
}
bitmap = Bitmap.createScaledBitmap(BitmapFactory
.decodeFile(ImageData_Path.get(img_pos).getPath()),
width, height, true);
} catch (OutOfMemoryError e) {
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
System.gc();
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.RGB_565;
options.inSampleSize = 1;
options.inPurgeable = true;
bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos)
.getPath().toString(), options), width, height,true);
}
return bitmap;
ImageData_Path.get(img_pos).getPath() の代わりに、イメージパスを使用してください。
私の2セント:私はビットマップで私のOOMエラーを解決しました:
a)画像を2倍に拡大縮小する
b)私のカスタムAdapterでListView用に Picasso libraryを使い、getViewで次のように1回呼び出します。Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);
通常、Androidデバイスのヒープサイズは16MBしかありません(デバイス/ OSによって異なります。post Heap Sizes を参照)。画像を読み込んでいて16MBのサイズを超えた場合は、 SDカードから、またはリソースから、あるいはネットワークからでもイメージをロードするためのビットマップは、getImageUriを使用して、より多くのメモリを必要とします。
そのようなOutofMemoryException
はSystem.gc()
などを呼び出して完全に解決することはできません。
活動ライフサイクル を参照することによって
アクティビティ状態は、各プロセスのメモリ使用量と各プロセスの優先順位に応じて、OS自体によって決定されます。
使用されている各ビットマップ画像のサイズと解像度を検討してください。私は、サイズを小さくし、より低い解像度にリサンプリングする、ギャラリーのデザインを参考にすることをお勧めします(1つの小さな画像PNGと1つの元の画像)
このコードはdrawableから大きなビットマップをロードするのを助けます
public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {
Context context;
public BitmapUtilsTask(Context context) {
this.context = context;
}
/**
* Loads a bitmap from the specified url.
*
* @param url The location of the bitmap asset
* @return The bitmap, or null if it could not be loaded
* @throws IOException
* @throws MalformedURLException
*/
public Bitmap getBitmap() throws MalformedURLException, IOException {
// Get the source image's dimensions
int desiredWidth = 1000;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);
int srcWidth = options.outWidth;
int srcHeight = options.outHeight;
// Only scale if the source is big enough. This code is just trying
// to fit a image into a certain width.
if (desiredWidth > srcWidth)
desiredWidth = srcWidth;
// Calculate the correct inSampleSize/scale value. This helps reduce
// memory use. It should be a power of 2
int inSampleSize = 1;
while (srcWidth / 2 > desiredWidth) {
srcWidth /= 2;
srcHeight /= 2;
inSampleSize *= 2;
}
// Decode with inSampleSize
options.inJustDecodeBounds = false;
options.inDither = false;
options.inSampleSize = inSampleSize;
options.inScaled = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inPurgeable = true;
Bitmap sampledSrcBitmap;
sampledSrcBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);
return sampledSrcBitmap;
}
/**
* The system calls this to perform work in a worker thread and delivers
* it the parameters given to AsyncTask.execute()
*/
@Override
protected Bitmap doInBackground(Object... item) {
try {
return getBitmap();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
ここでのすべての解決策はIMAGE_MAX_SIZEを設定する必要があります。これにより、より強力なハードウェアを持つデバイスが制限され、画像サイズが小さすぎるとHD画面で見苦しくなります。
私は、Samsung Galaxy S3と、それほど強力ではないものを含む他のいくつかの機器で、より強力な機器を使用するとより良い画質で動作する解決策を思いつきました。
その要点は、特定のデバイス上のアプリに割り当てられている最大メモリを計算し、次にこのメモリを超えずに可能な限り小さくなるようにスケールを設定することです。これがコードです:
public static Bitmap decodeFile(File f)
{
Bitmap b = null;
try
{
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);
try
{
BitmapFactory.decodeStream(fis, null, o);
}
finally
{
fis.close();
}
// In Samsung Galaxy S3, typically max memory is 64mb
// Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
// If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
// We try use 25% memory which equals to 16mb maximum for one bitmap
long maxMemory = Runtime.getRuntime().maxMemory();
int maxMemoryForImage = (int) (maxMemory / 100 * 25);
// Refer to
// http://developer.Android.com/training/displaying-bitmaps/cache-bitmap.html
// A full screen GridView filled with images on a device with
// 800x480 resolution would use around 1.5MB (800*480*4 bytes)
// When bitmap option's inSampleSize doubled, pixel height and
// weight both reduce in half
int scale = 1;
while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
scale *= 2;
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
try
{
b = BitmapFactory.decodeStream(fis, null, o2);
}
finally
{
fis.close();
}
}
catch (IOException e)
{
}
return b;
}
このビットマップで使用される最大メモリを最大割り当てメモリの25%に設定します。必要に応じてこれを調整し、このビットマップがクリーンアップされ、使用後にメモリに残っていないことを確認してください。通常、私はこのコードを使用して画像の回転(ソースとデスティネーションのビットマップ)を実行しているので、アプリはメモリに2つのビットマップを同時にロードする必要があります。
これが誰かに役立つことを願っています..
それはあなたが使ったイメージのサイズが非常に大きいようです。だからいくつかの古いデバイスクラッシュはヒープメモリがいっぱいであるために起こります。古いデバイス(ハニカムまたはICSまたは任意のローエンドモデルデバイス)ではAndroid:largeHeap="true"
を使ってみてくださいアプリケーションタグの下にマニフェストファイルを作成するか、以下のコードを使用してビットマップのサイズを小さくします。
Bitmap bMap;
BitmapFactory.Options options = new BitmapFactory.Options();
options.InSampleSize = 8;
bMap= BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);
ビットマップサイズを小さくするために4、12、16を指定することもできます。
BitmapFactory.Options options = new Options();
options.inSampleSize = 32;
//img = BitmapFactory.decodeFile(imageids[position], options);
Bitmap theImage = BitmapFactory.decodeStream(imageStream,null, options);
Bitmap img=theImage.copy(Bitmap.Config.RGB_565,true);
theImage.recycle();
theImage = null;
System.gc();
//ivlogdp.setImageBitmap(img);
Runtime.getRuntime().gc();
Thomas Vervestの方法を試しましたが、IMAGE_MAX_SIZEが2048の場合、画像サイズ2592 x 1944に対してスケール1が返されます。
このバージョンは他の人から提供された他のすべてのコメントに基づいて私のために働いた:
private Bitmap decodeFile (File f) {
Bitmap b = null;
try {
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options ();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream (f);
try {
BitmapFactory.decodeStream (fis, null, o);
} finally {
fis.close ();
}
int scale = 1;
for (int size = Math.max (o.outHeight, o.outWidth);
(size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options ();
o2.inSampleSize = scale;
fis = new FileInputStream (f);
try {
b = BitmapFactory.decodeStream (fis, null, o2);
} finally {
fis.close ();
}
} catch (IOException e) {
}
return b;
}
OutOfMemoryを修正するには、次のコードを試してください。
public Bitmap loadBitmap(String URL, BitmapFactory.Options options) {
Bitmap bitmap = null;
InputStream in = null;
options.inSampleSize=4;
try {
in = OpenHttpConnection(URL);
Log.e("In====>", in+"");
bitmap = BitmapFactory.decodeStream(in, null, options);
Log.e("URL====>", bitmap+"");
in.close();
} catch (IOException e1) {
}
return bitmap;
}
そして
try {
BitmapFactory.Options bmOptions;
bmOptions = new BitmapFactory.Options();
bmOptions.inSampleSize = 1;
if(studentImage != null){
galleryThumbnail= loadBitmap(IMAGE_URL+studentImage, bmOptions);
}
galleryThumbnail=getResizedBitmap(galleryThumbnail, imgEditStudentPhoto.getHeight(), imgEditStudentPhoto.getWidth());
Log.e("Image_Url==>",IMAGE_URL+studentImage+"");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
こんにちはリンクを訪問してください http://developer.Android.com/training/displaying-bitmaps/index.html
または単に与えられた関数でビットマップを取得しようとする
private Bitmap decodeBitmapFile (File f) {
Bitmap bitmap = null;
try {
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options ();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream (f);
try {
BitmapFactory.decodeStream (fis, null, o);
} finally {
fis.close ();
}
int scale = 1;
for (int size = Math.max (o.outHeight, o.outWidth);
(size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);
// Decode with input-stram SampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options ();
o2.inSampleSize = scale;
fis = new FileInputStream (f);
try {
bitmap = BitmapFactory.decodeStream (fis, null, o2);
} finally {
fis.close ();
}
} catch (IOException e) {
}
return bitmap ;
}
この概念を使用して、これはあなたを助けるでしょう、その後イメージビューでimagebitmapを設定します
public static Bitmap convertBitmap(String path) {
Bitmap bitmap=null;
BitmapFactory.Options bfOptions=new BitmapFactory.Options();
bfOptions.inDither=false; //Disable Dithering mode
bfOptions.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared
bfOptions.inInputShareable=true; //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
bfOptions.inTempStorage=new byte[32 * 1024];
File file=new File(path);
FileInputStream fs=null;
try {
fs = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
if(fs!=null)
{
bitmap=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
}
} catch (IOException e) {
e.printStackTrace();
} finally{
if(fs!=null) {
try {
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}
あなたが60と60のような高さと幅で大きい画像から小さい画像を作りたい、そしてリストビューを速くスクロールしたいなら、この概念を使ってください
public static Bitmap decodeSampledBitmapFromPath(String path, int reqWidth,
int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeFile(path, options);
return bmp;
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float) height / (float) reqHeight);
} else {
inSampleSize = Math.round((float) width / (float) reqWidth);
}
}
return inSampleSize;
}
お役に立てば幸いです。
開発者サイトから助けを借りることができます ここ
すべての答えを調べた後、私は誰も画像処理のためのGlide APIについて言及していなかったことに驚きました。優れたライブラリで、ビットマップ管理の複雑さをすべて抽象化しています。このライブラリと1行のコードですばやく画像を読み込んでサイズを変更できます。
Glide.with(this).load(yourImageResource).into(imageview);
リポジトリはこちらから入手できます。 https://github.com/bumptech/glide
Manifest.xmlファイルに次の行を追加します。
<application
Android:hardwareAccelerated="false"
Android:largeHeap="true"
<activity>
</activity>
</application>
ビットマップのメモリリークまたはOOMを回避するためのベストプラクティス
あなたが私のように怠け者なら。 Picassoライブラリを使って画像を読み込むことができます。 http://square.github.io/picasso/
Picasso.with(context).load(R.drawable.landing_screen).into(imageView1); Picasso.with(context).load("file:///Android_asset/DvpvklR.png").into(imageView2); Picasso.with(context).load(new File(...)).into(imageView3);
私は私のために働いたデコードファイル記述子を使用しました:
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
FileDescriptor fd = fileInputStream.getFD();
Bitmap imageBitmap = decodeSampledBitmapFromDescriptor(fd , 612,
816);
imageView.setImageBitmap(imageBitmap);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ファイルディスクリプタからサンプルビットマップをデコードするコード:
/**
* Decode and sample down a bitmap from a file input stream to the requested width and height.
*
* @param fileDescriptor The file descriptor to read from
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public static Bitmap decodeSampledBitmapFromDescriptor(
FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
}
/**
* Calculate an inSampleSize for use in a {@link Android.graphics.BitmapFactory.Options} object when decoding
* bitmaps using the decode* methods from {@link Android.graphics.BitmapFactory}. This implementation calculates
* the closest inSampleSize that will result in the final decoded bitmap having a width and
* height equal to or larger than the requested width and height. This implementation does not
* ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
* results in a larger bitmap which isn't as useful for caching purposes.
*
* @param options An options object with out* params already populated (run through a decode*
* method with inJustDecodeBounds==true
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @return The value to be used for inSampleSize
*/
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;
// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger inSampleSize).
final float totalPixels = width * height;
// Anything more than 2x the requested pixels we'll sample down further
final float totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++;
}
}
return inSampleSize;
}
これは適切なビットマップを取得し、メモリ消費を減らすでしょう
Java
Bitmap bm = null;
BitmapFactory.Options bmpOption = new BitmapFactory.Options();
bmpOption.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(file);
BitmapFactory.decodeStream(fis, null, bmpOption);
fis.close();
int scale = 1;
if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE) {
scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE /
(double) Math.max(bmpOption.outHeight, bmpOption.outWidth)) / Math.log(0.5)));
}
BitmapFactory.Options bmpOption2 = new BitmapFactory.Options();
bmpOption2.inSampleSize = scale;
fis = new FileInputStream(file);
bm = BitmapFactory.decodeStream(fis, null, bmpOption2);
fis.close();
コトリン
val bm:Bitmap = null
val bmpOption = BitmapFactory.Options()
bmpOption.inJustDecodeBounds = true
val fis = FileInputStream(file)
BitmapFactory.decodeStream(fis, null, bmpOption)
fis.close()
val scale = 1
if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE)
{
scale = Math.pow(2.0, Math.ceil((Math.log((IMAGE_MAX_SIZE / Math.max(bmpOption.outHeight, bmpOption.outWidth) as Double)) / Math.log(0.5))).toInt().toDouble()).toInt()
}
val bmpOption2 = BitmapFactory.Options()
bmpOption2.inSampleSize = scale
fis = FileInputStream(file)
bm = BitmapFactory.decodeStream(fis, null, bmpOption2)
fis.close()