对于想了解universalimageloader相关的读者,本文将提供新的信息,我们将详细介绍universal-image-loader,并且为您提供关于AndroidImageLoader(An
对于想了解universalimageloader 相关的读者,本文将提供新的信息,我们将详细介绍universal-image-loader,并且为您提供关于Android ImageLoader(Android-Universal-Image-Loader)【1】概述及使用简介、Android Universal Image Loader 源码分析、Android Universal ImageLoader 缓存图片、Android-Universal-Image-Loader 源码解读的有价值信息。
本文目录一览:- universalimageloader 相关(universal-image-loader)
- Android ImageLoader(Android-Universal-Image-Loader)【1】概述及使用简介
- Android Universal Image Loader 源码分析
- Android Universal ImageLoader 缓存图片
- Android-Universal-Image-Loader 源码解读
universalimageloader 相关(universal-image-loader)
// 使用方法
ImageLoader.getInstance().displayImage(advert.getImgUrl(), imageView, mOptions,
new SimpleImageLoadingListener() {
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
super.onLoadingFailed(imageUri, view, failReason);
// 加载失败时的处理
Log.i("tag", "imageUri=" + imageUri);
}
});
//application 初始化相关设置参数,oncreate()调用
// 初始化 ImageLoader
private void initImageLoaderConfig() {
String cacheDir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/ybs/imageloader";
File cacheFile = new File(cacheDir);
if (!cacheFile.exists())
cacheFile.mkdir();
ImageLoaderConfiguration.Builder config = new ImageLoaderConfiguration.Builder(getApplicationContext());
config.threadPriority(Thread.MIN_PRIORITY + 2);
config.denyCacheImageMultipleSizesInMemory();
config.diskCache(new UnlimitedDiscCache(cacheFile));
config.diskCacheFileNameGenerator(new Md5FileNameGenerator());
config.diskCacheSize(50 * 1024 * 1024); // 50 MiB
config.tasksProcessingOrder(QueueProcessingType.LIFO);
config.threadPoolSize(3);
config.writeDebugLogs();
config.memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024)).memoryCacheSize(2 * 1024 * 1024);
com.nostra13.universalimageloader.utils.L.disableLogging();
// Initialize ImageLoader with configuration.
// config.imageDownloader(new
// BaseImageDownloader(getApplicationContext(), 300, readTimeout))
ImageLoader.getInstance().init(config.build());
}
//*** 以下代码来自 来源:梦痕的专栏 www.2cto.com/kf/201412/361295.html**********************************************************************************************************
// 参数配置相关说明
DisplayImageOptions options =
new
DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.ic_stub)
// 设置图片下载期间显示的图片
.showImageForEmptyUri(R.drawable.ic_empty)
// 设置图片Uri为空或是错误的时候显示的图片
.showImageOnFail(R.drawable.ic_error)
// 设置图片加载或解码过程中发生错误显示的图片
.resetViewBeforeLoading(
false
)
// default 设置图片在加载前是否重置、复位
.delayBeforeLoading(
1000
)
// 下载前的延迟时间
.cacheInMemory(
false
)
// default 设置下载的图片是否缓存在内存中
.cacheOnDisk(
false
)
// default 设置下载的图片是否缓存在SD卡中
.preProcessor(...)
.postProcessor(...)
.extraForDownloader(...)
.considerExifParams(
false
)
// default
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2)
// default 设置图片以如何的编码方式显示
.bitmapConfig(Bitmap.Config.ARGB_8888)
// default 设置图片的解码类型
.decodingOptions(...)
// 图片的解码设置
.displayer(
new
SimpleBitmapDisplayer())
// default 还可以设置圆角图片new RoundedBitmapDisplayer(20)
.handler(
new
Handler())
// default
.build();
// 注:如果 DisplayImageOption 没有传递给 ImageLoader.displayImage (…) 方法,那么从配置默 //// 认显示选项
//(ImageLoaderConfiguration.defaultDisplayImageOptions (…)) 将被使用。
1
).imageScaleType(ImageScaleType imageScaleType)
//设置图片的缩放方式
缩放类型mageScaleType:
EXACTLY :图像将完全按比例缩小的目标大小
EXACTLY_STRETCHED:图片会缩放到目标大小完全
IN_SAMPLE_INT:图像将被二次采样的整数倍
IN_SAMPLE_POWER_OF_2:图片将降低
2
倍,直到下一减少步骤,使图像更小的目标大小
NONE:图片不会调整
2
).displayer(BitmapDisplayer displayer)
//设置图片的显示方式
显示方式displayer:
RoundedBitmapDisplayer(
int
roundPixels)设置圆角图片
FakeBitmapDisplayer()这个类什么都没做
FadeInBitmapDisplayer(
int
durationMillis)设置图片渐显的时间
SimpleBitmapDisplayer()正常显示一张图片
.considerExifParams(
true
)
//是否考虑JPEG图像EXIF参数(旋转,翻转)
.displayer(
new
FadeInBitmapDisplayer(
100
))
// 图片加载好后渐入的动画时间
// 相关构造方法
imageUrl 图片的URL地址
imageView 显示图片的ImageView控件
options DisplayImageOptions配置信息
listener 图片下载情况的监听
progressListener 图片下载进度的监听
ImageLoader.getInstance().displayImage(uri, imageView);
2
、 ImageLoader.getInstance().displayImage(uri, imageView, options);
3
、 ImageLoader.getInstance().displayImage(uri, imageView, listener);
4
、 ImageLoader.getInstance().displayImage(uri, imageView, options, listener);
5
、 ImageLoader.getInstance().displayImage(uri, imageView, options, listener, progressListener);
|
注意事项
1、如果你经常出现 oom,你可以尝试:
1) 禁用在内存中缓存 cacheInMemory (false),如果 oom 仍然发生那么似乎你的应用程序有内存泄漏,使用 MemoryAnalyzer 来检测它。否则尝试以下步骤 (尝试所有或几个)
2) 减少配置的线程池的大小 (.threadPoolSize(...)
),建议 1~5
3) 在显示选项中使用 .bitmapConfig (Bitmap.Config.RGB_565) . RGB_565 模式消耗的内存比 ARGB_8888 模式少两倍.
4) 配置中使用.diskCacheExtraOptions (480, 320, null)
5) 配置中使用 .memoryCache (newWeakMemoryCache ()) 或者完全禁用在内存中缓存 (don''t call .cacheInMemory ()).
6) 在显示选项中使用.imageScaleType (ImageScaleType.EXACTLY) 或 .imageScaleType (ImageScaleType.IN_SAMPLE_INT)
7) 避免使用 RoundedBitmapDisplayer. 调用的时候它使用 ARGB-8888 模式创建了一个新的 Bitmap 对象来显示,对于内存缓存模式 (ImageLoaderConfiguration.memoryCache (...)) 你可以使用已经实现好的方法.
2、ImageLoader 是根据 ImageView 的 height,width 确定图片的宽高
3、一定要对 ImageLoaderConfiguration 进行初始化,否则会报错
4、开启缓存后默认会缓存到外置 SD 卡如下地址 (/sdcard/Android/data/[package_name]/cache). 如果外置 SD 卡不存在,会缓存到手机。缓存到 Sd 卡需要在 AndroidManifest.xml 文件中进行如下配置
<uses-permission android:name=
"android.permission.WRITE_EXTERNAL_STORAGE"
></uses-permission>
内存缓存模式可以使用以下已实现的方法 (ImageLoaderConfiguration.memoryCache (...))
1) 缓存只使用强引用
LruMemoryCache (缓存大小超过指定值时,删除最近最少使用的 bitmap) -- 默认情况下使用
2) 缓存使用弱引用和强引用
?
1 2 3 4 5 |
|
3) 缓存使用弱引用
WeakMemoryCache(没有限制缓存)
6、本地缓存模式可以使用以下已实现的方法 (ImageLoaderConfiguration.diskCache (...))
?
1 2 3 4 |
|
Android ImageLoader(Android-Universal-Image-Loader)【1】概述及使用简介
Android ImageLoader(Android-Universal-Image-Loader)【1】概述及使用简介
一,前言:为什么要引入 Android-Universal-Image-Loader?
众所周知,简单的几个 ImageView 加载几个图像资源、或者这几个图像资源是从本地加载时无需考虑过多直接加载即可,但当成千上百个 ImageView 加载成千上百个图像、尤其是当这些图片还是从网络中异步获取,那么需要考虑的问题细节很多很繁琐且容易出错,现在随便举例其中几条:
(1)最基本的问题,网络不可靠,可能在不可靠网络加载过程中,图片加载发生难以预估的失败。
(2)已经从网络或本地中加载成功的图片,应该避免重复加载,重复加载造成网络流量浪费,以及设备计算资源的重复浪费,因此需要考虑图片缓存策略。缓存分为两级缓存:第一级:内存缓存,第二级:“硬盘” 缓存(通常是手机的外置存储如 SD 卡和内置存储)。实现这样的层级缓存策略需要自己维护和组织。内存缓存可以考虑使用 Android 的 LruCache,详情参考我的另外两篇文章:
a、《使用新式 LruCache 取代 SoftReference 缓存图片,Android 异步加载图片》,文章链接地址:http://blog.csdn.net/zhangphil/article/details/43667415
b、或者自己按照 LruCache 设计思路实现和管理内存管理,《基于 Java LinkedList, 实现 Android 大数据缓存策略》,文章链接地址:http://blog.csdn.net/zhangphil/article/details/44116885
硬盘缓存则要自己建立缓存索引和缓存文件结构(如何建立缓存目录?内存在何时机把硬盘缓存的图片加入等等问题)。
(3)设想这一种情况,在一个 Android 竖直方向上 ListView 中有成千上万条图片 item,每条 item 中的图片均需从网络获取。用户手指在屏幕上快速滑动,滑动过程中,极有可能可见视野内的图片还没有加载完成后,用户已经快速的往下滑看下面的图片去了。而上面已经消失的图片加载线程如果置之不理任由其运作,那么,当用户在不断的下拉和上拉过程中,将会造成线程不断的重建和运行,内存开销极大。而对于用户来来,最紧迫的当前可见视野的图片加载显示可能因为线程过多而被无限期拖延到最后显示。这种情况一般得应对策略师自己维护和管理一个线程池(关于 Java 线程池,详情请参考我的另外一篇文章:《Java 线程池:ExecutorService,Executors》,文章链接地址:http://blog.csdn.net/zhangphil/article/details/43898637 ),自己管理和维护多线程下载任务队列,显然,需要考虑的线程队列问题很多,很繁琐。
等等还有很多未列举出来的细节问题。
为了避免重复造轮子,这种情况下最好考虑使用一些业界比较成熟稳定的开源框架。
Android ImageLoader(Android-Universal-Image-Loader),是 github 上的一个第三方开源图像加载库。该项目在 github 上的链接地址:
https://github.com/nostra13/Android-Universal-Image-Loader
Android-Universal-Image-Loader 主要应用领域是 ImageView 加载图片。该开源框架对上述问题给予了充分的解决。并提供了其他额外的附加功能(如加载的图片尺寸,加载动画等等)。
二、Android-Universal-Image-Loader 使用简介。
首先到 Android-Universal-Image-Loader 官方网址下载项目包,使用可以分为两种方法
(1)把 Android-Universal-Image-Loader 的全部实现源代码 (*.java) 放入到自己的项目目录 src 下,当作是自己的源代码使用。
(2)导入 Android-Universal-Image-Loader 的 jar 库文件,比如 universal-image-loader-1.9.4.jar。
两种方式都可以,看个人偏好。
我用的是第一种方法,这样可以方便查阅甚至直接二次定制修改 Android-Universal-Image-Loader 的源代码为自己所用。
代码结构层次如图:
然后就可以直接使用,现给出一个示例。
测试用的 MainActivity.java:
package zhangphil.imageloader;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
public class MainActivity extends ListActivity {
private ImageLoader mImageLoader = null;
// 加载的图片资源URL
private final String ZHANGPHIL_CSDN_LOGO_URL = "http://avatar.csdn.net/9/7/A/1_zhangphil.jpg";
// 加载的数目,假定数据总量很大
private final int ITEM_COUNT = 10000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView lv = this.getListView();
ArrayAdapter adapter = new MyArrayAdapter(this, -1);
lv.setAdapter(adapter);
mImageLoader = ImageLoader.getInstance();
mImageLoader.init(getImageLoaderConfiguration());
}
private ImageLoaderConfiguration getImageLoaderConfiguration() {
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(
this)
.threadPoolSize(3)
// 线程数量
.threadPriority(Thread.NORM_PRIORITY)
// 线程优先级
.tasksProcessingOrder(QueueProcessingType.FIFO)
.denyCacheImageMultipleSizesInMemory()
.memoryCacheSize(1024 * 1024 * 10) // 内存缓存的容量10MB
.diskCacheFileCount(100)// 缓存的文件数量
.diskCacheSize(1024 * 1014 * 100)// 硬盘缓存的大小100MB
.writeDebugLogs()// 输出日志
.build();
return config;
}
private DisplayImageOptions getDisplayImageOptions() {
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.loading)
// 加载过程中显示的图片
.showImageForEmptyUri(R.drawable.ic_launcher)
// 空URI显示的图片
.showImageOnFail(R.drawable.error)
// 加载失败时候显示内容
.cacheInMemory(true)
// 缓存到内存
.cacheOnDisk(true)
// 缓存到硬盘
.considerExifParams(true)
.displayer(new FadeInBitmapDisplayer(1000))// 淡入加载图片显示
.build();
return options;
}
private class MyArrayAdapter extends ArrayAdapter {
private LayoutInflater inflater;
private int resId = R.layout.item;
private DisplayImageOptions mDisplayImageOptions;
public MyArrayAdapter(Context context, int resource) {
super(context, resource);
inflater = LayoutInflater.from(getContext());
mDisplayImageOptions = getDisplayImageOptions();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
convertView = inflater.inflate(resId, null);
ImageView imageView = (ImageView) convertView
.findViewById(R.id.image);
mImageLoader.displayImage(ZHANGPHIL_CSDN_LOGO_URL, imageView,
mDisplayImageOptions);
return convertView;
}
@Override
public int getCount() {
return ITEM_COUNT;
}
}
// private File getMyCacheDir() {
// File sdRoot = Environment.getExternalStorageDirectory();
// String myImageLoaderCacheFileDir = "ImageLodaerCache";
// File cacheFileDir = new File(sdRoot, myImageLoaderCacheFileDir);
// return cacheFileDir;
// }
}
Item.xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/image" >
</ImageView>
素材 error.png 和 Loading.gif 可以根据个人的需要选取不同的图片资源。
ImageLoader 在使用之前需要做一些初始化工作,配置 ImageLoaderConfiguration 和 DisplayImageOptions 。然后就可以直接使用 ImageLoader 的 displayImage()方法从网络或本地存储中异步加载图片资源。而关于图片资源的缓存和异步下载线程池队列则交由 ImageLoader 为我们妥善在后台管理好。
Android Universal Image Loader 源码分析
1. 功能介绍
1.1 Android Universal Image Loader
Android Universal Image Loader 是一个强大的、可高度定制的图片缓存,本文简称为 UIL。
简单的说 UIL 就做了一件事 —— 获取图片并显示在相应的控件上。
1.2 基本使用
1.2.1 初始化
添加完依赖后在 Application 或 Activity 中初始化 ImageLoader,如下:
public class YourApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this)
// 添加你的配置需求
.build();
ImageLoader.getInstance().init(configuration);
}
}
其中 configuration 表示 ImageLoader 的配置信息,可包括图片最大尺寸、线程池、缓存、下载器、解码器等等。
1.2.2 Manifest 配置
<manifest>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".YourApplication"
…… >
……
</application>
</manifest>
添加网络权限。如果允许磁盘缓存,需要添加写外设的权限。
1.2.3 下载显示图片
下载图片,解析为 Bitmap 并在 ImageView 中显示。
imageLoader.displayImage(imageUri, imageView);
下载图片,解析为 Bitmap 传递给回调接口。
imageLoader.loadImage(imageUri, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
// 图片处理
}
});
以上是简单使用,更复杂 API 见本文详细设计。
1.3 特点
- 可配置度高。支持任务线程池、下载器、解码器、内存及磁盘缓存、显示选项等等的配置。
- 包含内存缓存和磁盘缓存两级缓存。
- 支持多线程,支持异步和同步加载。
- 支持多种缓存算法、下载进度监听、ListView 图片错乱解决等。
2. 总体设计
2.1. 总体设计图
上面是 UIL 的总体设计图。整个库分为 ImageLoaderEngine,Cache 及 ImageDownloader,ImageDecoder,BitmapDisplayer,BitmapProcessor 五大模块,其中 Cache 分为 MemoryCache 和 DiskCache 两部分。
简单的讲就是 ImageLoader 收到加载及显示图片的任务,并将它交给 ImageLoaderEngine,ImageLoaderEngine 分发任务到具体线程池去执行,任务通过 Cache 及 ImageDownloader 获取图片,中间可能经过 BitmapProcessor 和 ImageDecoder 处理,最终转换为 Bitmap 交给 BitmapDisplayer 在 ImageAware 中显示。
2.2. UIL 中的概念
简单介绍一些概念,在 4. 详细设计中会仔细介绍。
ImageLoaderEngine:任务分发器,负责分发 LoadAndDisplayImageTask 和 ProcessAndDisplayImageTask 给具体的线程池去执行,本文中也称其为 engine,具体参考 4.2.6 ImageLoaderEngine.java。
ImageAware:显示图片的对象,可以是 ImageView 等,具体参考 4.2.9 ImageAware.java。
ImageDownloader:图片下载器,负责从图片的各个来源获取输入流,具体参考 4.2.22 ImageDownloader.java。
Cache:图片缓存,分为 MemoryCache 和 DiskCache 两部分。
MemoryCache:内存图片缓存,可向内存缓存缓存图片或从内存缓存读取图片,具体参考 4.2.24 MemoryCache.java。
DiskCache:本地图片缓存,可向本地磁盘缓存保存图片或从本地磁盘读取图片,具体参考 4.2.38 DiskCache.java。
ImageDecoder:图片解码器,负责将图片输入流 InputStream 转换为 Bitmap 对象,具体参考 4.2.53 ImageDecoder.java。
BitmapProcessor:图片处理器,负责从缓存读取或写入前对图片进行处理。具体参考 4.2.61 BitmapProcessor.java。
BitmapDisplayer:将 Bitmap 对象显示在相应的控件 ImageAware 上,具体参考 4.2.56 BitmapDisplayer.java。
LoadAndDisplayImageTask:用于加载并显示图片的任务,具体参考 4.2.20 LoadAndDisplayImageTask.java。
ProcessAndDisplayImageTask:用于处理并显示图片的任务,具体参考 4.2.19 ProcessAndDisplayImageTask.java。
DisplayBitmapTask:用于显示图片的任务,具体参考 4.2.18 DisplayBitmapTask.java。
3. 流程图
上图为图片加载及显示流程图,在 uil 库中给出,这里用中文重新画出。
4. 详细设计
4.1 类关系图
4.2 核心类功能介绍
4.2.1 ImageLoader.java
图片加载器,对外的主要 API,采取了单例模式,用于图片的加载和显示。
主要函数:
(1). getInstance()
得到 ImageLoader 的单例。通过双层是否为 null 判断提高性能。
(2). init(ImageLoaderConfiguration configuration)
初始化配置参数,参数 configuration 为 ImageLoader 的配置信息,包括图片最大尺寸、任务线程池、磁盘缓存、下载器、解码器等等。
实现中会初始化 ImageLoaderEngine engine 属性,该属性为任务分发器。
(3). displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)
加载并显示图片或加载并执行回调接口。ImageLoader 加载图片主要分为三类接口:
- displayImage (…) 表示异步加载并显示图片到对应的 ImageAware 上。
- loadImage (…) 表示异步加载图片并执行回调接口。
- loadImageSync (…) 表示同步加载图片。
以上三类接口最终都会调用到这个函数进行图片加载。函数参数解释如下:
uri: 图片的 uri。uri 支持多种来源的图片,包括 http、https、file、content、assets、drawable 及自定义,具体介绍可见 ImageDownloader。
imageAware: 一个接口,表示需要加载图片的对象,可包装 View。
options: 图片显示的配置项。比如加载前、加载中、加载失败应该显示的占位图片,图片是否需要在磁盘缓存,是否需要在内存缓存等。
listener: 图片加载各种时刻的回调接口,包括开始加载、加载失败、加载成功、取消加载四个时刻的回调函数。
progressListener: 图片加载进度的回调接口。
函数流程图如下:
4.2.2 ImageLoaderConfiguration.java
ImageLoader 的配置信息,包括图片最大尺寸、线程池、缓存、下载器、解码器等等。
主要属性:
(1). Resources resources
程序本地资源访问器,用于加载 DisplayImageOptions 中设置的一些 App 中图片资源。
(2). int maxImageWidthForMemoryCache
内存缓存的图片最大宽度。
(3). int maxImageHeightForMemoryCache
内存缓存的图片最大高度。
(4). int maxImageWidthForDiskCache
磁盘缓存的图片最大宽度。
(5). int maxImageHeightForDiskCache
磁盘缓存的图片最大高度。
(6). BitmapProcessor processorForDiskCache
图片处理器,用于处理从磁盘缓存中读取到的图片。
(7). Executor taskExecutor
ImageLoaderEngine 中用于执行从源获取图片任务的 Executor。
(18). Executor taskExecutorForCachedImages
ImageLoaderEngine 中用于执行从缓存获取图片任务的 Executor。
(19). boolean customExecutor
用户是否自定义了上面的 taskExecutor。
(20). boolean customExecutorForCachedImages
用户是否自定义了上面的 taskExecutorForCachedImages。
(21). int threadPoolSize
上面两个默认线程池的核心池大小,即最大并发数。
(22). int threadPriority
上面两个默认线程池的线程优先级。
(23). QueueProcessingType tasksProcessingType
上面两个默认线程池的线程队列类型。目前只有 FIFO, LIFO 两种可供选择。
(24). MemoryCache memoryCache
图片内存缓存。
(25). DiskCache diskCache
图片磁盘缓存,一般放在 SD 卡。
(26). ImageDownloader downloader
图片下载器。
(27). ImageDecoder decoder
图片解码器,内部可使用我们常用的 BitmapFactory.decode (…) 将图片资源解码成 Bitmap 对象。
(28). DisplayImageOptions defaultDisplayImageOptions
图片显示的配置项。比如加载前、加载中、加载失败应该显示的占位图片,图片是否需要在磁盘缓存,是否需要在内存缓存等。
(29). ImageDownloader networkDeniedDownloader
不允许访问网络的图片下载器。
(30). ImageDownloader slowNetworkDownloader
慢网络情况下的图片下载器。
4.2.3 ImageLoaderConfiguration.Builder.java 静态内部类
Builder 模式,用于构造参数繁多的 ImageLoaderConfiguration。
其属性与 ImageLoaderConfiguration 类似,函数多是属性设置函数。
主要函数及含义:
(1). build()
按照配置,生成 ImageLoaderConfiguration。代码如下:
public ImageLoaderConfiguration build() {
initEmptyFieldsWithDefaultValues();
return new ImageLoaderConfiguration(this);
}
(2). initEmptyFieldsWithDefaultValues()
初始化值为 null 的属性。若用户没有配置相关项,UIL 会通过调用 DefaultConfigurationFactory 中的函数返回一个默认值当配置。
taskExecutorForCachedImages、taskExecutor 及 ImageLoaderEngine 的 taskDistributor 的默认值如下:
parameters | taskDistributor | taskExecutorForCachedImages/taskExecutor |
---|---|---|
corePoolSize | 0 | 3 |
maximumPoolSize | Integer.MAX_VALUE | 3 |
keepAliveTime | 60 | 0 |
unit | SECONDS | MILLISECONDS |
workQueue | SynchronousQueue | LIFOLinkedBlockingDeque / LinkedBlockingQueue |
priority | 5 | 3 |
diskCacheFileNameGenerator 默认值为 HashCodeFileNameGenerator。
memoryCache 默认值为 LruMemoryCache。如果内存缓存不允许缓存一张图片的多个尺寸,则用 FuzzyKeyMemoryCache 做封装,同一个图片新的尺寸会覆盖缓存中该图片老的尺寸。
diskCache 默认值与 diskCacheSize 和 diskCacheFileCount 值有关,如果他们有一个大于 0,则默认为 LruDiskCache,否则使用无大小限制的 UnlimitedDiskCache。
downloader 默认值为 BaseImageDownloader。
decoder 默认值为 BaseImageDecoder。
详细及其他属性默认值请到 DefaultConfigurationFactory 中查看。
(3). denyCacheImageMultipleSizesInMemory()
设置内存缓存不允许缓存一张图片的多个尺寸,默认允许。
后面会讲到 View 的 getWidth () 在初始化前后的不同值与这个设置的关系。
(4). diskCacheSize(int maxCacheSize)
设置磁盘缓存的最大字节数,如果大于 0 或者下面的 maxFileCount 大于 0,默认的 DiskCache 会用 LruDiskCache,否则使用无大小限制的 UnlimitedDiskCache。
(5). diskCacheFileCount(int maxFileCount)
设置磁盘缓存文件夹下最大文件数,如果大于 0 或者上面的 maxCacheSize 大于 0,默认的 DiskCache 会用 LruDiskCache,否则使用无大小限制的 UnlimitedDiskCache。
4.2.4 ImageLoaderConfiguration.NetworkDeniedImageDownloader.java 静态内部类
不允许访问网络的图片下载器,实现了 ImageDownloader 接口。
实现也比较简单,包装一个 ImageDownloader 对象,通过在 getStream (…) 函数中禁止 Http 和 Https Scheme 禁止网络访问,如下:
@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
switch (Scheme.ofUri(imageUri)) {
case HTTP:
case HTTPS:
throw new IllegalStateException();
default:
return wrappedDownloader.getStream(imageUri, extra);
}
}
4.2.5 ImageLoaderConfiguration.SlowNetworkImageDownloader.java 静态内部类
慢网络情况下的图片下载器,实现了 ImageDownloader 接口。
通过包装一个 ImageDownloader 对象实现,在 getStream (…) 函数中当 Scheme 为 Http 和 Https 时,用 FlushedInputStream 代替 InputStream 处理慢网络情况,具体见后面 FlushedInputStream 的介绍。
4.2.6 ImageLoaderEngine.java
LoadAndDisplayImageTask 和 ProcessAndDisplayImageTask 任务分发器,负责分发任务给具体的线程池。
主要属性:
(1). ImageLoaderConfiguration configuration
ImageLoader 的配置信息,可包括图片最大尺寸、线程池、缓存、下载器、解码器等等。
(2). Executor taskExecutor
用于执行从源获取图片任务的 Executor,为 configuration 中的 taskExecutor,如果为 null,则会调用 DefaultConfigurationFactory.createExecutor (…) 根据配置返回一个默认的线程池。
(3). Executor taskExecutorForCachedImages
用于执行从缓存获取图片任务的 Executor,为 configuration 中的 taskExecutorForCachedImages,如果为 null,则会调用 DefaultConfigurationFactory.createExecutor (…) 根据配置返回一个默认的线程池。
(4). Executor taskDistributor
任务分发线程池,任务指 LoadAndDisplayImageTask 和 ProcessAndDisplayImageTask,因为只需要分发给上面的两个 Executor 去执行任务,不存在较耗时或阻塞操作,所以用无并发数 (Int 最大值) 限制的线程池即可。
(5). Map cacheKeysForImageAwares
ImageAware 与内存缓存 key 对应的 map,key 为 ImageAware 的 id,value 为内存缓存的 key。
(6). Map uriLocks
图片正在加载的重入锁 map,key 为图片的 uri,value 为标识其正在加载的重入锁。
(7). AtomicBoolean paused
是否被暂停。如果为 true,则所有新的加载或显示任务都会等待直到取消暂停 (为 false)。
(8). AtomicBoolean networkDenied
是否不允许访问网络,如果为 true,通过 ImageLoadingListener.onLoadingFailed (…) 获取图片,则所有不在缓存中需要网络访问的请求都会失败,返回失败原因为网络访问被禁止。
(9). AtomicBoolean slowNetwork
是否是慢网络情况,如果为 true,则自动调用 SlowNetworkImageDownloader 下载图片。
(10). Object pauseLock
暂停的等待锁,可在 engine 被暂停后调用这个锁等待。
主要函数:
(1). void submit(final LoadAndDisplayImageTask task)
添加一个 LoadAndDisplayImageTask。直接用 taskDistributor 执行一个 Runnable,在 Runnable 内部根据图片是否被磁盘缓存过确定使用 taskExecutorForCachedImages 还是 taskExecutor 执行该 task。
(2). void submit(ProcessAndDisplayImageTask task)
添加一个 ProcessAndDisplayImageTask。直接用 taskExecutorForCachedImages 执行该 task。
(3). void pause()
暂停图片加载任务。所有新的加载或显示任务都会等待直到取消暂停 (为 false)。
(4). void resume()
继续图片加载任务。
(5). stop()
暂停所有加载和显示图片任务并清除这里的内部属性值。
(6). fireCallback(Runnable r)
taskDistributor 立即执行某个任务。
(7). getLockForUri(String uri)
得到某个 uri 的重入锁,如果不存在则新建。
(8). createTaskExecutor()
调用 DefaultConfigurationFactory.createExecutor (…) 创建一个线程池。
(9). getLoadingUriForView(ImageAware imageAware)
得到某个 imageAware 正在加载的图片 uri。
(10). prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey)
准备开始一个 Task。向 cacheKeysForImageAwares 中插入 ImageAware 的 id 和图片在内存缓存中的 key。
(11). void cancelDisplayTaskFor(ImageAware imageAware)
取消一个显示任务。从 cacheKeysForImageAwares 中删除 ImageAware 对应元素。
(12). denyNetworkDownloads(boolean denyNetworkDownloads)
设置是否不允许网络访问。
(13). handleSlowNetwork(boolean handleSlowNetwork)
设置是否慢网络情况。
4.2.7 DefaultConfigurationFactory.java
为 ImageLoaderConfiguration 及 ImageLoaderEngine 提供一些默认配置。
主要函数:
(1). createExecutor(int threadPoolSize, int threadPriority, QueueProcessingType tasksProcessingType)
创建线程池。
threadPoolSize 表示核心池大小 (最大并发数)。
threadPriority 表示线程优先级。
tasksProcessingType 表示线程队列类型,目前只有 FIFO, LIFO 两种可供选择。
内部实现会调用 createThreadFactory (…) 返回一个支持线程优先级设置,并且以固定规则命名新建的线程的线程工厂类 DefaultConfigurationFactory.DefaultThreadFactory。
(2). createTaskDistributor()
为 ImageLoaderEngine 中的任务分发器 taskDistributor 提供线程池,该线程池为 normal 优先级的无并发大小限制的线程池。
(3). createFileNameGenerator()
返回一个 HashCodeFileNameGenerator 对象,即以 uri HashCode 为文件名的文件名生成器。
(4). createDiskCache(Context context, FileNameGenerator diskCacheFileNameGenerator, long diskCacheSize, int diskCacheFileCount)
创建一个 Disk Cache。如果 diskCacheSize 或者 diskCacheFileCount 大于 0,返回一个 LruDiskCache,否则返回无大小限制的 UnlimitedDiskCache。
(5). createMemoryCache(Context context, int memoryCacheSize)
创建一个 Memory Cache。返回一个 LruMemoryCache,若 memoryCacheSize 为 0,则设置该内存缓存的最大字节数为 App 最大可用内存的 1/8。
这里 App 的最大可用内存也支持系统在 Honeycomb 之后 (ApiLevel>= 11) application 中 android:largeHeap="true" 的设置。
(6). createImageDownloader(Context context)
创建图片下载器,返回一个 BaseImageDownloader。
(7). createImageDecoder(boolean loggingEnabled)
创建图片解码器,返回一个 BaseImageDecoder。
(8). createBitmapDisplayer()
创建图片显示器,返回一个 SimpleBitmapDisplayer。
4.2.8 DefaultConfigurationFactory.DefaultThreadFactory
默认的线程工厂类,为
DefaultConfigurationFactory.createExecutor(…)
和
DefaultConfigurationFactory.createTaskDistributor(…)
提供线程工厂。支持线程优先级设置,并且以固定规则命名新建的线程。
PS:重命名线程是个很好的习惯,它的一大作用就是方便问题排查,比如性能优化,用 TraceView 查看线程,根据名字很容易分辨各个线程。
4.2.9 ImageAware.java
需要显示图片的对象的接口,可包装 View 表示某个需要显示图片的 View。
主要函数:
(1). View getWrappedView()
得到被包装的 View,图片在该 View 上显示。
(2). getWidth () 与 getHeight ()
得到宽度高度,在计算图片缩放比例时会用到。
(3). getId()
得到唯一标识 id。ImageLoaderEngine 中用这个 id 标识正在加载图片的 ImageAware 和图片内存缓存 key 的对应关系,图片请求前会将内存缓存 key 与新的内存缓存 key 进行比较,如果不相等,则之前的图片请求会被取消。这样当 ImageAware 被复用时就不会因异步加载 (前面任务未取消) 而造成错乱了。
4.2.10 ViewAware.java
封装 Android View 来显示图片的抽象类,实现了 ImageAware 接口,利用 Reference 来 Warp View 防止内存泄露。
主要函数:
(1). ViewAware(View view, boolean checkActualViewSize)
构造函数。
view 表示需要显示图片的对象。
checkActualViewSize 表示通过 getWidth () 和 getHeight () 获取图片宽高时返回真实的宽和高,还是 LayoutParams 的宽高,true 表示返回真实宽和高。
如果为 true 会导致一个问题,View 在还没有初始化完成时加载图片,这时它的真实宽高为 0,会取它 LayoutParams 的宽高,而图片缓存的 key 与这个宽高有关,所以当 View 初始化完成再次需要加载该图片时,getWidth () 和 getHeight () 返回的宽高都已经变化,缓存 key 不一样,从而导致缓存命中失败会再次从网络下载一次图片。可通过 ImageLoaderConfiguration.Builder.denyCacheImageMultipleSizesInMemory () 设置不允许内存缓存缓存一张图片的多个尺寸。
(2). setImageDrawable(Drawable drawable)
如果当前操作在主线程并且 View 没有被回收,则调用抽象函数 setImageDrawableInto (Drawable drawable, View view) 去向 View 设置图片。
(3). setImageBitmap(Bitmap bitmap)
如果当前操作在主线程并且 View 没有被回收,则调用抽象函数 setImageBitmapInto (Bitmap bitmap, View view) 去向 View 设置图片。
4.2.11 ImageViewAware.java
封装 Android ImageView 来显示图片的 ImageAware,继承了 ViewAware,利用 Reference 来 Warp View 防止内存泄露。
如果 getWidth () 函数小于等于 0,会利用反射获取 mMaxWidth 的值作为宽。
如果 getHeight () 函数小于等于 0,会利用反射获取 mMaxHeight 的值作为高。
4.2.12 NonViewAware.java
仅包含处理图片相关信息却没有需要显示图片的 View 的 ImageAware,实现了 ImageAware 接口。常用于加载图片后调用回调接口而不是显示的情况。
4.2.13 DisplayImageOptions.java
图片显示的配置项。比如加载前、加载中、加载失败应该显示的占位图片,图片是否需要在磁盘缓存,是否需要在 memory 缓存等。
主要属性及含义:
(1). int imageResOnLoading
图片正在加载中的占位图片的 resource id,优先级比下面的 imageOnLoading 高,当存在时,imageOnLoading 不起作用。
(2). int imageResForEmptyUri
空 uri 时的占位图片的 resource id,优先级比下面的 imageForEmptyUri 高,当存在时,imageForEmptyUri 不起作用。
(3). int imageResOnFail
加载失败时的占位图片的 resource id,优先级比下面的 imageOnFail 高,当存在时,imageOnFail 不起作用。
(4). Drawable imageOnLoading
加载中的占位图片的 drawabled 对象,默认为 null。
(5). Drawable imageForEmptyUri
空 uri 时的占位图片的 drawabled 对象,默认为 null。
(6). Drawable imageOnFail
加载失败时的占位图片的 drawabled 对象,默认为 null。
(7). boolean resetViewBeforeLoading
在加载前是否重置 view,通过 Builder 构建的对象默认为 false。
(8). boolean cacheInMemory
是否缓存在内存中,通过 Builder 构建的对象默认为 false。
(9). boolean cacheOnDisk
是否缓存在磁盘中,通过 Builder 构建的对象默认为 false。
(10). ImageScaleType imageScaleType
图片的缩放类型,通过 Builder 构建的对象默认为 IN_SAMPLE_POWER_OF_2。
(11). Options decodingOptions;
为 BitmapFactory.Options,用于 BitmapFactory.decodeStream (imageStream, null, decodingOptions) 得到图片尺寸等信息。
(12). int delayBeforeLoading
设置在开始加载前的延迟时间,单位为毫秒,通过 Builder 构建的对象默认为 0。
(13). boolean considerExifParams
是否考虑图片的 EXIF 信息,通过 Builder 构建的对象默认为 false。
(14). Object extraForDownloader
下载器需要的辅助信息。下载时传入 ImageDownloader.getStream (String, Object) 的对象,方便用户自己扩展,默认为 null。
(15). BitmapProcessor preProcessor
缓存在内存之前的处理程序,默认为 null。
(16). BitmapProcessor postProcessor
缓存在内存之后的处理程序,默认为 null。
(17). BitmapDisplayer displayer
图片的显示方式,通过 Builder 构建的对象默认为 SimpleBitmapDisplayer。
(18). Handler handler
handler 对象,默认为 null。
(19). boolean isSyncLoading
是否同步加载,通过 Builder 构建的对象默认为 false。
4.2.14 DisplayImageOptions.Builder.java 静态内部类
Builder 模式,用于构造参数繁多的 DisplayImageOptions。
其属性与 DisplayImageOptions 类似,函数多是属性设置函数。
4.2.15 ImageLoadingListener.java
图片加载各种时刻的回调接口,可在图片加载的某些点做监听。
包括开始加载 (onLoadingStarted)、加载失败 (onLoadingFailed)、加载成功 (onLoadingComplete)、取消加载 (onLoadingCancelled) 四个回调函数。
4.2.16 SimpleImageLoadingListener.java
实现 ImageLoadingListener 接口,不过各个函数都是空实现,表示不在 Image 加载过程中做任何回调监听。
ImageLoader.displayImage (…) 函数中当入参 listener 为空时的默认值。
4.2.17 ImageLoadingProgressListener.java
Image 加载进度的回调接口。其中抽象函数
void onProgressUpdate(String imageUri, View view, int current, int total)
会在获取图片存储到文件系统时被回调。其中 total 表示图片总大小,为网络请求结果 Response Header 中 content-length 字段,如果不存在则为 -1。
4.2.18 DisplayBitmapTask.java
显示图片的 Task,实现了 Runnable 接口,必须在主线程调用。
主要函数:
(1) run()
首先判断 imageAware 是否被 GC 回收,如果是直接调用取消加载回调接口 ImageLoadingListener.onLoadingCancelled (…);
否则判断 imageAware 是否被复用,如果是直接调用取消加载回调接口 ImageLoadingListener.onLoadingCancelled (…);
否则调用 displayer 显示图片,并将 imageAware 从正在加载的 map 中移除。调用加载成功回调接口 ImageLoadingListener.onLoadingComplete (…)。
对于 ListView 或是 GridView 这类会缓存 Item 的 View 来说,单个 Item 中如果含有 ImageView,在滑动过程中可能因为异步加载及 View 复用导致图片错乱,这里对 imageAware 是否被复用的判断就能很好的解决这个问题。原因类似:Android ListView 滑动过程中图片显示重复错位闪烁问题原因及解决方案。
4.2.19 ProcessAndDisplayImageTask.java
处理并显示图片的 Task,实现了 Runnable 接口。
主要函数:
(1) run()
主要通过 imageLoadingInfo 得到 BitmapProcessor 处理图片,并用处理后的图片和配置新建一个 DisplayBitmapTask 在 ImageAware 中显示图片。
4.2.20 LoadAndDisplayImageTask.java
加载并显示图片的 Task,实现了 Runnable 接口,用于从网络、文件系统或内存获取图片并解析,然后调用 DisplayBitmapTask 在 ImageAware 中显示图片。
主要函数:
(1) run()
获取图片并显示,核心代码如下:
bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
bmp = tryLoadBitmap();
...
...
...
if (bmp != null && options.isCacheInMemory()) {
L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
configuration.memoryCache.put(memoryCacheKey, bmp);
}
}
……
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
从上面代码段中可以看到先是从内存缓存中去读取 bitmap 对象,若 bitmap 对象不存在,则调用 tryLoadBitmap () 函数获取 bitmap 对象,获取成功后若在 DisplayImageOptions.Builder 中设置了 cacheInMemory (true), 同时将 bitmap 对象缓存到内存中。
最后新建 DisplayBitmapTask 显示图片。
函数流程图如下:
- 判断图片的内存缓存是否存在,若存在直接执行步骤 8;
- 判断图片的磁盘缓存是否存在,若存在直接执行步骤 5;
- 从网络上下载图片;
- 将图片缓存在磁盘上;
- 将图片 decode 成 bitmap 对象;
- 根据 DisplayImageOptions 配置对图片进行预处理 (Pre-process Bitmap);
- 将 bitmap 对象缓存到内存中;
- 根据 DisplayImageOptions 配置对图片进行后处理 (Post-process Bitmap);
- 执行 DisplayBitmapTask 将图片显示在相应的控件上。
流程图可以参见 3. 流程图。
(2) tryLoadBitmap()
从磁盘缓存或网络获取图片,核心代码如下:
File imageFile = configuration.diskCache.get(uri);
if (imageFile != null && imageFile.exists()) {
...
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
...
String imageUriForDecoding = uri;
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
imageFile = configuration.diskCache.get(uri);
if (imageFile != null) {
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
}
checkTaskNotActual();
bitmap = decodeImage(imageUriForDecoding);
...
}
首先根据 uri 看看磁盘中是不是已经缓存了这个文件,如果已经缓存,调用 decodeImage 函数,将图片文件 decode 成 bitmap 对象; 如果 bitmap 不合法或缓存文件不存在,判断是否需要缓存在磁盘,需要则调用 tryCacheImageOnDisk () 函数去下载并缓存图片到本地磁盘,再通过 decodeImage (imageUri) 函数将图片文件 decode 成 bitmap 对象,否则直接通过 decodeImage (imageUriForDecoding) 下载图片并解析。
(3) tryCacheImageOnDisk()
下载图片并存储在磁盘内,根据磁盘缓存图片最长宽高的配置处理图片。
loaded = downloadImage();
主要就是这一句话,调用下载器下载并保存图片。
如果你在 ImageLoaderConfiguration 中还配置了 maxImageWidthForDiskCache 或者 maxImageHeightForDiskCache,还会调用 resizeAndSaveImage () 函数,调整图片尺寸,并保存新的图片文件。
(4) downloadImage()
下载图片并存储在磁盘内。调用 getDownloader () 得到 ImageDownloader 去下载图片。
(4) resizeAndSaveImage(int maxWidth, int maxHeight)
从磁盘缓存中得到图片,重新设置大小及进行一些处理后保存。
(5) getDownloader()
根据 ImageLoaderEngine 配置得到下载器。
如果不允许访问网络,则使用不允许访问网络的图片下载器 NetworkDeniedImageDownloader;如果是慢网络情况,则使用慢网络情况下的图片下载器 SlowNetworkImageDownloader;否则直接使用 ImageLoaderConfiguration 中的 downloader。
4.2.21 ImageLoadingInfo.java
加载和显示图片任务需要的信息。
String uri 图片 url。
String memoryCacheKey 图片缓存 key。
ImageAware imageAware 需要加载图片的对象。
ImageSize targetSize 图片的显示尺寸。
DisplayImageOptions options 图片显示的配置项。
ImageLoadingListener listener 图片加载各种时刻的回调接口。
ImageLoadingProgressListener progressListener 图片加载进度的回调接口。
ReentrantLock loadFromUriLock 图片加载中的重入锁。
4.2.22 ImageDownloader.java
图片下载接口。待实现函数
getStream(String imageUri, Object extra)
表示通过 uri 得到 InputStream。
通过内部定义的枚举 Scheme, 可以看出 UIL 支持哪些图片来源。
HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");
4.2.23 BaseImageDownloader.java
ImageDownloader 的具体实现类。得到上面各种 Scheme 对应的图片 InputStream。
主要函数
(1). getStream(String imageUri, Object extra)
在 getStream (…) 函数内根据不同 Scheme 类型获取图片输入流。
@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
switch (Scheme.ofUri(imageUri)) {
case HTTP:
case HTTPS:
return getStreamFromNetwork(imageUri, extra);
case FILE:
return getStreamFromFile(imageUri, extra);
case CONTENT:
return getStreamFromContent(imageUri, extra);
case ASSETS:
return getStreamFromAssets(imageUri, extra);
case DRAWABLE:
return getStreamFromDrawable(imageUri, extra);
case UNKNOWN:
default:
return getStreamFromOtherSource(imageUri, extra);
}
}
具体见下面各函数介绍。
(2). getStreamFromNetwork(String imageUri, Object extra)
通过 HttpURLConnection 从网络获取图片的 InputStream。支持 response code 为 3xx 的重定向。这里有个小细节代码如下:
try {
imageStream = conn.getInputStream();
} catch (IOException e) {
// Read all data to allow reuse connection (http://bit.ly/1ad35PY)
IoUtils.readAndCloseStream(conn.getErrorStream());
throw e;
}
在发生异常时会调用 conn.getErrorStream () 继续读取 Error Stream,这是为了利于网络连接回收及复用。但有意思的是在 Froyo (2.2) 之前,HttpURLConnection 有个重大 Bug,调用 close () 函数会影响连接池,导致连接复用失效,不少库通过在 2.3 之前使用 AndroidHttpClient 解决这个问题。
(3). getStreamFromFile(String imageUri, Object extra)
从文件系统获取图片的 InputStream。如果 uri 是 video 类型,则需要单独得到 video 的缩略图返回,否则按照一般读取文件操作返回。
(4). getStreamFromContent(String imageUri, Object extra)
从 ContentProvider 获取图片的 InputStream。
如果是 video 类型,则先从 MediaStore 得到 video 的缩略图返回;
如果是联系人类型,通过 ContactsContract.Contacts.openContactPhotoInputStream (res, uri) 读取内容返回。
否则通过 ContentResolver.openInputStream (…) 读取内容返回。
(5). getStreamFromAssets(String imageUri, Object extra)
从 Assets 中获取图片的 InputStream。
(6). getStreamFromDrawable(String imageUri, Object extra)
从 Drawable 资源中获取图片的 InputStream。
(7). getStreamFromOtherSource(String imageUri, Object extra)
UNKNOWN (自定义) 类型的处理,目前是直接抛出不支持的异常。
4.2.24 MemoryCache.java
Bitmap 内存缓存接口,需要实现的接口包括 get (…)、put (…)、remove (…)、clear ()、keys ()。
4.2.25 BaseMemoryCache.java
实现了 MemoryCache 主要函数的抽象类,以 Map\> softMap 做为缓存池,利于虚拟机在内存不足时回收缓存对象。提供抽象函数:
protected abstract Reference<Bitmap> createReference(Bitmap value)
表示根据 Bitmap 创建一个 Reference 做为缓存对象。Reference 可以是 WeakReference、SoftReference 等。
4.2.26 WeakMemoryCache.java
以 WeakReference<Bitmap> 做为缓存 value 的内存缓存,实现了 BaseMemoryCache。
实现了 BaseMemoryCache 的 createReference (Bitmap value) 函数,直接返回一个 new WeakReference<Bitmap>(value) 做为缓存 value。
4.2.27 LimitedMemoryCache.java
限制总字节大小的内存缓存,继承自 BaseMemoryCache 的抽象类。
会在 put (…) 函数中判断总体大小是否超出了上限,是则循环删除缓存对象直到小于上限。删除顺序由抽象函数
protected abstract Bitmap removeNext()
决定。抽象函数
protected abstract int getSize(Bitmap value)
表示每个元素大小。
4.2.28 LargestLimitedMemoryCache.java
限制总字节大小的内存缓存,会在缓存满时优先删除 size 最大的元素,继承自 LimitedMemoryCache。
实现了 LimitedMemoryCache 缓存 removeNext () 函数,总是返回当前缓存中 size 最大的元素。
4.2.29 UsingFreqLimitedMemoryCache.java
限制总字节大小的内存缓存,会在缓存满时优先删除使用次数最少的元素,继承自 LimitedMemoryCache。
实现了 LimitedMemoryCache 缓存 removeNext () 函数,总是返回当前缓存中使用次数最少的元素。
4.2.30 LRULimitedMemoryCache.java
限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素,继承自 LimitedMemoryCache。
通过 new LinkedHashMap<String, Bitmap>(10, 1.1f, true) 作为缓存池。LinkedHashMap 第三个参数表示是否需要根据访问顺序 (accessOrder) 排序,true 表示根据 accessOrder 排序,最近访问的跟最新加入的一样放到最后面,false 表示根据插入顺序排序。这里为 true 且缓存满时始终删除第一个元素,即始终删除最近最少访问的元素。
实现了 LimitedMemoryCache 缓存 removeNext () 函数,总是返回第一个元素,即最近最少使用的元素。
4.2.31 FIFOLimitedMemoryCache.java
限制总字节大小的内存缓存,会在缓存满时优先删除先进入缓存的元素,继承自 LimitedMemoryCache。
实现了 LimitedMemoryCache 缓存 removeNext () 函数,总是返回最先进入缓存的元素。
以上所有 LimitedMemoryCache 子类都有个问题,就是 Bitmap 虽然通过 WeakReference<Bitmap> 包装,但实际根本不会被虚拟机回收,因为他们子类中同时都保留了 Bitmap 的强引用。大都是 UIL 早期实现的版本,不推荐使用。
4.2.32 LruMemoryCache.java
限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素,实现了 MemoryCache。LRU (Least Recently Used) 为最近最少使用算法。
以 new LinkedHashMap<String, Bitmap>(0, 0.75f, true) 作为缓存池。LinkedHashMap 第三个参数表示是否需要根据访问顺序 (accessOrder) 排序,true 表示根据 accessOrder 排序,最近访问的跟最新加入的一样放到最后面,false 表示根据插入顺序排序。这里为 true 且缓存满时始终删除第一个元素,即始终删除最近最少访问的元素。
在 put (…) 函数中通过 trimToSize (int maxSize) 函数判断总体大小是否超出了上限,是则删除第缓存池中第一个元素,即最近最少使用的元素,直到总体大小小于上限。
LruMemoryCache 功能上与 LRULimitedMemoryCache 类似,不过在实现上更加优雅。用简单的实现接口方式,而不是不断继承的方式。
4.2.33 LimitedAgeMemoryCache.java
限制了对象最长存活周期的内存缓存。
MemoryCache 的装饰者,相当于为 MemoryCache 添加了一个特性。以一个 MemoryCache 内存缓存和一个 maxAge 做为构造函数入参。在 get (…) 时判断如果对象存活时间已经超过设置的最长时间,则删除。
4.2.34 FuzzyKeyMemoryCache.java
可以将某些原本不同的 key 看做相等,在 put 时删除这些相等的 key。
MemoryCache 的装饰者,相当于为 MemoryCache 添加了一个特性。以一个 MemoryCache 内存缓存和一个 keyComparator 做为构造函数入参。在 put (…) 时判断如果 key 与缓存中已有 key 经过 Comparator 比较后相等,则删除之前的元素。
4.2.35 FileNameGenerator.java
根据 uri 得到文件名的接口。
4.2.36 HashCodeFileNameGenerator.java
以 uri 的 hashCode 作为文件名。
4.2.37 Md5FileNameGenerator.java
以 uri 的 MD5 值作为文件名。
4.2.38 DiskCache.java
图片的磁盘缓存接口。
主要函数:
(1) File get(String imageUri)
根据原始图片的 uri 去获取缓存图片的文件。
(2) boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener)
保存 imageStream 到磁盘中,listener 表示保存进度且可在其中取消某些段的保存。
(3) boolean save(String imageUri, Bitmap bitmap)
保存图片到磁盘。
(4) boolean remove(String imageUri)
根据图片 uri 删除缓存图片。
(5) void close()
关闭磁盘缓存,并释放资源。
(6) void clear()
清空磁盘缓存。
(7) File getDirectory()
得到磁盘缓存的根目录。
4.2.39 BaseDiskCache.java
一个无大小限制的本地图片缓存,实现了 DiskCache 主要函数的抽象类。
图片缓存在 cacheDir 文件夹内,当 cacheDir 不可用时,则使用备库 reserveCacheDir。
主要函数:
(1). save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener)
先根据 imageUri 得到目标文件,将 imageStream 先写入与目标文件同一文件夹的 .tmp 结尾的临时文件内,若未被 listener 取消且写入成功则将临时文件重命名为目标文件并返回 true,否则删除临时文件并返回 false。
(2). save(String imageUri, Bitmap bitmap)
先根据 imageUri 得到目标文件,通过 Bitmap.compress (…) 函数将 bitmap 先写入与目标文件同一文件夹的 .tmp 结尾的临时文件内,若写入成功则将临时文件重命名为目标文件并返回 true,否则删除临时文件并返回 false。
(3). File getFile(String imageUri)
根据 imageUri 和 fileNameGenerator 得到文件名,返回 cacheDir 内该文件,若 cacheDir 不可用,则使用备库 reserveCacheDir。
4.2.40 LimitedAgeDiskCache.java
限制了缓存对象最长存活周期的磁盘缓存,继承自 BaseDiskCache。
在 get (…) 时判断如果缓存对象存活时间已经超过设置的最长时间,则删除。在 save (…) 时保存当存时间作为对象的创建时间。
4.2.41 UnlimitedDiskCache.java
一个无大小限制的本地图片缓存。与 BaseDiskCache 无异,只是用了个意思明确的类名。
4.2.42 DiskLruCache.java
限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素。
通过缓存目录下名为 journal 的文件记录缓存的所有操作,并在缓存 open 时读取 journal 的文件内容存储到 LinkedHashMap<String, Entry> lruEntries 中,后面 get (String key) 获取缓存内容时,会先从 lruEntries 中得到图片文件名返回文件。
LRU 的实现跟上面内存缓存类似,lruEntries 为 new LinkedHashMap<String, Entry>(0, 0.75f, true),LinkedHashMap 第三个参数表示是否需要根据访问顺序 (accessOrder) 排序,true 表示根据 accessOrder 排序,最近访问的跟最新加入的一样放到最后面,false 表示根据插入顺序排序。这里为 true 且缓存满时 trimToSize () 函数始终删除第一个元素,即始终删除最近最少访问的文件。
来源于 JakeWharton 的开源项目 DiskLruCache,具体分析请等待 DiskLruCache 源码解析 完成。
4.2.43 LruDiskCache.java
限制总字节大小的内存缓存,会在缓存满时优先删除最近最少使用的元素,实现了 DiskCache。
内部有个 DiskLruCache cache 属性,缓存的存、取操作基本都是由该属性代理完成。
4.2.44 StrictLineReader.java
通过 readLine () 函数从 InputStream 中读取一行,目前仅用于磁盘缓存操作记录文件 journal 的解析。
4.2.45 Util.java
工具类。
String readFully (Reader reader) 读取 reader 中内容。
deleteContents (File dir) 递归删除文件夹内容。
4.2.46 ContentLengthInputStream.java
InputStream 的装饰者,可通过 available () 函数得到 InputStream 对应数据源的长度 (总字节数)。主要用于计算文件存储进度即图片下载进度时的总进度。
4.2.47 FailReason.java
图片下载及显示时的错误原因,目前包括:
IO_ERROR 网络连接或是磁盘存储错误。
DECODING_ERROR decode image 为 Bitmap 时错误。
NETWORK_DENIED 当图片不在缓存中,且设置不允许访问网络时的错误。
OUT_OF_MEMORY 内存溢出错误。
UNKNOWN 未知错误。
4.2.48 FlushedInputStream.java
为了解决早期 Android 版本 BitmapFactory.decodeStream (…) 在慢网络情况下 decode image 异常的 Bug。
主要通过重写 FilterInputStream 的 skip (long n) 函数解决,确保 skip (long n) 始终跳过了 n 个字节。如果返回结果即跳过的字节数小于 n,则不断循环直到 skip (long n) 跳过 n 字节或到达文件尾。
4.2.49 ImageScaleType.java
Image 的缩放类型,目前包括:
NONE 不缩放。
NONE_SAFE 根据需要以整数倍缩小图片,使得其尺寸不超过 Texture 可接受最大尺寸。
IN_SAMPLE_POWER_OF_2 根据需要以 2 的 n 次幂缩小图片,使其尺寸不超过目标大小,比较快的缩小方式。
IN_SAMPLE_INT 根据需要以整数倍缩小图片,使其尺寸不超过目标大小。
EXACTLY 根据需要缩小图片到宽或高有一个与目标尺寸一致。
EXACTLY_STRETCHED 根据需要缩放图片到宽或高有一个与目标尺寸一致。
4.2.50 ViewScaleType.java
ImageAware 的 ScaleType。
将 ImageView 的 ScaleType 简化为两种 FIT_INSIDE 和 CROP 两种。FIT_INSIDE 表示将图片缩放到至少宽度和高度有一个小于等于 View 的对应尺寸,CROP 表示将图片缩放到宽度和高度都大于等于 View 的对应尺寸。
4.2.51 ImageSize.java
表示图片宽高的类。
scaleDown (…) 等比缩小宽高。
scale (…) 等比放大宽高。
4.2.52 LoadedFrom.java
图片来源枚举类,包括网络、磁盘缓存、内存缓存。
4.2.53 ImageDecoder.java
将图片转换为 Bitmap 的接口,抽象函数:
Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException;
表示根据 ImageDecodingInfo 信息得到图片并根据参数将其转换为 Bitmap。
4.2.54 BaseImageDecoder.java
实现了 ImageDecoder。调用 ImageDownloader 获取图片,然后根据 ImageDecodingInfo 或图片 Exif 信息处理图片转换为 Bitmap。
主要函数:
(1). decode(ImageDecodingInfo decodingInfo)
调用 ImageDownloader 获取图片,再调用 defineImageSizeAndRotation (…) 函数得到图片的相关信息,调用 prepareDecodingOptions (…) 得到图片缩放的比例,调用 BitmapFactory.decodeStream 将 InputStream 转换为 Bitmap,最后调用 considerExactScaleAndOrientatiton (…) 根据参数将图片放大、翻转、旋转为合适的样子返回。
(2). defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo)
得到图片真实大小以及 Exif 信息 (设置考虑 Exif 的条件下)。
(3). defineExifOrientation(String imageUri)
得到图片 Exif 信息中的翻转以及旋转角度信息。
(4). prepareDecodingOptions(ImageSize imageSize, ImageDecodingInfo decodingInfo)
得到图片缩放的比例。
- 如果 scaleType 等于 ImageScaleType.NONE,则缩放比例为 1;
- 如果 scaleType 等于 ImageScaleType.NONE_SAFE,则缩放比例为 (int) Math.ceil (Math.max ((float) srcWidth /maxWidth, (float) srcHeight /maxHeight));
- 否则,调用 ImageSizeUtils.computeImageSampleSize (…) 计算缩放比例。
在 computeImageSampleSize (…) 中 - 如果 viewScaleType 等于 ViewScaleType.FIT_INSIDE;
1.1 如果 scaleType 等于 ImageScaleType.IN_SAMPLE_POWER_OF_2,则缩放比例从 1 开始不断 *2 直到宽或高小于最大尺寸;
1.2 否则取宽和高分别与最大尺寸比例中较大值,即 Math.max (srcWidth /targetWidth, srcHeight /targetHeight)。 - 如果 scaleType 等于 ViewScaleType.CROP;
2.1 如果 scaleType 等于 ImageScaleType.IN_SAMPLE_POWER_OF_2,则缩放比例从 1 开始不断 *2 直到宽和高都小于最大尺寸。
2.2 否则取宽和高分别与最大尺寸比例中较小值,即 Math.min (srcWidth /targetWidth, srcHeight /targetHeight)。 - 最后判断宽和高是否超过最大值,如果是 *2 或是 +1 缩放。
(5). considerExactScaleAndOrientatiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo, int rotation, boolean flipHorizontal)
根据参数将图片放大、翻转、旋转为合适的样子返回。
4.2.55 ImageDecodingInfo.java
Image Decode 需要的信息。
String imageKey 图片。
String imageUri 图片 uri,可能是缓存文件的 uri。
String originalImageUri 图片原 uri。
ImageSize targetSize 图片的显示尺寸。
imageScaleType 图片的 ScaleType。
ImageDownloader downloader 图片的下载器。
Object extraForDownloader 下载器需要的辅助信息。
boolean considerExifParams 是否需要考虑图片 Exif 信息。
Options decodingOptions 图片的解码信息,为 BitmapFactory.Options。
4.2.56 BitmapDisplayer.java
在 ImageAware 中显示 bitmap 对象的接口。可在实现中对 bitmap 做一些额外处理,比如加圆角、动画效果。
4.2.57 FadeInBitmapDisplayer.java
图片淡入方式显示在 ImageAware 中,实现了 BitmapDisplayer 接口。
4.2.58 RoundedBitmapDisplayer.java
为图片添加圆角显示在 ImageAware 中,实现了 BitmapDisplayer 接口。主要通过 BitmapShader 实现。
4.2.59 RoundedVignetteBitmapDisplayer.java
为图片添加渐变效果的圆角显示在 ImageAware 中,实现了 BitmapDisplayer 接口。主要通过 RadialGradient 实现。
4.2.60 SimpleBitmapDisplayer.java
直接将图片显示在 ImageAware 中,实现了 BitmapDisplayer 接口。
4.2.61 BitmapProcessor.java
图片处理接口。可用于对图片预处理 (Pre-process Bitmap) 和后处理 (Post-process Bitmap)。抽象函数:
public interface BitmapProcessor {
Bitmap process(Bitmap bitmap);
}
用户可以根据自己需求去实现它。比如你想要为你的图片添加一个水印,那么可以自己去实现 BitmapProcessor 接口,在 DisplayImageOptions 中配置 Pre-process 阶段预处理图片,这样设置后存储在文件系统以及内存缓存中的图片都是加了水印后的。如果只希望在显示时改变不动原图片,可以在 BitmapDisplayer 中处理。
4.2.62 PauseOnScrollListener.java
可在 View 滚动过程中暂停图片加载的 Listener,实现了 OnScrollListener 接口。
它的好处是防止滚动中不必要的图片加载,比如快速滚动不希望滚动中的图片加载。在 ListView 或 GridView 中 item 加载图片最好使用它,简单的一行代码:
gridView.setOnScrollListener(new PauseOnScrollListener(ImageLoader.getInstance(), false, true));
主要的成员变量:
pauseOnScroll 触摸滑动 (手指依然在屏幕上) 过程中是否暂停图片加载。
pauseOnFling 甩指滚动 (手指已离开屏幕) 过程中是否暂停图片加载。
externalListener 自定义的 OnScrollListener 接口,适用于 View 原来就有自定义 OnScrollListener 情况设置。
实现原理:
重写 onScrollStateChanged (…) 函数判断不同的状态下暂停或继续图片加载。
OnScrollListener.SCROLL_STATE_IDLE 表示 View 处于空闲状态,没有在滚动,这时候会加载图片。
OnScrollListener.SCROLL_STATE_TOUCH_SCROLL 表示 View 处于触摸滑动状态,手指依然在屏幕上,通过 pauseOnScroll 变量确定是否需要暂停图片加载。这种时候大都属于慢速滚动浏览状态,所以建议继续图片加载。
OnScrollListener.SCROLL_STATE_FLING 表示 View 处于甩指滚动状态,手指已离开屏幕,通过 pauseOnFling 变量确定是否需要暂停图片加载。这种时候大都属于快速滚动状态,所以建议暂停图片加载以节省资源。
4.2.63 QueueProcessingType.java
任务队列的处理类型,包括 FIFO 先进先出、LIFO 后进先出。
4.2.64 LIFOLinkedBlockingDeque.java
后进先出阻塞队列。重写 LinkedBlockingDeque 的 offer (…) 函数如下:
@Override
public boolean offer(T e) {
return super.offerFirst(e);
}
让 LinkedBlockingDeque 插入总在最前,而 remove () 本身始终删除第一个元素,所以就变为了后进先出阻塞队列。
实际一般情况只重写 offer (…) 函数是不够的,但因为 ThreadPoolExecutor 默认只用到了 BlockingQueue 的 offer (…) 函数,所以这种简单重写后做为 ThreadPoolExecutor 的任务队列没问题。
LIFOLinkedBlockingDeque.java 包下的 LinkedBlockingDeque.java、BlockingDeque.java、Deque.java 都是 Java 1.6 源码中的,这里不做分析。
4.2.65 DiskCacheUtils.java
磁盘缓存工具类,可用于查找或删除某个 uri 对应的磁盘缓存。
4.2.66 MemoryCacheUtils.java
内存缓存工具类。可用于根据 uri 生成内存缓存 key,缓存 key 比较,根据 uri 得到所有相关的 key 或图片,删除某个 uri 的内存缓存。
generateKey(String imageUri, ImageSize targetSize)
根据 uri 生成内存缓存 key,key 规则为 [imageUri]_[width] x [height]。
4.2.67 StorageUtils.java
得到图片 SD 卡缓存目录路径。
缓存目录优先选择 / Android/data/[app_package_name]/cache;若无权限或不可用,则选择 App 在文件系统的缓存目录 context.getCacheDir ();若无权限或不可用,则选择 /data/data/[app_package_name]/cache。
如果缓存目录选择了 / Android/data/[app_package_name]/cache,则新建.nomedia 文件表示不允许类似 Galley 这些应用显示此文件夹下图片。不过在 4.0 系统有 Bug 这种方式不生效。
4.2.68 ImageSizeUtils.java
用于计算图片尺寸、缩放比例相关的工具类。
4.2.69 IoUtils.java
IO 相关工具类,包括 stream 拷贝,关闭等。
4.2.70 L.java
Log 工具类。
5. 杂谈
聊聊 LRU
UIL 的内存缓存默认使用了 LRU 算法。 LRU: Least Recently Used 近期最少使用算法,选用了基于链表结构的 LinkedHashMap 作为存储结构。
假设情景:内存缓存设置的阈值只够存储两个 bitmap 对象,当 put 第三个 bitmap 对象时,将近期最少使用的 bitmap 对象移除。
图 1: 初始化 LinkedHashMap, 并按使用顺序来排序,accessOrder = true;
图 2: 向缓存池中放入 bitmap1 和 bitmap2 两个对象。
图 3: 继续放入第三个 bitmap3,根据假设情景,将会超过设定缓存池阈值。
图 4: 释放对 bitmap1 对象的引用。
图 5: bitmap1 对象被 GC 回收。
Android Universal ImageLoader 缓存图片
项目介绍:
Android上最让人头疼的莫过于从网络获取图片、显示、回收,任何一个环节有问题都可能直接OOM,这个项目或许能帮到你。Universal Image Loader for Android的目的是为了实现异步的网络图片加载、缓存及显示,支持多线程异步加载。它最初来源于Fedor Vlasov的项目,且自此之后,经过大规模的重构和改进。
特性列举:
多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等
支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置
支持图片的内存缓存,文件系统缓存或者SD卡缓存
支持图片下载过程的监听
根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存
较好的控制图片的加载过程,例如暂停图片加载,重新开始加载图片,一般使用在ListView,GridView中,滑动过程中暂停加载图片,停止滑动的时候去加载图片
提供在较慢的网络下对图片进行加载
使用过程:
创建默认的ImageLoader,所有的操作都由ImageLoader控制。该类使用单例设计模式,所以如果要获取该类的实力,需要调用getInstance()方法。在使用ImageLoader显示图片之前,你首先要初始化它的配置,调用ImageLoaderConfiguration的init()方法,然后你就可以实现各种的显示了。
//创建默认的ImageLoader配置参数 ImageLoaderConfiguration configuration = ImageLoaderConfiguration .createDefault(this); //Initialize ImageLoader with configuration. ImageLoader.getInstance().init(configuration);
自定义配置imageloader,就像你已经知道的,首先,你需要使用ImageLoaderConfiguration对象来初始化ImageLoader。由于ImageLoader是单例,所以在程序开始的时候只需要初始化一次就好了。建议你在Activity的onCreate()方法中初始化。如果一个ImageLoader已经初始化过,再次初始化不会有任何效果。下面我们通过ImageLoaderConfiguration.Builder创建一个设置
File cacheDir =StorageUtils.getownCacheDirectory(this,"imageloader/Cache"); ImageLoaderConfigurationconfig = new ImageLoaderConfiguration .Builder(this) .memoryCacheExtraOptions(480,800) // maxwidth,max height,即保存的每个缓存文件的最大长宽 .threadPoolSize(3)//线程池内加载的数量 .threadPriority(Thread.norM_PRIORITY -2) .denyCacheImageMultipleSizesInMemory() .memoryCache(new UsingFreqLimitedMemoryCache(2* 1024 * 1024)) // You can pass your own memory cache implementation/你可以通过自己的内存缓存实现 .memoryCacheSize(2 * 1024 * 1024) .discCacheSize(50 * 1024 * 1024) .discCacheFileNameGenerator(newMd5FileNameGenerator())//将保存的时候的URI名称用MD5 加密 .tasksProcessingOrder(QueueProcessingType.LIFO) .discCacheFileCount(100) //缓存的文件数量 .discCache(new UnlimiteddiscCache(cacheDir))//自定义缓存路径 .defaultdisplayImageOptions(displayImageOptions.createSimple()) .imageDownloader(new BaseImageDownloader(this,5 * 1000,30 * 1000)) // connectTimeout (5 s),readTimeout (30 s)超时时间 .writeDebugLogs() // Remove for releaseapp .build();//开始构建 ImageLoader.getInstance().init(config);
得到imageLoader
ImageLoader imageLoader imageLoader = ImageLoader.getInstance();
使用过程:
(1)图像操作是否参与缓存以及图像效果的配置操作
displayImageOptions options = new displayImageOptions.Builder() .showImageOnLoading(R.drawable.ic_stub) //加载图片时的图片 .showImageForEmptyUri(R.drawable.ic_empty) //没有图片资源时的默认图片 .showImageOnFail(R.drawable.ic_error) //加载失败时的图片 .cacheInMemory(true) //启用内存缓存 .cacheOndisk(true) //启用外存缓存 .considerExifParams(true) //启用EXIF和JPEG图像格式 .displayer(new RoundedBitmapdisplayer(20)) //设置显示风格这里是圆角矩形 .build();
displayImageOptions以下是所有默认配置参数根据需求可以自定义配置
private int imageResOnLoading = 0; private int imageResForEmptyUri = 0; private int imageResOnFail = 0; private Drawable imageOnLoading = null; private Drawable imageForEmptyUri = null; private Drawable imageOnFail = null; private boolean resetViewBeforeLoading = false; private boolean cacheInMemory = false; private boolean cacheOndisk = false; private ImageScaleType imageScaleType = ImageScaleType.IN_SAMPLE_POWER_OF_2; private Options decodingOptions = new Options(); private int delayBeforeLoading = 0; private boolean considerExifParams = false; private Object extraForDownloader = null; private BitmapProcessor preProcessor = null; private BitmapProcessor postProcessor = null; private Bitmapdisplayer displayer = DefaultConfigurationFactory.createBitmapdisplayer(); private Handler handler = null; private boolean isSyncLoading = false;
(2)图片加载监听器在这里吧可以设置加载时的动画或者进度条之类的东西这里
ImageLoadingListener animateFirstListener = new AnimateFirstdisplayListener(); private static class AnimateFirstdisplayListener extends SimpleImageLoadingListener { static final List<String> displayedImages = Collections.synchronizedList(new LinkedList<String>()); @Override public void onLoadingComplete(String imageUri,View view,Bitmap loadedImage) { if (loadedImage != null) { ImageView imageView = (ImageView) view; boolean firstdisplay = !displayedImages.contains(imageUri); if (firstdisplay) { FadeInBitmapdisplayer.animate(imageView,500); displayedImages.add(imageUri); } } } }
(3)简单设置就可以给ImageView添加图片了
imageLoader.displayImage(imageUrl,imageview,options,animateFirstListener);
对于本地的图片 ,在其绝对地址前面要加入"file://"。网络图片就直接写路径了。
由于我的这个是最新的包,可能跟以前老的版本不同,看到有些网友说的是:
String imageUri = "http://site.com/image.png"; // 网络图片 String imageUri = "file:///mnt/sdcard/image.png"; //SD卡图片 String imageUri = "content://media/external/audio/albumart/13"; // 媒体文件夹 String imageUri = "assets://image.png"; // assets String imageUri = "drawable://" + R.drawable.image; // drawable文件
缓存的清理:
缓存的清理可以按需求来定,可以再每个Activity的生命周期函数onDestroy中清理也可以单独设置让用户自行清理。
@Override public void onDestroy() { super.onDestroy(); imageLoader.clearMemoryCache(); imageLoader.cleardiskCache(); }
GirdView,ListView加载图片:
相信大部分人都是使用GridView,ListView来显示大量的图片,而当我们快速滑动GridView,ListView,我们希望能停止图片的加载,而在GridView,ListView停止滑动的时候加载当前界面的图片,这个框架当然也提供这个功能,使用起来也很简单,它提供了PauSEOnScrollListener这个类来控制ListView,GridView滑动过程中停止去加载图片,该类使用的是代理模式
listView.setonScrollListener(new PauSEOnScrollListener(imageLoader,pauSEOnScroll,pauSEOnFling)); gridView.setonScrollListener(new PauSEOnScrollListener(imageLoader,pauSEOnFling));
第一个参数就是我们的图片加载对象ImageLoader,第二个是控制是否在滑动过程中暂停加载图片,如果需要暂停传true就行了,第三个参数控制猛的滑动界面的时候图片是否加载
OutOfMemoryError:
虽然这个框架有很好的缓存机制,有效的避免了OOM的产生,一般的情况下产生OOM的概率比较小,但是并不能保证OutOfMemoryError永远不发生,这个框架对于OutOfMemoryError做了简单的catch,保证我们的程序遇到OOM而不被crash掉,但是如果我们使用该框架经常发生OOM,我们应该怎么去改善呢?
减少线程池中线程的个数,在ImageLoaderConfiguration中的(.threadPoolSize)中配置,推荐配置1-5
在displayImageOptions选项中配置bitmapConfig为Bitmap.Config.RGB_565,因为默认是ARGB_8888, 使用RGB_565会比使用ARGB_8888少消耗2倍的内存
在ImageLoaderConfiguration中配置图片的内存缓存为memoryCache(newWeakMemoryCache()) 或者不使用内存缓存
在displayImageOptions选项中设置.imageScaleType(ImageScaleType.IN_SAMPLE_INT)或者imageScaleType(ImageScaleType.EXACTLY)
通过上面这些,相信大家对Universal-image-loader框架的使用已经非常的了解了,我们在使用该框架的时候尽量的使用displayImage()方法去加载图片,loadImage()是将图片对象回调到ImageLoadingListener接口的onLoadingComplete()方法中,需要我们手动去设置到ImageView上面,displayImage()方法中,对ImageView对象使用的是Weak references,方便垃圾回收器回收ImageView对象,如果我们要加载固定大小的图片的时候,使用loadImage()方法需要传递一个ImageSize对象,而displayImage()方法会根据ImageView对象的测量值,或者android:layout_width and android:layout_height设定的值,或者android:maxWidth and/or android:maxHeight设定的值来裁剪图片
Android-Universal-Image-Loader 源码解读
Android-Universal-Image-Loader 源码解读
Tips:文章为拜读@CodingForAndroid 后有感而做的分享,先对作者表示感谢,附原文地址:http://blog.csdn.net/u011733020
0 前言
在Android开发中,对于图片的加载可以说是个老生常谈的问题了,图片加载是一个比较坑的地方,处理不好,会有各种奇怪的问题,比如 加载导致界面卡顿,程序crash。
因此 如何高效的加载大量图片,以及如何加载大分辨率的图片到内存,是我们想要开发一款优质app时不得不去面对与解决的问题。
-
通常开发中,我们只有两种选择:
使用开源框架
自己去实现处理图片的加载与缓存。
通常一开始让我们自己去写,我们会无从下手,因此先去分析一下开源的思路,对我们的成长很有必要。
目前使用频率较高的图片缓存框架有 Universal-Image-Loader、android-Volley、Picasso、Fresco和Glide五大Android开源组件。
首先排除android-Volley 孰优孰劣,后面再去验证,剩下的四种对于 图片加载缓存的思想,从大方向上应该是类似的
而Android-Universal-Image-Loader 作为一款比较经典的框架,从早期到现在一直都比较常见,这里就拿Android-Universal-Image-Loader 来看一下它对图片处理的思想,以帮助我们理解,以便于我们也能写出类似的框架。
1 工作流程
前面介绍了如何在我们的项目中使用Android-Universal-Image-Loader,本文看一下UIL的工作过程。
在看之前我们先看一下官方的这张图片,它代表着所有条件下的执行流程:
-
图片给出的加载过程分别对应三种情况:
当内存中有该 bitmap 时,直接显示。
当本地有该图片时,加载进内存,然后显示。
内存本地都没有时,请求网络,下载到本地,接下来加载进内存,然后显示。
* 过程分析:
-
最终展示图片还是调用的 ImageLoader 这个类中的 display() 方法,那么我们就把注意力集中到ImageLoader 这个类上,看下display() 内部怎么实现的。
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageLoadingListener listener, ImageLoadingProgressListener progressListener) { // 首先检查初始化配置,configuration == null 抛出异常 checkConfiguration(); if (imageAware == null) { throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS); } if (listener == null) { listener = defaultListener; } if (options == null) { options = configuration.defaultDisplayImageOptions; } // 当 目标uri "" 时这种情况的处理 if (TextUtils.isEmpty(uri)) { engine.cancelDisplayTaskFor(imageAware); listener.onLoadingStarted(uri, imageAware.getWrappedView()); if (options.shouldShowImageForEmptyUri()) { imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources)); } else { imageAware.setImageDrawable(null); } listener.onLoadingComplete(uri, imageAware.getWrappedView(), null); return; } // 根据 配置的大小与图片实际大小得出 图片尺寸 ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize()); String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize); engine.prepareDisplayTaskFor(imageAware, memoryCacheKey); listener.onLoadingStarted(uri, imageAware.getWrappedView()); // 首先从内存中取,看是否加载过,有缓存直接用 Bitmap bmp = configuration.memoryCache.get(memoryCacheKey); if (bmp != null && !bmp.isRecycled()) { L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey); if (options.shouldPostProcess()) { ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, progressListener, engine.getLockForUri(uri)); ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo, defineHandler(options)); if (options.isSyncLoading()) { displayTask.run(); } else { engine.submit(displayTask); } } else { options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE); listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp); } } else { // 没有缓存 if (options.shouldShowImageOnLoading()) { imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources)); } else if (options.isResetViewBeforeLoading()) { imageAware.setImageDrawable(null); } ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, progressListener, engine.getLockForUri(uri)); LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo, defineHandler(options)); if (options.isSyncLoading()) { displayTask.run(); } else { engine.submit(displayTask); } }
}
可以看到这个方法还不长,大体流程也向前面图中描述的:
首先判断传入的目标url 是" ",如果空,是否配置了默认的图片,接下来重点在url 是合法的情况下,去加载bitmap,首先从内存中去取,看能否取到(如果前面加载到内存,并且缓存过,没有被移除,则可以取到),如果取到则直接展示就可以了。
-
如果没有在内存中取到,接下来执行LoadAndDisplayImageTask 这个任务,主要还是看run()方法的执行过程:
@Override public void run() { if (waitIfPaused()) return; if (delayIfNeed()) return; ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock; L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey); if (loadFromUriLock.isLocked()) { L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey); } loadFromUriLock.lock(); Bitmap bmp; try { checkTaskNotActual(); bmp = configuration.memoryCache.get(memoryCacheKey); if (bmp == null || bmp.isRecycled()) { // cache 中没有,下载 bmp = tryLoadBitmap(); if (bmp == null) return; // listener callback already was fired checkTaskNotActual(); checkTaskInterrupted(); if (options.shouldPreProcess()) { L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey); bmp = options.getPreProcessor().process(bmp); if (bmp == null) { L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey); } } // 加入到内存的缓存 if (bmp != null && options.isCacheInMemory()) { L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey); //LruMemoryCache configuration.memoryCache.put(memoryCacheKey, bmp); } } else { loadedFrom = LoadedFrom.MEMORY_CACHE; L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey); } if (bmp != null && options.shouldPostProcess()) { L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey); bmp = options.getPostProcessor().process(bmp); if (bmp == null) { L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey); } } checkTaskNotActual(); checkTaskInterrupted(); } catch (TaskCancelledException e) { fireCancelEvent(); return; } finally { loadFromUriLock.unlock(); } DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom); runTask(displayBitmapTask, syncLoading, handler, engine); }
这里一开始进行了一些基本的判断,比如是否当前暂停加载,延时加载等情况。
接下来,因为设置到了下载读写等过程,所以加了 锁,保证线程安全,下载过程是在 上面的// cache 中没有,这个注释下面的tryLoadBitmap() 方法中进行的,这个方法中做了什么,我们一会在看,现在继续往下走,下载后拿到了bitmap,接着进行判断是否 把bitmap加入到内存中的缓存中。 最后在 DisplayBitmapTask 的run 方法中setImageBitmap设置为背景。
这就是大体工作流程,也是前面说的的 三种情况
内存中有,直接显示。
内存中没有 本地有,加载进内存并显示。
本地没有,网络下载,本地保存,加载进内存,显示。
-
接下来再看前面说的下载方法tryLoadBitmap(), 由于比较长,这里只看关键的 try代码块中的操作:
// 尝试 本地文件中是否有缓存 File imageFile = configuration.diskCache.get(uri); if (imageFile != null && imageFile.exists() && imageFile.length() > 0) { L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey); loadedFrom = LoadedFrom.DISC_CACHE; checkTaskNotActual(); bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath())); } // 本地也没有 if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) { L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey); loadedFrom = LoadedFrom.NETWORK; String imageUriForDecoding = uri; if (options.isCacheOnDisk() && tryCacheImageOnDisk()) { imageFile = configuration.diskCache.get(uri); if (imageFile != null) { imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath()); } } checkTaskNotActual(); bitmap = decodeImage(imageUriForDecoding);
就是前面的情况,先看本地文件,如果有,加载进内存,并显示。
如果没有则 下载,首先判断是否允许保存到本地,如果允许则下载到本地,接下来通过bitmap = decodeImage(imageUriForDecoding);拿到目标bitmap ,并返回 用于显示。
2 缓存策略分析
通过上图,我们可以总结出 UIL采用的是 内存(memory cache)+本地(disk cache) 的两级缓存策略。
-
采用缓存的好处有以下几点:
减少每次请求网络下载消耗的的流量。
复用直接从内存/本地中获取,提高了加载速度。
那么我们接下来看一下UIL 是采取哪些方式去缓存内存和本地文件的。
通过查看UIL的lib库我们可以看出,整个lib 主要有三个包组成
cache:管理缓存
core:下载的核心
utils:一些辅助工具。
utils 不用管,剩下的两部分就是整个项目的精髓: 下载展示 和缓存。
我们这里先看一下cache:
也是由两部分组成:磁盘和内存。
DiskCache(本地缓存)
disc 有两种cache类型:
第一类是是基于DiskLruCache的LruDiskCache
第二类是基于BaseDiskCache的LimitedAgeDiskCache 和UnlimitedDiskCache 。
这两种的相同点是都是将请求到的图片 inputStream写到本地文件中。不同点在鱼管理方式不同,
LruDiskCache是根据 size > maxSize || fileCount > maxFileCount || 或者存的数据超过2000条而自动去删除。
LimitedAgeDiskCache 是根据存入时间与当前时间差,是否大于过期时间 去判断是从新下载 还是重复利用。
UnlimitedDiskCache:这个就是不限制cache大小,只要disk 上有空间 就可以保存到本地。
以上三个都实现了DiskCache 这个接口,具体工作过程是 save get remove clear 等几个方法,类似于数据库的 curd 操作。
MemoryCache(内存缓存)
-
memory的缓存 的实现类比较多,都是实现了 MemoryCache 这个接口
public interface MemoryCache {
/** Puts value into cache by key 根据Key将Value添加进缓存中 @return <b>true</b> - if value was put into cache successfully, <b>false</b> - if value was <b>not</b> put into cache */ boolean put(String key, Bitmap value); /** Returns value by key. If there is no value for key then null will be returned. */ 根据Key 取Value Bitmap get(String key); /** Removes item by key */ 根据Key移除对应的Value Bitmap remove(String key); /** Returns all keys of cache */ 返回所有的缓存Keys Collection<String> keys(); /** Remove all items from cache */ 情况缓存的map void clear();
}
-
比较多,不一一说,拿比较常用的LruLimitedMemoryCache 说一下吧
@Override public boolean put(String key, Bitmap value) { boolean putSuccessfully = false; // Try to add value to hard cache // 当前要存入的 size int valueSize = getSize(value); // 约定的最大size int sizeLimit = getSizeLimit(); //当前存在的size 大小 int curCacheSize = cacheSize.get(); //如果当前没有满,存入 if (valueSize < sizeLimit) { // 判断 存入后如果 超出了约定的 maxsize 则删除掉最早的那一条 while (curCacheSize + valueSize > sizeLimit) { Bitmap removedValue = removeNext(); if (hardCache.remove(removedValue)) { curCacheSize = cacheSize.addAndGet(-getSize(removedValue)); } } hardCache.add(value); cacheSize.addAndGet(valueSize); putSuccessfully = true; } // 如果过大,则不存入到上面的集合,则将value 先new WeakReference<Bitmap>(value)中,然后在加入Map<k v> 中 // Add value to soft cache super.put(key, value); return putSuccessfully; }
注释的很详细, 首先判断大小可以加入List<Bitmap> hardCache 这样一个集合中, 如果可以则加入在判断 当前集合是否超出 设置的默认最大值,如果该图片不能加入到这个集合中,那么首先将value 添加到WeakReference<Bitmap>(value)中,然后将WeakReference 作为value 添加到另一个Map 中保存。
3 总结
前面看上去比较不好理解,第一遍看可能会觉得很乱,这里在总结一下加载的过程:
今天关于universalimageloader 相关和universal-image-loader的介绍到此结束,谢谢您的阅读,有关Android ImageLoader(Android-Universal-Image-Loader)【1】概述及使用简介、Android Universal Image Loader 源码分析、Android Universal ImageLoader 缓存图片、Android-Universal-Image-Loader 源码解读等更多相关知识的信息可以在本站进行查询。
本文标签: