自己动手封装图片三级缓存网络请求框架(类似imageloader)

图片三级缓存原理

   三级缓存指内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求拉取图片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。

  关于Java中对象的软引用(SoftReference),如果一个对象具有软引用,内存空间足够,垃 圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高 速缓存。使用软引用能防止内存泄露,增强程序的健壮性。

总体设计

自己动手封装图片三级缓存网络请求框架(类似imageloader)_第1张图片

按模块分:控制图片加载逻辑模块、图片下载引擎模块、文件缓存模块、内存缓存模块。

核心模块分析

控制图片加载逻辑模块

此模块主要封装了图片加载的逻辑,是一个异步任务。具体代码如下:

public class CacheImageAsyncTask extends AsyncTask<String, Integer, Bitmap> {
    private ImageView imageView;
    private ImageFileCache fileCache;
    private ImageMemoryCache memoryCache;
    private String imgType;
    private String url;
    private int toW = 0;
    private int toH = 0;

    public CacheImageAsyncTask(ImageView imageView) {
        this.imageView = imageView;
        fileCache = MyApplication.getFileCache();
        memoryCache = MyApplication.getMemoryCache();
    }


    public CacheImageAsyncTask(ImageView imageView,int toW,int toH) {
        this.imageView = imageView;
        fileCache = MyApplication.getFileCache();
        memoryCache = MyApplication.getMemoryCache();
        this.toH = toH;
        this.toW = toW;
    }

    /** * 加载图片给特定的imageview * * @param imageView */
    public CacheImageAsyncTask(ImageView imageView, String imgType) {
        this.imageView = imageView;
        fileCache = MyApplication.getFileCache();
        memoryCache = MyApplication.getMemoryCache();
        this.imgType = imgType;
    }


    public Bitmap getBitmap(String url,int toW,int toH) {

        // 从内存缓存中获取图片
        Bitmap result = memoryCache.getBitmapFromCache(url);
        if (result == null) {
            // 文件缓存中获取
            result = fileCache.getImage(url);
            if (result == null) {
                // 从网络获取
                result = ImageGetFromHttp.downloadBitmap(url,toW,toH);
                if (result != null) {
                    fileCache.saveBitmap(result, url);
                    memoryCache.addBitmapToCache(url, result);
                }
            } else {
                // 添加到内存缓存
                memoryCache.addBitmapToCache(url, result);
            }
        }
        return result;
    }

    public Bitmap getBitmap(String url) {
        return getBitmap(url,0,0);
    }

    protected Bitmap doInBackground(String... params) {
        url = params[0];
        if(toH!=0 && toW!=0){
            return getBitmap(url,toW,toH);
        }else{
            return getBitmap(url);
        }

    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (bitmap != null) {
            if (imageView != null) {
                if (imgType != null && imgType.equals("avatar")) {
                    bitmap = ImageUtil.getRoundBitmap(bitmap);
                }
                Object tag = imageView.getTag();
                if (tag != null && tag instanceof String) {
                    String s = (String) tag;
                    if (s.equals(url)) {
                        imageView.setImageBitmap(bitmap);
                    }
                }
            }
        }
    }


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

    }
}

内存缓存模块

内存缓存模块封装了lrucache。具体代码如下:

public class ImageMemoryCache {
    /** * 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。 * 硬引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。 */
    private static final int SOFT_CACHE_SIZE = 15;  //软引用缓存容量
    private static LruCache<String, Bitmap> mLruCache;  //硬引用缓存
    private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache;  //软引用缓存

    public ImageMemoryCache(Context context) {
        int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
        int cacheSize = 1024 * 1024 * memClass / 4;  //硬引用缓存容量,为系统可用内存的1/4
        mLruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                if (value != null)
                    return value.getRowBytes() * value.getHeight();
                else
                    return 0;
            }

            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                if (oldValue != null)
                    // 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
                    mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
            }
        };
        mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true) {
            private static final long serialVersionUID = 6040103833179403725L;

            @Override
            protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
                if (size() > SOFT_CACHE_SIZE) {
                    return true;
                }
                return false;
            }
        };
    }

    /** * 从缓存中获取图片 */
    public Bitmap getBitmapFromCache(String url) {
        Bitmap bitmap;
        //先从硬引用缓存中获取
        synchronized (mLruCache) {
            bitmap = mLruCache.get(url);
            if (bitmap != null) {
                //如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除
                mLruCache.remove(url);
                mLruCache.put(url, bitmap);
                return bitmap;
            }
        }
        //如果硬引用缓存中找不到,到软引用缓存中找
        synchronized (mSoftCache) {
            SoftReference<Bitmap> bitmapReference = mSoftCache.get(url);
            if (bitmapReference != null) {
                bitmap = bitmapReference.get();
                if (bitmap != null) {
                    //将图片移回硬缓存
                    mLruCache.put(url, bitmap);
                    mSoftCache.remove(url);
                    return bitmap;
                } else {
                    mSoftCache.remove(url);
                }
            }
        }
        return null;
    }

    /** * 添加图片到缓存 */
    public void addBitmapToCache(String url, Bitmap bitmap) {
        if (bitmap != null) {
            synchronized (mLruCache) {
                mLruCache.put(url, bitmap);
            }
        }
    }

    public void clearCache() {
        mSoftCache.clear();
    }
}

文件缓存模块

public class ImageFileCache {
    private static final String CACHDIR = "wantToGoImgCache";
    private static final String WHOLESALE_CONV = ".cach";

    private static final int MB = 1024*1024;
    private static final int CACHE_SIZE = 10;
    private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;

    public ImageFileCache() {
        //清理文件缓存
        removeCache(getDirectory());
    }

