android picasso 显示本地图片,剖析Picasso加载压缩本地图片流程(解决Android 5.0部分机型无法加载本地图片的问题)...
之前项目中使用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部分机型无法加载本地图片的问题)...相关推荐
- 剖析Picasso加载压缩本地图片流程(解决Android 5.0部分机型无法加载本地图片的问题)
之前项目中使用Picasso遇到了一个问题:在Android 5.0以上版本的部分手机上使用Picasso加载本地图片会失败.为了解决这个问题,研究了一下Picasso加载和压缩本地图片的流程,才有了 ...
- 电脑管理android手机版下载失败怎么办,腾讯手机管家PC版轻松解决Android文件管理难题...
智能手机使用时间越来越长,照片.视频.音乐.应用软件等不断增多.起初,还能把各类文件管理的有条不紊,但随着不断的拷贝.新建.删除,眼看着手机中的文件夹越来越多.越来越复杂,手机中的文件资料也开始像杂货 ...
- Android 系统(78)---《android framework常用api源码分析》之 app应用安装流程
<android framework常用api源码分析>之 app应用安装流程 <android framework常用api源码分析>android生态在中国已经发展非常庞大 ...
- android打开app白色页面,完美解决Android App启动页有白屏闪过的问题
应用启动的时候有短暂的白屏,如图: 可以通过设置theme的方式来解决 @color/colorPrimary @color/colorPrimaryDark @color/colorAccent t ...
- 解决android模拟器不能访问网络的问题
解决android模拟器不能访问网络的问题 参考文章: (1)解决android模拟器不能访问网络的问题 (2)https://www.cnblogs.com/oriental/p/11750330. ...
- 解决Android图库不识别.nomedia的问题
解决Android图库不识别.nomedia的问题 参考文章: (1)解决Android图库不识别.nomedia的问题 (2)https://www.cnblogs.com/TianFang/arc ...
- 解决 Android 双卡双待识别
Android开发的问题 其实很好狠.今天看到一篇解决android 机型 双卡 管理的问题.看到一篇比较好的文章就转载过来了 = = 转载:http://blog.csdn.net/banketre ...
- android中Picasso显示网络图片
使用之前导入jar包(picasso.jar网上搜搜都有)或者在android studio中添加依赖compile 'com.squareup.picasso:picasso:2.5.2' 使用方式 ...
- Android 框架学习4:一次读懂热门图片框架 Picasso 源码及流程
Android 框架学习1:EventBus 3.0 的特点与如何使用 Android 框架学习2:源码分析 EventBus 3.0 如何实现事件总线 Android 框架学习3:我从 EventB ...
最新文章
- 多平台Gstreamer Multiplatform
- Spring Cloud(八)高可用的分布式配置中心 Spring Cloud Config
- 又要头秃?2020年七大AI编程语言大盘点
- 计算机考研不压分的学校,考研院校里,这些学校不压分、不歧视专科生,值得关注...
- Determine destination location of apt-get install package?
- Spring Cloud和Dubbo
- HDU_1874 畅通工程续(SPFA)
- python总结函数图像_PIL使用小结(crop和paste函数)
- Java LinkedList的实现原理详解
- ​​使用win7超级终端连接华为交换机并配置端口镜像
- C语言趣味程序百例精解
- js距离单位换算_英语中常用的度量衡等单位,与我们用的不一样,这些差异点快收藏...
- 访问云服务器储存的mp4_访问云服务器储存的mp4
- Linux笔记-nohup和
- FFplay源码分析-rtmp入口
- Ionic3 Demo
- 解决Mybatis报错问题:Type interface com.tjcu.dao.UserDao is not known to the MapperRegistry.
- Vxe Table一些简单的应用和踩坑记录
- ES6、ES7、ES8、ES9、ES10、ES11、ES12、ES13新特性大全
- Vue2基础、组件化编程、脚手架、Vuex、Vue路由、UI组件库
热门文章
- can总线不加末端电阻_CAN总线入门
- JAVA之旅(一)——基本常识,JAVA概念,开发工具,关键字/标识符,变量/常量,进制/进制转换,运算符,三元运算
- 高德地图计算多个轨迹点的距离
- 想辞职转行做程序员,需要学习哪些内容?
- 树形DP——HDU 1011 Starship Troopers
- Anjuta—基于GTK+/GNOME的C/C++优秀开发环境
- Nginx 配置ssl证书 windows
- Docker拉取Mysql镜像并启动访问及docker常用命令介绍
- element中的js弹框效果
- EXCELL自动获取当前时间并显示当前时间是今年的第几周