首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Android Bitmap 載入 OOM 解決:大圖次採樣、記憶體管理與現代圖片庫

     編輯:程式設計綜合問答

錯誤訊息:


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)。

核心解決方案:inSampleSize 次採樣載入

只載入**縮小後**的像素資料,大幅降低記憶體佔用。

完整工具方法


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 快取避免重複解碼、自動淘汰舊圖列表、畫廊、重複顯示圖片
釋放 Bitmapbitmap.recycle() (API < 21) / 讓 GC 回收不再使用的大圖片立即釋放

現代最佳實務:直接使用成熟圖片庫

手動管理次採樣、快取、生命週期、執行緒池、錯誤重試、佔位圖、**極易出錯且難維護**。**生產環境請直接使用**:

  • Glide:Glide.with(context).load(url).override(200,200).into(imageView) — 自動次採樣、生命週期感知、GIF/視訊支援
  • Coil (Kotlin 協程):imageView.load(url) { crossfade(true) } — 輕量、現代 API、內建快取
  • Picasso:Picasso.get().load(url).resize(200,200).into(imageView) — 簡單穩定
  • Fresco (Facebook):將 Bitmap 放入 **Ashmem 共享記憶體**,不佔用 Java Heap,適合極大圖片/長列表

針對錯誤日誌的具體分析


6291456-byte external allocation too large  // 嘗試分配 6MB 連續記憶體失敗
bitmap size exceeds VM budget               // 單一 Bitmap 超過 VM 預算
  • 6MB ≈ 2000x800 像素 (ARGB_8888) 或 1200x1200 像素
  • 若原圖 4000x3000,inSampleSize=2 降為 2000x1500 ≈ 12MB,仍過大;需 inSampleSize=4 降為 1000x750 ≈ 3MB 才安全
  • 日誌顯示 Drawable.createFromPath → ImageView.setImageURI → SimpleCursorAdapter.bindView:ListView 適配器直接載入原圖路徑,**必須在 bindView 中改用次採樣載入**

ListView/RecyclerView 適配器修正範例


@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:**次採樣載入 + 尺寸限制 + 成熟圖片庫**,三管齊下才能安心。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved