我是靠谱客的博主 无辜学姐,最近开发中收集的这篇文章主要介绍Glide preload和into的区别,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

一、背景

  贝壳2.6.0版本使用Glide preload方法替换了部分显示图片的方式, 在灰度期间发现控件显示了错误的图片或者崩溃问题。

Fatal Exception: java.lang.RuntimeException:Canvas: trying to use a recycled bitmap android.graphics.Bitmap@25e89bf at android.graphics.Canvas.throwIfCannotDraw(Canvas.java:1271) at android.view.DisplayListCanvas.throwIfCannotDraw(DisplayListCanvas.java:257) at android.graphics.Canvas.drawBitmap(Canvas.java:1415) at com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawable.draw(GlideBitmapDrawable.java:101)

http://bugly.ke.com/#/2/exceptionList/crash/qaCrashDetail/2/All/1553766449032/1554284849032/All/All/All/e71fba44bfc3a1e14fc63b9f69f2cbc9

 

二、原因分析

    Glide使用activityResources、LruResourceCache、LruBitmapPool等3级内存和文件缓存LazyDiskCacheProvider。

 

  activeResources是个Map, key值(EngineKey)根据10个参数组合生成,value是ResourceWeakReference类型; resource是EngineResource类并实现引用计数。

activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
public void recycle() {
    if (acquired > 0) {
        throw new IllegalStateException("Cannot recycle a resource while it is still acquired");
    }
    if (isRecycled) {
        throw new IllegalStateException("Cannot recycle a resource that has already been recycled");
    }
    isRecycled = true;
    resource.recycle();  //实际上将Bitmap添加到BitmapPool
}
 
/**
 * Increments the number of consumers using the wrapped resource. Must be called on the main thread.
 *
 * <p>
 *     This must be called with a number corresponding to the number of new consumers each time new consumers
 *     begin using the wrapped resource. It is always safer to call acquire more often than necessary. Generally
 *     external users should never call this method, the framework will take care of this for you.
 * </p>
 */
void acquire() {
    if (isRecycled) {
        throw new IllegalStateException("Cannot acquire a recycled resource");
    }
    if (!Looper.getMainLooper().equals(Looper.myLooper())) {
        throw new IllegalThreadStateException("Must call acquire on the main thread");
    }
    ++acquired;
}
 
/**
 * Decrements the number of consumers using the wrapped resource. Must be called on the main thread.
 *
 * <p>
 *     This must only be called when a consumer that called the {@link #acquire()} method is now done with the
 *     resource. Generally external users should never callthis method, the framework will take care of this for
 *     you.
 * </p>
 */
void release() {
    if (acquired <= 0) {
        throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
    }
    if (!Looper.getMainLooper().equals(Looper.myLooper())) {
        throw new IllegalThreadStateException("Must call release on the main thread");
    }
    if (--acquired == 0) {
        listener.onResourceReleased(key, this); 
    }
}

LruResourceCache使用最近最少使用算法跟保存从activeResources移出的resource, 如果LruCache满了则移除记录并添加到LruBitmapPool。

 

LruBitmapPool的作用是缓存废弃的Bitmap(包括从activeResources或者LurResourcesCache移出的), 每次解码图片时先从bitmappool找是否有合适的bitmap实例复用,找到了则从BitmapPool里移出,找不到则实例化个Bitmap对象。

                                      

                                                                                 into方式bitmap转换关系

                                                               使用BitmapPool

 

三、preload和into区别

  preload会创建PreloadTarget, 在回调onResourceReady时执行了Glide.clear(this),  将当前GlideDrawable从activeResources移动到LruResourcesCache。 即执行了cache.put(cacheKey, resource);

public final class PreloadTarget<Z> extends SimpleTarget<Z> {
 
    /**
     * Returns a PreloadTarget.
     *
     * @param width The width in pixels of the desired resource.
     * @param height The height in pixels of the desired resource.
     * @param <Z> The type of the desired resource.
     */
    public static <Z> PreloadTarget<Z> obtain(int width, int height) {
        return new PreloadTarget<Z>(width, height);
    }
 
    private PreloadTarget(int width, int height) {
        super(width, height);
    }
 
    @Override
    public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) {
        Glide.clear(this);
    }
}

 

 划重点: 如果当前界面在onResourceReady回调函数里使用drawable刷新到界面, 那么可能导致crash或者渲染错误图片的问题。 原因是LruCache里缓存的图片可能被移出到BitmapPool; 而Glide在transform图片时会从BitmapPool里取Bitmap对象(UI显示错误的图片), 也可能执行bitmap的recycle方法(程序崩溃)。  即在onResourceReady里使用的GlideDrawable内存区域可能在其它地方篡改。

 

into会创建GenericTarget, 在回调onResourceReady时

public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback,
        ResourceCallback {
 ...
public void onResourceReady(Resource<?> resource) {
    if (resource == null) {
        onException(new Exception("Expected to receive a Resource<R> with an object of " + transcodeClass
                + " inside, but instead got null."));
        return;
    }
 
    Object received = resource.get();
    if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
        releaseResource(resource);
        onException(new Exception("Expected to receive an object of " + transcodeClass
                + " but instead got " + (received != null ? received.getClass() : "") + "{" + received + "}"
                + " inside Resource{" + resource + "}."
                + (received != null ? "" : " "
                    + "To indicate failure return a null Resource object, "
                    + "rather than a Resource object containing null data.")
        ));
        return;
    }
 
    if (!canSetResource()) {
        releaseResource(resource);
        // We can't set the status to complete before asking canSetResource().
        status = Status.COMPLETE;
        return;
    }
 
    onResourceReady(resource, (R) received);
}
 
/**
 * Internal {@link #onResourceReady(Resource)} where arguments are known to be safe.
 *
 * @param resource original {@link Resource}, never <code>null</code>
 * @param result object returned by {@link Resource#get()}, checked for type and never <code>null</code>
 */
private void onResourceReady(Resource<?> resource, R result) {
    // We must call isFirstReadyResource before setting status.
    boolean isFirstResource = isFirstReadyResource();
    status = Status.COMPLETE;
    this.resource = resource;
 
    if (requestListener == null || !requestListener.onResourceReady(result, model, target, loadedFromMemoryCache,
            isFirstResource)) {
        GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource);
        target.onResourceReady(result, animation);  //执行GlideDrawbleImageViewTarget的setResource方法,即view.setImageDrawable
    }
 
    notifyLoadSuccess();
 
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("Resource ready in " + LogTime.getElapsedMillis(startTime) + " size: "
                + (resource.getSize() * TO_MEGABYTE) + " fromCache: " + loadedFromMemoryCache);
    }
}
....
 
 
}

 

四、结论

      Glide的preload回调函数onResourceReady返回的resource并不可靠, 在使用时要新创建个实例或者保存成文件后使用它。

LJImageLoader.with(getContext()).url(ConstantUtil.URL_CDN_WALLET_REWARD_SELECTED)
    .dontAnimate()
    .listener(new ILoadListener() {
      @Override public boolean onException(Exception e, String model) {
        return false;
      }
 
      @Override public boolean onResourceReady(Drawable resource, String model) {
        if (resource != null) {
          Bitmap bmp = Tools.drawableToBitmap(resource);
          if (bmp != null) {
            rootView.setBackground(new BitmapDrawable(getResources(), bmp));
          }
        }
        return false;
      }
    }).preload();

 

 

最后

以上就是无辜学姐为你收集整理的Glide preload和into的区别的全部内容,希望文章能够帮你解决Glide preload和into的区别所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(33)

评论列表共有 0 条评论

立即
投稿
返回
顶部