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

來源:互聯網
上載者:User

標籤:

一、OOM問題出現的情境和原因

  一個好的app總少不了精美的圖片,所以Android開發中圖片的載入總是避免不了的,而在載入圖片過程中,如果處理不當則會出現OOM的問題。那麼如何徹底解決這個問題呢?本文將具體介紹這方面的知識。

  首先我們來總結一下,在載入圖片過程中出現的OOM的情境無非就這麼幾種:

1、  載入的圖片過大

2、  一次載入的圖片過多

3、  以上兩種情況兼有

  那麼為什麼在以上情境下會出現OOM問題呢?實際上在API文檔中有著明確的說明,出現OMM的主要原因有兩點:

1、行動裝置會限制每個app所能夠使用的記憶體,最小為16M,有的裝置分配的會更多,如24、32M、64M等等不一,總之會有限制,不會讓你無限制的使用。

2、在andorid中圖片載入到記憶體中是以位元影像的方式儲存的,在android2.3之後預設情況下使用ARGB_8888,這種方式下每個像素要使用4各位元組來儲存。所以載入圖片是會佔用大量的記憶體。

         情境和原因我們都分析完了,下面我們來看看如何解決這些問題。

二、解決大圖載入問題

  首先先來解決大圖載入的問題,一般在實際應用中展示圖片時,因螢幕尺寸及布局顯示的原因,我們沒有必要載入原始大圖,只需要按照比例採樣縮放即可。這樣即節省記憶體又能保證圖片不失真,具體實施步驟如下:

1、在不載入圖片內容的基礎上,去解碼圖片得到圖片的尺寸資訊

  這裡需要用的BitmapFactory的decode系列方法和BitmapFactory.Options。當使用decode系列方法載入圖片時,一定要將Options的inJustDecodeBounds屬性設定為true。

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

  根據縮放比例,會比原始大圖節省很多記憶體,如下:

三、批量載入大圖

  下面我們看看如何批量載入大圖,首先第一步還是我們上面所講到的,要根據介面展示圖片控制項的大小來確定圖片的縮放比例。在此我們使用gridview載入本地圖片為例,具體步驟如下:

1、通過系統提供的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();    }
2、自訂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工具類,為其添加一個線程池對象,用來管理用於下載圖片的子線程。

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

  通過改造後使用者體驗明顯好多了,如下:

  雖然效果有所提升,但是在載入過程中還存在兩個比較嚴重的問題:

1、  圖片錯位顯示

2、  當我們滑動速度過快的時候,圖片載入速度過慢

  經過分析原因不難找出,主要是因為我們時候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方法中非同步載入圖片前先從記憶體中取,具體代碼請下載案例。

四、總結

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

1、  不要載入原始大圖,根據顯示控制項進行比例縮放後載入其縮圖。

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

3、  使用緩衝,根據實際情況確定是否使用雙緩衝和緩衝大小。

  

  小夥伴們看懂了嘛?想要自己測試的,可以點擊“下載工程”運行測試哦!

 

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.