加載圖片OOM問題總結

加載圖片oom問題

一、OOM出現的場景和緣由

場景以下:緩存

一、加載的圖片過大
二、一次加載的圖片過多
三、上述兩種狀況兼有

主要緣由:網絡

一、移動設備會限制每一個APP所可以使用的內存,最小爲16M,有的設備分配較多,但總的來講,都會有所限制。
二、在Android中圖片加載到內存中是以位圖的方式存儲的,在Android2.3以後默認狀況下使用ARGB_8888,這種方式下每一個像素要使用4個字節來存儲。加載圖片會佔用大量的內存。

二、如何解決大圖加載問題

在實際加載圖片的時候,咱們不多加載原始大圖,通常都是按照比例採樣縮放,這樣既節省內存又保證圖片不失真。具體實施步驟以下:異步

(1)、在不加載圖片內容的基礎上,去解碼圖片獲得圖片的尺寸信息

這裏須要用的BitmapFactory的decode系列方法和BitmapFactory.Options。當使用decode系列方法加載圖片時,必定要將Options的inJustDecodeBounds屬性設置爲true。ide

BitmapFactory.Options options = new BitmapFactory.Options();
 options.inJustDecodeBounds=true;
 BitmapFactory.decodeFile(path, options);

(2)、根據獲取的圖片的尺寸和要展現在界面的尺寸計算縮放比例

複製代碼

public 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) {
            if (width > height) {
                inSampleSize = Math.round((float) height / (float) reqHeight);
            } else {
                inSampleSize = Math.round((float) width / (float) reqWidth);
            }
        }
        return inSampleSize;
    }

複製代碼

(3)、根據計算的比例縮放圖片

//計算圖片的縮放比例
 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
 options.inJustDecodeBounds = false;
 Bitmap bitmap= BitmapFactory.decodeFile(path, options);

三、批量加載大圖

首先要根據界面展現圖片控件的大小來肯定縮放比例,this

複製代碼

public class PhotoWallAdapter extends ArrayAdapter<String> implements OnScrollListener {
 
    /**
     * 記錄全部正在下載或等待下載的任務。
     */
    private Set<BitmapWorkerTask> taskCollection;
 
    /**
     * 圖片緩存技術的核心類,用於緩存全部下載好的圖片,在程序內存達到設定值時會將最少最近使用的圖片移除掉。
     */
    private LruCache<String, Bitmap> mMemoryCache;
 
    /**
     * GridView的實例
     */
    private GridView mPhotoWall;
 
    /**
     * 第一張可見圖片的下標
     */
    private int mFirstVisibleItem;
 
    /**
     * 一屏有多少張圖片可見
     */
    private int mVisibleItemCount;
 
    /**
     * 記錄是否剛打開程序,用於解決進入程序不滾動屏幕,不會下載圖片的問題。
     */
    private boolean isFirstEnter = true;
 
