LruCache:從網絡加載圖片緩存實例

OOM異常

堆內存用於存儲實例對象,當程序不斷建立對象,而且對象都有引用指向,那麼垃圾回收機制就不會清理這些對象,當對象多到擠滿堆內存的上限後,就產生OOM異常。Android系統爲每一個應用程序使用的內存設置了一個上限。這個上限值能夠用下面的方法取得: long maxSize = Runtime.getRuntime().maxMemory();java

OOM異常一般分爲下面幾種狀況:
1.內存泄漏致使的OOM:new出來的不少對象已經不須要了,但仍然有引用指向,因此垃圾回收機制沒法回收。
    其場景相似於:建立了一個Handler,而且執行了一個Delay的任務沒有完成,此時擁有此Handler對象的宿主對象亦不能被回收。
    或者,static的方法或成員太多,被外部使用,而外部的牽引對象沒有對其進行釋放,那麼整個static的類都不會被釋放,也就形成內存泄漏。
2.內存溢出:new出來的對象都是須要的,但堆內存過小裝不下了。
  如在一個GridView中顯示圖片,若是不壓縮圖片,採用爲1920*1080的32位圖片,每一個將佔用1920*1080*4=7MB內存,如此大的圖片,只能存儲幾張,超出便可致使內存溢出。
3.關於 Bitmap 引發的泄漏,網上還有另外一種說法:
  一個進程的內存能夠由2個部分組成:java使用內存,C使用內存,這兩個內存的和必須小於16M(假設爲這麼多),否則就會出現你們熟悉的OOM,這個就是第一種OOM的狀況。
  一旦內存分配給Java後,之後這塊內存即便釋放後,也只能給Java的使用,這個估計和java虛擬機裏把內存分紅好幾塊進行緩存的緣由有關。
  因此若是Java忽然佔用了一個大塊內存,即便很快釋放了,C代碼也沒法使用,而Bitmap的生成是經過malloc進行內存分配的,佔用的是C的內存,若是Bitmap須要的內存大於C可用內存也會致使OOM。android

避免OOM

對於java中再也不使用的資源須要儘快的釋放,即設置成null。
儘可能少用static方法和static成員。
對於再也不使用的bitmap應該手動調用recycle方法,而且設置成null。圖片還應儘可能使用軟引用方式,這樣能夠加快垃圾回收。web


LruCache

內存緩存技術對那些大量佔用應用程序寶貴內存的圖片提供了快速訪問的方法。其中最核心的類是LruCache,這個類很是適合用來緩存圖片,它的主要算法原理是把最近使用的對象用強引用存儲在 LinkedHashMap 中,而且把最近最少使用的對象在緩存值達到預設定值以前從內存中移除。算法

爲了可以選擇一個合適的緩存大小給LruCache, 有如下多個因素應該放入考慮範圍內,例如:緩存

1.設備能夠爲每一個應用程序可分配多大的內存。
2.屏幕上一次最多須要顯示多少個圖片,有多少圖片須要進行預加載,由於有可能很快也會顯示在屏幕上。
3.屏幕大小和分辨率。一個超高分辨率的設備比起一個較低分辨率的設備,在持有相同數量圖片的時候,須要更大的緩存空間。
4.圖片的尺寸和大小,還有每張圖片會佔據多少內存空間。
5.圖片被訪問的頻率有多高。如某些圖片的訪問頻率比其它圖片要高,應該使用多個 LruCache 對象來區分不一樣組的圖片。
6.維持好數量和質量之間的平衡。存儲多個低像素的圖片,而在後臺線程加載高像素的圖片會更有效。網絡

並無一個指定的緩存大小能夠知足全部的應用程序,應該去分析程序內存的使用狀況,而後制定出一個合適的解決方案。
一個過小的緩存空間,有可能形成圖片頻繁地被釋放和從新加載,這並無好處。而一個太大的緩存空間,則更有可能會引發 java.lang.OutOfMemory 的異常,由於 LruCache 中的強引用不能被釋放,而程序又須要內存。app

