【问题标题】:How to make Picasso/Glide work with Html.ImageGetter for caching images?如何使 Picasso/Glide 与 Html.ImageGetter 一起用于缓存图像?
【发布时间】:2015-10-09 13:50:05
【问题描述】:

感谢@Budius 所做的所有努力。

我的应用程序的大部分图像工作可以由 Picasso/Glide 处理,但是,一些图像由Html.fromHtml 显示在TextView 中。而且TextView中的图片也经常被使用。

但是,对于传递给Html.fromHtmlImageGetter,我不知道如何使用Picasso/Glide 实现getDrawable() 方法。是否可以为TextView 和其他位图中的这些图片共享同一个 Picasso/Glide 缓存?

或者我应该使用自定义LruCache 来分别缓存ImageGetter 中的这些图片吗?这种方式会增加OOM错误的风险吗?而且我认为使用 2 个不同的系统处理图像会产生不必要的工作量。

更新:我尝试使用毕加索的.get(),但文档说

/**
 * The result of this operation is not cached in memory because the underlying    
 * {@link Cache} implementation is not guaranteed to be thread-safe.
 */

所以在这种情况下不使用缓存。

更新: @Budius 的答案是对的,但是缺少为Drawable 设置边界的代码,导致Drawable 没有显示在TextView 中。于是我将DrawableWrapper类中的代码修改为:

public void setWrappedDrawable(Drawable drawable) {
    if (mDrawable != null) {
        mDrawable.setCallback(null);
    }
    mDrawable = drawable;
    if (drawable != null) {
        mDrawable.setBounds(0,0,mDrawable.getIntrinsicWidth(),mDrawable.getIntrinsicHeight());
        drawable.setCallback(this);
    }
}

更新:,问题仍未解决。如果您实施上述解决方案,TextView 中的图像会出现一些奇怪的行为。有时候图片无法刷新,除非你切换到另一个应用再切换回来,而且图片的位置严重不正确。

更新:我已经在下面发布了所有测试代码。还是有一些bug。如果没有占位符,它仍然会抛出 NPE。使用占位符,行为很奇怪。我第一次输入TestActivity,它会显示占位符,但不会变成下载的图片。但是当我切换到另一个应用程序或按下返回按钮并再次输入TestActivity 后,会显示图片(可能是因为它在缓存中?)。

而且图片的大小是正确的,但仍然没有留下图像的位置。如果我调用mDrawable.setBounds(getBounds()); 而不是mDrawable.setBounds(0,0,getIntrinsicWidth(),getIntrinsicHeight());,它将不会显示。

DrawableWrapper

public class DrawableWrapper extends Drawable implements Drawable.Callback {
    private Drawable mDrawable;

    public DrawableWrapper(Drawable drawable) {
        setWrappedDrawable(drawable);
    }

    @Override
    public void draw(Canvas canvas) {
        mDrawable.draw(canvas);
    }
    @Override
    public int getIntrinsicWidth() {
        return 384;
    }

    @Override
    public int getIntrinsicHeight() {
        return 216;
    }
    //... other delegation methods are omitted

    public void setWrappedDrawable(Drawable drawable) {
        if (mDrawable != null) {
            mDrawable.setCallback(null);
        }
        mDrawable = drawable;
        if (drawable != null) {
            mDrawable.setBounds(0,0,getIntrinsicWidth(),getIntrinsicHeight());
            drawable.setCallback(this);
        }
    }
}

PicassoTargetDrawable

public class PicassoTargetDrawable extends DrawableWrapper
        implements Target {

    private Context context;

    public PicassoTargetDrawable(Context context) {
        super(new ColorDrawable(0));
        // use application context to not leak activity
        this.context = context.getApplicationContext();
    }

    public void onBitmapFailed(Drawable errorDrawable) {
        setWrappedDrawable(errorDrawable);
        invalidateSelf();
    }

    public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
        setWrappedDrawable(new BitmapDrawable(context.getResources(), bitmap));
        context = null;
        invalidateSelf();
    }

    public void onPrepareLoad(Drawable placeHolderDrawable) {
        setWrappedDrawable(placeHolderDrawable);
        invalidateSelf();
    }
}

测试活动

