錯誤訊息:
java.lang.OutOfMemoryError: bitmap size exceeds VM budget
at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
...
原因:嘗試將高解析度圖片(如 4000x3000 像素 ≈ 48MB 記憶體)完整載入 Heap,超過 Dalvik/ART 單一 App 記憶體上限(通常 16-256MB)。
只載入**縮小後**的像素資料,大幅降低記憶體佔用。
public static Bitmap decodeSampledBitmapFromFile(String path, int reqWidth, int reqHeight) {
// 1. 僅解析邊界資訊,不載入像素
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
// 2. 計算最佳縮放比例 (2 的冪次)
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 3. 正式解碼載入像素
options.inJustDecodeBounds = false;
// 可選:設定解碼格式為 RGB_565 節省一半記憶體 (無透明通道)
// options.inPreferredConfig = Bitmap.Config.RGB_565;
return BitmapFactory.decodeFile(path, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
if (reqWidth == 0 || reqHeight == 0) return 1;
int inSampleSize = 1;
if (options.outHeight > reqHeight || options.outWidth > reqWidth) {
final int halfH = options.outHeight / 2;
final int halfW = options.outWidth / 2;
while ((halfH / inSampleSize) >= reqHeight && (halfW / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
// ImageView 顯示大小 200x200 dp
int targetW = dpToPx(200);
int targetH = dpToPx(200);
Bitmap bitmap = decodeSampledBitmapFromFile(filePath, targetW, targetH);
imageView.setImageBitmap(bitmap);
| 技巧 | 效果 | 適用場景 |
|---|---|---|
inPreferredConfig = RGB_565 | 記憶體減半 (16-bit vs 32-bit) | 不需透明通道的照片、背景圖 |
inPurgeable = true (API < 21) | 允許系統在記憶體不足時丟棄像素 | 舊版 Android 大量圖片快取 |
inMutable = true | 允許像素修改,支援重複使用 Bitmap | 自訂快取池、圖片處理管線 |
使用 LruCache 快取 | 避免重複解碼、自動淘汰舊圖 | 列表、畫廊、重複顯示圖片 |
| 釋放 Bitmap | bitmap.recycle() (API < 21) / 讓 GC 回收 | 不再使用的大圖片立即釋放 |
手動管理次採樣、快取、生命週期、執行緒池、錯誤重試、佔位圖、**極易出錯且難維護**。**生產環境請直接使用**:
Glide.with(context).load(url).override(200,200).into(imageView) — 自動次採樣、生命週期感知、GIF/視訊支援imageView.load(url) { crossfade(true) } — 輕量、現代 API、內建快取Picasso.get().load(url).resize(200,200).into(imageView) — 簡單穩定
6291456-byte external allocation too large // 嘗試分配 6MB 連續記憶體失敗
bitmap size exceeds VM budget // 單一 Bitmap 超過 VM 預算
inSampleSize=2 降為 2000x1500 ≈ 12MB,仍過大;需 inSampleSize=4 降為 1000x750 ≈ 3MB 才安全Drawable.createFromPath → ImageView.setImageURI → SimpleCursorAdapter.bindView:ListView 適配器直接載入原圖路徑,**必須在 bindView 中改用次採樣載入**
@Override
public void bindView(View view, Context context, Cursor cursor) {
ImageView iv = (ImageView) view.findViewById(R.id.image);
String path = cursor.getString(cursor.getColumnIndex("image_path"));
// ❌ 原寫法:直接載入原圖 → OOM
// iv.setImageURI(Uri.parse(path));
// ✅ 修正:使用 Glide 自動處理次採樣、快取、生命週期
Glide.with(context)
.load(new File(path))
.override(targetWidth, targetHeight) // 關鍵:限制最大尺寸
.centerCrop()
.placeholder(R.drawable.placeholder)
.into(iv);
}
徹底解決 Bitmap OOM:**次採樣載入 + 尺寸限制 + 成熟圖片庫**,三管齊下才能安心。