Android 通用功能封装2——图片加载

http://rainmonth.github.io/posts/A210203.html

为什么要封装

现有的图片加载框架已经很好了,如Fresco、Glide等都有十分丰富的功能,但是会存在一个问题,如果没有在这个基础上进行封装,而是直接代码中使用的话,那么万一哪天我想把网络加载库从Fresco换成Glide的话,那么要替换的东西就很多,工作量就很大,所以封装时为了 控制反转,具体使用什么图片加载框架由使用者决定,不管用哪个加载库,对使用者而言,调用的API都是一样的,这就方便了 项目的移植,方便了功能的扩展,而且由于代码高内聚,低耦合,最终出问题的概率也会大大降低,这就是二次封装的意义。

封装方法

具体的封装策略就是提取各个加载框架的共性,同时支持某些加载框架的特性,共性是基础,指的是图片加载框架都具有的通用功能,特性值的是 只有某些加载框架才支持的特殊配制,这两个在封装的时候都需要考虑到,看下面这个类 ILoadStrategy

图片加载常用功能

常用功能对应的接口 ILoadStrategy 的具体实现如下:

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* 图片加载策略
*
* @author randy
* @date 2021/06/04 11:49 AM
*/
public interface ILoadStrategy {
/**
* 加载图片
*
* @param loadConfig 加载图片配置
* @param view 加载目标对象,ImageView or SimpleDraweeView
* @param callback 加载回调
* @param extendOption 额外配置接口
*/
void loadImage(LoadConfig loadConfig, View view, Callback callback,
ExtendedOptions extendOption);

/**
* 清除缓存
*
* @param type 清除类型
* {@link LoaderConst.CacheClearType#CLEAR_ALL_CACHE}
* {@link LoaderConst.CacheClearType#CLEAR_MEM_CACHE}
* {@link LoaderConst.CacheClearType#CLEAR_DISK_CACHE}
*/
void clearCache(int type);

/**
* 清除指定缓存
*
* @param type 清除类型
* {@link LoaderConst.CacheClearType#CLEAR_ALL_CACHE}
* {@link LoaderConst.CacheClearType#CLEAR_MEM_CACHE}
* {@link LoaderConst.CacheClearType#CLEAR_DISK_CACHE}
* @param loadConfig 加载图片配置
*/
void clearCacheKey(int type, LoadConfig loadConfig);

/**
* 是否已经缓存到本地
*
* @param loadConfig 加载图片配置
* @param extendedOption 额外配置接口
* @return Boolean 是否已经缓存到本地
*/
boolean isCache(LoadConfig loadConfig, ExtendedOptions extendedOption);

/**
* 获取本地缓存
*
* @param loadConfig 加载图片配置
* @param extendOption 额外配置接口
* @return File
*/
File getLocalCache(LoadConfig loadConfig, ExtendedOptions extendOption);

/**
* 获取本地缓存bitmap
*
* @param loadConfig 加载图片配置
* @param extendOption 额外配置接口
* @return Bitmap
*/
Bitmap getLocalCacheBitmap(LoadConfig loadConfig, ExtendedOptions extendOption);


/**
* 获取本地缓存大小
*
* @return Long
*/
long getCacheSize(LoadConfig loadConfig);


/**
* 下载图片
*
* @param loadConfig 加载图片配置
* @param callback 加载回调
* @param extendOption 额外配置接口
*/
void downloadOnly(LoadConfig loadConfig, Callback callback, ExtendedOptions extendOption);

/**
* 不同加载库可能支持的额外配置
*/
interface ExtendedOptions {
/**
* 不同的图片加载库 独有的一些配置
*
* @param option 配置对象
* Glide com.bumptech.glide.request.RequestOptions
* Picasso com.squareup.picasso.RequestCreator
* Fresco com.facebook.imagepipeline.request.ImageRequestBuilder
*/
void onOptionsInit(Object option);
}

/**
* 回调接口
*/
@UiThread
interface Callback {
void onStart();

void onSuccess(Object result);

void onFail(Exception error);
}
}

