造簡(jiǎn)單的圖片加載框架——ImageLoader的實(shí)現(xiàn)

本文出自 “阿敏其人” 簡(jiǎn)書博客脱惰,轉(zhuǎn)載或引用請(qǐng)注明出處搏嗡。

本篇主要做的事情,利用LruCache和DiskLruCache造一個(gè)ImageLoader拉一。
對(duì)于OOM和LruCache和DiskLruCache還了解的猴子可以先看一下這兩篇:
安卓OOM和Bitmap圖片二級(jí)緩存機(jī)制(一)
安卓OOM和Bitmap圖片二級(jí)緩存機(jī)制(二)

一采盒、ImageLoader應(yīng)該具備的功能:

  • 圖片的同步加載
  • 圖片的異步處理
  • 圖片壓縮
  • 內(nèi)存緩存
  • 磁盤緩存
  • 網(wǎng)絡(luò)拉取圖片



同步加載: 就是以二級(jí)緩存的方式同步獲取圖片
異步加載: 異步,多線程蔚润,二級(jí)緩存獲取圖片
圖片壓縮: 壓縮圖片磅氨,有效防止OOM
內(nèi)存緩存: 利用LruCache實(shí)現(xiàn)
磁盤緩存: 利用DiskLruCache實(shí)現(xiàn)
網(wǎng)絡(luò)拉取: 沒有緩存時(shí)嫡纠,網(wǎng)絡(luò)拉取



其中烦租,LruCahce和DiskLruCache是這個(gè)圖片框架的核心,除此之外除盏,我們還需要注意的在ListView或者GridView上的一個(gè)復(fù)用問題叉橱。

(前提準(zhǔn)備:網(wǎng)上拿到DiskLruCache的類源碼,放入我們的工程中)


二者蠕、實(shí)現(xiàn)分析:


1窃祝、首先在ImageLoader這個(gè)方法里面初始化Lruchche和DiskLruCache這兩個(gè)類

    private ImageLoader(Context context) {
        mContext = context.getApplicationContext();
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
        File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
        if (!diskCacheDir.exists()) {
            diskCacheDir.mkdirs();
        }
        if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
            try {
                mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1,
                        DISK_CACHE_SIZE);
                mIsDiskLruCacheCreated = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


2、我們圖片需要壓縮踱侣,為了解耦粪小,我們把圖片壓縮單獨(dú)設(shè)置建立一個(gè)壓縮類

public class ImageResizer {
    private static final String TAG = "ImageResizer";

    public ImageResizer() {
    }

    public Bitmap decodeSampledBitmapFromResource(Resources res,
            int resId, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fd, null, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fd, null, options);
    }

    public int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {
        if (reqWidth == 0 || reqHeight == 0) {
            return 1;
        }

        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        Log.d(TAG, "origin, w= " + width + " h=" + height);
        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, "sampleSize:" + inSampleSize);
        return inSampleSize;
    }
}


3甩栈、LruCache的取存

取和存

    private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    private Bitmap getBitmapFromMemCache(String key) {
        return mMemoryCache.get(key);
    }



刪除的話滿了就自動(dòng)刪除


4、DiskLruCache的取存刪

取和刪


    /**
     * 磁盤緩存的添加
     * @param url
     * @param reqWidth
     * @param reqHeight
     * @return
     * @throws IOException
     * 
     * 
     * 磁盤緩存的添加:添加需要通過Editor來完成糕再,利用commit和abort方法來 提交 和 撤銷 操作
     */
    private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
            throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException("can not visit network from UI Thread.");
        }
        if (mDiskLruCache == null) {
            return null;
        }
        
        String key = hashKeyFormUrl(url);
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);
        if (editor != null) {
            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
            if (downloadUrlToStream(url, outputStream)) {
                editor.commit();
            } else {
                editor.abort();
            }
            mDiskLruCache.flush();
        }
        return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
    }

    /**
     * 磁盤緩存的讀取
     * @param url
     * @param reqWidth
     * @param reqHeight
     * @return
     * @throws IOException
     * 讀攘棵弧:需要通過Snapshot來完成,通過Snapshot可以得到磁盤緩存對(duì)象對(duì)應(yīng)的FileInputStream
     * 但是FileInputStream無法很好地進(jìn)行壓縮
     * 所以我們通過 FileDescripot 來加載壓縮后的圖片突想,得到加載后的Bitmap添加到內(nèi)存緩存中
     */
    private Bitmap loadBitmapFromDiskCache(String url, int reqWidth,
            int reqHeight) throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Log.w(TAG, "load bitmap from UI Thread, it's not recommended!");
        }
        if (mDiskLruCache == null) {
            return null;
        }

        Bitmap bitmap = null;
        String key = hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
        if (snapShot != null) {
            FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
                    reqWidth, reqHeight);
            if (bitmap != null) {
                addBitmapToMemoryCache(key, bitmap);
            }
        }

        return bitmap;
    }


5殴蹄、同步接口的實(shí)現(xiàn)

以同步接口的方式去加載圖片,走的是內(nèi)存猾担,磁盤接著網(wǎng)絡(luò)的獲取渠道
同步接口的不能再主線程執(zhí)行袭灯,需要在外部的線程調(diào)用


/**
     * 
     * 同步加載  (從內(nèi)存緩存、磁盤緩存绑嘹、網(wǎng)絡(luò))
     * @param uri http url
     * @param reqWidth the width ImageView desired
     * @param reqHeight the height ImageView desired
     * @return bitmap, maybe null.
     * 
     * 同步加載的設(shè)計(jì)步驟:
     * 先從內(nèi)存緩存嘗試加載圖片稽荧,找不到就去磁盤緩存拿,磁盤緩存拿不到就去網(wǎng)絡(luò)拿
     * 這個(gè)方法不能再線程執(zhí)行工腋,在主線程執(zhí)行就拋異常(有一個(gè)檢查當(dāng)前線程的Looper是否為主線程的Looper的判斷)
     */
    public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            Log.d(TAG, "loadBitmapFromMemCache,url:" + uri);
            return bitmap;
        }

        try {
            bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
            if (bitmap != null) {
                Log.d(TAG, "loadBitmapFromDisk,url:" + uri);
                return bitmap;
            }
            bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
            Log.d(TAG, "loadBitmapFromHttp,url:" + uri);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (bitmap == null && !mIsDiskLruCacheCreated) {
            Log.w(TAG, "encounter error, DiskLruCache is not created.");
            bitmap = downloadBitmapFromUrl(uri);
        }

        return bitmap;
    }



6姨丈、異步接口的設(shè)計(jì)

    /**
     * 異步接口
     * load bitmap from memory cache or disk cache or network async, then bind imageView and bitmap.
     * NOTE THAT: should run in UI Thread
     * @param uri http url
     * @param imageView bitmap's bind object
     */
    public void bindBitmap(final String uri, final ImageView imageView) {
        bindBitmap(uri, imageView, 0, 0);
    }


    /**
     * 異步接口
     * @param uri
     * @param imageView
     * @param reqWidth
     * @param reqHeight
     * 
     * 實(shí)現(xiàn)過程:
     * 首先先去內(nèi)存緩存讀取圖片
     * 如果讀取就直接返回結(jié)果
     * 
     * 如果讀取不到就調(diào)用loadBitmap方法,當(dāng)圖片加載成功后再將圖片擅腰,圖片的地址以及需要綁定的imageView封裝成
     * 一個(gè)LoaderResult對(duì)象蟋恬,然后再通過mHMainandler向主線程發(fā)送一條消息,這樣就可以在imageView中設(shè)置圖片了
     * 之所以通過Handler中轉(zhuǎn)是因?yàn)樽泳€程無法直接更新UI
     * 
     * bindBitmap中用到了線程池和Handler
     * 
     * 
     */
    public void bindBitmap(final String uri, final ImageView imageView,
            final int reqWidth, final int reqHeight) {
        imageView.setTag(TAG_KEY_URI, uri);
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }

        Runnable loadBitmapTask = new Runnable() {

            @Override
            public void run() {
                Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
                if (bitmap != null) {
                    LoaderResult result = new LoaderResult(imageView, uri, bitmap);
                    mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
                }
            }
        };
        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
    }


7趁冈、解決View的復(fù)用錯(cuò)位

每次設(shè)置圖片之前都檢查他的url有沒有改變歼争,如果改變就不再給其設(shè)置圖片,這樣就解決了圖片錯(cuò)誤的問題

    private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            LoaderResult result = (LoaderResult) msg.obj;
            ImageView imageView = result.imageView;
            imageView.setImageBitmap(result.bitmap);
            String uri = (String) imageView.getTag(TAG_KEY_URI);
            if (uri.equals(result.uri)) {
                imageView.setImageBitmap(result.bitmap);
            } else {
                Log.w(TAG, "set image bitmap,but url has changed, ignored!");
            }
        };
    };


完整代碼

ImageLoader完整代碼

public class ImageLoader {

    private static final String TAG = "ImageLoader";

    public static final int MESSAGE_POST_RESULT = 1;

    private static final int CPU_COUNT = Runtime.getRuntime()
            .availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final long KEEP_ALIVE = 10L;

    private static final int TAG_KEY_URI = R.id.imageloader_uri;
    private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
    private static final int IO_BUFFER_SIZE = 8 * 1024;
    private static final int DISK_CACHE_INDEX = 0;
    private boolean mIsDiskLruCacheCreated = false;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());
        }
    };

    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
            KEEP_ALIVE, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(), sThreadFactory);
    
    private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            LoaderResult result = (LoaderResult) msg.obj;
            ImageView imageView = result.imageView;
            imageView.setImageBitmap(result.bitmap);
            String uri = (String) imageView.getTag(TAG_KEY_URI);
            if (uri.equals(result.uri)) {
                imageView.setImageBitmap(result.bitmap);
            } else {
                Log.w(TAG, "set image bitmap,but url has changed, ignored!");
            }
        };
    };

    private Context mContext;
    private ImageResizer mImageResizer = new ImageResizer();
    private LruCache<String, Bitmap> mMemoryCache;
    private DiskLruCache mDiskLruCache;

    private ImageLoader(Context context) {
        mContext = context.getApplicationContext();
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
        File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
        if (!diskCacheDir.exists()) {
            diskCacheDir.mkdirs();
        }
        if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
            try {
                mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1,
                        DISK_CACHE_SIZE);
                mIsDiskLruCacheCreated = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * build a new instance of ImageLoader
     * @param context
     * @return a new instance of ImageLoader
     */
    public static ImageLoader build(Context context) {
        return new ImageLoader(context);
    }

    // 內(nèi)存緩存的添加
    private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    // 內(nèi)存緩存的讀取
    private Bitmap getBitmapFromMemCache(String key) {
        return mMemoryCache.get(key);
    }

    /**
     * 異步接口
     * load bitmap from memory cache or disk cache or network async, then bind imageView and bitmap.
     * NOTE THAT: should run in UI Thread
     * @param uri http url
     * @param imageView bitmap's bind object
     */
    public void bindBitmap(final String uri, final ImageView imageView) {
        bindBitmap(uri, imageView, 0, 0);
    }


    /**
     * 異步接口
     * @param uri
     * @param imageView
     * @param reqWidth
     * @param reqHeight
     *
     * 實(shí)現(xiàn)過程:
     * 首先先去內(nèi)存緩存讀取圖片
     * 如果讀取就直接返回結(jié)果
     *
     * 如果讀取不到就調(diào)用loadBitmap方法渗勘,當(dāng)圖片加載成功后再將圖片沐绒,圖片的地址以及需要綁定的imageView封裝成
     * 一個(gè)LoaderResult對(duì)象,然后再通過mHMainandler向主線程發(fā)送一條消息旺坠,這樣就可以在imageView中設(shè)置圖片了
     * 之所以通過Handler中轉(zhuǎn)是因?yàn)樽泳€程無法直接更新UI
     *
     * bindBitmap中用到了線程池和Handler
     *
     *
     */
    public void bindBitmap(final String uri, final ImageView imageView,
            final int reqWidth, final int reqHeight) {
        imageView.setTag(TAG_KEY_URI, uri);
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }

        Runnable loadBitmapTask = new Runnable() {

            @Override
            public void run() {
                Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
                if (bitmap != null) {
                    LoaderResult result = new LoaderResult(imageView, uri, bitmap);
                    mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
                }
            }
        };
        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
    }

    /**
     *
     * 同步加載  (從內(nèi)存緩存乔遮、磁盤緩存、網(wǎng)絡(luò))
     * @param uri http url
     * @param reqWidth the width ImageView desired
     * @param reqHeight the height ImageView desired
     * @return bitmap, maybe null.
     *
     * 同步加載的設(shè)計(jì)步驟:
     * 先從內(nèi)存緩存嘗試加載圖片价淌,找不到就去磁盤緩存拿申眼,磁盤緩存拿不到就去網(wǎng)絡(luò)拿
     * 這個(gè)方法不能再線程執(zhí)行,在主線程執(zhí)行就拋異常(有一個(gè)檢查當(dāng)前線程的Looper是否為主線程的Looper的判斷)
     */
    public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            Log.d(TAG, "loadBitmapFromMemCache,url:" + uri);
            return bitmap;
        }

        try {
            bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
            if (bitmap != null) {
                Log.d(TAG, "loadBitmapFromDisk,url:" + uri);
                return bitmap;
            }
            bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
            Log.d(TAG, "loadBitmapFromHttp,url:" + uri);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (bitmap == null && !mIsDiskLruCacheCreated) {
            Log.w(TAG, "encounter error, DiskLruCache is not created.");
            bitmap = downloadBitmapFromUrl(uri);
        }

        return bitmap;
    }

    private Bitmap loadBitmapFromMemCache(String url) {
        final String key = hashKeyFormUrl(url);
        Bitmap bitmap = getBitmapFromMemCache(key);
        return bitmap;
    }

    /**
     * 磁盤緩存的添加
     * @param url
     * @param reqWidth
     * @param reqHeight
     * @return
     * @throws IOException
     *
     *
     * 磁盤緩存的添加:添加需要通過Editor來完成蝉衣,利用commit和abort方法來 提交 和 撤銷 操作
     */
    private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
            throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException("can not visit network from UI Thread.");
        }
        if (mDiskLruCache == null) {
            return null;
        }
        
        String key = hashKeyFormUrl(url);
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);
        if (editor != null) {
            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
            if (downloadUrlToStream(url, outputStream)) {
                editor.commit();
            } else {
                editor.abort();
            }
            mDiskLruCache.flush();
        }
        return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
    }

    /**
     * 磁盤緩存的讀取
     * @param url
     * @param reqWidth
     * @param reqHeight
     * @return
     * @throws IOException
     * 讀壤ㄊ:需要通過Snapshot來完成,通過Snapshot可以得到磁盤緩存對(duì)象對(duì)應(yīng)的FileInputStream
     * 但是FileInputStream無法很好地進(jìn)行壓縮
     * 所以我們通過 FileDescripot 來加載壓縮后的圖片病毡,得到加載后的Bitmap添加到內(nèi)存緩存中
     */
    private Bitmap loadBitmapFromDiskCache(String url, int reqWidth,
            int reqHeight) throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Log.w(TAG, "load bitmap from UI Thread, it's not recommended!");
        }
        if (mDiskLruCache == null) {
            return null;
        }

        Bitmap bitmap = null;
        String key = hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
        if (snapShot != null) {
            FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
                    reqWidth, reqHeight);
            if (bitmap != null) {
                addBitmapToMemoryCache(key, bitmap);
            }
        }

        return bitmap;
    }

    public boolean downloadUrlToStream(String urlString,
            OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;

        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(),
                    IO_BUFFER_SIZE);
            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);

            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (IOException e) {
            Log.e(TAG, "downloadBitmap failed." + e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            MyUtils.close(out);
            MyUtils.close(in);
        }
        return false;
    }

    private Bitmap downloadBitmapFromUrl(String urlString) {
        Bitmap bitmap = null;
        HttpURLConnection urlConnection = null;
        BufferedInputStream in = null;

        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(),
                    IO_BUFFER_SIZE);
            bitmap = BitmapFactory.decodeStream(in);
        } catch (final IOException e) {
            Log.e(TAG, "Error in downloadBitmap: " + e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            MyUtils.close(in);
        }
        return bitmap;
    }

    private String hashKeyFormUrl(String url) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(url.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(url.hashCode());
        }
        return cacheKey;
    }

    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }

    public File getDiskCacheDir(Context context, String uniqueName) {
        boolean externalStorageAvailable = Environment
                .getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
        final String cachePath;
        if (externalStorageAvailable) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }

        return new File(cachePath + File.separator + uniqueName);
    }

    @TargetApi(VERSION_CODES.GINGERBREAD)
    private long getUsableSpace(File path) {
        if (Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
            return path.getUsableSpace();
        }
        final StatFs stats = new StatFs(path.getPath());
        return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
    }

    private static class LoaderResult {
        public ImageView imageView;
        public String uri;
        public Bitmap bitmap;

        public LoaderResult(ImageView imageView, String uri, Bitmap bitmap) {
            this.imageView = imageView;
            this.uri = uri;
            this.bitmap = bitmap;
        }
    }
}



