图片加载核心就那些东西,这里设计一个图片加载框架,涉及到本地加载和网络加载,内存缓存和硬盘缓存,等等
思路
在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