之前项目中使用Picasso遇到了一个问题:在Android 5.0以上版本的部分手机上使用Picasso加载本地图片会失败。为了解决这个问题,研究了一下Picasso加载和压缩本地图片的流程,才有了这篇文章。

我们知道,Picasso加载本地图片有两种方法,一种是new File(path),另外一种是url = "file://" + path。尤其后一种在picasso2.5.2及之前版本一定要加"file"协议,否则加载图片出错。

对于上面提到的问题,只出现在使用Picasso加载本地图片并且使用了resize方法的时候。

而且发现只存在Android 5.0以上版本的部分手机上。比如我们发现5.0、5.0.1有问题,5.1.1没有问题;同时有的5.0的手机没问题,而有的会出现这个问题。

由于有时本地照片较大,所以虽然可以不使用resize方法来保证图片正常加载,但是这样内存开销会急剧增加,所以我们不可避免的要解决这个问题。

经研究发现问题出现在picasso的BitmapHunter类中,当得到读取了网络或本地图片后,会调用BitmapHunter的decodeStream来进行处理,该方法代码如下:

static Bitmap decodeStream(InputStream stream, Request request) throws IOException {

MarkableInputStream markStream = new MarkableInputStream(stream);

stream = markStream;

long mark = markStream.savePosition(65536); // TODO fix this crap. (1)

final BitmapFactory.Options options = RequestHandler.createBitmapOptions(request);

final boolean calculateSize = RequestHandler.requiresInSampleSize (options );

boolean isWebPFile = Utils.isWebPFile( stream);

boolean isPurgeable = request .purgeable && android.os.Build.VERSION.SDK_INT < 21;

markStream.reset( mark);

// We decode from a byte array because, a) when decoding a WebP network stream, BitmapFactory

// throws a JNI Exception, so we workaround by decoding a byte array, or b) user requested

// purgeable, which only affects bitmaps decoded from byte arrays.

if (isWebPFile || isPurgeable) { (2)

byte[] bytes = Utils. toByteArray(stream);

if (calculateSize) {

BitmapFactory. decodeByteArray(bytes, 0, bytes.length, options);

RequestHandler. calculateInSampleSize(request.targetWidth, request.targetHeight , options ,

request);

}

return BitmapFactory. decodeByteArray(bytes, 0, bytes.length, options);

} else {

if (calculateSize) { (3)

BitmapFactory. decodeStream(stream, null, options); (4)

RequestHandler. calculateInSampleSize(request.targetWidth, request.targetHeight , options ,

request);

markStream.reset(mark ); (5)

}

Bitmap bitmap = BitmapFactory. decodeStream(stream, null, options);

if (bitmap == null) {

// Treat null as an IO exception, we will eventually retry.

throw new IOException("Failed to decode stream.");

}

return bitmap;

}

}

这里有一个MarkableInputStream是继承InputStream的,复写了其中的几个方法,其中部分代码如下:

public long savePosition(int readLimit) {

long offsetLimit = offset + readLimit ;

if (limit < offsetLimit) {

setLimit( offsetLimit);

}

return offset;

}

private void setLimit( long limit ) {

try {

if (reset < offset && offset <= this.limit ) {

in.reset();

in.mark(( int) (limit - reset ));

skip( reset, offset);

} else {

reset = offset;

in.mark(( int) (limit - offset ));

}

this. limit = limit;

} catch (IOException e) {

throw new IllegalStateException( "Unable to mark: " + e );

}

}

public void reset(long token ) throws IOException {

if (offset > limit || token < reset) {

throw new IOException("Cannot reset" );

}

in.reset();

skip(reset, token);

offset = token;

}

@Override public int read() throws IOException {

int result = in.read();

if (result != -1) {

offset++;

}

return result;

}

@Override public int read(byte [] buffer ) throws IOException {

int count = in.read(buffer);

if (count != -1) {

offset += count;

}

return count;

}

@Override public int read(byte [] buffer , int offset, int length) throws IOException {

int count = in.read(buffer, offset, length);

if (count != -1) {

this. offset += count;

}

return count;

}

从上面的代码可以看出,MarkableInputStream在read的时候会动态改变offset的值。

