微信公众号:CodingAndroid
cnblog:http://www.cnblogs.com/angel88/
CSDN:http://blog.csdn.net/xinpengfei521

  • 需求:设计一个图片加载工具类。
  • 要求:职责单一、可扩展性强、实现三级缓存,遵循开闭原则。

1.改造前原始代码

  1. package com.anloq.sdk.imageloader;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.util.LruCache;
    import android.widget.ImageView;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    /**
    * Created by xpf on 2017/10/22 :)
    * Function:
    */
    public class ImageLoader {
    // 图片缓存
    LruCache<String, Bitmap> mImageCache;
    // 线程池,线程池数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool(
    Runtime.getRuntime().availableProcessors());
    public ImageLoader() {
    initImageCache();
    }
    /**
    * 初始化图片缓存大小
    */
    private void initImageCache() {
    // 计算可使用的最大内存
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    // 取1/4的可用内存作为缓存
    final int cacheSize = maxMemory / 4;
    mImageCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap value) {
    return value.getRowBytes() * value.getHeight() / 1024;
    }
    };
    }
    /**
    * 加载显示图片
    *
    * @param url
    * @param imageView
    */
    public void displayImage(final String url, final ImageView imageView) {
    imageView.setTag(url);
    mExecutorService.submit(new Runnable() {
    @Override
    public void run() {
    Bitmap bitmap = downloadImage(url);
    if (bitmap == null) return;
    if (imageView.getTag().equals(url)) {
    imageView.setImageBitmap(bitmap);
    }
    mImageCache.put(url, bitmap);
    }
    });
    }
    /**
    * 下载图片
    *
    * @param imageUrl
    * @return
    */
    private Bitmap downloadImage(String imageUrl) {
    Bitmap bitmap = null;
    try {
    URL url = new URL(imageUrl);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    bitmap = BitmapFactory.decodeStream(conn.getInputStream());
    conn.disconnect();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }

2.遵循单一原则将原始类分为加载和缓存两个类(功能)

2.1.图片加载类为:

  1. package com.anloq.sdk.imageloader;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.widget.ImageView;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    /**
    * Created by xpf on 2017/10/22 :)
    * Function:
    */
    public class ImageLoader {
    // 图片缓存
    ImageCache mImageCache = new ImageCache();
    // 线程池,线程池数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool(
    Runtime.getRuntime().availableProcessors());
    /**
    * 加载显示图片
    *
    * @param url
    * @param imageView
    */
    public void displayImage(final String url, final ImageView imageView) {
    // 优先从缓存中加载
    Bitmap bitmap = mImageCache.get(url);
    if (bitmap != null) {
    imageView.setImageBitmap(bitmap);
    return;
    }
    imageView.setTag(url);
    mExecutorService.submit(new Runnable() {
    @Override
    public void run() {
    Bitmap bitmap = downloadImage(url);
    if (bitmap == null) return;
    if (imageView.getTag().equals(url)) {
    imageView.setImageBitmap(bitmap);
    }
    mImageCache.put(url, bitmap);
    }
    });
    }
    /**
    * 下载图片
    *
    * @param imageUrl
    * @return
    */
    private Bitmap downloadImage(String imageUrl) {
    Bitmap bitmap = null;
    try {
    URL url = new URL(imageUrl);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    bitmap = BitmapFactory.decodeStream(conn.getInputStream());
    conn.disconnect();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }

2.2.缓存类为

  1. package com.anloq.sdk.imageloader;
    import android.graphics.Bitmap;
    import android.util.LruCache;
    /**
    * Created by xpf on 2017/10/22 :)
    * Function:图片缓存类
    */
    public class ImageCache {
    // 图片LRU缓存
    LruCache<String, Bitmap> mImageCache;
    public ImageCache() {
    initImageCache();
    }
    /**
    * 初始化图片缓存大小
    */
    private void initImageCache() {
    // 计算可使用的最大内存
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    // 取1/4的可用内存作为缓存
    final int cacheSize = maxMemory / 4;
    mImageCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap value) {
    return value.getRowBytes() * value.getHeight() / 1024;
    }
    };
    }
    public void put(String url, Bitmap bitmap) {
    mImageCache.put(url, bitmap);
    }
    public Bitmap get(String url) {
    return mImageCache.get(url);
    }
    }

