天天看點

ImageLoader實作Bitmap三級緩存

對開發藝術中的ImageLoader作了下整理。

三級緩存分别為 記憶體, 硬碟, 網絡 , 其中記憶體與硬碟存儲用到 LruCache與DiskLruCache.

用法會封裝在工具類裡面,先看ImageLoader的實作步驟

1. 單例實作ImageLoader, 構造方法中開啟LruCache與DiskLruCache

2. 三個私有化方法,分别從緩存中取出Bitmap

3. 同步加載方法 loadBitmap

4. 異步加載方法 bindBitmap, 開啟線程放入線程池中去執行, 線程中調用同步方法

5. 定義出線程池

6. 定義handler,在handler中去設定imageView

LruCacheDiskCache的代碼都被封裝到的工具類,這個類結構已經相當清晰。下面是源碼

public class ImageLoader {
    private static final String TAG = "ImageLoader";
    private static final int TAG_KEY_URL = R.id.ivIcon;
    private static final int MESSAGE_POST_RESULT = ;
    private Context mContext;
    private LruCache<String, Bitmap> mMemoryCache;
    private DiskLruCache mDiskCache;

    private static final long DISK_CACHE_SIZE =  *  * ; // 指定DiskLruCache緩存空間大小  10M的空間
    private static final String CACHE_DIR_NAME = "bitmap"; // 緩存檔案夾名稱
    private static final int MEMORY_CACHE_SIZE = (int) (Runtime.getRuntime().maxMemory() / ) / ; //指定LruCache緩存大小 記憶體的八分之一

    /****************線程池相關參數********************/
    private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() + ;
    private static final int MAX_POOL_SIZE = CORE_POOL_SIZE * ;
    private static final int KEEP_ALIVE = ;
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger();
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());
        }
    };

    // 建立線程池
    private static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE,
            TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(), sThreadFactory);

    // 實作運作在主線程的handler
    private Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            LoadResult result = (LoadResult) msg.obj;
            ImageView imageView = result.imageView;
            String url = (String) imageView.getTag(TAG_KEY_URL);
            if (url.equals(result.url)) { //解決加載出來後,ImageView已經滑過的問題
                imageView.setImageBitmap(result.bitmap);
            } else {
                Log.w(TAG, "set image bitmap ,but url has changed , ignored");
            }
        }
    };


    private ImageLoader(Context context) {
        mContext = context;
        mMemoryCache = ImageUtil.openLruCache(MEMORY_CACHE_SIZE);
        mDiskCache = ImageUtil.openDiskLruCache(mContext, DISK_CACHE_SIZE, CACHE_DIR_NAME);
    }

    public static ImageLoader build(Context context) {
        return new ImageLoader(context);
    }


    /**
     * 把bitmap放入記憶體中
     * @param key
     * @param bitmap
     */
    private void addBitmap2MemoryCache(String key, Bitmap bitmap) {
        if (loadBitmMapFromMemoryCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    /**
     * 從記憶體中加載
     * @param key
     * @return
     */
    private Bitmap loadBitmMapFromMemoryCache(String key) {
        return mMemoryCache.get(key);
    }

    /**
     * 從磁盤中加載
     * @param url
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) {
        Bitmap bitmap =  ImageUtil.getFromDiskLruCache(url, mDiskCache, reqWidth, reqHeight);
        if (bitmap != null) { //放入到記憶體中
            mMemoryCache.put(url, bitmap);
        }
        return bitmap;
    }

    /**
     * 從網絡中加載
     * @param url
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight) {
        if (Looper.myLooper() == Looper.getMainLooper()) { //判斷是否在主線程
            throw new RuntimeException("can not visit net work from UI Thread");
        }
        if (mDiskCache == null) {
            return null;
        }
        String key = ImageUtil.encode(url);
        ImageUtil.put2DisLruCache(url, mDiskCache);
        return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
    }

    /**
     * 同步加載
     * @return
     */
    public Bitmap loadBitmap(String url, int reqWidth, int reqHeight) {
        Bitmap bitmap = loadBitmMapFromMemoryCache(url);
        if (bitmap != null) {
            Log.d(TAG, "loadBitmMapFromMemoryCache, url:" + url);
            return bitmap;
        }

        bitmap = loadBitmapFromDiskCache(url, reqWidth, reqHeight);
        if (bitmap != null) {
            Log.d(TAG, "loadBitmapFromDiskCache, url:" + url);
            return bitmap;
        }

        bitmap = loadBitmapFromHttp(url, reqWidth, reqHeight);
        Log.d(TAG, "loadBitmapFromHttp, url:" + url);
        if (bitmap != null) {
            Log.d(TAG, "DiskLrucache is not created");
            bitmap = ImageUtil.downLoadImage(url);
        }
        return bitmap;
    }

    /**
     * 異步加載
     * @param url
     * @param imageView
     * @param reqWidth
     * @param reqHeight
     */
    public void bindBitmap(final String url, final ImageView imageView, final int reqWidth, final int reqHeight) {
        imageView.setTag(TAG_KEY_URL, url);
        Bitmap bitmap = loadBitmMapFromMemoryCache(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }

        Runnable loadBitmapTask = new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = loadBitmap(url, reqWidth, reqHeight);
                if (bitmap != null) {
                    LoadResult result = new LoadResult(imageView, url, bitmap);
                    Message msg = mHandler.obtainMessage(MESSAGE_POST_RESULT, result);
                    mHandler.sendMessage(msg);
                }
            }
        };

        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
    }

    /**
     * 消息的封裝實體
     */
    private static class LoadResult {
        public ImageView imageView;
        public String url;
        public Bitmap bitmap;

        public LoadResult(ImageView imageView, String url, Bitmap bitmap) {
            this.imageView = imageView;
            this.url = url;
            this.bitmap = bitmap;
        }
    }
}
           

