Glide 使用 V4.3.1 填坑之旅

老规矩先列出所有的坑坑洼洼

  1. 生成 GlideApp 的坑
  2. 缓存 diskCacheStrategy 的坑
  3. using 接口的坑
  4. 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 这里面去了。
动画的加载可以指定数据 来源进行加载。

例如指定

  1. 如果图片是从 REMOTE 拉取的要执行(不执行)动画
  2. 如果图片是从 Local 本地拉取的要执行(不执行)动画
  3. 如果图片是从 DATA_DISK_CACHE 拉取的要执行(不执行)动画
  4. 如果图片是从 RESOURCE_DISK_CACHE 拉取的要执行(不执行)动画
  5. 如果图片是从 MEMORY_CACHE 拉取的要执行(不执行)动画
    这个鼓励一下去看下源码,自己玩玩。

总结:
自从换了Glide 4.3.1 OOM 不再出现,而且优化了很多逻辑。特别是在缓存和网络数据获取的重新调度,写的特别优雅。当然还有对 activity 和 fragment 的生命周期掌控也是有所改善。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容