上面基本包含了每个图片加载框架都会有的常用功能,包括:

  • 图片加载,通过loadImage实现;

  • 缓存清除,通过clearCache实现;

  • 清除指定图片缓存,通过clearCacheKey实现;

  • 判断图片是否缓存,通过isCache实现;

  • 获取缓存文件,通过getLocalCache实现;

  • 获取缓存的Bitmap,通过getLocalCacheBitmap实现;

  • 获取缓存配置的大小,通过getCacheSize实现;

  • 图片下载,通过downloadOnly实现;

然后就是定义了一个接口 ExtendedOptions,它就是用来保留不通请求框架所独有的配置信息的,如果加载图片时,需要使用Glide特有的一些加载配置参数,就可将 Glide的请求配置对象RequestOptions 作为object参数传递到ExtendedOptionsonOptionsInit(Object object),然后在这个方法的实现中将具体的特殊加载配置设置进去。

LoadConfg 为通用配置参数类,可以将通用的加载配置写到这个LoadConfig中,然后各个框架分别取实现这个类,作为配置参数,这个类最好采用 建造者模式 来编写。

当然,上面只是定义了接口,具体的实现需要不通的图片加载框架具体实现,下面给出几个主流加载框架的 ILoadStrategy 实现。

Glide对应实现

Glide对应的ILoadStrategy 实现:GlideLoadStrategy

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
public class GlideLoadStrategy implements ILoadStrategy {

@Override
public void loadImage(LoadConfig loadConfig, View view, Callback callback, ExtendedOptions extendOption) {
if (!(view instanceof ImageView)) {
throw new IllegalArgumentException("view must be ImageView");
}

loadImage(loadConfig, extendOption)
.load(loadConfig.mUri)
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e,
Object model,
Target<Drawable> target,
boolean isFirstResource) {
if (callback != null) {
callback.onFail(e);
}
return false;
}

@Override
public boolean onResourceReady(Drawable resource,
Object model,
Target<Drawable> target,
DataSource dataSource,
boolean isFirstResource) {
if (callback != null) {
callback.onSuccess(resource);
}
return false;
}
})
.into((ImageView) view);
}

@Override
public void clearCache(int type) {
if (type == LoaderConst.CacheClearType.CLEAR_DISK_CACHE) {
Glide.get(getContext()).clearDiskCache();
} else if (type == LoaderConst.CacheClearType.CLEAR_MEM_CACHE) {
Glide.get(getContext()).clearMemory();
} else {
Glide.get(getContext()).clearDiskCache();
Glide.get(getContext()).clearMemory();
}
}

@Override
public void clearCacheKey(int type, LoadConfig loadConfig) {
if (type == LoaderConst.CacheClearType.CLEAR_ALL_CACHE) {
clearDiskCacheKey(loadConfig);
clearMemCacheKey(loadConfig);
}
if (type == LoaderConst.CacheClearType.CLEAR_DISK_CACHE) {
clearDiskCacheKey(loadConfig);
}
if (type == LoaderConst.CacheClearType.CLEAR_MEM_CACHE) {
clearMemCacheKey(loadConfig);
}
}

/**
* 清除硬盘中的 key
*
* @param loadConfig 加载配置选项
*/
private void clearDiskCacheKey(LoadConfig loadConfig) {
DiskCache diskCache = DiskLruCacheWrapper.create(Glide.getPhotoCacheDir(getContext()),
loadConfig.mMaxDiskCacheSize);
GlideCacheKey key = new GlideCacheKey(loadConfig.mUri, EmptySignature.obtain());
diskCache.delete(key);
}

/**
* 清除内存中的 key
*/
private void clearMemCacheKey(LoadConfig loadConfig) {
GlideCacheKey memKey = new GlideCacheKey(loadConfig.mUri, EmptySignature.obtain());
MemoryCache cache = ReflectionHelper.INSTANCE.getField(Glide.get(getContext()), "memoryCache");
cache.remove(memKey);
}

@Override
public boolean isCache(LoadConfig loadConfig, ExtendedOptions extendedOption) {
return false;
}

@Override
public File getLocalCache(LoadConfig loadConfig, ExtendedOptions extendOption) {
return null;
}

