图片加载核心就那些东西,这里设计一个图片加载框架,涉及到本地加载和网络加载,内存缓存和硬盘缓存,等等
思路
在getView的时候开始框架的调用
- 配置一系列环境,包括加载策略,缓存策略,线程数量
 - 调用图片显示,从而添加请求到执行队列
 - 请求及转发请求,调用加载器,根据需要从本地或者网络得到图片
 - 得到的图片再选择缓存策略,硬盘缓存或者内存缓存
 - 最后将图片显示出来
 
实现的功能和用到的知识
根据用户需求可以灵活配置
支持高并发,图片加载的优先级
支持可以选择不同的加载策略,对加载策略进行扩展
二级缓存:加载图片时内存中已经加载了,则从内存中加载,不存在去外置卡中加载,外置还不存在则从网络下载
并对缓存策略可以扩展
支持从加载过程中显示默认加载图片
支持加载失败时,显示默认错误图片
图片显示自适应,从网络加载下来的图片经最佳比例压缩后显示
不能失真变形
支持请求转发,下载
用到的模式:
生产者 消费者模式
建造者模式
单例模式
模板方法模式
策略模式
用到的知识点
内存缓存 LruCache技术
硬盘缓存技术DiskLruCache技术
图片下载时请求转发
实现代码
首先是配置类,DisplayConfig和ImageLoaderConfig,这两个类主要用于显示及下载的初始化配置1
2
3
4
5
6//显示图片配置
public class DisplayConfig {
    //默认显示的图片ID
    public int loadingImage = -1;
    public int failedImage = -1;
}
1  | //图片下载配置  | 
然后是图片的请求类,包括BitmapRequest,RequestDispatcher和RequestQueue,用于完成Bitmap请求的封装,请求的转发及请求队列的管理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75//bitmap请求
public class BitmapRequest implements Comparable<BitmapRequest> {
    //加载策略
    private LoadPolicy loadPolicy = SimpeImageLoader.getInstance().getConfig().getLoadPolicy();
    //编号
    private int serialNo;
    //持有ImageView的软引用
    private SoftReference<ImageView> imageViewSoft;
    //图片路径
    private String imageUrl;
    //MD5图片路径
    private String imageUrlMD5;
    //下载完成监听
    public SimpeImageLoader.ImageListener imageListener;
    //设置显示配置
    private DisplayConfig displayConfig;
    public BitmapRequest(ImageView imageView, String imageUrl, DisplayConfig displayConfig,
                         SimpeImageLoader.ImageListener imageListener) {
        this.imageViewSoft = new SoftReference<>(imageView);
        //设置可见Image的Tag,防止图片错位
        imageView.setTag(imageUrl);
        this.imageUrl = imageUrl;
        this.imageUrlMD5 = MD5Utils.toMD5(imageUrl);
        if (displayConfig != null) {
            this.displayConfig = displayConfig;
        }
        this.imageListener = imageListener;
    }
    
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BitmapRequest that = (BitmapRequest) o;
        return serialNo == that.serialNo &&
                Objects.equals(loadPolicy, that.loadPolicy);
    }
    
    public int hashCode() {
        return Objects.hash(loadPolicy, serialNo);
    }
    public int getSerialNo() {
        return serialNo;
    }
    public void setSerialNo(int serialNo) {
        this.serialNo = serialNo;
    }
    public ImageView getImageView() {
        return imageViewSoft.get();
    }
    public String getImageUrl() {
        return imageUrl;
    }
    public String getImageUrlMD5() {
        return imageUrlMD5;
    }
    public DisplayConfig getDisplayConfig() {
        return displayConfig;
    }
    //间接比较,确定优先级
    
    public int compareTo(@NonNull BitmapRequest o) {
        return loadPolicy.compareto(o, this);
    }
}
1  | //转发器,请求转发线程,从请求队列中获取请求  | 
1  | //请求队列  | 
缓存策略类包括缓存接口BitmapCache,硬盘缓存DiskCache,内存缓存MemoryCache和双缓存DoubleCache1
2
3
4
5
6
7
8
9//缓存策略接口
public interface BitmapCache {
    //缓存Bitmap
    void put(BitmapRequest request, Bitmap bitmap);
    //获取Bitmap
    Bitmap get(BitmapRequest request);
    //移除缓存
    void remove(BitmapRequest request);
}
1  | //硬盘缓存策略  | 
1  | //内存缓存策略  | 
1  | public class DoubleCache implements BitmapCache{  | 
硬盘缓存调用了一个开源库DiskLruCache,用到了其中的DiskLruCache.java,IOUtils.java和StrictLineReader.java
接下来时加载策略,包括加载策略接口LoadPolicy,ReversePolicy和SerialPolicy1
2
3
4
5//加载策略接口
public interface LoadPolicy {
    //优先级比较
    int compareto(BitmapRequest request1,BitmapRequest request2);
}
1  | //逆序加载策略  | 
1  | //顺序加载策略  | 
然后是工具类,包括图片解码类BitmapDecoder,图片宽高计算类ImageViewHelper和MD5工具类MD5Utils1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34//解码图片
public abstract class BitmapDecoder {
    public Bitmap decodeBitmap(int reqWidth, int reqHeight) {
        //初始化Options
        BitmapFactory.Options options = new BitmapFactory.Options();
        //读取部分信息,获得图片宽高
        options.inJustDecodeBounds = true;
        //根据bitmap加载图片
        decodeBitmapWithOption(options);
        //计算图片缩放比例
        caculateSizeWithOption(options, reqWidth, reqHeight);
        //返回缩放后的Bitmap
        return decodeBitmapWithOption(options);
    }
    private void caculateSizeWithOption(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int width = options.outWidth;
        int height = options.outHeight;
        int inSampleSize = 1;
        if (width > reqWidth || height > reqHeight) {
            int widthRatio = Math.round((float) width / (float) reqWidth);
            int heightRatio = Math.round((float) height / (float) reqHeight);
            inSampleSize = Math.max(widthRatio, heightRatio);
        }
        options.inSampleSize = inSampleSize;
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inJustDecodeBounds = false;
        //内存不足时回收Bitmap
        options.inPurgeable = true;
        options.inInputShareable = true;
    }
    public abstract Bitmap decodeBitmapWithOption(BitmapFactory.Options options);
}
1  | public class ImageViewHelper {  | 
1  | public class MD5Utils {  | 
最后是加载类,包括加载接口Loader,加载抽象类AbstractLoader,硬盘加载器LocalLoader,网络加载器UrlLoader,空加载器NullLoader,图片加载器SimpeImageLoader,加载器管理LoaderManager1
2
3
4
5//加载器接口
public interface Loader {
    //加载图片
    void loadImage(BitmapRequest request);
}
1  | //抽象加载器  | 
1  | //硬盘加载器  | 
1  | //网络加载器  | 
1  | public class NullLoader extends AbstractLoader {  | 
1  | //图片加载器,单利对象  | 
1  | //加载器管理  | 
最后是测试类,这里我是用tomcat搭建服务器,使用975张图片做测试1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70public class MainActivity extends AppCompatActivity {
    private SimpeImageLoader imageLoader;
    private static final int COUNT = 975;
    private static final String path = "http://192.168.1.2:8080/test/";
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.list);
        GridView listview = (GridView) findViewById(R.id.listview);
        listview.setAdapter(new MyAdapter(this));
        //配置
        ImageLoaderConfig.Builder build = new ImageLoaderConfig.Builder();
        build.setThreadCount(3) //线程数量
                .setLoadPolicy(new ReversePolicy()) //加载策略
                .setCachePolicy(new DoubleCache(this)) //缓存策略
                .setLoadingImage(R.drawable.loading)
                .setFaildImage(R.drawable.not_found);
        ImageLoaderConfig config = build.build();
        //初始化
        imageLoader = SimpeImageLoader.getInstance(config);
    }
    class MyAdapter extends BaseAdapter {
        private LayoutInflater inflater;
        public MyAdapter(Context context) {
            inflater = LayoutInflater.from(context);
        }
        
        public int getCount() {
            return COUNT;
        }
        
        public Object getItem(int position) {
            return getUrl(position);
        }
        
        public long getItemId(int position) {
            return position;
        }
        
        public View getView(int position, View convertView, ViewGroup parent) {
            View item = inflater.inflate(R.layout.item, null);
            ImageView imageView = (ImageView) item.findViewById(R.id.iv);
            //请求图片
            imageLoader.displayImage(imageView, getUrl(position));
            return item;
        }
    }
    public String getUrl(int position) {
        if (position < 10)
            return path + "00" + position + ".jpg";
        else if (position < 100)
            return path + "0" + position + ".jpg";
        else if (position < COUNT)
            return path + position + ".jpg";
        else
            return null;
    }
}
其实,这就是缩水版的Glide