一个简单图片缓存框架的实现

作者: ahking17 | 来源:发表于2017-09-14 17:28 被阅读100次
    前言

    实际项目开发中, 一般不需要自己去写图片缓存框架, 直接用glide之类的成熟sdk是明智之举, 但如果自己开发SDK库给别人用, 例如我之前做的hola广告sdk, 是让别人集成到他们的app中使用的, 在这种情况下, 就需要自己来完成图片缓存框架的编写, 一方面是为了减少sdk的size, 另一方便, 如果我用glide的话, 客户的app也引用glide, 就可能会造成包冲突, 所以对于sdk库的开发来说, 应该尽量少引用第三方库.

    总体的设计目标

    首先要搞明白, 对图片缓存的实现目标. 对任何图片来说, 从网上下载后, 都要保存到磁盘上.
    而对于内存来说, 从网上下载的图片文件, 会根据文件生成Bitmap对象, 这个Bitmap对象是保存在内存上的. 但内存空间有限, 这就需要给内存缓存设置一个最大值, 当达到这个值后, 就根据LRU策略, 把最少使用的Bitmap对象从内存缓存空间中删除掉, 以释放出一些内存空间, 用来存储新得到的Bitmap对象.

    对于加载图片的实现思路是: 首先从内存中查看是否存在这个url对应的bitmap对象, 没有的话, 再检查磁盘中是否存在这个url所对应的图片文件, 还是没有的话, 从网上下载文件, 并且在下载文件后, 把所生成的bitmap对象放入内存缓存集合中 (LinkedHashMap), 并且还需要把这个文件写入到磁盘中.

    一些特别要注意的开发要点:

    (1) 往SD卡上存储图片文件时, 对于文件名的命名, 要特别注意, 一般来说, 网上的图片地址格式是: http://www.baidu/image/abc.png, 如果要以abc.png作为文件名保存的话, 就会有这样的问题, 由于Android手机上有一个媒体库, 它会定期扫描SD卡上的图片和视频文件, 并以ContentProvider的形式对外提供数据, 如果文件名是.png, 那么就会被媒体库收录进来, 在其他的图片查看器App中, 就会看到这些"私密"图片.
    解决办法是, 设置文件名时, 对原地址(http://www.baidu/image/abc.png)取MD5值, 以这个计算后的值作为文件名进行保存. 这样就避免了这些文件被媒体库收录的问题了.

    (2) 既然要用到LRU思想, 那么采用什么数据结构在内存中存储这些Bitmap对象?
    要使用LinkedHashMap来实现, 这是因为LinkedHashMap的特性决定的, 它有一个成员变量accessOrder指定了对象的存放顺序, false为按插入顺序存放, true为按访问顺序存放. 在LRU思想下, 设置accessOrder为true即可.
    这就正好满足了LRU的设计思想.
    具体的可以看我之前的文章:
    http://www.jianshu.com/p/e5c3b522e8c5

    代码实现

    MyImageLoader.java

    
    /**
     * Created by wangxin on 17-8-29.
     */
    /*      使用方法
            ImageView iv_type;
            MyImageLoader imageLoader = MyImageLoader.getInstance(this);
                    ArrayList<String> urls = new ArrayList<>();
                    urls.add("http://img5.imgtn.bdimg.com/it/u=3206533887,2931873948&fm=27&gp=0.jpg");
                    urls.add("http://img0.imgtn.bdimg.com/it/u=2138081325,2407756075&fm=27&gp=0.jpg");
                    urls.add("http://img4.imgtn.bdimg.com/it/u=196278633,1521334363&fm=27&gp=0.jpg");
                    urls.add("http://img3.imgtn.bdimg.com/it/u=3487766690,1195649711&fm=27&gp=0.jpg");
                    urls.add("http://img1.imgtn.bdimg.com/it/u=698943482,1934898431&fm=27&gp=0.jpg");
                    urls.add("http://img1.imgtn.bdimg.com/it/u=3187985879,2007978564&fm=27&gp=0.jpg");
                    urls.add("http://img5.imgtn.bdimg.com/it/u=760482064,3255510007&fm=27&gp=0.jpg");
    
                    for (String u : urls) {
                    imageLoader.loadImage(u, iv_type);
                    }
    
    */
    public class MyImageLoader {
        private static MyImageLoader mInstance;
        private static final int MAX_CAPACITY = 20;
        private static Context mContext;
    
        private MyImageLoader(Context context) {
            mContext = context;
        }
    
        public static MyImageLoader getInstance(Context context) {
            if(mInstance == null) {
                mInstance =  new MyImageLoader(context);
            }
            return mInstance;
        }
    
    
    
        private static LinkedHashMap<String, SoftReference<Bitmap>> firstCacheMap = new LinkedHashMap<String, SoftReference<Bitmap>>(MAX_CAPACITY) //这里设置一个初始大小值.
        {
            //LinkedHashMap每次添加一个新元素进来, 都会回调这个接口, 询问是否要移除掉最老的那个元素.
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, SoftReference<Bitmap>> eldest) {
                JLog.i();
                if(this.size() > MAX_CAPACITY) {
                    //返回true, 表示移除最老的那个元素
                    return true;
                }
                // 把最老的那个元素保存到磁盘中
                diskCache(eldest.getKey(), eldest.getValue());
                return false;
            }
        };
    
        //对外提供的API.
        public void loadImage(String key, ImageView view) {
            synchronized (view) {
                //检查缓存里是否有
                Bitmap bitmap = getFromCache(key);
                if(bitmap != null) {
                    //缓存存在, 直接显示
                    view.setBackgroundDrawable(new ColorDrawable(Color.GRAY));
                    view.setImageBitmap(bitmap);
                } else {
                    // 下载
                    view.setBackgroundDrawable(new ColorDrawable(Color.GRAY));
                    AsyncImageLoaderTask task = new AsyncImageLoaderTask(view);
                    task.execute(key);
                }
            }
        }
    
        private Bitmap getFromCache(String key) {
            //检查内存缓存
            synchronized (firstCacheMap) {
                if(firstCacheMap.get(key) != null) {
                    Bitmap bitmap = firstCacheMap.get(key).get();
                    if(bitmap != null) {
                        //把这个元素再放进去一次的目的是, 增加它的计数值, 表示它又被访问了一次.
                        firstCacheMap.put(key,new SoftReference<Bitmap>(bitmap));
                        return bitmap;
                    }
                }
    
                //检查磁盘
                Bitmap bitmap = getFromLocalKey(key);
                if (bitmap != null) {
                    firstCacheMap.put(key,new SoftReference<Bitmap>(bitmap));
                    return bitmap;
                }
                return null;
            }
        }
    
        // 检查SD卡中是否有该图片
        private Bitmap getFromLocalKey(String key) {
            String filename = MD5Utils.digest(key);
            if (filename == null) {
                return null;
            } else {
                String path = mContext.getCacheDir().getAbsolutePath() + filename;
                //如果这个path指定的文件不存在的话, 后面也会return null.
                InputStream is = null;
                try {
                    is = new FileInputStream(new File(path));
                    Bitmap bitmap = BitmapFactory.decodeStream(is);
                    return bitmap;
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } finally {
                    if (is != null) {
                        try {
                            is.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            return null;
        }
    
        //把图片保存到本地磁盘
        //缓存文件的路径: /data/data/com.hola.weather/cache/539911d002dd6b9e0f23c851e6eb40ce
        private static void diskCache(String key, SoftReference<Bitmap> value) {
            //消息摘要算法
            String fileName = MD5Utils.digest(key);
            String path = mContext.getCacheDir().getAbsolutePath()+"/"+fileName;
    JLog.i("path: " + path);
            FileOutputStream os = null;
            try {
                os = new FileOutputStream(new File(path));
                if(value.get() != null) {
                    value.get().compress(Bitmap.CompressFormat.JPEG, 100, os);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } finally {
                if(os != null) {
                    try {
                        os.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    
        class AsyncImageLoaderTask extends AsyncTask<String, Void, Bitmap> {
            private ImageView imageView;
            private String key;
    
            public AsyncImageLoaderTask(ImageView imageView) {
                this.imageView = imageView;
            }
    
            @Override
            protected Bitmap doInBackground(String... params) {
                this.key = params[0]; //图片的路径
                Bitmap bitmap = download(key);
                return bitmap;
            }
    
            private Bitmap download(String key) {
                InputStream is = null;
                try {
                    is = HttpUtil.download(key);
                    return BitmapFactory.decodeStream(is);
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (is != null) {
                        try {
                            is.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                return null;
            }
    
            @Override
            protected void onPostExecute(Bitmap bitmap) {
                super.onPostExecute(bitmap);
                if (bitmap != null) {
                    addFirstCache(key, bitmap);
                    imageView.setImageBitmap(bitmap);
                }
            }
        }
    
        private void addFirstCache(String key, Bitmap bitmap) {
            if (bitmap != null) {
                synchronized (firstCacheMap) {
                    firstCacheMap.put(key, new SoftReference<Bitmap>(bitmap));
                }
    
            }
        }
    
    }
    
    
    
    

    github:
    https://github.com/AandK/MyImageLoader/blob/master/MyImageLoader.java
    dong nao video note.
    --- DONE. ---

    相关文章

      网友评论

        本文标题:一个简单图片缓存框架的实现

        本文链接:https://www.haomeiwen.com/subject/rrbdsxtx.html