老规矩先列出所有的坑坑洼洼
- 生成 GlideApp 的坑
- 缓存 diskCacheStrategy 的坑
- using 接口的坑
- crossFade 加载动画换了用法
一、 生成 GlideApp 的坑
首先依赖:
compile 'com.github.bumptech.glide:glide:4.3.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.3.1'
compile 'com.github.bumptech.glide:annotations:4.3.1'
搞定一跑,结果并没有如尝所愿,编译个 GlideApp 出来。
解决方法:
在主项目工程新建一个类:
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
@Override
public boolean isManifestParsingEnabled() {
return false;
}
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDefaultRequestOptions(
new RequestOptions()
.format(DecodeFormat.PREFER_RGB_565)
);
}
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
// registry.replace(GlideUrl.class, InputStream.class, new NetworkDisablingLoader.Factory());
}
}
然后再 Build -> Make Project 大功告成 GlideApp api 出现了,可以在项目中欢快的使用。请看下面代码一点错误都没有。找回初恋般的感觉
GlideApp.with(fragment)
.load(uri)
.set(Downsampler.IS_USE_THUMB, true)
.centerCrop()
.transition(new DrawableTransitionOptions().crossFade())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.override(200,200)
.error(R.drawable.filemanager_photo_fail)
.placeholder(R.drawable.ready_to_loading_image)
.into(iconview);
二、缓存 diskCacheStrategy 坑
Glide 版本3.XX使用姿势 是这样子的
Glide.with(fragment)
.load(uri)
.diskCacheStrategy(RESULT) //缓存吗,转换后的图片
.into(iconview);
Glide 版本4.3.1使用姿势 是这样子的
GlideApp.with(fragment)
.load(uri)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.into(iconview);
V3 | V4 |
---|---|
ALL | ALL |
NONE | NONE |
SOURCE | DATA |
RESULT | RESOURCE |
V4 还增加了AUTOMATIC
(数据如果是从网络拉取,自动帮缓存源数据)
咦这个坑看起来不是特别大,别急,后面有连环坑等着你跳。
如果不设置 diskCacheStrategy
V4 是走 AUTOMATIC
, V3 是走 RESULT
对于我这种内存空间锱铢必较的人,我就好奇,我就才加载了一张 1000*1000 的图片(4M内存左右),缓存空间怎么一下子蹦 十来 M 了,怎么回事。原来是 AUTOMATIC
在作祟。
三、using 接口的坑
Glide V3 版本是有 using
这个接口可以对每一个请可以自定义 ModelLoader
,Glide V4 版本取消了 using
这个接口,本来可以使用 using
来实现加载进度。这下玩完了,关闭了。作者声称 要用 AppGlideModule
来避免对象重用。减少开销。这个怎么怎么破? 本人的破解之法:
public class ProgressManager {
public interface OnProgressListener {
void onProgress(long bytesRead, long totalBytes, boolean isDone);
}
private Map<String, WeakReference<OnProgressListener>> listeners = new ConcurrentHashMap<>();
private ProgressManager() {
}
// ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
private static volatile ProgressManager instance;
public static ProgressManager getInstance() {
if (null == instance) {
synchronized (ProgressManager.class) {
if (null == instance) {
instance = new ProgressManager();
}
}
}
return instance;
}
public void addProgressListener(String key, OnProgressListener progressListener) {
if (key == null || progressListener == null) return;
listeners.put(key, new WeakReference<>(progressListener));
}
public void removeProgressListener(String key) {
if (key == null) return;
listeners.remove(key);
}
public OnProgressListener getProgressListener(String key) {
if (key == null) return null;
WeakReference<OnProgressListener> listener = listeners.get(key);
OnProgressListener progressListener = null;
if(listener != null) {
progressListener = listener.get();
}
return progressListener;
}
}
public class ProgressLoader implements ModelLoader<GlideUrl,InputStream> {
@Nullable private final ModelCache<GlideUrl, GlideUrl> modelCache;
public ProgressLoader() {
this(null);
}
public ProgressLoader(ModelCache<GlideUrl, GlideUrl> modelCache) {
this.modelCache = modelCache;
}
@Nullable
@Override
public LoadData<InputStream> buildLoadData(GlideUrl model, int width, int height, Options options) {
GlideUrl url = model;
if (modelCache != null) {
url = modelCache.get(model, 0, 0);
if (url == null) {
modelCache.put(model, 0, 0, model);
url = model;
}
}
return new LoadData(url, new ExpandHttpUrlFetcher(url));
}
@Override
public boolean handles(GlideUrl glideUrl) {
return true;
}
/**
*
*/
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private final ModelCache<GlideUrl, GlideUrl> modelCache = new ModelCache<GlideUrl, GlideUrl>(500);
@Override
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new ProgressLoader(modelCache);
}
@Override
public void teardown() {
// Do nothing.
}
}
}
/**
* Created by jiong103 on 2017/9/22.
* <p>
* <p>
* <p>
* 模仿HttpUrlFetcher 写了一个 下载器
*/
public class ExpandHttpUrlFetcher implements DataFetcher<InputStream> {
public static final String TAG = "HttpUrlFetcher";
public static final int MAXIMUM_REDIRECTS = 5;
public final GlideUrl glideUrl;
public InputStream stream;
public volatile boolean isCancelled;
public ProgressManager.OnProgressListener progressListener;
private Call progressCall;
public ExpandHttpUrlFetcher(GlideUrl glideUrl) {
this.glideUrl = glideUrl;
this.progressListener = ProgressManager.getInstance()
.getProgressListener(glideUrl.toString());
}
@Override
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
final InputStream result;
try {
result = loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/,
glideUrl.getHeaders());
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to load data for url", e);
}
callback.onLoadFailed(e);
return;
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime)
+ " ms and loaded " + result);
}
callback.onDataReady(result);
}
private URL url;
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
throws IOException {
this.url = url;
if (redirects >= MAXIMUM_REDIRECTS) {
throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
// Comparing the URLs using .equals performs additional network I/O and is generally broken.
// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new IOException("In re-direct loop");
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
Request.Builder builder = new Request.Builder().url(url);
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
builder.addHeader(headerEntry.getKey(), headerEntry.getValue());
}
Request request = builder.build();
OkHttpClient.Builder builder1 = new OkHttpClient.Builder();
if (progressListener != null) {
builder1.addInterceptor(new ProgressInterceptor(progressListener)).build();
}
OkHttpClient client = builder1.build();
client.retryOnConnectionFailure();
progressCall = client.newCall(request);
if (isCancelled) {
return null;
}
Response response = progressCall.execute();
final int statusCode = response.code();
if (statusCode / 100 == 2) {
return getStreamForSuccessfulRequest(response);
} else if (statusCode / 100 == 3) {
String redirectUrlString = response.header("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new HttpException("Received empty or null redirect url");
}
URL redirectUrl = new URL(url, redirectUrlString);
// Closing the stream specifically is required to avoid leaking ResponseBodys in addition
// to disconnecting the url connection below. See #2352.
cleanup();
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else {
if (statusCode == -1) {
throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
}
throw new IOException("Request failed " + statusCode + ": " + response.message());
}
}
private InputStream getStreamForSuccessfulRequest(Response response)
throws IOException {
String charset = response.header("content-encoding");
if (TextUtils.isEmpty(charset)) {
String value = response.header("content-length");
int contentLength = -1;
try {
contentLength = Integer.parseInt(value);
} catch (NumberFormatException e) {
contentLength = -1;
}
stream = ContentLengthInputStream.obtain(response.body().byteStream(), contentLength);
} else {
stream = response.body().byteStream();
}
return stream;
}
@Override
public void cleanup() {
isCancelled = true;
if (stream != null) {
try {
stream.close();
} catch (Throwable e) {
e.printStackTrace();
}
}
if (progressCall != null) {
try {
progressCall.cancel();
} catch (Throwable e) {
e.printStackTrace();
}
}
if (progressListener != null) {
try {
ProgressManager.getInstance()
.removeProgressListener(glideUrl.toString());
progressListener = null;
} catch (Throwable e) {
e.printStackTrace();
}
}
}
@Override
public void cancel() {
// TODO: we should consider disconnecting the url connection here, but we can't do so directly because cancel is
// often called on the main thread.
cleanup();
}
@NonNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
public class ProgressInterceptor implements Interceptor {
private ProgressManager.OnProgressListener progressListener;
public ProgressInterceptor(ProgressManager.OnProgressListener progressListener) {
this.progressListener = progressListener;
}
@Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder().body(
new ProgressResponseBody(originalResponse.body(), progressListener)).build();
}
}
private class ProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private final ProgressManager.OnProgressListener progressListener;
private BufferedSource bufferedSource;
public ProgressResponseBody(ResponseBody responseBody,
ProgressManager.OnProgressListener progressListener) {
this.responseBody = responseBody;
this.progressListener = progressListener;
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
private Source source(Source source) {
return new ForwardingSource(source) {
long totalBytesRead = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
if (progressListener != null) {
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
progressListener.onProgress(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
}
return bytesRead;
}
};
}
}
}
好了下面开始使用:
ProgressManager.getInstance().addProgressListener(path, new ProgressManager.OnProgressListener() {
private long oldProgress;
@Override
public void onProgress(long bytesRead, long contentLength, boolean isDone) {
int tmp = (int) (bytesRead * 100 / contentLength);
if (oldProgress != tmp) {
Loading.setProgress(tmp);
oldProgress = tmp;
}
}
});
GlideApp.with(getContext())
.load(path)
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
Loading.setVisibility(View.GONE); ProgressManager.getInstance().removeProgressListener(path);
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
Loading.setVisibility(View.GONE); ProgressManager.getInstance().removeProgressListener(path);
return false;
}
})
.into(imageView);
好了虽然多了一个 ProgressManager
不过也还好。毕竟要加载图片进度的地方也不是特别多,不然这代码确实比较长了。目前从 StackOVerflow 还有 github 上面作者的 issue 没有找到比较好的解决方法,目前这么过吧。
四. crossFade 加载动画换了用法
咦我的动画接口不见了,V3是这么玩的
Glide.with(fragment)
.load(uri)
.crossFade()
.into(iconview);
那 V4 其实是这么玩的:
GlideApp.with(fragment)
.load(uri)
.transition(new DrawableTransitionOptions().crossFade())
.into(iconview);
居然加到 transition
这里面去了。
动画的加载可以指定数据 来源进行加载。
例如指定
- 如果图片是从 REMOTE 拉取的要执行(不执行)动画
- 如果图片是从 Local 本地拉取的要执行(不执行)动画
- 如果图片是从 DATA_DISK_CACHE 拉取的要执行(不执行)动画
- 如果图片是从 RESOURCE_DISK_CACHE 拉取的要执行(不执行)动画
- 如果图片是从 MEMORY_CACHE 拉取的要执行(不执行)动画
这个鼓励一下去看下源码,自己玩玩。
总结:
自从换了Glide 4.3.1 OOM 不再出现,而且优化了很多逻辑。特别是在缓存和网络数据获取的重新调度,写的特别优雅。当然还有对 activity 和 fragment 的生命周期掌控也是有所改善。