圖片過大致使OOM

原文:http://www.codeceo.com/article/android-load-image-oom.html

1、分析

在加載圖片過程當中出現的OOM的幾種狀況:html

一、  加載的圖片過大

二、  一次加載的圖片過多

三、  以上兩種狀況兼有

出現OMM的主要緣由有兩點:android

一、移動設備會限制每一個app所可以使用的內存,最小爲16M,有的設備分配的會更多,如2四、32M、64M等等不一,總之會有限制,不會讓你無限制的使用。緩存

二、在andorid中圖片加載到內存中是以位圖的方式存儲的,在android2.3以後默認狀況下使用ARGB_8888,這種方式下每一個像素要使用4各字節來存儲。因此加載圖片是會佔用大量的內存。app

 

2、解決大圖加載問題

首先先來解決大圖加載的問題,通常在實際應用中展現圖片時,因屏幕尺寸及佈局顯示的緣由,咱們沒有必要加載原始大圖,只須要按照比例採樣縮放便可。這樣即節省內存又能保證圖片不失真,具體實施步驟以下:異步

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

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

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

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

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;
    }

三、根據計算的比例縮放圖片。

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

根據縮放比例,會比原始大圖節省不少內存,效果圖以下:工具

Android之批量加載圖片OOM問題解決方案

 

3、批量加載大圖

下面咱們看看如何批量加載大圖,首先第一步仍是咱們上面所講到的,要根據界面展現圖片控件的大小來肯定圖片的縮放比例。在此咱們使用gridview加載本地圖片爲例,具體步驟以下:佈局

一、經過系統提供的contentprovider加載外部存儲器中的全部圖片地址

     private void loadPhotoPaths(){
        Cursor cursor= getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
        while(cursor.moveToNext()){
            String path = cursor.getString(cursor.getColumnIndex(MediaColumns.DATA));
            paths.add(path);
        }
        cursor.close();
    }

二、自定義adapter,在adapter的getview方法中加載圖片優化

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder=null;
        if(convertView==null){
            convertView = LayoutInflater.from(this.mContext).inflate(R.layout.grid_item_layout, null);
            holder = new ViewHolder();
            holder.photo=(ImageView)convertView.findViewById(R.id.photo);
            convertView.setTag(holder);
        }else{
            holder=(ViewHolder)convertView.getTag();
        }

        final String path = this.paths.get(position);
        holder.photo.setImageBitmap(imageLoader.getBitmapFromCache(path));
        return convertView;
    }

經過以上關鍵兩個步驟後,咱們發現程序運行後,用戶體驗特別差,半天沒有反應,很明顯這是由於咱們在主線程中加載大量的圖片,這是不合適的。在這裏咱們要將圖片的加載工做放到子線程中進行,改造自定義的ImageLoader工具類,爲其添加一個線程池對象,用來管理用於下載圖片的子線程。ui

    private ExecutorService executor;
    private ImageLoader(Context mContxt) {
        super();
        executor = Executors.newFixedThreadPool(3);
    }
    //加載圖片的異步方法,含有回調監聽
    public void loadImage(final ImageView view,
            final String path,
            final int reqWidth,
            final int reqHeight,
            final onBitmapLoadedListener callback){

        final Handler mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                case 1:
                    Bitmap bitmap = (Bitmap)msg.obj;
                    callback.displayImage(view, bitmap);
                    break;

                default:
                    break;
                }
            }

        };

        executor.execute(new Runnable() {
            @Override
            public void run() {
                    Bitmap bitmap = loadBitmapInBackground(path, reqWidth,
                            reqHeight);
                    putBitmapInMemey(path, bitmap);

                    Message msg = mHandler.obtainMessage(1);
                    msg.obj = bitmap;
                    mHandler.sendMessage(msg);
            }
        });
    }

經過改造後用戶體驗明顯好多了,效果圖以下:

Android之批量加載圖片OOM問題解決方案

雖然效果有所提高,可是在加載過程當中還存在兩個比較嚴重的問題:

一、圖片錯位顯示

二、當咱們滑動速度過快的時候,圖片加載速度過慢

通過分析緣由不難找出,主要是由於咱們時候holder緩存了grid的item進行重用和線程池中的加載任務過多所形成的,只須要對程序稍做修改,具體以下:

Adapter中:

        holder.photo.setImageResource(R.drawable.ic_launcher);
        holder.photo.setTag(path);
        imageLoader.loadImage(holder.photo,
                path, 
                DensityUtil.dip2px(80),
                DensityUtil.dip2px(80), 
                new onBitmapLoadedListener() {
            @Override
            public void displayImage(ImageView view, Bitmap bitmap) {
                String imagePath= view.getTag().toString();
                if(imagePath.equals(path)){
                    view.setImageBitmap(bitmap);
                }
            }
        });

ImageLoader中:

executor.execute(new Runnable() {
                @Override
                public void run() {
                    String key = view.getTag().toString();
                    if (key.equals(path)) {
                        Bitmap bitmap = loadBitmapInBackground(path, reqWidth,
                                reqHeight);
                        putBitmapInMemey(path, bitmap);

                        Message msg = mHandler.obtainMessage(1);
                        msg.obj = bitmap;
                        mHandler.sendMessage(msg);
                    }
                }
            });

爲了得到更好的用戶體驗,咱們還能夠繼續優化,即對圖片進行緩存,緩存咱們能夠分爲兩個部份內存緩存磁盤緩存,本文例子加載的是本地圖片全部只進行了內存緩存。對ImageLoader對象繼續修改,添加LruCache對象用於緩存圖片。

private ImageLoader(Context mContxt) {
        super();
        executor = Executors.newFixedThreadPool(3);
        //將應用的八分之一做爲圖片緩存
        ActivityManager am=(ActivityManager)mContxt.getSystemService(Context.ACTIVITY_SERVICE);
        int maxSize = am.getMemoryClass()*1024*1024/8;
        mCache = new LruCache<String, Bitmap>(maxSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes()*value.getHeight();
            }
        };
    }

    //存圖片到緩存
    public void putBitmapInMemey(String path,Bitmap bitmap){
        if(path==null)
            return;
        if(bitmap==null)
            return;
        if(getBitmapFromCache(path)==null){
            this.mCache.put(path, bitmap);
        }
    }

    public Bitmap getBitmapFromCache(String path){
        return mCache.get(path);
    }

在loadImage方法中異步加載圖片前先從內存中取,具體代碼請下載案例

4、總結

總結一下解決加載圖片出現OOM的問題主要有如下方法:

一、  不要加載原始大圖,根據顯示控件進行比例縮放後加載其縮略圖。

二、  不要在主線程中加載圖片,主要在listview和gridview中使用異步加載圖片是要注意處理圖片錯位和無用線程的問題。

三、  使用緩存,根據實際狀況肯定是否使用雙緩存和緩存大小。

相關文章
相關標籤/搜索