3.提高扩展性,增加SD卡缓存

以上将代码的功能分开了,逻辑更清晰了,职责也单一了,但是可扩展性还是比较差,接下来进行增加SD卡缓存。

3.1增加SD卡缓存类

  1. package com.anloq.sdk.imageloader;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    /**
    * Created by xpf on 2017/10/22 :)
    * Function:
    */
    public class DiskCache {
    static String cacheDir = "/sdcard/cache/image/";
    /**
    * 从SD卡中读取
    *
    * @param url
    * @return
    */
    public Bitmap get(String url) {
    return BitmapFactory.decodeFile(cacheDir + url);
    }
    /**
    * 缓存到SD卡中
    *
    * @param url
    * @param bmp
    */
    public void put(String url, Bitmap bmp) {
    FileOutputStream fos = null;
    try {
    fos = new FileOutputStream(cacheDir + url);
    bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
    } catch (FileNotFoundException e) {
    e.printStackTrace();
    } finally {
    if (fos != null) {
    try {
    fos.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }
    }

3.2ImageLoader中增加一个boolean值来设置使用哪种缓存方式

  1. package com.anloq.sdk.imageloader;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.widget.ImageView;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    /**
    * Created by xpf on 2017/10/22 :)
    * Function:
    */
    public class ImageLoader {
    // 内存缓存
    ImageCache mImageCache = new ImageCache();
    // SD卡缓存
    DiskCache mDiskCache = new DiskCache();
    // 是否使用SD卡缓存
    boolean isUseDiskCache = false;
    // 线程池,线程池数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool(
    Runtime.getRuntime().availableProcessors());
    /**
    * 加载显示图片
    *
    * @param url
    * @param imageView
    */
    public void displayImage(final String url, final ImageView imageView) {
    // 优先从缓存中加载
    Bitmap bitmap = isUseDiskCache ? mImageCache.get(url) : mDiskCache.get(url);
    if (bitmap != null) {
    imageView.setImageBitmap(bitmap);
    return;
    }
    imageView.setTag(url);
    mExecutorService.submit(new Runnable() {
    @Override
    public void run() {
    Bitmap bitmap = downloadImage(url);
    if (bitmap == null) return;
    if (imageView.getTag().equals(url)) {
    imageView.setImageBitmap(bitmap);
    }
    mImageCache.put(url, bitmap);
    }
    });
    }
    /**
    * 下载图片
    *
    * @param imageUrl
    * @return
    */
    private Bitmap downloadImage(String imageUrl) {
    Bitmap bitmap = null;
    try {
    URL url = new URL(imageUrl);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    bitmap = BitmapFactory.decodeStream(conn.getInputStream());
    conn.disconnect();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    /**
    * 设置是否使用SD卡缓存
    *
    * @param useDiskCache
    */
    public void setUseDiskCache(boolean useDiskCache) {
    isUseDiskCache = useDiskCache;
    }
    }

4.进一步改造,使用双缓存,优先使用内存加载,如果无再使用SD卡缓存

以上代码修改虽然增加了SD卡缓存,但是为了节省用户的流量及加载速度我们应该设计成优先使用内存加载,如果无再使用SD卡缓存。

4.1增加双缓存类

  1. package com.anloq.sdk.imageloader;
    import android.graphics.Bitmap;
    /**
    * Created by xpf on 2017/10/22 :)
    * Function:
    */
    public class DoubleCache {
    ImageCache mMemoryCache = new ImageCache();
    DiskCache mDiskCache = new DiskCache();
    /**
    * 优先使用内存加载,如果无再使用SD卡缓存
    *
    * @param url
    * @return
    */
    public Bitmap get(String url) {
    Bitmap bitmap = mMemoryCache.get(url);
    if (bitmap == null) {
    bitmap = mDiskCache.get(url);
    }
    return bitmap;
    }
    /**
    * 将图片缓存到内存和SD卡中
    *
    * @param url
    * @param bitmap
    */
    public void put(String url, Bitmap bitmap) {
    mMemoryCache.put(url, bitmap);
    mDiskCache.put(url, bitmap);
    }
    }

4.2ImageLoader增加双缓存配置

  1. package com.anloq.sdk.imageloader;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.widget.ImageView;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    /**
    * Created by xpf on 2017/10/22 :)
    * Function:
    */
    public class ImageLoader {
    // 内存缓存
    ImageCache mImageCache = new ImageCache();
    // SD卡缓存
    DiskCache mDiskCache = new DiskCache();
    // 双缓存
    DoubleCache mDoubleCache = new DoubleCache();
    // 是否使用SD卡缓存
    boolean isUseDiskCache = false;
    // 是否使用双缓存
    boolean isUseDoubleCache = false;
    // 线程池,线程池数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool(
    Runtime.getRuntime().availableProcessors());
    /**
    * 加载显示图片
    *
    * @param url
    * @param imageView
    */
    public void displayImage(final String url, final ImageView imageView) {
    // 优先从缓存中加载
    Bitmap bitmap = null;
    if (isUseDoubleCache) {
    bitmap = mDoubleCache.get(url);
    } else if (isUseDiskCache) {
    bitmap = mDiskCache.get(url);
    } else {
    bitmap = mImageCache.get(url);
    }
    if (bitmap != null) {
    imageView.setImageBitmap(bitmap);
    return;
    }
    imageView.setTag(url);
    mExecutorService.submit(new Runnable() {
    @Override
    public void run() {
    Bitmap bitmap = downloadImage(url);
    if (bitmap == null) return;
    if (imageView.getTag().equals(url)) {
    imageView.setImageBitmap(bitmap);
    }
    mImageCache.put(url, bitmap);
    }
    });
    }
    /**
    * 下载图片
    *
    * @param imageUrl
    * @return
    */
    private Bitmap downloadImage(String imageUrl) {
    Bitmap bitmap = null;
    try {
    URL url = new URL(imageUrl);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    bitmap = BitmapFactory.decodeStream(conn.getInputStream());
    conn.disconnect();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    /**
    * 设置是否使用SD卡缓存
    *
    * @param useDiskCache
    */
    public void setUseDiskCache(boolean useDiskCache) {
    isUseDiskCache = useDiskCache;
    }
    /**
    * 设置是否使用双缓存
    *
    * @param useDoubleCache
    */
    public void setUseDoubleCache(boolean useDoubleCache) {
    isUseDoubleCache = useDoubleCache;
    }
    }

以上改造总算可以了,但是这样每次增加缓存策略都要修改源代码,这样很有可能引入bug,所以我们的原则是要对修改关闭,对扩展开放,这样以后有新需求的时候我们就可以使用扩展的方法来实现。

5.抽象公共方法的接口

5.1接口抽取

  1. package com.anloq.sdk.imageloader;
    import android.graphics.Bitmap;
    /**
    * Created by xpf on 2017/10/22 :)
    * Function:
    */
    public interface ImageCache {
    Bitmap get(String url);
    void put(String url, Bitmap bitmap);
    }

5.2ImageLoader注入接口的实现类

  1. package com.anloq.sdk.imageloader;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.widget.ImageView;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    /**
    * Created by xpf on 2017/10/22 :)
    * Function:
    */
    public class ImageLoader {
    ImageCache mImageCache = new MemoryCache();
    // 线程池,线程池数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool(
    Runtime.getRuntime().availableProcessors());
    /**
    * 注入缓存实现
    *
    * @param mImageCache
    */
    public void setmImageCache(ImageCache mImageCache) {
    this.mImageCache = mImageCache;
    }
    /**
    * 加载显示图片
    *
    * @param url
    * @param imageView
    */
    public void displayImage(final String url, final ImageView imageView) {
    Bitmap bitmap = mImageCache.get(url);
    if (bitmap != null) {
    imageView.setImageBitmap(bitmap);
    return;
    }
    // 图片没有缓存提交到线程池中下载
    submitLoadRequest(url, imageView);
    }
    private void submitLoadRequest(final String url, final ImageView imageView) {
    imageView.setTag(url);
    mExecutorService.submit(new Runnable() {
    @Override
    public void run() {
    Bitmap bitmap = downloadImage(url);
    if (bitmap == null) return;
    if (imageView.getTag().equals(url)) {
    imageView.setImageBitmap(bitmap);
    }
    mImageCache.put(url, bitmap);
    }
    });
    }
    /**
    * 下载图片
    *
    * @param imageUrl
    * @return
    */
    private Bitmap downloadImage(String imageUrl) {
    Bitmap bitmap = null;
    try {
    URL url = new URL(imageUrl);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    bitmap = BitmapFactory.decodeStream(conn.getInputStream());
    conn.disconnect();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return bitmap;
    }
    }

5.3内存缓存、SD卡缓存和双缓存分别实现接口

  1. package com.anloq.sdk.imageloader;
    import android.graphics.Bitmap;
    /**
    * Created by xpf on 2017/10/22 :)
    * Function:
    */
    public class DoubleCache implements ImageCache {
    ImageCache mMemoryCache = new MemoryCache();
    ImageCache mDiskCache = new DiskCache();
    /**
    * 优先使用内存加载,如果无再使用SD卡缓存
    *
    * @param url
    * @return
    */
    @Override
    public Bitmap get(String url) {
    Bitmap bitmap = mMemoryCache.get(url);
    if (bitmap == null) {
    bitmap = mDiskCache.get(url);
    }
    return bitmap;
    }
    /**
    * 将图片缓存到内存和SD卡中
    *
    * @param url
    * @param bitmap
    */
    @Override
    public void put(String url, Bitmap bitmap) {
    mMemoryCache.put(url, bitmap);
    mDiskCache.put(url, bitmap);
    }
    }

内存缓存、SD卡缓存实现同上。

6.外部调用及设置缓存策略

  1. private void loadImage() {
    ImageLoader imageLoader = new ImageLoader();
    // 使用内存缓存
    imageLoader.setmImageCache(new MemoryCache());
    // 使用SD卡缓存
    imageLoader.setmImageCache(new DiskCache());
    // 使用双缓存
    imageLoader.setmImageCache(new DoubleCache());
    // 使用自定义的图片缓存
    imageLoader.setmImageCache(new ImageCache() {
    @Override
    public Bitmap get(String url) {
    return null;
    }
    @Override
    public void put(String url, Bitmap bitmap) {
    }
    });
    String imageUrl = "http://p1.meituan.net/160.0.80/xianfu/5e369ac9d6aa54125ad1b6562282b2ca36024.jpeg";
    imageLoader.displayImage(imageUrl, imageView);
    }

经过上述代码的重构,我们可以通过setImageCache(ImageCache cache)方法注入不同的缓存实现,来使得ImageLoader更简单、健壮、扩展性好灵活性也更高。以上三种缓存图片的具体实现完全不一样,但是它们都有一个共同的特点是都实现了ImageCache接口。当用户需要增加一种新的缓存策略时,我们只需新建一个实现ImageCache接口等待类就可以了,这样就实现了千变万化的缓存策略,并且新扩展的策略不会影响导致ImageLoader类的修改,这正是体现了“对修改关闭,对扩展开放的”原则,所以,我们在设计写代码的时候应该认真地进行思考,希望大家一起思考,一起学习,有所成长!

源码链接:https://github.com/xinpengfei520/MyImageLoader

如果本文对你有帮助,欢迎大家点赞、评论,码字不易,再小的支持也是对博主的莫大鼓励!

今天的分享就到这里注明,谢谢!


声明:文中部分代码摘抄自《Android源码设计模式》一书。

注:本文由博主原创,转载请注明出处,谢谢!

若在使用过程中遇到什么问题,或有好提议,欢迎在下方留言、评论,或者关注我的公众号“CodingAndroid”留言。

《Android源码设计模式》学习笔记之ImageLoader的更多相关文章

  1. MacOS10.9获取Android源码不完全笔记(2014)

    第一步:安装Macports 这个我就不叙述了,网上有无数教程 第二步:创建一个磁盘镜像 1.打开磁盘工具,然后: 第三步:使用Macport安装编译环境 1.打开终端输入以下内容 sudo port ...

  2. 《Android源码设计模式》--抽象工厂模式

    No1: 4种MediaPlayer Factory分别会生成不同的MediaPlayer基类:StagefrightPlayer.NuPlayerDriver.MidiFile和TestPlayer ...

  3. 《Android源码设计模式》--Builder模式

    No1: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 No2: 在Android源码中,最常用到的Builder模式就是AlertDialog.Builder No3: ...

  4. zepto 源码 $.contains 学习笔记

    $.contains(parent,node)  返回值为一个布尔值 ==> boolean parent,node我们需要检查的节点检查父节点是否包含给定的dom节点,如果两者是相同的节点,返 ...

  5. c++ stl源码剖析学习笔记(一)uninitialized_copy()函数

    template <class InputIterator, class ForwardIterator>inline ForwardIterator uninitialized_copy ...

  6. android源码追踪学习 RecipientsEditor

    RecipientsEditor 新建短信时输入收接者的editor, public class RecipientsEditor extends MultiAutoCompleteTextView  ...

  7. 《Android源码设计模式》--模板方法模式

    No1: 模板方法模式包括:抽象类(其中定义了一系列顺序方法).具体实现类A.具体实现类B 如果子类有实现不一样的细节,重写父类的某个方法即可 No2: AsyncTask对象调用execute方法后 ...

  8. 《Android源码设计模式》--状态模式--责任链模式--解释器模式--命令模式--观察者模式--备忘录模式--迭代器模式

    [状态模式] No1: Wifi设置界面是一个叫做WifiSetting的Fragment实现的 No2: 在不同的状态下对于扫描Wifi这个请求的处理是完全不一样的.在初始状态下扫描请求被直接忽略, ...

  9. requests源码阅读学习笔记

    0:此文并不想拆requests的功能,目的仅仅只是让自己以后写的代码更pythonic.可能会涉及到一部分requests的功能模块,但全看心情. 1.另一种类的初始化方式 class Reques ...

随机推荐

  1. popup方法

    popup方法: 一.创建一个页面 1.创建url和视图函数:: from django.shortcuts import render def p1(request): return render( ...

  2. spring-data-jpa 中,如果使用了one-to-many , many-to-one的注释,会在Jackson进行json字符串化的时候出现错误

    问题: spring-data-jpa 中,如果使用了one-to-many , many-to-one的注释,会在Jackson 2.7.0 进行json字符串化的时候出现错误. 解决办法: 通过在 ...

  3. zookeeper 笔记-机制的特点

    zookeeper的getData(),getChildren()和exists()方法都可以注册watcher监听.而监听有以下几个特性: 一次性触发(one-time trigger) 当数据改变 ...

  4. js的事件循环绑定和jQuery的隐式迭代

    js的事件循环绑定和jQuery的隐式迭代 js事件循环绑定 jQuery隐式迭代 先举一个例子:给定一个ul,点击列表内的每一个li元素,使它的背景色变红,下边分别用js代码和jQuery实现. & ...

  5. Yii2之ListView小部件

    ListView是yii框架中类似GridView,也是用于展示多条数据的小部件,相比GridView,ListView可以更加灵活地设置数据展示的格式. 下面以我自己做的一个使用ListView来展 ...

  6. Java基础笔记6

    OOP(Object Oriented Programmer) 面向对象编程     面向对象编程的语言:JAVA,C#,PHP,ASP 用途:把现实中的任何对象描述成java语言. java面向对象 ...

  7. Python Web框架篇:Django cookie和session

    part 1 概念 在Django里面,cookie和session都记录了客户端的某种状态,用来跟踪用户访问网站的整个回话. 两者最大的区别是cookie的信息是存放在浏览器客户端的,而sessio ...

  8. C++ new 解析重载

    C++ new 解析重载 new的三种形式: (1)operator new(运算符new) (2)new operator(new 操作) (3)placement new(特殊的new操作)(不分 ...

  9. 1041: [HAOI2008]圆上的整点

    1041: [HAOI2008]圆上的整点 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 4298  Solved: 1944[Submit][Sta ...

  10. HDU 5783 Divide the Sequence (训练题002 B)

    Description Alice has a sequence A, She wants to split A into as much as possible continuous subsequ ...