首次加载时,Target对象的onBitmapLoaded方法未被调用

136
在我的函数中:
public void getPointMarkerFromUrl(final String url, final OnBitmapDescriptorRetrievedListener listener) {
final int maxSize = context.getResources().getDimensionPixelSize(R.dimen.icon_max_size);
Target t = new Target() {
  @Override
  public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
    if (bitmap != null)
      listener.bitmapRetrieved(getBitmapDescriptorInCache(url, bitmap));
    else
      loadDefaultMarker(listener);
  }

  @Override
  public void onBitmapFailed(Drawable errorDrawable) {
    loadDefaultMarker(listener);
  }

  @Override
  public void onPrepareLoad(Drawable placeHolderDrawable) {
  }
};

Picasso.with(context)
    .load(url)
    .resize(maxSize, maxSize)
    .into(t);
}

当我第一次加载图片时,onBitmapLoaded()从未被调用。我已经阅读了一些类似于https://github.com/square/picasso/issues/39的主题,推荐使用fetch(Target t)方法(它似乎是弱引用的问题...),但是这个函数在最新版本的picasso(2.3.2)中不可用。我只有fetch()方法,但我不能同时使用into(mytarget)

请问如何使用带有自定义Target的fetch()方法?谢谢。

文档:http://square.github.io/picasso/javadoc/com/squareup/picasso/RequestCreator.html#fetch--


1
请确保使用Okhttp 2.0.0,当我使用Picasso 2.3.2和Okhttp 1.6.0时遇到了相同的问题。 - hakim
https://github.com/square/okhttp据我所知,如果您正在使用Picasso 2.3.2,则必须包括okhttp(和okio)库。 您是在使用Eclipse还是Android Studio? - hakim
我正在使用IntelliJ。我已经查看了我的gradle依赖项,但没有看到okhttp... Picasso似乎可以在没有它的情况下工作。 - psv
@psv,你是如何使用标记实现下面的解决方案的? - Mustafa Güven
8个回答

262

如其他回复者(@lukas和@mradzinski)所指出的,Picasso只保留对Target对象的弱引用。虽然您可以在其中一个类中存储对Target的强引用,但如果Target以任何方式引用View,这仍可能会导致问题,因为您还将有效地保留对该View的强引用(这是Picasso明确帮助您避免的事情之一)。

如果您处于这种情况,请建议将Target标记到View

final ImageView imageView = ... // The view Picasso is loading an image into
final Target target = new Target{...};
imageView.setTag(target);

采用这种方法有一个好处,即让Picasso为您处理一切。它将为每个视图管理WeakReference对象——一旦不再需要一个视图,处理图像的任何Target也将被释放,因此您不会因长期存在的Target而遭受内存泄漏,但只要其视图存在,Target就会持续存在。


是的!我选择创建一个目标对象的并行列表(List<Target>),以保留所有目标对象,但你的解决方案更好。目标对象“将持续存在,只要它的视图存在”。谢谢。 - psv
26
我没有图像查看器,那我该怎么解决这个问题呢?处理这种情况时,垃圾收集器是你最糟糕的敌人。 - tim687
4
你甚至可以将它存储在 ArrayList<Target> 中,这也能正常工作,只需要记得清空该数组列表即可 :-) - Oliver Dixon
2
在onBitmapLoaded和onBitmapFailed中,在处理完位图后,我也会执行imageView.setTag(null)。这样做不需要吗? - dev
1
onBitmapLoaded / onBitmapFailed 中,我们不应该将标签置空吗? - WindRider
@tim687 我也没有视图可以标记我的目标。我知道这已经过时了,但你找到解决方案了吗? - Cody

64

毕加索不持有对目标对象的强引用,因此它被垃圾回收并且 onBitmapLoaded 没有被调用。

解决方法很简单,只需对 Target 建立一个强引用即可。

public class MyClass {
   private Target mTarget = new Target() {...};

   public void getPointMarkerFromUrl(final String url, final OnBitmapDescriptorRetrievedListener listener) {

         Picasso.with(context)
         .load(url)
         .resize(maxSize, maxSize)
         .into(mTarget);
   }
}      

3
或者让您的 View 实现 Target 接口。 - dnkoutso
在文档中,它说你必须重写 Object.equals(Object)Object.hashCode() 方法。你有一个可用的示例吗? - chip
它在哪里写的?即使我对我的Target()进行强引用,我的问题仍然存在。 - psv
我现在已经安装了okHttp,加载速度有所提升,但是在第一次启动时仍然存在同样的问题。有什么想法吗? - psv
@psv:你解决了 Picasso 首次启动的问题吗?我也遇到了同样的问题。如果你已经解决了,你是怎么解决的呢? - TheDevMan
@TheDevMan,不,它还没有解决。我使用的解决方法是:将每个目标对象保存在ArrayList中以避免垃圾回收。这是一种“肮脏”的方法,会降低性能,但我没有其他解决方案。 - psv