下面是工具類中封裝了LruCache,DiskCache以及從網絡中下載下傳圖檔的具體實作

public class ImageUtil {

    /**
     * bitmap壓縮
     *
     * @param res
     * @param resId
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    /**
     * 通過檔案描述符的方法加載bitmap
     *
     * @param fd
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static Bitmap decodeSampledBitmapFromResource(FileDescriptor fd, int reqWidth, int reqHeight) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fd, null, options);

        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fd, null, options);
    }

    /**
     * 計算壓縮比例
     * @param options
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int height = options.outHeight;
        int width = options.outWidth;
        int inSampleSize = ;
        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / ;
            final int halfWidth = width / ;
            while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= ;
            }
        }
        return inSampleSize;
    }


    /**
     * 打開一個LruCache
     *
     * @return
     */
    public static LruCache<String, Bitmap> openLruCache(int caCheSize) {
        LruCache<String, Bitmap> imageCache = new LruCache<String, Bitmap>(caCheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) { //傳回bitmap的大小
                return bitmap.getRowBytes() * bitmap.getHeight() / ;
            }
        };
        return imageCache;
    }

    /**
     * 打開DiskLruCache
     *
     * @param context
     * @return
     */
    public static DiskLruCache openDiskLruCache(Context context, long maxSize, String cacheDirName) {
        DiskLruCache diskLruCache = null;
        File cacheDir = getDiskCacheDir(context, cacheDirName);
        if (!cacheDir.exists()) {
            cacheDir.mkdirs();
        }
        Log.i("cacheDir", cacheDir.getPath());
        try {
            if (getUsableSpace(cacheDir) > maxSize) {
                diskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), , maxSize);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return diskLruCache;
    }

    /**
     * 擷取緩存路徑
     *
     * @param context
     * @param name
     * @return
     */
    private static File getDiskCacheDir(Context context, String name) {
        String cacheDir;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            cacheDir = context.getExternalCacheDir().getPath();
        } else {
            cacheDir = context.getCacheDir().getPath();
        }
        return new File(cacheDir + File.separator + name);
    }

    /**
     * 擷取系統版本号
     *
     * @param context
     * @return
     */
    public static int getAppVersion(Context context) {
        try {
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), );
            return packageInfo.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return ;
    }

    /**
     * MD5加密
     *
     * @param url
     * @return
     */
    public static String encode(String url) {
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            byte[] result = digest.digest(url.getBytes());
            StringBuffer sb = new StringBuffer();
            for (byte b : result) {
                int num = b & ;
                String str = Integer.toHexString(num);
                if (str.length() == ) {
                    sb.append("0");
                }
                sb.append(str);
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            // can't reach
            return "";
        }
    }


    /**
     * 擷取可用空間
     * @param path
     * @return
     */
    public static long getUsableSpace(File path) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            return path.getUsableSpace();
        }
        StatFs stats = new StatFs(path.getPath());
        return stats.getBlockSizeLong() * stats.getAvailableBlocksLong();
    }

    /**
     * 下載下傳圖檔
     *
     * @param urlString
     * @param outputStream
     * @return
     */
    public static boolean downLoadImage(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(),  * );
            out = new BufferedOutputStream(outputStream,  * );
            int b;
            while ((b = in.read()) != -) {
                out.write(b);
            }
            return true;
        } catch (final IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 下載下傳圖檔
     * @param urlString
     * @return bitmap
     */
    public static Bitmap downLoadImage(String urlString) {
        HttpURLConnection urlConnection = null;
        BufferedInputStream in = null;
        Bitmap bitmap = null;
        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(),  * );
            BitmapFactory.decodeStream(in);
        } catch (final IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }
        return bitmap;
    }

    /**
     * 加載一個圖檔放入DisLruCache
     *
     * @param imageUrl
     * @param disLruCache
     */
    public static void put2DisLruCache(String imageUrl, DiskLruCache disLruCache) {
        String key = ImageUtil.encode(imageUrl);

        DiskLruCache.Editor editor = null;
        try {
            editor = disLruCache.edit(key);
            if (editor != null) {
                OutputStream outputStream = editor.newOutputStream();
                boolean success = ImageUtil.downLoadImage(imageUrl, outputStream);
                if (success) {
                    editor.commit();
                } else {
                    editor.abort();
                }
            }
            disLruCache.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 從DiskLruCache中取bitmap
     * @param imageUrl
     * @param disLruCache
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static Bitmap getFromDiskLruCache(String imageUrl, DiskLruCache disLruCache, int reqWidth, int reqHeight) {
        try {
            String key = encode(imageUrl);
            DiskLruCache.Snapshot snapshot = disLruCache.get(key);
            if (snapshot != null) {
                FileInputStream inputStream = (FileInputStream) snapshot.getInputStream();
                FileDescriptor fd = inputStream.getFD();
                Bitmap bitmap = decodeSampledBitmapFromResource(fd, reqWidth, reqHeight);
                return bitmap;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

}

           

繼續閱讀