    /** 从缓存中获取图片 **/
    public Bitmap getImage(final String url) {
        File file = new File(getDirectory(),MD5Util.md5(url));
        if (file.exists()) {
            //Log.d("ImageFileCache","绝对路径:"+file.getAbsolutePath());
            Bitmap bmp = BitmapFactory.decodeFile(file.getAbsolutePath());
            if (bmp == null) {
                file.delete();
            } else {
                updateFileTime(file.getAbsolutePath());
                return bmp;
            }
        }else{
            //Log.d("ImageFileCache","文件不存在");
        }
        return null;
    }

    /** 将图片存入文件缓存 **/
    public void saveBitmap(Bitmap bm, String url) {
        if (bm == null) {
            return;
        }
        //判断sdcard上的空间
        if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            //SD空间不足
            return;
        }
        //进行映射
        String filename = MD5Util.md5(url);
        //获取缓存目录
        File dirFile = getDirectory();
        //缓存目录不存在,建立缓存目录
        if (!dirFile.exists())
            dirFile.mkdirs();
        //新建文件对象
        File file = new File(dirFile,filename);
        try {
            //在缓存文件下创建文件
            file.createNewFile();

            OutputStream outStream = new FileOutputStream(file);
            bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
            outStream.flush();
            outStream.close();
        } catch (FileNotFoundException e) {
            Log.w("ImageFileCache", "FileNotFoundException");
        } catch (IOException e) {
            Log.w("ImageFileCache", "IOException");
        }
    }

    /** * 计算存储目录下的文件大小, * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定 * 那么删除40%最近没有被使用的文件 */
    private boolean removeCache(File dirFile) {
        File[] files = dirFile.listFiles();
        if (files == null) {
            return true;
        }
        if (!Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            return false;
        }

        int dirSize = 0;
        for (int i = 0; i < files.length; i++) {
            if (files[i].getName().contains(WHOLESALE_CONV)) {
                dirSize += files[i].length();
            }
        }

        if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            int removeFactor = (int) ((0.4 * files.length) + 1);
            Arrays.sort(files, new FileLastModifSort());
            for (int i = 0; i < removeFactor; i++) {
                if (files[i].getName().contains(WHOLESALE_CONV)) {
                    files[i].delete();
                }
            }
        }

        if (freeSpaceOnSd() <= CACHE_SIZE) {
            return false;
        }

        return true;
    }

    /** 修改文件的最后修改时间 **/
    public void updateFileTime(String path) {
        File file = new File(path);
        long newModifiedTime = System.currentTimeMillis();
        file.setLastModified(newModifiedTime);
    }

    /** 计算sdcard上的剩余空间 **/
    private int freeSpaceOnSd() {
        StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
        double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
        return (int) sdFreeMB;
    }


    /** 获得缓存目录 **/
    private File getDirectory() {
        File dir = new File(getSDPathFile(),CACHDIR);
        return dir;
    }

    /** 取SD卡路径 **/
    private File getSDPathFile() {
        File storageDirectory = null;
        boolean sdCardExist = Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED);  //判断sd卡是否存在
        if (sdCardExist) {
            storageDirectory = Environment.getExternalStorageDirectory();  //获取根目录
        }
        return storageDirectory;
    }

    /** * 根据文件的最后修改时间进行排序 */
    private class FileLastModifSort implements Comparator<File> {
        public int compare(File arg0, File arg1) {
            if (arg0.lastModified() > arg1.lastModified()) {
                return 1;
            } else if (arg0.lastModified() == arg1.lastModified()) {
                return 0;
            } else {
                return -1;
            }
        }
    }

}

图片下载引擎模块

一般会在下载时对图片做一个二次采样的操作,防止oom

public class ImageGetFromHttp {
    private static final String LOG_TAG = "ImageGetFromHttp";

    public static Bitmap downloadBitmap(String url,int toW,int toH) {
        Bitmap ret = null;
        try {
            URL u = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) u.openConnection();
            conn.setConnectTimeout(3000);
            conn.setRequestMethod("GET");
            conn.setRequestProperty("connection", "keep-alive");
            conn.connect();
            //获取输入流
            InputStream inputStream = conn.getInputStream();
            //读取图片数据到字节数组,便于后续的文件缓存操作,我们缓存的是二进制文件,而不是图片文件
            byte[] data = StreamUtil.readStream(inputStream);

            //对图片进行二次采样
            //进行图片的解码
            //1.进行图片的二次采样, 第一次获取图片尺寸,第二次缩放加载图片
            // 指定图片解码的时候,采用的参数
            BitmapFactory.Options opts = new BitmapFactory.Options();
            // 仅获取图片的宽高,图像的像素信息全都不加载
            // 不会占用太多内存
            opts.inJustDecodeBounds = true;
            // 注意,使用Options来设置解码的方式
            Bitmap bitmap = BitmapFactory.decodeByteArray(
                    data,
                    0,
                    data.length,
                    opts
            );
            int picW = opts.outWidth;
            int picH = opts.outHeight;
            //Log.d("testCaiYangbefore", picW + " " + picH);
            ret = ImagSampleUtil.getAfterBitmap(data, opts, toW, toH);
            //Log.d("testCaiYangafter", ret.getWidth() + " " + ret.getHeight());
            inputStream.close();
            //断开网络连接
            conn.disconnect();

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (ProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
            return ret;
        }


}

如何使用

ima = (ImageView) findViewById(R.id.imageView1);
new CacheImageAsyncTask(ima,this).execute("http://zhibo.tianyuan161.com/uploads/start_logo/2014/0429/d33063dfed90e18a8135156f97f02177.png");

代码下载

代码初稿来自csdn,做了部分优化。

https://github.com/zhujainxipan/FYForAndroidTest

你可能感兴趣的:(自己动手封装图片三级缓存网络请求框架(类似imageloader))