我们回到decodeStream()函数,代码(1)为markStream设定了一个值,经过MarkableInputStream的savePosition()和setLimit()函数,MarkableInputStream的类变量limit被赋值65536。代码(4)这里有读取操作,所以MarkableInputStream的类变量offset会改变,而代码(5)则调用MarkableInputStream的reset()函数,这个方法中先比较offset和limit,如果offset比limit大会抛出错误,加载过程就停止了,加载出错。

问题就出现在这里:

1、在正常的手机上,代码(4)执行完毕,由于options的inJustDecodeBounds为true,所以只读取图片的信息部分,offset这个变量的值也没有很大,比65536小,所以代码(5)的reset方法会正常执行,会正常加载本地图片。

2、但是在部分手机上,代码(4)执行完毕,offset这个变量的值远远比65536大,所以reset方法会抛出异常,加载出错,显示error图片。

这样我们就得出了结论:

在部分手机上,BitmapFactory. decodeStream(stream, null, options);这个方法的实现可能有差别,导致了问题的出现。

同时我们在代码(2)处可以看到,如果是本地图片而且是5.0以上版本,才走else流程,既有问题的代码。而且在代码(3)处则判断是否压缩,如果压缩才会走代码(4)到(5),否则不走这部分,就不会出错。这就解释了这个问题为什么会有如此出现机制。

最简单的解决方法:

使用it.sephiroth.android.library.picasso:picasso:2.5.2.4b这个版本,这个版本修复了这个bug。(注意Picasso官方版本一直停留在2.5.2这个版本,但是这个版本有几个问题,所以尽量使用2.5.2.4b这个可能是非官方维护的版本)

那么这个问题到底如何解决的?我们来看看2.5.2.4b的源码。

主要的处理方法是MarkableInputStream的每个read方法中添加一个limit的处理,如下:

public int read() throws IOException {

if (!this.allowExpire && this.offset + 1L > this.limit) {

this .setLimit(this.limit + ( long)this .limitIncrement);

}

int result = this.in.read() ;

if(result != - 1) {

++this .offset;

}

return result;

}

public int read(byte [] buffer) throws IOException {

if (!this.allowExpire && this.offset + (long )buffer.length > this.limit) {

this .setLimit(this.offset + ( long)buffer.length + (long)this .limitIncrement);

}

int count = this.in.read(buffer) ;

if(count != - 1) {

this .offset += (long)count ;

}

return count;

}

public int read(byte [] buffer, int offset , int length) throws IOException {

if (!this.allowExpire && this.offset + (long )length > this.limit) {

this .setLimit(this.offset + ( long)length + (long)this .limitIncrement);

}

int count = this.in.read(buffer , offset, length);

if(count != - 1) {

this .offset += (long)count ;

}

return count;

}

可以看见,在每个read方法开始都会坐下判断并重新为limit赋值,这样limit就不是65536这样的固定值了。也保证了正常情况下offset比limit小,不会再reset方法中抛出错误了。

而且这个版本可以自动添加"file"协议,所以本地图片使用url方式的时候,不必使用"file://"+ path这种形式,直接使用path即可。2.5.2.4b处理的方法如下:

RequestCreator(Picasso picasso , Uri uri, int resourceId) {

if (picasso.shutdown) {

throw new IllegalStateException("Picasso instance already shut down. Cannot submit new requests.") ;

} else {

if (null != uri) {

String scheme = uri.getScheme();

if( null == scheme) {

uri = Uri.fromFile(new File(uri.getPath())) ;

}

}

this .picasso = picasso;

this.data = new Builder(uri, resourceId, picasso.defaultBitmapConfig) ;

this.data.setCache(picasso.getCache()) ;

}

}

可以看到,如果uri没有协议,则自动添加"file"协议。

本篇内容就这样了,在picasso 2.5.2版本还存在一个问题:在Android 5.0以下版本加载https图片出错,如果你遇到了这个问题,请阅读解决Picasso在Android 5.0以下版本不兼容https导致图片不显示这篇文章。