使用LruCache,須要重寫sizeOf方法,返回佔用的內存大小。下面是一個圖片緩存的實現:webapp

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.util.Log;

public class BitmapCache {

    private static final String TAG = "debug";

    private LruCache<String, Bitmap> mBitmapCache;

    public BitmapCache() {

        // 獲取到可用內存的最大值,使用內存超出這個值會引發OutOfMemory異常。
        long maxSize = Runtime.getRuntime().maxMemory();

        Log.d(TAG, "maxMemory size = " + toMB(maxSize));

        // LruCache 使用的緩存值,使用系統分配給應用程序大小的 1/8
        maxSize = maxSize >> 3;
        // maxSize = 1 << 1024 << 1024;
        Log.d(TAG, "cache used maxSize = " + toMB(maxSize));

        mBitmapCache = new LruCache<String, Bitmap>((int) maxSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
    }

    public void add(String key, Bitmap value) {
        mBitmapCache.put(key, value);
    }

    public void remove(String key) {
        mBitmapCache.remove(key);
    }

    public Bitmap get(String key) {
        return mBitmapCache.get(key);
    }

    public boolean containsKey(String key) {
        return mBitmapCache.get(key) != null;
    }

    public static long toMB(long byteOfSize) {
        return byteOfSize >> 20;
    }
}

 

使用緩存技術從網絡下載圖片顯示的實例類

BitmapDownloadTask 類從網絡下載圖片,並將其解析爲適配ImageView大小的格式,以減小對內存的佔用,並取得一個良好的顯示效果。ide

import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;

import java.lang.ref.SoftReference;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;

public class BitmapDownloadTask extends AsyncTask<Void, Void, Bitmap> {

    private static final String TAG = "debug";

    private String mImgUrl;
    private SoftReference<ImageView> mImageViewSoftReference;
    private BitmapCache mBitmapCache;
    private List<BitmapDownloadTask> mTaskList;
    private int mReqWidth;
    private int mReqHeight;

    public BitmapDownloadTask(BitmapCache bitmapCache, List<BitmapDownloadTask> tasks, ImageView imgView, String url) {
        mBitmapCache = bitmapCache;
        mTaskList = tasks;
        mImageViewSoftReference = new SoftReference<>(imgView);
        mImgUrl = url;
        mReqWidth = imgView.getMeasuredWidth();
        mReqHeight = imgView.getMeasuredHeight();
    }

    @Override
    protected Bitmap doInBackground(Void... params) {
        Bitmap bitmap;
        HttpURLConnection conn = null;
        try {
            URL url = new URL(mImgUrl);
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);
            bitmap = BitmapTools.decodeSampledBitmapFromInputStream(conn.getInputStream(), mReqWidth, mReqHeight);
            Log.d(TAG, "bitmap size: " + bitmap.getWidth() + "/" + bitmap.getHeight() + ", " + bitmap.getConfig()
                    .name() + ", " + format(bitmap.getByteCount()));
        } catch (Exception e) {
            Log.e(TAG, "", e);
            bitmap = null;
        } finally {
            if (conn != null)
                conn.disconnect();
        }

        return bitmap;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {

        mTaskList.remove(this);

        if (bitmap != null) {
            mBitmapCache.add(mImgUrl, bitmap);
        }

        ImageView imgView = mImageViewSoftReference.get();
        if (isCancelled() || imgView == null || imgView.getTag() != mImgUrl) {
            bitmap = null;
        }

        if (bitmap != null) {
            imgView.setImageBitmap(bitmap);
        }

        super.onPostExecute(bitmap);
    }

    private String format(long byteOfSize) {
        long KB = byteOfSize >> 10;
        long MB = KB >> 10;
        if (MB != 0) {
            return MB + " MB";
        }
        return KB + " KB";
    }
}

 

public class BitmapTools {