附上DiskLruCache源碼
DiskLruCache源碼參考地址




說明濒翻,本文主要參考《Android開發(fā)藝術(shù)探索》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子有送,更是在濱河造成了極大的恐慌淌喻,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雀摘,死亡現(xiàn)場(chǎng)離奇詭異裸删,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)阵赠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門涯塔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人清蚀,你說我怎么就攤上這事匕荸。” “怎么了枷邪?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵榛搔,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我东揣,道長(zhǎng)践惑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任救斑,我火速辦了婚禮童本,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脸候。我一直安慰自己,他們只是感情好绑蔫,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布运沦。 她就那樣靜靜地躺著,像睡著了一般配深。 火紅的嫁衣襯著肌膚如雪携添。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天篓叶,我揣著相機(jī)與錄音烈掠,去河邊找鬼。 笑死缸托,一個(gè)胖子當(dāng)著我的面吹牛左敌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播俐镐,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼矫限,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起叼风,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤取董,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后无宿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茵汰,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年孽鸡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了经窖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梭灿,死狀恐怖画侣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情堡妒,我是刑警寧澤配乱,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站皮迟,受9級(jí)特大地震影響搬泥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伏尼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一忿檩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧爆阶,春花似錦燥透、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至故河,卻和暖如春吱韭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鱼的。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工理盆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人凑阶。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓猿规,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親晌砾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坎拐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容