@Override
public Bitmap getLocalCacheBitmap(LoadConfig loadConfig, ExtendedOptions extendOption) {
RequestBuilder<Bitmap> bitmapRequestBuilder = loadImage(loadConfig, extendOption).asBitmap().load(loadConfig.mUri);
try {
return bitmapRequestBuilder.submit().get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}

@Override
public long getCacheSize(LoadConfig loadConfig) {
DiskCache cache = DiskLruCacheWrapper.create(Glide.getPhotoCacheDir(getContext()),
loadConfig.mMaxDiskCacheSize);
DiskLruCache diskLruCache = ReflectionHelper.INSTANCE.getField(cache, "diskLruCache");
return diskLruCache.size();
}

@Override
public void downloadOnly(LoadConfig loadConfig, Callback callback, ExtendedOptions extendOption) {
loadImage(loadConfig, extendOption).downloadOnly().load(loadConfig.mUri).into(new GlideLoadTarget(callback));
}

/**
* 获取 Context
* todo 进行替换
*
* @return Context
*/
private Context getContext() {
return Utils.getApp();
}

private RequestManager loadImage(LoadConfig loadConfig, ExtendedOptions extendedOptions) {
return Glide.with(getContext().getApplicationContext())
.setDefaultRequestOptions(getRequestOptions(loadConfig, extendedOptions));
}

@SuppressLint("CheckResult")
private RequestOptions getRequestOptions(LoadConfig loadConfig, ExtendedOptions extendedOptions) {
RequestOptions requestOptions = new RequestOptions();
if (loadConfig.placeholderId > 0) {
requestOptions.placeholder(loadConfig.placeholderId);
}

if (loadConfig.errorId > 0) {
requestOptions.error(loadConfig.errorId);
}

if (loadConfig.isCircle) {
requestOptions.circleCrop();
}
if (loadConfig.mSize != null) {
requestOptions.override(loadConfig.mSize.x, loadConfig.mSize.y);
}
if (loadConfig.mTransformations != null && loadConfig.mTransformations.size() > 0) {
try {
requestOptions.transform((Transformation<Bitmap>) loadConfig.mTransformations);
} catch (Exception e) {
e.printStackTrace();
}
}
if (extendedOptions != null) {
extendedOptions.onOptionsInit(requestOptions);
}
return requestOptions;
}
}

Fresco对应实现