    private static final String TAG = "debug";

    public static class Size {

        public Size(int width, int height) {
            this.width = width;
            this.height = height;
        }

        public void resize(int width, int height) {
            this.width = width;
            this.height = height;
        }

        public int getWidth() {
            return width;
        }

        public int getHeight() {
            return height;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            Size size = (Size) o;

            if (width != size.width)
                return false;
            return height == size.height;
        }

        @Override
        public int hashCode() {
            int result = width;
            result = 31 * result + height;
            return result;
        }

        @Override
        public String toString() {
            return "Size{" + "width=" + width + ", height=" + height + '}';
        }

        private int width;
        private int height;
    }

    public static Bitmap decodeSampledBitmapFromPath(String pathName, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(pathName, opts);
        // Calculate inSampleSize
        opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight);
        // Decode bitmap with inSampleSize set
        opts.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(pathName, opts);
    }

    public static Bitmap decodeSampledBitmapFromData(byte[] data, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(data, 0, data.length, opts);
        // Calculate inSampleSize
        opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight);
        // Decode bitmap with inSampleSize set
        opts.inJustDecodeBounds = false;
        return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
    }

    public static Bitmap decodeSampledBitmapFromResource(Resources res, int id, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, id, opts);
        // Calculate inSampleSize
        opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight);
        // Decode bitmap with inSampleSize set
        opts.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, id, opts);
    }

    public static Bitmap decodeSampledBitmapFromInputStream(InputStream is, int reqWidth, int reqHeight) {
        Bitmap bitmap = BitmapFactory.decodeStream(is);
        if (bitmap == null)
            return null;
        // Log.d(TAG, "req size: " + reqWidth + ", " + reqHeight);
        // Log.d(TAG, "bitmap size: " + bitmap.getWidth() + ", " + bitmap.getHeight());
        if (bitmap.getWidth() > reqWidth || bitmap.getHeight() > reqHeight) {
            Size srcSize = new Size(bitmap.getWidth(), bitmap.getHeight());
            Size reqSize = new Size(reqWidth, reqHeight);
            Size newSize = calculateNewSize(srcSize, reqSize);
            bitmap = Bitmap.createScaledBitmap(bitmap, newSize.getWidth(), newSize.getHeight(), false);
        }
        return bitmap;
    }

    public static Size calculateNewSize(Size srcSize, Size reqSize) {
        int newWidth;
        int newHeight;
        if (srcSize.getWidth() > srcSize.getHeight()) {
            newWidth = reqSize.getWidth();
            newHeight = newWidth * srcSize.getHeight() / srcSize.getWidth();
        } else {
            newHeight = reqSize.getHeight();
            newWidth = newHeight * srcSize.getWidth() / srcSize.getHeight();
        }
        return new Size(newWidth, newHeight);
    }

    public static 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) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;
            // Calculate the largest inSampleSize value that is a power of 2 and
            // keeps both height and width larger than the requested height and
            // width.
            while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
        Log.d(TAG, "inSampleSize=" + inSampleSize);
        return inSampleSize;
    }
}

 

 

Activity類,在滑動時取消全部的任務,沒有滑動時自動加載可見範圍內的全部圖片,以免加載時滑動產生卡頓的問題。佈局

public class LruAcy extends AppCompatActivity {

    private static final String TAG = "debug";

    private BitmapCache mBitmapCache;
    private List<BitmapDownloadTask> mBitmapDownloadTasks;

