开发app之第三方框架的使用
一、简介
在gitHub上你会发现有很多带有针对性处理优化的一些开源项目, 比如请求网络优化、 加载网络图片优化
等, 并且这些开源的项目都提供了依赖库可以使第三方app集成使用, 这些就属于第三方框架。
二、框架分类
针对开发app中的不同环节, 我们可以根据自己的需要来使用整套的或者单个第三方框架使用; 下面介绍几种
比较有些名气的第三方框架:
1,XUtils
项目git地址:https://github.com/wyouflf/xUtils
XUtils是一个整套第三方框架;xUtils包含了很多实用的android工具, 主要四大模块分别为DbUtils模块、
ViewUtils模块、HttpUtils模块以及BitmapUtils模块;
DbUtils模块:
android中的orm( 依赖注入) 框架, 一行代码就可以进行增删改查;
支持事务, 默认关闭;
可通过注解自定义表名, 列名, 外键, 唯一性约束,NOT NULL约束,CHECK约束等( 需要混
淆的时候请注解表名和列名) ;
支持绑定外键, 保存实体时外键关联实体自动保存或更新;
自动加载外键关联实体, 支持延时加载;
支持链式表达查询, 更直观的查询语义, 参考下面的介绍或sample中的例子。
ViewUtils模块:
android中的ioc框架, 完全注解方式就可以进行UI绑定和事件绑定;
新的事件绑定方式, 使用混淆工具混淆后仍可正常工作;
目前支持常用的11种事件绑定, 参见ViewCommonEventListener类和包
com.lidroid.xutils.view.annotation.event。
HttpUtils模块:
支持同步, 异步方式的请求;
支持大文件上传, 上传大文件不会oom;
支持GET,POST,PUT,MOVE,COPY,DELETE,HEAD请求;
下载支持301/302重定向, 支持设置是否根据Content-Disposition重命名下载的文件;
返回文本内容的GET请求支持缓存, 可设置默认过期时间和针对当前请求的过期时间。
BitmapUtils模块:
加载bitmap的时候无需考虑bitmap加载过程中出现的oom和android容器快速滑动时候出现的
图片错位等现象;
支持加载网络图片和本地图片;
内存管理使用lru算法, 更好的管理bitmap内存;
可配置线程加载线程数量, 缓存大小, 缓存路径, 加载显示动画等...
但是据反映使用BitmapUtils有时候会出现图片加载不出情况, 或者还是出现小几率的OutofMerry, 只能期
待xUtils继续更新优化了;
2,Volley
项目git地址:https://github.com/mcxiaoke/android-volley
Volley是2013年Google I/O大会上推出了一个新的网络通信框架;Volley可是说是把AsyncHttpClient(也
是一个网络封装框架)和Universal-Image-Loader(之前特别出名的一个加载网络图片的框架, 下面会介绍)的优
点集于了一身, 既可以像AsyncHttpClient一样非常简单地进行HTTP通信, 也可以像Universal-ImageLoader一样轻松加载网络上的图片; 它的设计目标就是非常适合去进行数据量不大, 但通信频繁的网络操作, 而
对于大数据量的网络操作, 比如说下载文件等,Volley的表现就会非常糟糕。
Volley 的优点:
自动调度网络请求;
高并发网络连接;
通过标准的HTTP cache coherence( 高速缓存一致性) 缓存磁盘和内存透明的响应;
支持指定请求的优先级;
网络请求cancel机制。 我们可以取消单个请求, 或者指定取消请求队列中的一个区域;
框架容易被定制, 例如, 定制重试或者回退功能;
包含了调试与追踪工具;
请求池, 连接池,Network池
Volley除了支持大数据下载的缺点外, 还有一个就是Volley默认不支持HTTPS, 因此如果使用Volley框架
在使用HTTPS请求的接口必须自己进行证书信任相关处理;
Https跟http区别:
https相对于http安全, ( 通过证书里面的密钥进行请求加密)
volley框架内也有对加载网络图片进行处理的ImageLoader,NetworkImagerView,setImagUrl("图片
地址");
3,Universal-Image-Loader
项目git地址:https://github.com/nostra13/Android-Universal-Image-Loader
Universal-Image-Loader是之前很多开发者最常用的图片异步加载、 缓存的开源框架之一, 由于其对加载图片
内存优化处理非常好, 所以很多app都可以发现它的影子;
原理图:
ImageLoad的使用:(1),下载git项目或者jar包;(2),AndroidManifest中配置权限;
(3),在Application中初始化;
public static voidinitImageLoader(Context context) {
ImageLoaderConfiguration.Builder config =newImageLoaderConfiguration.Builder(context);
config.threadPriority(Thread.NORM_PRIORITY-2);//设置线程池线程数
config.denyCacheImageMultipleSizesInMemory();//清空cache缓存
config.diskCacheFileNameGenerator(newMd5FileNameGenerator());//存储文件名md5加密
config.diskCacheSize(50*1024*1024);//硬盘最大缓存为50 MiB
config.tasksProcessingOrder(QueueProcessingType.LIFO);//线程为后进先出型
config.writeDebugLogs();// Remove for release app
// Initialize ImageLoader with configuration.
ImageLoader.getInstance().init(config.build());
}(4),加载网络图片;
ImageLoader imageLoader = ImageLoader.getInstance();
image
Loader.displayImage("http://pic.tuanche.com/ams/20160215/14555012465214662.png",img_test);(5),加载本地图片;
ImageLoader.getInstance().loadImage("本地路径",newSimpleImageLoadingListener() {
@Override
public voidonLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
}@
Override
public voidonLoadingFailed(String imageUri, View view, FailReason failReason) {
}
});
常用的两种框架介绍(图片处理和网络请求框架)
4,Fresco(也是我们项目中要使用的)
项目git地址:https://github.com/facebook/fresco
Fresco是facebook推出的一款强大的图片加载的框架; 主要有Image Pipeline和Drawees两大模块;
Image Pipeline模块:
Fresco中设计有一个叫做Image Pipeline的模块。 它负责从网络, 从本地文件系统, 本地资源加载图
片。 为了最大限度节省空间和CPU时间, 它含有3级缓存设计(2级内存,1级磁盘) 。
Drawees模块:
Fresco中设计有一个叫做Drawees模块, 它会在图片加载完成前显示占位图, 加载成功后自动替换为目标
图片。 当图片不再显示在屏幕上时, 它会及时地释放内存和空间占用。
Fresco特性
(1)内存管理
解压后的图片, 即Android中的Bitmap, 占用大量的内存。 大的内存占用势必引发更加频繁的GC。 在5.0以下,GC将会
显著地引发界面卡顿。
在5.0以下系统,Fresco将图片放到一个特别的内存区域。 当然, 在图片不显示的时候, 占用的内存会自动被释放。 这
会使得APP更加流畅, 减少因图片内存占用而引发的OOM。
(2)图片加载
Fresco的Image Pipeline允许你用很多种方式来自定义图片加载过程, 比如:
为同一个图片指定不同的远程路径, 或者使用已经存在本地缓存中的图片
先显示一个低清晰度的图片, 等高清图下载完之后再显示高清图
加载完成回调通知
对于本地图, 如有EXIF缩略图, 在大图加载完成之前, 可先显示缩略图
缩放或者旋转图片
对已下载的图片再次处理
(3)图片绘制
Fresco的Drawees设计, 带来一些有用的特性:
自定义居中焦点
圆角图, 当然圆圈也行
下载失败之后, 点击重现下载
自定义占位图, 自定义overlay,或者进度条
指定用户按压时的overlay
(4)图片的渐进式呈现
渐进式的JPEG图片格式已经流行数年了, 渐进式图片格式先呈现大致的图片轮廓, 然后随着图片下载的继续, 呈现
逐渐清晰的图片, 这对于移动设备, 尤其是慢网络有极大的利好, 可带来更好的用户体验。
Android本身的图片库不支持此格式, 但是Fresco支持。 使用时, 和往常一样, 仅仅需要提供一个图片的URI即可, 剩
下的事情,Fresco会处理。
(5)支持gif等动态图加载;
Fresco的使用:(1),gitHub下载依赖库或者jar包;Anroid Studio可以通过compile方式加载:
compile'com.facebook.fresco:fresco:0.8.1'
(2),添加网络权限;
(3),在Application中初始化Fresco;Fresco.initialize(this);
(3),layout布局文件中声明控件;
android:id="@+id/image_view"
android:layout_width="300dp"
android:layout_height="300dp"
fresco:placeholderImage="@mipmap/ic_launcher"/>(4),加载网络图片
Uriuri =Uri.parse("http://pic1.nipic.com/2008-09-08/200898163242920_2.jpg");
image_view.setImageURI(uri);
注: 加载本地图片把Url地址改成本地地址即可;
当然上面提到的SimpleDraweeView只是Drawee其中的控件, 没有什么很特别的需求使用它就够了, 下面贴一下它里面的一
些属性:
android:id="@+id/image_view"
android:layout_width="300dp"
android:layout_height="300dp"
fresco:fadeDuration="300"
fresco:actualImageScaleType="focusCrop"
fresco:placeholderImage="@color/wait_color"
fresco:placeholderImageScaleType="fitCenter"
fresco:failureImage="@drawable/error"
fresco:failureImageScaleType="centerInside"
fresco:retryImage="@drawable/retrying"
fresco:retryImageScaleType="centerCrop"
fresco:progressBarImage="@drawable/progress_bar"
fresco:progressBarImageScaleType="centerInside"
fresco:progressBarAutoRotateInterval="1000"
fresco:backgroundImage="@color/blue"
fresco:overlayImage="@drawable/watermark"
fresco:pressedStateOverlayImage="@color/red"
fresco:roundAsCircle="false"
fresco:roundedCornerRadius="1dp"
fresco:roundTopLeft="true"
fresco:roundTopRight="false"
fresco:roundBottomLeft="false"
fresco:roundBottomRight="true"
fresco:roundWithOverlayColor="@color/corner_color"
fresco:roundingBorderWidth="2dp"
fresco:roundingBorderColor="@color/border_color"
/>
简单介绍一下上面的属性:
placeholderImage就是所谓的展位图啦, 在图片没有加载出来之前你看到的就是它
failureIamge看到名字就知道是什么了, 图片加载失败时显示的图片就是它了
retryImage图片加载失败时显示, 提示用户点击重新加载, 重复加载4次还是没有加载出来的时候才会显示failureImage的图片
progressBarImage进度条图片
backgroundImage背景图片, 这里的背景图片首先被绘制
overlayImage设置叠加图, 在xml中只能设置一张叠加图片, 如果需要多张图片的话, 需要在java代码中设置哦
pressedStateOverlayImage设置点击状态下的叠加图, 此叠加图不能缩放
ImageScaleType这个就是各种各样的图片缩放样式了,center,centerCrop,fouseCrop,centerInside,fitCenter,fitStart,fitEnd,fitXY
剩下的属性就是对圆角的处理了;
5,Okhttp(也是我们项目中要使用的)
项目git地址:https://github.com/square/okhttp
其他下载地址:https://github.com/hongyangAndroid/okhttp-utils
Okhttp是square公司提供的一个高效、 请求快速、 多功能的开源网络框架;
Okhttp支持SPDY协议(SPDY协议在性能上对HTTP做了很大的优化) ;
Okhttp主要包含如下:
一般的get请求
一般的post请求
基于Http Post的文件上传( 类似表单)
文件下载/加载图片
上传下载的进度回调
支持取消某个请求
支持自定义Callback
支持HEAD、DELETE、PATCH、PUT(请求方式)
支持session的保持(Session主要维持用户登录状态)
支持自签名网站https的访问, 提供方法设置下证书就行
Okhttp的使用:(1),加载Okhttp依赖库;
compile'com.squareup.okhttp3:okhttp:3.4.1'(2),在Application中配置生成OkhttpClient;
OkHttpClient okHttpClient= newOkHttpClient.Builder()
// .addInterceptor(new LoggerInterceptor("TAG"))
.connectTimeout(10000L, TimeUnit.MILLISECONDS)
.readTimeout(10000L, TimeUnit.MILLISECONDS)
//其他配置
.build();
OkHttpUtils.initClient(okHttpClient);(3),GET请求
String url="http://www.csdn.net/";
OkHttpUtils
.get()
.url(url)
.addParams("username","hyman")
.addParams("password","123")
.build()
.execute(newStringCallback()
{
@Override
public voidonError(Request request, Exception e)
{ } @
Override
public voidonResponse(String response)
{ }
});
POST请求:
OkHttpUtils
.post()
.url(url)
.addParams("username","hyman")
.addParams("password","123")
.build()
.execute(callback);
概述:
图片加载的工作流(task flow)都是3级缓存的流程;图片的内存缓存一定是LruCache实现;图片下载和读取线程的调度一定是通过线程池管理
画图说明图片加载原理
Glide的使用
详情查看https://github.com/bumptech/glide
介绍:
专注于处理平滑滑动的图片类库
默认使用HttpUrlConnection下载图片
支持设置渐渐显示的动画
支持设置加载中的图片
添加依赖
compile 'com.github.bumptech.glide:glide:3.7.0'
使用Glide加载图片
Glide.with(this)
.load("")
.centerCrop()//设置从中间剪切
.placeholder(R.mipmap.ic_launcher)//设置加载过程中显示的图片
.error(R.mipmap.ic_launcher)//设置加载失败显示的图片
.crossFade()//设置渐渐显示的效果
.into(image);
Picasso的使用
详情查看https://github.com/square/picasso
介绍:
Square开源的比较早的图片加载类库
自动处理adapter中的ImageView的回收时取消下载图片
支持加载多种来源的图片,比如网络,sd卡,res资源
支持设置占位图片
支持对图片的自定义处理
添加依赖
compile 'com.squareup.picasso:picasso:2.5.2'
使用Picasso加载图片
Picasso.with(this)
.load("url")
.placeholder(R.mipmap.ic_launcher)
.error(R.mipmap.ic_launcher)
.centerCrop()
.noFade()//设置不需要渐渐显示的动画效果
.resize(120,120)//指定压缩参考的宽高比
.into(image);
加载其他资源路径的图片
Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);
Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);
Picasso.with(context).load(new File(...)).into(imageView3);
注意:如果不设置resize(120,120),则Picasso会加载整个图片,显然这样消耗的内存比较大,一般都需要指定一下,而Glide内部已经默认参考了控件的宽高来进行缩放了。
Fresco的使用
详情查看https://github.com/facebook/fresco
介绍:
Facebook开源的专注于优化java堆内存,最大程度减少OOM
在Android4.4以及以下,将图片存储在Android的一块特殊的内存区域,这会让图片处理更加快速
支持Gif和WebP格式的图片
添加依赖
compile 'com.facebook.fresco:fresco:0.11.0'
首先初始化Fresco,一般在Application的onCreate中初始化
//先初始化Fresco
Fresco.initialize(this);
使用Fresco提供的SimpleDraweeView显示图片
draweeView.setImageURI("url");
由于使用的是自定义控件加载图片,那么通过定义属性来进行设置:
属性解释:
placeholderImage就是所谓的展位图啦,在图片没有加载出来之前你看到的就是它
failureIamge看到名字就知道是什么了,图片加载失败时显示的图片就是它了
retryImage图片加载失败时显示,提示用户点击重新加载,重复加载4次还是没有加载出来的时候才会显示failureImage的图片
progressBarImage进度条图片
backgroundImage背景图片,这里的背景图片首先被绘制
overlayImage设置叠加图,在xml中只能设置一张叠加图片,如果需要多张图片的话,需要在java代码中设置哦
pressedStateOverlayImage设置点击状态下的叠加图,此叠加图不能缩放
ImageScaleType这个就是各种各样的图片缩放样式了,center,centerCrop,fouseCrop,centerInside,fitCenter,fitStart,fitEnd,fitXY
图片加载总结
UniversalImageLoader :老牌优秀的图片加载类库,特点是配置项丰富,支持圆形图片效果显示以及添加图片加载动画 。
Picasso : Square公司出品。也是很早期出现的图片加载库。默认加载图片不会压缩,并且图片渲染模式是ARGB_8888,占用内存相比Glide稍微高一点,但是可以指定图片加载的宽高,便会依据图片的宽高进行缩放 。
Glide :专门为优化Picasso而生,所以API和Picasso简直一模一样。内部会自动根据图片的宽高来压缩图片,并且图片渲染模式为RGB_565,内存占用会减少一半,专门针对滑动中的图片加载有优化 。和Picasso相比,推荐使用Glide。
Fresco : Facebook公司开源的。特点是在android4.4以及以下,将图片的放入Android native的C++内存中,而不是Java堆内存 ,所以占用的Java堆内存很小,大大减小了程序出现OOM的几率;支持WebP和Gif显示;支持多种图片的显示配置,比如圆形。
重磅更新 2017-02-16
前言
主流图片加载库的对比
Android-Universal-Image-Loader
Picasso
Glide
Fresco
按需选择图片加载库
如何更好地封装图片加载库
源码地址
部分参考链接
更新
2016-12-09 ll You must not call setTag on a view Glide is targeting
2016-12-13 ll 添加loadGifWithPrepareCall方法
2016-12-26 ll 更新loadGifWithProgress方法
2017-1-10 ll 统一加载图片进度回调方法为loadImageWithProgress弃用并删除loadGifWithProgress方法
2016-12-26 ll 自定义GlideModule 并将 Glide与okhttp3集成
2017-1-6 ll GIF帧显示不完全 2017-1-10补充说明PS
2017-1-6 ll You cannot start a load for a destroyed activity
2017-1-10 ll 简单说说图片适配的问题
2017-1-10 ll 添加saveImage方法实现图片的本地自定义保存功能
重磅更新 || 2017-02-16
使用ImageLoaderUtil实现一个真正意义的图集功能,持续完善和更新中
Gallery
Gallery
Gallery
重要的东西贴三遍!
前言
图片加载是Android开发中最最基础的功能,为了降低开发周期和难度,我们经常会选用一些图片加载的开源库
选取第三方SDK需要谨慎
二次封装
注意:所有改动更新会同步到 GitHub
主流图片加载库的对比
共同点
使用简单:一句话实现图片的获取和显示
可配置性高:可配置各种解码、缓存、下载机制
自适应程度高:根据系统性能调整配置策略(如CPU核数决定最大并发数、内存决定内存缓存大小、网络状态变化调整最大并发数)
多级缓存
支持多种数据源
支持多种Displayer
兼容性好(可以配合okhttp等库进行使用)
Android-Universal-Image-Loader
简介
作者:nostra13
面世时间:2011
star数(截止到发稿):14509
https://github.com/nostra13/Android-Universal-Image-Loader
优点
支持下载进度监听(ImageLoadingListener)
可在View滚动中暂停图片加载(PauseOnScrollListener)
默认实现多种内存缓存算法(最大最先删除,使用最少最先删除,最近最少使用,先进先删除,当然自己也可以配置缓存算法)
缺点
从2015.11.27之后不再维护,项目中不建议使用
Picasso
简介
作者:JakeWharton(Square)
面世时间:2012
star数(截止到发稿):12076
https://github.com/square/picasso
优点
包较小(100k)
取消不在视野范围内图片资源的加载
使用最少的内存完成复杂的图片转换
自动添加二级缓存
任务调度优先级处理
并发线程数根据网络类型调整
图片的本地缓存交给同为Square出品的okhttp处理,控制图片的过期时间
缺点
Glide
简介
作者:Sam sjudd (Google)
面世时间:2013
star数(截止到发稿):12067
https://github.com/bumptech/glide
优点
多种图片格式的缓存,适用于更多的内容表现形式(如Gif、WebP、缩略图、Video)
生命周期集成(根据Activity或者Fragment的生命周期管理图片加载请求)
高效处理Bitmap(bitmap的复用和主动回收,减少系统回收压力)
高效的缓存策略,灵活(Picasso只会缓存原始尺寸的图片,Glide缓存的是多种规格),加载速度快且内存开销小(默认Bitmap格式的不同,使得内存开销是Picasso的一半)
缺点
方法较多较复杂,因为相当于在Picasso上的改进,包较大(500k),影响不是很大
Fresco
简介
作者:Facebook
面世时间:2015
star数(截止到发稿):11235
https://github.com/facebook/fresco
优点
最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区)
大大减少OOM(在更底层的Native层对OOM进行处理,图片将不再占用App的内存)
适用于需要高性能加载大量图片的场景
缺点
包较大(2~3M)
用法复杂
底层涉及c++领域,阅读源码深入学习难度大
按需选择图片加载库
如何更好地封装图片加载库
为什么要封装?
先从现在面对的情形来看,项目中使用图片加载的地方都是使用的类似下面的语句
ImageLoader.getInstance ().displayImage (imageUrl, imageView,options)
然而现在ImageLoader已经停止维护且已经无法满足项目需求,我们需要替换,这时你会发现如果换库的话,所有涉及到的地方都要修改(Android-Universal-Image-Loader已经和图片加载的业务逻辑严重地耦合在一起了),工作量可见一斑,这就是不封装在切库时面临的窘境! 那怎么解决那? 计算机史上有个万能的解决方案就是,如果原有层面解决不了问题,那么就请再加一层!
/**
* Created by soulrelay on 2016/10/11 13:42.
* Class Note:
* use this class to load image,single instance
*/
public class ImageLoaderUtil {
public static final int PIC_DEFAULT_TYPE = 0 ;
public static final int LOAD_STRATEGY_DEFAULT = 0 ;
private static ImageLoaderUtil mInstance;
private BaseImageLoaderStrategy mStrategy;
public ImageLoaderUtil () {
mStrategy = new GlideImageLoaderStrategy();
}
public static ImageLoaderUtil getInstance () {
if (mInstance == null ) {
synchronized (ImageLoaderUtil.class) {
if (mInstance == null ) {
mInstance = new ImageLoaderUtil();
return mInstance;
}
}
}
return mInstance;
}
/**
* 统一使用App context
* 可能带来的问题:http://stackoverflow.com/questions/31964737/glide-image-loading-with-application-context
*
* @param url
* @param placeholder
* @param imageView
*/
public void loadImage (String url, int placeholder, ImageView imageView) {
mStrategy.loadImage(imageView.getContext(), url, placeholder, imageView);
}
public void loadGifImage (String url, int placeholder, ImageView imageView) {
mStrategy.loadGifImage(url, placeholder, imageView);
}
public void loadImage (String url, ImageView imageView) {
mStrategy.loadImage(url, imageView);
}
/**
* 展示图片加载进度
*/
public void loadImageWithProgress (String url, ImageView imageView, ProgressLoadListener listener) {
mStrategy.loadImageWithProgress(url,imageView,listener);
}
public void loadGifWithProgress (String url, ImageView imageView, ProgressLoadListener listener) {
mStrategy.loadGifWithProgress(url,imageView,listener);
}
/**
* 策略模式的注入操作
*
* @param strategy
*/
public void setLoadImgStrategy (BaseImageLoaderStrategy strategy) {
mStrategy = strategy;
}
/**
* 清除图片磁盘缓存
*/
public void clearImageDiskCache (final Context context) {
mStrategy.clearImageDiskCache(context);
}
/**
* 清除图片内存缓存
*/
public void clearImageMemoryCache (Context context) {
mStrategy.clearImageMemoryCache(context);
}
/**
* 根据不同的内存状态,来响应不同的内存释放策略
*
* @param context
* @param level
*/
public void trimMemory (Context context, int level) {
mStrategy.trimMemory(context, level);
}
/**
* 清除图片所有缓存
*/
public void clearImageAllCache (Context context) {
clearImageDiskCache(context.getApplicationContext());
clearImageMemoryCache(context.getApplicationContext());
}
/**
* 获取缓存大小
*
* @return CacheSize
*/
public String getCacheSize (Context context) {
return mStrategy.getCacheSize(context);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
所有需要图片显示的地方使用如下方法进行调用:
入口唯一,所有图片加载都在ImageLoaderUtil这一个地方统一管理,使用了单例模式(据说单元素的枚举类型已经成为实现Singleton的最佳方法,你可以试试 ),
高效地封装减少了切库(只需要切换图片加载策略)带来的代价,默认采用GlideImageLoaderStrategy
总结:外部表现一致,内部灵活处理原则。
/**
* 图片加载库的封装演示案例
* Created by soulrelay on 2016/12/11 19:18
*/
public class MainActivity extends AppCompatActivity {
@BindView (R.id.iv_normal)
ImageView ivNormal;
@BindView (R.id.iv_gif)
ImageView ivGif;
@BindView (R.id.iv_gif1)
ImageView ivGif1;
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this );
initView();
}
private void initView () {
ImageLoaderUtil.getInstance().loadImage("http://image.sports.baofeng.com/25a3dbb0c99c5e48e52e60941ed230be" , R.drawable.bg_default_video_common_small, ivNormal);
ImageLoaderUtil.getInstance().loadImage("http://image.sports.baofeng.com/19ce5d6ac3b4fff255196f200b1d3079" , R.drawable.bg_default_video_common_small, ivGif);
ImageLoaderUtil.getInstance().loadGifImage("http://image.sports.baofeng.com/19ce5d6ac3b4fff255196f200b1d3079" , R.drawable.bg_default_video_common_small, ivGif1);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
效果图如下所示:
使用策略模式封装图片加载策略
如果你对策略模式不是很熟,请先参考策略模式和状态模式 首先我们需要抽象出一个图片加载的基础接口BaseImageLoaderStrategy 基本功能主要包括
正常加载图片
针对于GIF图片的特殊加载
加载图片的进度回调
清除缓存
获取缓存大小等
其它特殊需求自己封装,最好不要破坏策略模式的整体结构
/**
* Created by soulrelay on 2016/10/11.
* Class Note:
* abstract class/interface defined to load image
* (Strategy Pattern used here)
*/
public interface BaseImageLoaderStrategy {
void loadImage(String url, ImageView imageView);
void loadImage(String url, int placeholder, ImageView imageView);
void loadImage(Context context, String url, int placeholder, ImageView imageView);
void loadGifImage(String url, int placeholder, ImageView imageView);
void loadImageWithProgress(String url, ImageView imageView, ProgressLoadListener listener);
void loadGifWithProgress(String url, ImageView imageView, ProgressLoadListener listener);
void clearImageDiskCache(final Context context);
void clearImageMemoryCache(Context context);
void trimMemory(Context context, int level);
String getCacheSize(Context context);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
需要说明的一点是:
当封装的方法参数比较少时可以按照上述方式进行抽象,如果需要传递的参数较多,可以考虑使用建造者模式建造者模式
例如封装一个ImageLoaderConfiguration,包含如下参数等等,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
type 图片加载的类型(大图、小图、中图)
url 需要解析的url
placeHolder 当没有成功加载的时候显示的图片
imgView ImageView的实例
loadStrategy 加载策略
当然这里我没有使用建造模式,考虑到目前使用的对象还不算复杂(传参比较简单),而且如果使用建造者模式有可能每次都要new一个新的对象实例,虽然开销可以接受
使用ImageLoaderUtil的过程中,注意内存泄露的问题(静态单例的生命周期与App一样,当一个单例的对象长久不用时,不会被垃圾收集机制回收)
然后基于每个图片库的各自方式来进行相应策略的封装,需要使用哪种策略,只需要通过ImageLoaderUtil的setLoadImgStrategy(BaseImageLoaderStrategy strategy)方法将相应的策略注入,相关类图关系如下所示:
不同的图片加载库实现不同的图片加载策略 这里只是给出Glide的图片加载策略类GlideImageLoaderStrategy作为参考
Glide依赖v4包,且需要配置android.permission.INTERNET和android.permission.WRITE_EXTERNAL_STORAGE(忘记配置权限,图片加载不出来,还看不出什么异常)
其中部分方法使用到了RequestListener的回调(这里是因为项目中的一些特殊需求而添加,如统计图片首次加载时长来测试一下图片cdn服务器的速度等)
在使用Glide的过程中遇到了一些问题,部分已经在注释中说明
之所以针对gif单独封装,是因为在使用的过程中发现,当在列表中加载大量gif会有OOM的问题,所以通过asGif进行特殊标明,即使这样也会出现类似问题,同时暂时通过skipMemoryCache(true)跳过内存缓存,之后有更好的办法会继续补充,各位看官如有良策,希望可以不吝赐教
Glide本身不提供图片的progress回调,所以关于进度回调的解决方案参照的是 ProgressGlide,并做了些许改动集成到项目中
期间发现了一个很好的问题Android的App中线程池的使用,具体使用多少个线程池?,其中一个答主的关于图片加载库线程池策略的分析很好,值得体会,简单摘录如下:
UIL的线程池处理非常简单粗暴,没有根据CPU数量来选择,也没有根据网络状况的变化进行调整;
Picasso的线程池会根据网络状况的变化进行调整,在Wifi下线程数为4,而4G下线程数为3, 3G下为2, 2G下为1,默认状况为3;
Glide加载缓存未命中的线程池会根据根据CPU的数量和Java虚拟机中可用的处理器数量来选择合适的线程数,但是最多不超过4;而加载缓存命中的图片的线程池默认大小为1.
/**
* Created by soulrelay on 2016/10/11 13:48.
* Class Note:
* using {@link Glide} to load image
*/
public class GlideImageLoaderStrategy implements BaseImageLoaderStrategy {
@Override
public void loadImage (String url, int placeholder, ImageView imageView) {
loadNormal(imageView.getContext(), url, placeholder, imageView);
}
@Override
public void loadImage (Context context, String url, int placeholder, ImageView imageView) {
loadNormal(context, url, placeholder, imageView);
}
/**
* 无holder的gif加载
*
* @param url
* @param imageView
*/
@Override
public void loadImage (String url, ImageView imageView) {
Glide.with(imageView.getContext()).load(url).dontAnimate()
.placeholder(imageView.getDrawable())
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(imageView);
}
@Override
public void loadGifImage (String url, int placeholder, ImageView imageView) {
loadGif(imageView.getContext(), url, placeholder, imageView);
}
@Override
public void loadImageWithProgress (String url, final ImageView imageView, final ProgressLoadListener listener) {
Glide.with(imageView.getContext()).using(new ProgressModelLoader(new ProgressUIListener() {
@Override
public void update (final int bytesRead, final int contentLength) {
imageView.post(new Runnable() {
@Override
public void run () {
listener.update(bytesRead, contentLength);
}
});
}
})).load(url).asBitmap().dontAnimate().
listener(new RequestListener() {
@Override
public boolean onException (Exception e, Object model, Target target, boolean isFirstResource) {
listener.onException();
return false ;
}
@Override
public boolean onResourceReady (Bitmap resource, Object model, Target target, boolean isFromMemoryCache, boolean isFirstResource) {
listener.onResourceReady();
return false ;
}
}).into(imageView);
}
@Override
public void loadGifWithProgress (String url, final ImageView imageView, final ProgressLoadListener listener) {
Glide.with(imageView.getContext()).using(new ProgressModelLoader(new ProgressUIListener() {
@Override
public void update (final int bytesRead, final int contentLength) {
imageView.post(new Runnable() {
@Override
public void run () {
listener.update(bytesRead, contentLength);
}
});
}
})).load(url).asGif().skipMemoryCache(true ).dontAnimate().
listener(new RequestListener() {
@Override
public boolean onException (Exception e, String model, Target target, boolean isFirstResource) {
listener.onException();
return false ;
}
@Override
public boolean onResourceReady (GifDrawable resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) {
listener.onResourceReady();
return false ;
}
}).into(imageView);
}
@Override
public void clearImageDiskCache (final Context context) {
try {
if (Looper.myLooper() == Looper.getMainLooper()) {
new Thread(new Runnable() {
@Override
public void run () {
Glide.get(context.getApplicationContext()).clearDiskCache();
}
}).start();
} else {
Glide.get(context.getApplicationContext()).clearDiskCache();
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void clearImageMemoryCache (Context context) {
try {
if (Looper.myLooper() == Looper.getMainLooper()) {
Glide.get(context.getApplicationContext()).clearMemory();
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void trimMemory (Context context, int level) {
Glide.get(context).trimMemory(level);
}
@Override
public String getCacheSize (Context context) {
try {
return CommonUtils.getFormatSize(CommonUtils.getFolderSize(Glide.getPhotoCacheDir(context.getApplicationContext())));
} catch (Exception e) {
e.printStackTrace();
}
return "" ;
}
/**
* load image with Glide
*/
private void loadNormal (final Context ctx, final String url, int placeholder, ImageView imageView) {
/**
* 为其添加缓存策略,其中缓存策略可以为:Source及None,None及为不缓存,Source缓存原型.如果为ALL和Result就不行.然后几个issue的连接:
https://github.com/bumptech/glide/issues/513
https://github.com/bumptech/glide/issues/281
https://github.com/bumptech/glide/issues/600
modified by xuqiang
*/
final long startTime = System.currentTimeMillis();
Glide.with(ctx).load(url).dontAnimate()
.placeholder(placeholder)
.diskCacheStrategy(DiskCacheStrategy.SOURCE).listener(new RequestListener() {
@Override
public boolean onException (Exception e, String model, Target target, boolean isFirstResource) {
return false ;
}
@Override
public boolean onResourceReady (GlideDrawable resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) {
return false ;
}
})
.into(imageView);
}
/**
* load image with Glide
*/
private void loadGif (final Context ctx, String url, int placeholder, ImageView imageView) {
final long startTime = System.currentTimeMillis();
Glide.with(ctx).load(url).asGif().dontAnimate()
.placeholder(placeholder).skipMemoryCache(true )
.diskCacheStrategy(DiskCacheStrategy.SOURCE).listener(new RequestListener() {
@Override
public boolean onException (Exception e, String model, Target target, boolean isFirstResource) {
return false ;
}
@Override
public boolean onResourceReady (GifDrawable resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) {
return false ;
}
})
.into(imageView);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
源码地址
ImageLoaderUtil
部分参考链接
http://www.jianshu.com/p/97994c9693f9 https://www.zhihu.com/question/37804956 http://www.jianshu.com/p/e26130a93289 http://www.cnblogs.com/android-blogs/p/5737611.html
更新
2016-12-09 ll You must not call setTag() on a view Glide is targeting
项目中在使用Glide图片加载框架时遇到该错误 报错原因大致是因为Glide加载的iamgeView调用了setTag()方法导致的错误,因为Glide已经默认为ImageView设置的Tag
相关解决方案已经在Glide 3.6.0(issue #370)被引进,实测可行 在AndroidManifest.xml中加入
<application
android:name =".App" >
然后在App中添加如下代码:
public class App extends Application {
@Override public void onCreate () {
super .onCreate();
ViewTarget.setTagId(R.id.glide_tag);
}
}
在src/main/values/ids.xml添加如下代码:
<resources >
<item type ="id" name ="glide_tag" />
resources >
2016-12-13 ll 添加loadGifWithPrepareCall方法
2016.12.13
只想知道图片是否准备完毕(包括来自网络或者sdcard),区别于loadImageWithProgress和loadGifWithProgress的进度回调
Tips:使用Glide加载图片注意ImageView的Scaletype的设置
public interface BaseImageLoaderStrategy {
void loadGifWithPrepareCall(String url, ImageView imageView, SourceReadyListener listener);
}
public class GlideImageLoaderStrategy implements BaseImageLoaderStrategy {
@Override
public void loadGifWithPrepareCall (String url, ImageView imageView, final SourceReadyListener listener) {
Glide.with(imageView.getContext()).load(url).asGif().dontAnimate()
.skipMemoryCache(true )
.diskCacheStrategy(DiskCacheStrategy.SOURCE).
listener(new RequestListener() {
@Override
public boolean onException (Exception e, String model, Target target, boolean isFirstResource) {
return false ;
}
@Override
public boolean onResourceReady (GifDrawable resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) {
listener.onResourceReady(resource.getIntrinsicWidth(),resource.getIntrinsicHeight());
return false ;
}
}).into(imageView);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ImageLoaderUtil {
public void loadGifWithPrepareCall (String url, ImageView imageView, SourceReadyListener listener) {
mStrategy.loadGifWithPrepareCall(url,imageView,listener);
}
}
2016-12-26 ll 更新loadGifWithProgress方法
2017-1-10 ll 统一加载图片进度回调方法为loadImageWithProgress,弃用并删除loadGifWithProgress方法
具体细节查看GitHub最新代码
2016-12-26 ll 自定义GlideModule 并将 Glide与okhttp3集成
1.自定义一个GlideModule
/**
* DES:自定义一个GlideModule
*
* GlideModule 是一个抽象方法,全局改变 Glide 行为的一个方式,
* 通过全局GlideModule 配置Glide,用GlideBuilder设置选项,用Glide注册ModelLoader等。
*
*/
public class MyGlideModule implements GlideModule {
@Override
public void applyOptions (Context context, GlideBuilder builder) {
int maxMemory = (int ) Runtime.getRuntime().maxMemory();
int memoryCacheSize = maxMemory / 8 ;
builder.setMemoryCache(new LruResourceCache(memoryCacheSize));
builder.setBitmapPool(new LruBitmapPool(memoryCacheSize));
}
@Override
public void registerComponents (Context context, Glide glide) {
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2.AndroidManifest.xml注册
<manifest ... >
<application ... >
<meta-data
android:name ="com.baofeng.soulrelay.utils.imageloader.MyGlideModule"
android:value ="GlideModule" />
application >
manifest >
3、 Glide与OkHttp3集成
compile 'com.squareup.okhttp3:okhttp:3.4.2'
compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar '
4、添加混淆处理
#--------------------Glide-----------------------#
-keep public class * implements com .bumptech .glide .module .GlideModule
-keep public enum com .bumptech .glide .load .resource .bitmap .ImageHeaderParser $** {
**[] $VALUES
public *
}
-keepnames class com .baofeng .soulrelay .utils .imageloader .MyGlideModule
# or more generally:
-keep public class * implements com .bumptech .glide .module .GlideModule
-keep class com .bumptech .glide .integration .okhttp 3.OkHttpGlideModule
#--------------------Glide-----------------------#
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
2017-1-6 ll GIF帧显示不完全 2017-1-10补充说明PS
相关问题在issues1649中被提到和解决(目前glide:3.7.0的确存在这个问题) 具体解决方法是: glide:3.8.0-SNAPSHOT修复了关于GIF展示的一些bug,实测可用 Gradle配置修改如下:
Add the snapshot repo to your list of repositories:
repositories {
jcenter()
maven {
name 'glide-snapshot'
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
}
And then change your dependencies to the v3 snapshot version:
dependencies {
compile 'com.github.bumptech.glide:glide:3.8.0-SNAPSHOT'
compile 'com.github.bumptech.glide:okhttp-integration:1.5.0-SNAPSHOT'
}
2017-1-10补充说明PS
PS:提供一个gif图 帧提取工具GIFFrame.exe 据我分析,那些没有显示完整的GIF图片,里面的部分帧图片本身就不是完整的,但是之前的Glide并没有做很好的处理,所以显示效果有缺陷,当然最新的3.8.0-SNAPSHOT解决了这个问题,但是在显示的时候仍有瑕疵(有一些重叠,当然我觉得这也跟gif图的做工有关)
2017-1-6 ll You cannot start a load for a destroyed activity
完整异常信息:
FATAL EXCEPTION: main
Process: com .sports .baofeng , PID: 9170
java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
at com .bumptech .glide .d .k .b (SourceFile:134 )
at com .bumptech .glide .d .k .a (SourceFile:102 )
at com .bumptech .glide .d .k .a (SourceFile:87 )
at com .bumptech .glide .i .c (SourceFile:629 )
at com .storm .durian .common .utils .imageloader .b .a (SourceFile:1194 )
at com .storm .durian .common .utils .imageloader .c .a (SourceFile:52 )
at com .sports .baofeng .specialtopic .SpecialTopicDetailFixActivity .a (SourceFile:311 )
at com .sports .baofeng .specialtopic .SpecialTopicDetailFixActivity .a (SourceFile:1347 )
at com .sports .baofeng .specialtopic .d .a (SourceFile:1052 )
at com .sports .baofeng .specialtopic .c $1.a (SourceFile:1064 )
at com .storm .durian .common .b .b $1.onPostExecute (SourceFile:57 )
at android.os .AsyncTask .finish (AsyncTask.java :651 )
at android.os .AsyncTask .access $500(AsyncTask.java :180 )
at android.os .AsyncTask $InternalHandler.handleMessage (AsyncTask.java :668 )
at android.os .Handler .dispatchMessage (Handler.java :102 )
at android.os .Looper .loop (Looper.java :158 )
at android.app .ActivityThread .main (ActivityThread.java :7225 )
at java.lang .reflect .Method .invoke (Native Method)
at com .android .internal .os .ZygoteInit $MethodAndArgsCaller.run (ZygoteInit.java :1230 )
at com .android .internal .os .ZygoteInit .main (ZygoteInit.java :1120 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
以上异常出现的几率为random 初步分析原因是:进入页面然后又迅速退出,导致AsyncTask中的onPostExecute在调用Glide加载图片时出现如上异常,当然也跟对AsyncTask的管理有关 同样的问题参考issues138 简单的摘要:
you fire an async task and then finish() in which case you just need to pass getApplicationContext instead of this when creating the callback/asynctask
按照上面这个理解的话,如果是在AsyncTask中的onPostExecute执行时 调用Glide加载图片,context最好使用ApplicationContext
对ImageLoaderUtil做如下更新,添加方法
void loadImageWithAppCxt(String url, ImageView imageView);
@Override
public void loadImageWithAppCxt(String url, ImageView imageView) {
Glide.with (imageView.getContext ().getApplicationContext ()).load (url).dontAnimate ()
.placeholder (imageView.getDrawable ())
.diskCacheStrategy (DiskCacheStrategy.SOURCE )
.into (imageView)
}
public void loadImageWithAppCxt (String url, ImageView imageView) {
mStrategy.loadImageWithAppCxt(url,imageView);
}
2017-1-10 ll 简单说说图片适配的问题
过多的概念不赘述,可以先参考Android屏幕适配全攻略(最权威的官方适配指导) 这里主要描述一种现象,明白的话自然觉得很简单!
<ImageView
android:id ="@+id/iv_gif"
android:layout_width ="wrap_content"
android:layout_height ="wrap_content"
android:scaleType ="centerInside"
/>
假设现在要加载一张200px * 200px的GIF图片(图片基于1280 * 720),这张图片的宽高设置为wrap_content,如果在1920 * 1080分辨率的手机上显示,相对于1280 * 720(假设屏幕尺寸相同),在视觉效果上会显得小,这其实是Android系统基于手机像素密度的一种自适配,单一变化条件下,1920 * 1080分辨率的手机的像素密度是1280 * 720的1.5倍 假设如果系统的自适配让你觉得在高分辨率手机上显得图片过小(像素密度高,200个像素显示起来就比较挤),可以通过自己的计算来改变这种现象 ImageLoaderUtil提供如下加载成功回调的方法(并且会把图片的宽高告诉你):这里有个参数设置,看需求来计算,粗略点可以只使用宽度比例来算,如下面的例子显示,参数为AppParams.screenWidth / 720,当然也可以获取屏幕密度,1920 * 1080的屏幕密度为3,1280 * 720的为2,所以参数可以设置为AppParams.density/2(在两种分辨率上看着视觉上一样)
ImageLoaderUtil.getInstance().loadGifWithPrepareCall(url, mImageView, new SourceReadyListener() {
@Override
public void onResourceReady (int width, int height) {
ViewGroup.LayoutParams params = mImageView.getLayoutParams();
params .height = height * AppParams.screenWidth / 720 ;
params .width = width * AppParams.screenWidth / 720 ;
mImageView.setLayoutParams(params );
progressBar.setVisibility(View.GONE);
}
});
2017-1-10 ll 添加saveImage方法,实现图片的本地自定义保存功能
已同步到GitHub ImageLoaderUtil
/**
* @param context
* @param url 图片url
* @param savePath 保存路径
* @param saveFileName 保存文件名
* @param listener 文件保存成功与否的监听器
*/
public void saveImage (Context context, String url, String savePath, String saveFileName, ImageSaveListener listener) {
mStrategy.saveImage(context, url, savePath, saveFileName, listener);
}
ImageLoaderUtil.getInstance().saveImage(getActivity(), url,
Environment.getExternalStorageDirectory().getAbsolutePath() + "/bfsports" ,
"bfsports" + System.currentTimeMillis(), new ImageSaveListener() {
@Override
public void onSaveSuccess () {
handler.obtainMessage(MSG_PIC_SAVE_SUCC).sendToTarget();
}
@Override
public void onSaveFail () {
handler.obtainMessage(MSG_PIC_SAVE_FAIL).sendToTarget();
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13