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

Android PagerAdapter OOM:getCount 返回值錯誤導致無限創建 View 解析

     編輯:程式設計綜合問答

現象:`ViewPager` 只需顯示幾張圖片,滑動幾下就拋 `OutOfMemoryError: Failed to allocate a 22118416-byte allocation`。核心原因:**`getCount()` 返回值過大或邏輯錯誤,導致 `instantiateItem` 被無限調用,瘋狂創建 View/位圖耗盡記憶體**。

錯誤代碼分析


@Override
public int getCount() {
    if (null != imageList) {
        return imageList.length; // 假設 length = 5
    }
    return 0;
}

// 問題不在這裡,關鍵看 instantiateItem 邏輯
@Override
public Object instantiateItem(View container, int position) {
    View v = inflater.inflate(R.layout.activity_scene_page, null);
    ImageView image = v.findViewById(R.id.scene_img);
    image.setBackgroundResource(imageList[position]); // 若 position >= length 會崩潰,但 OOM 說明 position 在範圍內卻被重複調用
    ((ViewPager) container).addView(v);
    return v;
}

真兇:`getCount()` 被修改為 `imageList.length % 10000` 或類似邏輯

有開發者為了實現「無限循環滑動」,將 `getCount()` 改為 `Integer.MAX_VALUE` 或 `length * 10000`,但 **`instantiateItem` 內部直接用 `position` 索引 `imageList` 且無取模**,導致:

  • `position` 從 0 遞增到極大值
  • 每次都 `inflate` 新 View、載入圖片資源
  • `destroyItem` 雖會移除,但 `ViewPager` 預載機制會保留鄰近頁面,記憶體持續增長
  • 最終 OOM

正確的無限循環實現(取模索引 + 限制實例數)


public class LoopPagerAdapter extends PagerAdapter {
    private final Context context;
    private final int[] imageResIds;
    private final LayoutInflater inflater;

    public LoopPagerAdapter(Context context, int[] imageResIds) {
        this.context = context;
        this.imageResIds = imageResIds;
        this.inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        // 無限循環:返回極大值,但實際頁面數 = imageResIds.length
        return imageResIds.length == 0 ? 0 : Integer.MAX_VALUE;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        // ✅ 關鍵:取模映射到真實資源索引
        int realPosition = position % imageResIds.length;

        View v = inflater.inflate(R.layout.activity_scene_page, container, false);
        ImageView image = v.findViewById(R.id.scene_img);
        
        // ✅ 使用 Glide/Picasso 異步載入 + 縮放,避免 OOM
        Glide.with(context)
             .load(imageResIds[realPosition])
             .override(800, 600) // 限制解碼尺寸
             .into(image);

        container.addView(v);
        return v;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View) object);
        // 可選:釋放圖片引用
        View v = (View) object;
        ImageView iv = v.findViewById(R.id.scene_img);
        Glide.with(context).clear(iv);
    }
}

記憶體優化關鍵點

優化點做法
圖片解碼尺寸`BitmapFactory.Options.inSampleSize` 或 Glide `.override(w,h)` 限制最大尺寸
使用圖片加載庫Glide / Picasso / Coil 自動處理緩存、生命週期、OOM 保護
及時釋放`destroyItem` 中 `Glide.clear(imageView)`、置 null
限制預載頁面`viewPager.setOffscreenPageLimit(1)` 預設 1,別設過大
避免在 instantiateItem 分配大對象複用 View(`convertView` 模式)或使用 `RecyclerView` 替代 `ViewPager`

快速排查 OOM 步驟

  1. Android Studio → Profiler → Memory → Capture heap dump → 分析 `byte[]`、`Bitmap` 佔用來源。
  2. 檢查 `getCount()` 返回值是否合理(不應極大除非正確取模)。
  3. 確認 `instantiateItem` 是否重複載入同一圖片資源未釋放。
  4. 加入 Glide 等庫統一管理圖片生命週期。

結論:**`getCount()` 返回極大值實現無限循環時,必須在 `instantiateItem` 內 `position % realCount` 取模索引,並配合圖片加載庫限制解碼尺寸、及時釋放**,即可徹底解決 OOM。

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