    private GridView mImageWall;
    private BaseAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.acy_lru);

        mBitmapCache = new BitmapCache();
        mBitmapDownloadTasks = new ArrayList<>();

        mImageWall = (GridView) findViewById(R.id.image_wall);

        mAdapter = new BaseAdapter(this);
        mImageWall.setOnScrollListener(mOnScrollListener);
        mImageWall.setAdapter(mAdapter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        cancelAll();
    }

    private void setImageView(String url, ImageView imgView) {
        Bitmap bitmap = mBitmapCache.get(url);
        if (bitmap == null) {
            imgView.setImageResource(R.mipmap.ic_launcher);
        } else {
            imgView.setImageBitmap(bitmap);
        }
    }

    private void cancelAll() {
        for (BitmapDownloadTask task : mBitmapDownloadTasks) {
            task.cancel(true);
        }
        mBitmapDownloadTasks.clear();
    }

    private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {
        try {
            for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
                String imageUrl = ImageUrls.IMAGE_URLS[i];
                Bitmap bitmap = mBitmapCache.get(imageUrl);
                ImageView imageView = (ImageView) mImageWall.findViewWithTag(imageUrl);
                if (imageView == null)
                    continue;
                // Log.d(TAG, "bitmap=" + bitmap + ", imageView=" + imageView);
                if (bitmap == null) {
                    BitmapDownloadTask task = new BitmapDownloadTask(mBitmapCache, mBitmapDownloadTasks, imageView,
                            imageUrl);
                    mBitmapDownloadTasks.add(task);
                    task.execute();
                } else {
                    imageView.setImageBitmap(bitmap);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private AbsListView.OnScrollListener mOnScrollListener = new AbsListView.OnScrollListener() {

        private boolean mIsFirstRun = true;
        private int firstVisibleItem;
        private int visibleItemCount;

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if (scrollState == SCROLL_STATE_IDLE) {
                loadBitmaps(firstVisibleItem, visibleItemCount);
            } else {
                cancelAll();
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            this.firstVisibleItem = firstVisibleItem;
            this.visibleItemCount = visibleItemCount;
            if (mIsFirstRun && totalItemCount > 0) {
                mIsFirstRun = false;
                loadBitmaps(firstVisibleItem, visibleItemCount);
            }
        }
    };

    private class BaseAdapter extends android.widget.BaseAdapter {

        private LayoutInflater mInflater;

        public BaseAdapter(Context context) {
            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }

        @Override
        public int getCount() {
            return ImageUrls.IMAGE_URLS.length;
        }

        @Override
        public String getItem(int position) {
            return ImageUrls.IMAGE_URLS[position];
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.image_layout, parent, false);
            }

            String url = getItem(position);
            ImageView img = (ImageView) convertView.findViewById(R.id.img);
            img.setTag(url);
            setImageView(url, img);

            return convertView;
        }
    }
}

 

界面佈局 acy_lru.xml image_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.john.webapp.LruAcy">

    <GridView
        android:id="@+id/image_wall"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:horizontalSpacing="5dp"
        android:numColumns="4"
        android:verticalSpacing="5dp"/>

</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <ImageView
        android:id="@+id/img"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:scaleType="fitXY"/>

</LinearLayout>

 

測試使用的Url

public class ImageUrls {

    public final static String[] IMAGE_URLS = new String[]{
            "http://img.my.csdn.net/uploads/201309/01/1378037235_3453.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037235_9280.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037234_3539.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037234_6318.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037194_2965.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037193_1687.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037193_1286.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037192_8379.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037178_9374.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037177_1254.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037177_6203.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037152_6352.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037151_9565.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037151_7904.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037148_7104.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037129_8825.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037128_5291.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037128_3531.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037127_1085.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037095_7515.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037094_8001.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037093_7168.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037091_4950.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949643_6410.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949642_6939.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949630_4505.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949630_4593.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949629_7309.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949629_8247.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949615_1986.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949614_8482.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949614_3743.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949614_4199.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949599_3416.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949599_5269.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949598_7858.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949598_9982.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949578_2770.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949578_8744.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949577_5210.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949577_1998.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949482_8813.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949481_6577.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949480_4490.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949455_6792.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949455_6345.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949442_4553.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949441_8987.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949441_5454.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949454_6367.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949442_4562.jpg"
    };
}
相關文章
相關標籤/搜索