    public PhotoWallAdapter(Context context, int textViewResourceId, String[] objects,
            GridView photoWall) {
        super(context, textViewResourceId, objects);
        mPhotoWall = photoWall;
        taskCollection = new HashSet<BitmapWorkerTask>();
        // 獲取應用程序最大可用內存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 8;
        // 設置圖片緩存大小爲程序最大可用內存的1/8
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getByteCount();
            }
        };
        mPhotoWall.setOnScrollListener(this);
    }
 
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final String url = getItem(position);
        View view;
        if (convertView == null) {
            view = LayoutInflater.from(getContext()).inflate(R.layout.photo_layout, null);
        } else {
            view = convertView;
        }
        final ImageView photo = (ImageView) view.findViewById(R.id.photo);
        // 給ImageView設置一個Tag,保證異步加載圖片時不會亂序
        photo.setTag(url);
        setImageView(url, photo);
        return view;
    }
 
    /**
     * 給ImageView設置圖片。首先從LruCache中取出圖片的緩存,設置到ImageView上。若是LruCache中沒有該圖片的緩存,
     * 就給ImageView設置一張默認圖片。
     * 
     * @param imageUrl
     *            圖片的URL地址,用於做爲LruCache的鍵。
     * @param imageView
     *            用於顯示圖片的控件。
     */
    private void setImageView(String imageUrl, ImageView imageView) {
        Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
        } else {
            imageView.setImageResource(R.drawable.empty_photo);
        }
    }
 
    /**
     * 將一張圖片存儲到LruCache中。
     * 
     * @param key
     *            LruCache的鍵,這裏傳入圖片的URL地址。
     * @param bitmap
     *            LruCache的鍵,這裏傳入從網絡上下載的Bitmap對象。
     */
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemoryCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }
 
    /**
     * 從LruCache中獲取一張圖片,若是不存在就返回null。
     * 
     * @param key
     *            LruCache的鍵,這裏傳入圖片的URL地址。
     * @return 對應傳入鍵的Bitmap對象,或者null。
     */
    public Bitmap getBitmapFromMemoryCache(String key) {
        return mMemoryCache.get(key);
    }
 
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // 僅當GridView靜止時纔去下載圖片,GridView滑動時取消全部正在下載的任務
        if (scrollState == SCROLL_STATE_IDLE) {
            loadBitmaps(mFirstVisibleItem, mVisibleItemCount);
        } else {
            cancelAllTasks();
        }
    }
 
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
            int totalItemCount) {
        mFirstVisibleItem = firstVisibleItem;
        mVisibleItemCount = visibleItemCount;
        // 下載的任務應該由onScrollStateChanged裏調用,但首次進入程序時onScrollStateChanged並不會調用,
        // 所以在這裏爲首次進入程序開啓下載任務。
        if (isFirstEnter && visibleItemCount > 0) {
            loadBitmaps(firstVisibleItem, visibleItemCount);
            isFirstEnter = false;
        }
    }
 
    /**
     * 加載Bitmap對象。此方法會在LruCache中檢查全部屏幕中可見的ImageView的Bitmap對象,
     * 若是發現任何一個ImageView的Bitmap對象不在緩存中,就會開啓異步線程去下載圖片。
     * 
     * @param firstVisibleItem
     *            第一個可見的ImageView的下標
     * @param visibleItemCount
     *            屏幕中總共可見的元素數
     */
    private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {
        try {
            for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
                String imageUrl = Images.imageThumbUrls[i];
                Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
                if (bitmap == null) {
                    BitmapWorkerTask task = new BitmapWorkerTask();
                    taskCollection.add(task);
                    task.execute(imageUrl);
                } else {
                    ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);
                    if (imageView != null && bitmap != null) {
                        imageView.setImageBitmap(bitmap);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 取消全部正在下載或等待下載的任務。
     */
    public void cancelAllTasks() {
        if (taskCollection != null) {
            for (BitmapWorkerTask task : taskCollection) {
                task.cancel(false);
            }
        }
    }
 
    /**
     * 異步下載圖片的任務。
     * 
     * @author guolin
     */
    class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
 
        /**
         * 圖片的URL地址
         */
        private String imageUrl;
 
        @Override
        protected Bitmap doInBackground(String... params) {
            imageUrl = params[0];
            // 在後臺開始下載圖片
            Bitmap bitmap = downloadBitmap(params[0]);
            if (bitmap != null) {
                // 圖片下載完成後緩存到LrcCache中
                addBitmapToMemoryCache(params[0], bitmap);
            }
            return bitmap;
        }
 
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            // 根據Tag找到相應的ImageView控件,將下載好的圖片顯示出來。
            ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);
            if (imageView != null && bitmap != null) {
                imageView.setImageBitmap(bitmap);
            }
            taskCollection.remove(this);
        }
 
        /**
         * 創建HTTP請求,並獲取Bitmap對象。
         * 
         * @param imageUrl
         *            圖片的URL地址
         * @return 解析後的Bitmap對象
         */
        private Bitmap downloadBitmap(String imageUrl) {
            Bitmap bitmap = null;
            HttpURLConnection con = null;
            try {
                URL url = new URL(imageUrl);
                con = (HttpURLConnection) url.openConnection();
                con.setConnectTimeout(5 * 1000);
                con.setReadTimeout(10 * 1000);
                bitmap = BitmapFactory.decodeStream(con.getInputStream());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (con != null) {
                    con.disconnect();
                }
            }
            return bitmap;
        }
 
    }
 
}

複製代碼

因爲咱們使用了LruCache來緩存圖片,因此不須要擔憂內存溢出的狀況,當LruCache中存儲圖片的總大小達到容量上限的時候,會自動把最近最少使用的圖片從緩存中移除。url

相關文章
相關標籤/搜索