比较大的图片无法加载
如果你的项目中有以下的代码,并且使用 2.5.2 (以前有没有这个问题我没有去验证),很可能会出现较大的图片无法加载
Picasso.with(context).load(uri)
.resize(width, height)
.into(target);
目前的解决办法是将gradle文件中的这一句
compile 'com.squareup.picasso:picasso:2.5.2'
替换为
compile 'com.squareup.picasso:picasso:2.6.0-SNAPSHOT'
也就是说在 2.5.2 以后的代码会修复这个BUG(然而Picasso已经N年不更新了,据说打算release 3.0.0)
那么这个问题为何会出现呢? 这里需要通过下面的代码拿到 Picasso 加载图片失败的日志
mPicasso = new Picasso.Builder(context.getApplicationContext())
.listener(new Picasso.Listener() {
@Override
public void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception) {
Log.e(TAG, uri.toString(), exception);
}
}).build();
当图片加载无法加载的时候会有这样一条log
这个异常是由 MarkableInputStream.java 抛出,对应的代码如下
public void reset(long token) throws IOException {
if (offset > limit || token < reset) {
throw new IOException("Cannot reset");
}
in.reset();
skip(reset, token);
offset = token;
}
其中limit的大小是65536,而offset是表示读取流的位置,在 BitmapHunter.java 中调用了 MarkableInputStream.java 中的 reset()
方法,具体代码如下:
static Bitmap decodeStream(InputStream stream, Request request) throws IOException {
...
final boolean calculateSize = RequestHandler.requiresInSampleSize(options);
...
if (calculateSize) {
BitmapFactory.decodeStream(stream, null, options);
RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,
request);
markStream.reset(mark);
}
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
...
}
这里是图片加载比较常见的操作,先计算bitmap的options,然后才会真正的将图片加载到内存中。在获取正确的options之后,Picasso会将整个流reset,这个时候如果offset大于limit就会抛出异常,导致图片加载失败。
Picasso在 2.5.2 以后使用了动态调整limit的方式,增长limit大小,从而解决了这个问题
Reference:
Added dynamic limit option to MarkableInputStream
java.io.IOException: Cannot reset (Huge photo loading)
首次加载的时候图片无法显示
如果你的代码是下面这种写法,那么恭喜你,这个问题也是可以解决的
Picasso.with(context).load(image.getUri())
.into(new Target() {
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
// your code
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
// your code
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
// your code
}
});
解决方法是把 Target 通过强引用(比如类的属性或者Map之类)保存起来,例如
class Test {
private Picasso mPicasso;
private final Map<String, Target> mTargetMap;
Test(Context context) {
mPicasso = new Picasso.Builder(context.getApplicationContext())
.listener(new Picasso.Listener() {
@Override
public void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception) {
Log.e(TAG, uri.toString(), exception);
}
}).build();
mPicasso.setLoggingEnabled(true);
mTargetMap = new HashMap<>();
}
public void loadBitmap() {
Target target = new Target() {
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
};
mTargetMap.put(key, target);
mPicasso.load(uri)
.into(mTargetMap.get(key));
}
}
这个问题产生的原因是target在Picasso内部是通过弱引用保存的,就会导致target很容易被GC清理,这时候 onBitmapLoaded
就不会被回调了
Reference:
Target.onBitmapLoaded() method not called sometimes