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

前言

实际项目开发中, 一般不需要自己去写图片缓存框架, 直接用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. ---

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容