Fresco 对应的ILoadStrategy实现 FrescoLoadStrategy

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
public class FrescoLoadStrategy extends BaseFrescoLoadStrategy implements ILoadStrategy {

public FrescoLoadStrategy(ImagePipelineConfig config) {
if (config == null) {
config = ImagePipelineConfig.newBuilder(Utils.getApp())
.setDownsampleEnabled(true)
.build();
}
Fresco.initialize(Utils.getApp(), config);
}

@Override
public void loadImage(LoadConfig loadConfig, View view, Callback callback,
ExtendedOptions extendOption) {
if (!(view instanceof SimpleDraweeView)) {
throw new IllegalArgumentException("view must be SimpleDraweeView");
}

try {
SimpleDraweeView target = (SimpleDraweeView) view;
initFrescoView(target, loadConfig);
ImageRequest imageRequest = buildImageRequestWithResource(loadConfig, extendOption);
ImageRequest lowRequest = buildLowImageRequest(target, loadConfig, extendOption);
target.setController(buildDraweeController(target, loadConfig, callback, imageRequest,
lowRequest));
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public void clearCache(int type) {
if (type == LoaderConst.CacheClearType.CLEAR_ALL_CACHE) {
Fresco.getImagePipeline().clearCaches();
}
if (type == LoaderConst.CacheClearType.CLEAR_MEM_CACHE) {
Fresco.getImagePipeline().clearMemoryCaches();
}
if (type == LoaderConst.CacheClearType.CLEAR_DISK_CACHE) {
Fresco.getImagePipeline().clearDiskCaches();
}
}

@Override
public void clearCacheKey(int type, LoadConfig loadConfig) {
Uri loadUri = getUri(loadConfig.mUri);
if (type == LoaderConst.CacheClearType.CLEAR_ALL_CACHE) {
Fresco.getImagePipeline().evictFromCache(loadUri);
}
if (type == LoaderConst.CacheClearType.CLEAR_MEM_CACHE) {
Fresco.getImagePipeline().evictFromMemoryCache(loadUri);
}
if (type == LoaderConst.CacheClearType.CLEAR_DISK_CACHE) {
Fresco.getImagePipeline().evictFromDiskCache(loadUri);
}
}

@Override
public boolean isCache(LoadConfig loadConfig, ExtendedOptions extendedOption) {
return isCached(Utils.getApp(), loadConfig.mUri);
}

@Override
public File getLocalCache(LoadConfig loadConfig, ExtendedOptions extendOption) {
Uri loadUri = getUri(loadConfig.mUri);
if (!isCached(Utils.getApp(), loadUri)) {
return null;
}
ImageRequest imageRequest = ImageRequest.fromUri(loadUri);
CacheKey cacheKey = DefaultCacheKeyFactory.getInstance()
.getEncodedCacheKey(imageRequest, Utils.getApp());
BinaryResource resource = ImagePipelineFactory.getInstance()
.getMainFileCache().getResource(cacheKey);
if (resource instanceof FileBinaryResource) {
return ((FileBinaryResource) resource).getFile();
} else {
return null;
}
}

@Override
public Bitmap getLocalCacheBitmap(LoadConfig loadConfig, ExtendedOptions extendOption) {
Uri loadUri = getUri(loadConfig.mUri);
if (!isCached(Utils.getApp(), loadUri))
return null;
ImageRequest request = buildImageRequestWithResource(loadConfig, extendOption);
CacheKey cacheKey = DefaultCacheKeyFactory.getInstance()
.getBitmapCacheKey(request, Utils.getApp());
CloseableReference<CloseableImage> resource = ImagePipelineFactory.getInstance()
.getBitmapCountingMemoryCache().get(cacheKey);
if (resource != null && resource.get() instanceof CloseableBitmap) {
return ((CloseableBitmap) resource.get()).getUnderlyingBitmap();
} else {
return null;
}
}

@Override
public long getCacheSize(LoadConfig loadConfig) {
return ImagePipelineFactory.getInstance().getMainFileCache().getSize();
}

@Override
public void downloadOnly(LoadConfig loadConfig, Callback callback,
ExtendedOptions extendOption) {
ImageRequest imageRequest = buildImageRequestWithResource(loadConfig, extendOption);
ImagePipeline imagePipeline = Fresco.getImagePipeline();
DataSource<Void> dataSource = imagePipeline.prefetchToDiskCache(imageRequest,
Utils.getApp());
dataSource.subscribe(new BaseDataSubscriber<Void>() {

@Override
protected void onNewResultImpl(@NonNull DataSource<Void> dataSource) {
File file = getLocalCache(loadConfig, extendOption);
if (callback != null) {
callback.onSuccess(file);
}
}

@Override
protected void onFailureImpl(@NonNull DataSource<Void> dataSource) {
if (callback != null) {
callback.onFail(null);
}
}
}, CallerThreadExecutor.getInstance());
}

}

其中 BaseFrescoLoadStrategy 实现如下:

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
public class BaseFrescoLoadStrategy {

boolean isCached(Context context, String uri) {
try {
return isCached(context, Uri.parse(uri));
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 是否缓存
*
* @param context caller context
* @param uri the load Uri
* @return true if had cached
*/
boolean isCached(Context context, Uri uri) {
ImagePipeline pipeline = Fresco.getImagePipeline();
DataSource<Boolean> dataSource = pipeline.isInDiskCache(uri);
if (dataSource == null) {
return false;
}
ImageRequest imageRequest = ImageRequest.fromUri(uri);
CacheKey cacheKey = DefaultCacheKeyFactory.getInstance()
.getEncodedCacheKey(imageRequest, context);
BinaryResource resource = ImagePipelineFactory.getInstance()
.getMainFileCache().getResource(cacheKey);
return resource != null && dataSource.getResult() != null && dataSource.getResult();
}

/**
* 获取要加载的 Uri
*
* @param targetUri 待加载的地址,可能是String,也可能是Uri
* @return 要加载的Uri
*/
Uri getUri(Object targetUri) {
Uri uri = null;
if (targetUri instanceof String) {
try {
uri = Uri.parse((String) targetUri);
} catch (Exception e) {
e.printStackTrace();
}
}
if (targetUri instanceof Uri) {
uri = (Uri) targetUri;
}

return uri;
}


/**
* 初始化要展示图像的View
*
* @param simpleDraweeView 展示图片的View
* @param loadConfig 加载配置
*/
void initFrescoView(SimpleDraweeView simpleDraweeView, LoadConfig loadConfig) {
if (loadConfig.placeholderId > 0) {
simpleDraweeView.getHierarchy().setPlaceholderImage(loadConfig.placeholderId);
}
if (loadConfig.errorId > 0) {
simpleDraweeView.getHierarchy().setFailureImage(loadConfig.errorId);
}
if (loadConfig.isCircle) {
setRoundingParams(simpleDraweeView, getRoundingParams(simpleDraweeView).setRoundAsCircle(true));
} else {
setRoundingParams(simpleDraweeView, getRoundingParams(simpleDraweeView).setRoundAsCircle(false));
}
}

ImageRequest buildImageRequestWithResource(LoadConfig loadConfig,
ILoadStrategy.ExtendedOptions extendedOptions) {
String remoteTarget = loadConfig.mUri;
ImageRequestBuilder builder;
// todo 增加不同显示方式的判断
Uri uri = Uri.parse(remoteTarget);
builder = ImageRequestBuilder.newBuilderWithSource(uri);

if (loadConfig.mSize != null && loadConfig.mSize.x > 0 && loadConfig.mSize.y > 0) {
ResizeOptions options = new ResizeOptions(loadConfig.mSize.x, loadConfig.mSize.y);
builder.setResizeOptions(options);
} else {
builder.setResizeOptions(null);
}
if (loadConfig.mTransformations != null && loadConfig.mTransformations.size() > 0) {
Object object = loadConfig.mTransformations.get(0);
if (object instanceof BasePostprocessor) {
builder.setPostprocessor((Postprocessor) object);
}
}
if (extendedOptions != null) {
extendedOptions.onOptionsInit(builder);
}
return builder.build();
}

ImageRequest buildLowImageRequest(SimpleDraweeView simpleDraweeView,
LoadConfig loadConfig,
ILoadStrategy.ExtendedOptions extendOption) {
/*String lowThumbnail = null
if (TextUtils.isEmpty(fresco.getLowThumbnailUrl())) {
return null
}
lowThumbnail = fresco.getLowThumbnailUrl()
val uri = Uri.parse(lowThumbnail)
return ImageRequest.fromUri(uri)*/
return null;
}

DraweeController buildDraweeController(SimpleDraweeView simpleDraweeView,
LoadConfig loadConfig,
ILoadStrategy.Callback callback,
ImageRequest imageRequest,
ImageRequest lowRequest) {
PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder();
builder.setImageRequest(imageRequest)
.setAutoPlayAnimations(loadConfig.isPlayGif)
//.setTapToRetryEnabled(fresco.getTapToRetryEnabled())
.setLowResImageRequest(lowRequest);
if (callback != null) {
builder.setControllerListener(new ControllerListener<ImageInfo>() {
@Override
public void onSubmit(String id, Object callerContext) {
callback.onStart();
}

@Override
public void onFinalImageSet(String id, @Nullable ImageInfo imageInfo,
@Nullable Animatable animatable) {
callback.onSuccess(new Exception(id));
}

@Override
public void onIntermediateImageSet(String id, @Nullable ImageInfo imageInfo) {
callback.onSuccess(new Exception(id));
}

@Override
public void onIntermediateImageFailed(String id, Throwable throwable) {
callback.onFail(new Exception(throwable));
}

@Override
public void onFailure(String id, Throwable throwable) {
callback.onFail(new Exception(throwable));
}

@Override
public void onRelease(String id) {

}
});
} else {
builder.setControllerListener(new BaseControllerListener<>());
}
builder.setOldController(simpleDraweeView.getController());
return builder.build();
}

RoundingParams getRoundingParams(SimpleDraweeView simpleDraweeView) {
RoundingParams roundingParams = simpleDraweeView.getHierarchy().getRoundingParams();
if (roundingParams == null) {
roundingParams = new RoundingParams();
}
return roundingParams;
}

void setRoundingParams(SimpleDraweeView simpleDraweeView, RoundingParams roundingParmas) {
simpleDraweeView.getHierarchy().setRoundingParams(roundingParmas);
}
}

Picasso对应实现

Picasso 对应的 ILoadStrategy 实现PicassoLoadStrategy

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
public class PicassoLoadStrategy implements ILoadStrategy {

private final Picasso mPicasso;

public PicassoLoadStrategy(Picasso.Builder builder) {
if (builder != null) {
mPicasso = builder.build();
} else {
mPicasso = Picasso.get();
}
}

@Override
public void loadImage(LoadConfig loadConfig, View view, Callback callback, ExtendedOptions extendOption) {
if (!(view instanceof ImageView)) {
throw new IllegalArgumentException("View must be ImageView");
}
RequestCreator requestCreator = getRequest(loadConfig, extendOption);
if (requestCreator != null) {
requestCreator.into((ImageView) view, new com.squareup.picasso.Callback() {
@Override
public void onSuccess() {
if (callback != null) {
callback.onSuccess(null);
}
}

@Override
public void onError(Exception e) {
if (callback != null) {
callback.onFail(e);
}
}
});
}

}

@Override
public void clearCache(int type) {
try {
Cache cache = ReflectionHelper.INSTANCE.getField(mPicasso, "cache");
cache.clear();
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public void clearCacheKey(int type, LoadConfig loadConfig) {
mPicasso.invalidate(loadConfig.mUri);
}

@Override
public boolean isCache(LoadConfig loadConfig, ExtendedOptions extendedOption) {
Log.e(getClass().getSimpleName(), "not support for picasso");
return false;
}

@Override
public File getLocalCache(LoadConfig loadConfig, ExtendedOptions extendOption) {
Log.e(getClass().getSimpleName(), "not support for picasso");
return null;
}

@Override
public Bitmap getLocalCacheBitmap(LoadConfig loadConfig, ExtendedOptions extendOption) {
Bitmap bitmap = null;
try {
bitmap = getRequest(loadConfig, extendOption) != null ? getRequest(loadConfig, extendOption).get() : null;
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}

@Override
public long getCacheSize(LoadConfig loadConfig) {
return mPicasso.getSnapshot().size;
}

@Override
public void downloadOnly(LoadConfig loadConfig, Callback callback, ExtendedOptions extendOption) {
RequestCreator requestCreator = getRequest(loadConfig, extendOption);
if (requestCreator != null) {
requestCreator.into(new Target() {
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
if (callback != null) {
callback.onStart();
}
}

@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
if (callback != null) {
callback.onSuccess(bitmap);
}
}

@Override
public void onBitmapFailed(Exception e, Drawable errorDrawable) {
if (callback != null) {
callback.onFail(e);
}
}
});
}
}


private RequestCreator getRequest(LoadConfig loadConfig, ExtendedOptions extendedOptions) {
RequestCreator requestCreator = null;
requestCreator = mPicasso.load(loadConfig.mUri);

if (requestCreator != null) {
if (loadConfig.placeholderId > 0) {
requestCreator.placeholder(loadConfig.placeholderId);
}
if (loadConfig.errorId > 0) {
requestCreator.error(loadConfig.errorId);
}
if (loadConfig.isCircle) {
// todo
}
if (loadConfig.mSize != null) {
requestCreator.resize(loadConfig.mSize.x, loadConfig.mSize.y);
}
if (loadConfig.mTransformations != null && loadConfig.mTransformations.size() > 0) {
requestCreator.transform((Transformation) loadConfig.mTransformations);
}
if (extendedOptions != null) {
extendedOptions.onOptionsInit(requestCreator);
}
}

return requestCreator;
}
}

图片加载类

在不同图片加载对 ILoadStrategy的具体实现了之后,需要一个类来决定使用那个加载策略来进行图片的加载操作,这就是接下来这个ImageLoader的作用了,通过在构造这个 ImageLoader 实例时为其传递一个 ILoadStrategy 的具体实现类,或者通过调用setLoadStrategy方法来设置具体的 ILoadStrategy,简单的说,这个类知识 一个代理,具体的加载实现都是通过 其成员变量 ILoadStrategy 来决定的。类的具体实现如下:

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
public class ImageLoader implements ILoadStrategy{
private final ILoadStrategy mLoadStrategy;

public ImageLoader(ILoadStrategy loadStrategy) {
mLoadStrategy = loadStrategy;
}

@Override
public void loadImage(LoadConfig loadConfig, View view, Callback callback, ExtendedOptions extendOption) {
mLoadStrategy.loadImage(loadConfig, view, callback, extendOption);
}

@Override
public void clearCache(int type) {
mLoadStrategy.clearCache(type);
}

@Override
public void clearCacheKey(int type, LoadConfig loadConfig) {
mLoadStrategy.clearCacheKey(type, loadConfig);
}

@Override
public boolean isCache(LoadConfig loadConfig, ExtendedOptions extendedOption) {
return mLoadStrategy.isCache(loadConfig, extendedOption);
}

@Override
public File getLocalCache(LoadConfig loadConfig, ExtendedOptions extendOption) {
return mLoadStrategy.getLocalCache(loadConfig, extendOption);
}

@Override
public Bitmap getLocalCacheBitmap(LoadConfig loadConfig, ExtendedOptions extendOption) {
return mLoadStrategy.getLocalCacheBitmap(loadConfig, extendOption);
}

@Override
public long getCacheSize(LoadConfig loadConfig) {
return mLoadStrategy.getCacheSize(loadConfig);
}

@Override
public void downloadOnly(LoadConfig loadConfig, Callback callback, ExtendedOptions extendOption) {
mLoadStrategy.downloadOnly(loadConfig, callback, extendOption);
}
}

到这,图片加载基本功能封装就完成了,调用者如果需要使用不通的图片加载框架,只需要在构造 ImageLoader 实例时传入不同的 ILoadStrategy 实现即可,但大多数情况下,一个App通常只需要使用一种 图片加载框架,所以最好创建一个管理类,用单例模式来进行管理,只创建一个 ImageLoader 实例,然后通过 setLoadStrategy 方法来设置具体的 ILoadStrategy 实现,这样,调用者只需要关心如何使用这个 ImageLoader 实例即可,而不需要关心具体的 ILoadStrategy 实现。

关于ILoadStrategy

由于最终干活的事 ILoadStrategy 的具体实现类,而这个类需要根据调用者传入的参数进行构造,这里利用简单工厂模式来构造,具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LoaderStrategyFactory {
public static ILoadStrategy getLoadStrategy(@LoaderConst.LoaderType int type) {
switch (type) {
default:
case LoaderConst.LOADER_TYPE_FRESCO:
return new FrescoLoadStrategy(null);
case LoaderConst.LOADER_TYPE_PICASSO:
return new PicassoLoadStrategy(null);
case LoaderConst.LOADER_TYPE_GLIDE:
return new GlideLoadStrategy();
case LoaderConst.LOADER_TYPE_UIL:
return new UilLoadStrategy();
}
}
}

关于LoadConfig

这里的 LoadConfig值的是加载图片的配置,这个类分通用配置和不通框架的具体配置,通用配置在 Base里声明,不通加载库特有的配置在 具体的子类中说明,这样就在保持共性的同时,还兼顾了不通加载库的特性,关于类的具体实现这里就不贴代码了,由于配置项较多,就采用 建造者模式来完成具体 LoadConfig的构造了。

为了避免 每次都要创建 LoadConfig对象,可以创建几个常用的配置对象,缓存在内存中,如默认的配置,这样在 LoadConfig 为空时,可以直接采用 默认的。

总结

通过策略模式来完成具体加载策略的实现,通过建造者模式来完成不通加载配置的构建,通过单例模式来实现加载框架的管理和设置,通过工厂模式来完成 具体策略实例的创建,然后整个封装的基本思想就是 控制反转,尽量达到代码的高内聚低耦合,方便后续维护、扩展。

参考文章

Android 图片加载库如何封装