android picasso 显示本地图片,剖析Picasso加载压缩本地图片流程(解决Android 5.0部分机型无法加载本地图片的问题)...相关推荐

  1. 剖析Picasso加载压缩本地图片流程(解决Android 5.0部分机型无法加载本地图片的问题)

    之前项目中使用Picasso遇到了一个问题:在Android 5.0以上版本的部分手机上使用Picasso加载本地图片会失败.为了解决这个问题,研究了一下Picasso加载和压缩本地图片的流程,才有了 ...

  2. 电脑管理android手机版下载失败怎么办,腾讯手机管家PC版轻松解决Android文件管理难题...

    智能手机使用时间越来越长,照片.视频.音乐.应用软件等不断增多.起初,还能把各类文件管理的有条不紊,但随着不断的拷贝.新建.删除,眼看着手机中的文件夹越来越多.越来越复杂,手机中的文件资料也开始像杂货 ...

  3. Android 系统(78)---《android framework常用api源码分析》之 app应用安装流程

    <android framework常用api源码分析>之 app应用安装流程 <android framework常用api源码分析>android生态在中国已经发展非常庞大 ...

  4. android打开app白色页面,完美解决Android App启动页有白屏闪过的问题

    应用启动的时候有短暂的白屏,如图: 可以通过设置theme的方式来解决 @color/colorPrimary @color/colorPrimaryDark @color/colorAccent t ...

  5. 解决android模拟器不能访问网络的问题

    解决android模拟器不能访问网络的问题 参考文章: (1)解决android模拟器不能访问网络的问题 (2)https://www.cnblogs.com/oriental/p/11750330. ...

  6. 解决Android图库不识别.nomedia的问题

    解决Android图库不识别.nomedia的问题 参考文章: (1)解决Android图库不识别.nomedia的问题 (2)https://www.cnblogs.com/TianFang/arc ...

  7. 解决 Android 双卡双待识别

    Android开发的问题 其实很好狠.今天看到一篇解决android 机型 双卡 管理的问题.看到一篇比较好的文章就转载过来了 = = 转载:http://blog.csdn.net/banketre ...

  8. android中Picasso显示网络图片

    使用之前导入jar包(picasso.jar网上搜搜都有)或者在android studio中添加依赖compile 'com.squareup.picasso:picasso:2.5.2' 使用方式 ...

  9. Android 框架学习4:一次读懂热门图片框架 Picasso 源码及流程

    Android 框架学习1:EventBus 3.0 的特点与如何使用 Android 框架学习2:源码分析 EventBus 3.0 如何实现事件总线 Android 框架学习3:我从 EventB ...

最新文章

  1. 多平台Gstreamer Multiplatform
  2. Spring Cloud(八)高可用的分布式配置中心 Spring Cloud Config
  3. 又要头秃?2020年七大AI编程语言大盘点
  4. 计算机考研不压分的学校,考研院校里,这些学校不压分、不歧视专科生,值得关注...
  5. Determine destination location of apt-get install package?
  6. Spring Cloud和Dubbo
  7. HDU_1874 畅通工程续(SPFA)
  8. python总结函数图像_PIL使用小结(crop和paste函数)
  9. Java LinkedList的实现原理详解
  10. ​​使用win7超级终端连接华为交换机并配置端口镜像
  11. C语言趣味程序百例精解
  12. js距离单位换算_英语中常用的度量衡等单位,与我们用的不一样,这些差异点快收藏...
  13. 访问云服务器储存的mp4_访问云服务器储存的mp4
  14. Linux笔记-nohup和
  15. FFplay源码分析-rtmp入口
  16. Ionic3 Demo
  17. 解决Mybatis报错问题:Type interface com.tjcu.dao.UserDao is not known to the MapperRegistry.
  18. Vxe Table一些简单的应用和踩坑记录
  19. ES6、ES7、ES8、ES9、ES10、ES11、ES12、ES13新特性大全
  20. Vue2基础、组件化编程、脚手架、Vuex、Vue路由、UI组件库

热门文章

  1. can总线不加末端电阻_CAN总线入门
  2. JAVA之旅(一)——基本常识,JAVA概念,开发工具,关键字/标识符,变量/常量,进制/进制转换,运算符,三元运算
  3. 高德地图计算多个轨迹点的距离
  4. 想辞职转行做程序员,需要学习哪些内容?
  5. 树形DP——HDU 1011 Starship Troopers
  6. Anjuta—基于GTK+/GNOME的C/C++优秀开发环境
  7. Nginx 配置ssl证书 windows
  8. Docker拉取Mysql镜像并启动访问及docker常用命令介绍
  9. element中的js弹框效果
  10. EXCELL自动获取当前时间并显示当前时间是今年的第几周