27
如果我有ImageView,我会简单地这样做:imageView.setTag(target); 我使用下一个解决方案将位图加载到通知中,所以我只需要位图。 因此创建一个Set来存储Target对象,并在完成加载后将其删除。
final Set<Target> protectedFromGarbageCollectorTargets = new HashSet<>();

private void loadBitmap(String url) {
   Target bitmapTarget = new BitmapTarget();
   protectedFromGarbageCollectorTargets.add(bitmapTarget);
   Picasso.with(context).load(url).into(bitmapTarget);
}

class BitmapTarget implements Target {

        @Override
        public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
           
                    //handle bitmap
                    protectedFromGarbageCollectorTargets.remove(this);
                }
            }
        }

        @Override
        public void onBitmapFailed(Drawable drawable) {
            protectedFromGarbageCollectorTargets.remove(this);
        }

        @Override
        public void onPrepareLoad(Drawable drawable) {
 
        }
    }

太棒了,感谢你提供的代码片段。 - Qadir Hussain
什么是nEvent? 目标位图Target = new BitmapTarget(nEvent); - NimaAzhd
@NimaAzhd 你可以忽略它。 - Oleksandr B

13
ImageView profile = new ImageView(context);
        Picasso.with(context).load(URL).into(profile, new Callback() {
            @Override
            public void onSuccess() {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {//You will get your bitmap here

                        Bitmap innerBitmap = ((BitmapDrawable) profile.getDrawable()).getBitmap();
                    }
                }, 100);
            }

            @Override
            public void onError() {

            }
        });

1
它也解决了我的问题。我想要将它与通知一起使用。有时图片会通过Target下载,有时则不会。但是在使用ImageView之后,我能够每次都成功加载图片。 - Raveesh G S
1
就我个人而言,在除了所有选项之外,这是最好的解决方案! - Noor Hossain
@RaveeshGS 在 Android 中使用通知构建器使用 setLargeIcon(Bitmap) 来设置图像。我不知道你怎么能使用 ImageView 而不是 setLargeIcon!? - NimaAzhd
@NimaAzhd 他们正在使用ImageView来下载位图。下载后,您可以在任何地方使用它。 - Raghav Satyadev

5

以下是针对不使用视图的解决方法。这个辅助方法使用一个列表来临时存储目标对象,直到返回结果,以避免被垃圾回收:

private List<Target> targets = new ArrayList<>();

public void downloadBitmap(final Context context, final String url, final MyCallback callback) {
    Target target = new Target() {

        @Override
        public void onBitmapLoaded(final Bitmap bitmap, Picasso.LoadedFrom from) {
            targets.clear();
            callback.onSuccess(bitmap);
        }

        @Override
        public void onBitmapFailed(Exception e, Drawable errorDrawable) {
            targets.clear();
            callback.onFailure(null);
        }

        @Override
        public void onPrepareLoad(Drawable placeHolderDrawable) {
        }
    };
    targets.add(target);
    Picasso.with(context).load(url).into(target);
}

5

我遇到了类似的问题,持有目标的引用并没有起到任何帮助作用,所以我使用了以下代码来返回一个位图:


Bitmap bitmap = picasso.with(appContext).load(url).get();

缺点是——没有回调函数,不能在主线程上调用此函数,必须在后台线程上运行此函数,如下例所示:


handlerThread = new HandlerThread(HANDLER_THREAD_NAME);
handlerThread.start();

Handler handler = new Handler(handlerThread.getLooper());
handler.post(new Runnable() {
    @Override
    public void run() {
        Bitmap bitmap = null;
        try {
            bitmap = picasso.with(appContext).load(url).get();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (bitmap != null) {
                //do whatever you wanna do with the picture.
                //for me it was using my own cache
                imageCaching.cacheImage(imageId, bitmap);
            }
        }
    }
});

另一个更好的选择是使用Glide

我的项目需要使用两个不同的图片下载API来展示图片库,并让用户能够选择使用哪个API。因此我必须同时使用它们。

不得不说,结果令人惊讶,Glide的API在所有方面都表现出色(Glide的目标没有弱引用),而Picasso则让我头疼不已(这是我第一次使用Glide,以前通常使用Picasso,看起来今天要改变一下 ^^)。


3

像 @lukas 所说的那样(并引用),Picasso没有对Target对象保持强引用。要避免垃圾收集,您必须对该对象保持强引用。

关于fetch()方法。文档非常清楚地说明了fetch()不应与ImageView或Target一起使用,它只是为了“预热”缓存,除此之外什么也做不了,所以您将无法按照您想要的方式使用它。

我建议您像 @lukas 解释的那样保持强引用,这应该可以解决问题。如果不能,请在项目的GitHub页面上打开一个新问题。


0

我曾经遇到过同样的问题,但当我将依赖项更改为下面提到的内容时,现在它可以正常工作了。

 implementation 'com.squareup.picasso:picasso:2.5.2'
 implementation 'com.squareup.okhttp:okhttp:2.3.0'
 implementation 'com.squareup.okhttp:okhttp-urlconnection:2.3.0'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接