public class TestActivity extends FragmentActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView textView = new TextView(this);
        textView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
        setContentView(textView);
        String html = "<div>test<br/>" +
                "<img src=\"http://i2.cdn.turner.com/money/dam/assets/150910165544-elon-evo-open-still-384x216.png\"></img>" +
                "<br/>/test</div>";
        textView.setText(Html.fromHtml(html, new Html.ImageGetter() {
            @Override
            public Drawable getDrawable(String source) {
                PicassoTargetDrawable d = new PicassoTargetDrawable(TestActivity.this);
                Picasso.with(TestActivity.this)
                        .load(source)
                        //add placeholder here
                        .into(d);
                return d;
            }
        }, null));
    }
}

【问题讨论】:

  • 你能从Picasso的Target得到的Bitmap制作一个BitmapDrawable吗?
  • 我认为这是一个异步进程,所以它不适用于getDrawable()。您能否更具体地说明如何编码?

标签: android caching picasso android-bitmap android-glide


【解决方案1】:

我的建议是返回一个 wrap drawable。并继续使用 Picasso 下载图像。

在以下链接中,您可以找到一个 DrawableWrapper,它来自 Google 的支持库,但它不是公共文档的一部分,所以我将整个代码复制到您的项目中 https://android.googlesource.com/platform/frameworks/support/+/master/v7/appcompat/src/android/support/v7/graphics/drawable/DrawableWrapper.java

然后你从它创建一个PicassoTargetDrawable

public class PicassoTargetDrawable extends DrawableWrapper
        implements Picasso.Target {

    private Context context;

    public PicassoTargetDrawable(Context context) {
        super(new ColorDrawable(0));
        // use application context to not leak activity
        this.context = context.getApplicationContext();
    }

    public void onBitmapFailed(Drawable errorDrawable) {
        setWrappedDrawable(errorDrawable);
        invalidateSelf();
    }

    public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
        setWrappedDrawable(new BitmapDrawable(context.getResources(), bitmap));
        context = null;
        invalidateSelf();
    }

    public void onPrepareLoad(Drawable placeHolderDrawable) {
        setWrappedDrawable(placeHolderDrawable);
        invalidateSelf();
    }
}

那么就是加载它的问题

public void Drawable getDrawable(String source) {
    PicassoTargetDrawable d = new PicassoTargetDrawable(context);
    Picasso.with(context)
       .load(source)
       ..... add here onError and placeholder drawables
       .into(d);
    return d;
}

PS.: 写了这么多,没多看,可能会有一些错别字和一些问题需要整理,但你理解这个概念肯定够了。

更新: 只是更正您的代码。

TextView 已经告诉 WrapDrawable 它应该使用的边界。如果您告诉新的 mDrawable 它可以使用它想要的任何尺寸,它会使用它想要的任何尺寸。因此,您应该传递给 WrapDrawable 的尺寸,而不是传递它自己的固有宽度/高度

public void setWrappedDrawable(Drawable drawable) {
    if (mDrawable != null) {
       mDrawable.setCallback(null);
    }
    mDrawable = drawable;
    if (drawable != null) {     
        mDrawable.setBounds(getBounds());
        drawable.setCallback(this);
    }
}

【讨论】:

  • 谢谢。但是当我从后台线程调用Html.fromHtml() 时,它会抛出一个异常,因为必须从主线程调用.into。当我从 UI 线程调用它时,它会在 DrawableWrapper 中为 mDrawable.draw(canvas) 抛出 NPE。代码是Spanned text = Html.fromHtml(html, getImageGetter(), null); textView.setText(text);
  • 如果我在.into() 之前调用.placeholder(),则不会引发异常。但图片也没有显示。
  • 您必须从 UI 线程调用它。显然WrapDrawable 不保护agasint null,所以我刚刚在PicassoTargetDrawable 构造函数中添加了一个透明的drawable。再次检查代码,我只是在我的答案上编辑了它。
  • 我更新了代码,但仍然遇到同样的问题:使用 placeholder(),它不会抛出异常,但既不显示占位符也不显示目标图像。没有它,就会抛出 NPE。
  • 我的测试结果是图片下载成功但是没有显示。
猜你喜欢
  • 2020-09-23
  • 2018-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-03
  • 1970-01-01
  • 2015-12-01
  • 2014-09-23
相关资源
最近更新 更多