Android Fresco 框架动态图支持模块源码深度剖析
一、引言
在 Android 开发中,高效处理和展示动态图(如 GIF、WebP 动画等)是一个常见需求。Fresco 作为 Facebook 开源的强大图片加载和显示库,其动态图支持模块为开发者提供了便捷且高性能的解决方案。本文将深入探讨 Fresco 框架的动态图支持模块,从源码层面进行细致分析,帮助开发者更好地理解和使用该模块。
二、Fresco 框架基础概述
2.1 Fresco 简介
Fresco 是一个功能丰富的 Android 图片加载库,具有内存管理优化、支持多种图片格式、强大的缓存机制等特点。它将图片加载和显示的各个环节进行了抽象和封装,使得开发者可以更轻松地处理图片相关的任务。
2.2 核心组件
Fresco 的核心组件包括 ImagePipeline
、Drawee
等。ImagePipeline
负责图片的加载、解码和缓存管理,而 Drawee
则负责图片的显示和交互。
java
java
// 初始化 Fresco
Fresco.initialize(context);
// 创建一个 SimpleDraweeView 用于显示图片
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
Uri uri = Uri.parse("https://example.com/image.gif");
draweeView.setImageURI(uri);
上述代码展示了如何使用 Fresco 加载并显示一张动态图。通过 Fresco.initialize(context)
初始化 Fresco,然后使用 SimpleDraweeView
来显示指定 URI 的图片。
三、动态图支持模块的整体架构
3.1 模块层次结构
Fresco 的动态图支持模块主要分为以下几个层次:
- 接口层:定义了动态图加载、解码和显示的接口,为上层调用提供统一的抽象。
- 实现层:实现了接口层定义的接口,完成具体的动态图处理逻辑,如 GIF 解码、WebP 动画解码等。
- 缓存层:负责动态图数据的缓存管理,提高加载效率和减少网络请求。
- 显示层:将解码后的动态图帧显示在界面上,实现动画效果。
3.2 模块交互流程
当需要加载一张动态图时,整体的交互流程如下:
- 发起请求 :开发者通过
SimpleDraweeView
或其他方式发起动态图加载请求,指定图片的 URI。 - 缓存检查 :
ImagePipeline
首先检查内存缓存和磁盘缓存中是否存在该动态图数据。如果存在,则直接从缓存中获取;否则,发起网络请求下载数据。 - 数据下载 :如果缓存中没有数据,
ImagePipeline
会通过网络请求下载动态图数据。 - 解码处理:下载完成后,将数据传递给相应的解码器进行解码,如 GIF 解码器或 WebP 解码器。
- 帧处理:解码器将动态图数据解码为一系列帧,并提供帧的相关信息(如帧数量、帧间隔时间等)。
- 显示动画 :
Drawee
组件根据帧信息,依次显示每一帧,实现动态图的动画效果。
四、动态图加载流程源码分析
4.1 请求发起
当调用 SimpleDraweeView.setImageURI(uri)
方法时,会触发动态图加载请求。具体源码如下:
java
java
// SimpleDraweeView.java
@Override
public void setImageURI(Uri uri) {
// 创建一个 ImageRequest 对象,用于封装图片请求信息
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(uri)
.setProgressiveRenderingEnabled(mProgressiveRenderingEnabled)
.setAutoRotateEnabled(mAutoRotateEnabled)
.build();
// 调用 setImageRequest 方法处理请求
setImageRequest(imageRequest);
}
@Override
public void setImageRequest(ImageRequest imageRequest) {
// 获取 DraweeControllerBuilderSupplier 对象
DraweeControllerBuilderSupplier draweeControllerBuilderSupplier = getControllerBuilderSupplier();
// 创建 DraweeControllerBuilder 对象
DraweeControllerBuilder controllerBuilder = draweeControllerBuilderSupplier.get();
// 设置图片请求信息到 DraweeControllerBuilder 中
controllerBuilder.setImageRequest(imageRequest);
// 调用 setController 方法设置控制器
setController(controllerBuilder.build());
}
上述代码中,setImageURI
方法首先创建一个 ImageRequest
对象,封装了图片的 URI 以及其他请求参数。然后通过 DraweeControllerBuilder
构建一个 DraweeController
对象,并将 ImageRequest
设置到控制器中。最后调用 setController
方法将控制器设置到 SimpleDraweeView
中。
4.2 缓存检查
ImagePipeline
在处理请求时,会首先检查缓存中是否存在该动态图数据。相关源码如下:
java
java
// ImagePipeline.java
@Override
public <INFO> DataSource<CloseableReference<INFO>> fetchDecodedImage(
ImageRequest imageRequest,
Object callerContext,
ImageRequest.RequestLevel lowestPermittedRequestLevel) {
// 首先检查内存缓存
if (lowestPermittedRequestLevel.compareTo(ImageRequest.RequestLevel.BITMAP_MEMORY_CACHE) >= 0) {
DataSource<CloseableReference<CloseableImage>> dataSource =
mBitmapMemoryCache.get(imageRequest, callerContext);
if (dataSource != null && dataSource.isFinished() && dataSource.getResult() != null) {
return (DataSource<CloseableReference<INFO>>) dataSource;
}
}
// 若内存缓存中没有,检查磁盘缓存
if (lowestPermittedRequestLevel.compareTo(ImageRequest.RequestLevel.DISK_CACHE) >= 0) {
DataSource<EncodedImage> diskCacheDataSource =
mDiskCache.fetchFromDiskCache(imageRequest);
if (diskCacheDataSource != null && diskCacheDataSource.isFinished() && diskCacheDataSource.getResult() != null) {
return (DataSource<CloseableReference<INFO>>) decodeImageFromDiskCache(diskCacheDataSource, imageRequest, callerContext);
}
}
// 若磁盘缓存中也没有,发起网络请求
return fetchImageFromNetwork(imageRequest, callerContext);
}
在 fetchDecodedImage
方法中,首先检查内存缓存(BITMAP_MEMORY_CACHE
),如果内存缓存中存在该动态图数据,则直接返回。若内存缓存中没有,再检查磁盘缓存(DISK_CACHE
)。如果磁盘缓存中有数据,则调用 decodeImageFromDiskCache
方法进行解码。若磁盘缓存中也没有数据,则调用 fetchImageFromNetwork
方法发起网络请求。
4.3 数据下载
当缓存中没有数据时,ImagePipeline
会发起网络请求下载动态图数据。相关源码如下:
java
java
// ImagePipeline.java
private <INFO> DataSource<CloseableReference<INFO>> fetchImageFromNetwork(
ImageRequest imageRequest,
Object callerContext) {
// 创建一个网络请求数据源
DataSource<EncodedImage> networkDataSource = mNetworkFetcher.fetch(imageRequest, callerContext);
// 创建一个解码数据源,用于对下载的数据进行解码
DataSource<CloseableReference<CloseableImage>> decodeDataSource =
mImageDecoder.decodeFromEncodedImage(networkDataSource, imageRequest, callerContext);
return (DataSource<CloseableReference<INFO>>) decodeDataSource;
}
在 fetchImageFromNetwork
方法中,首先调用 mNetworkFetcher.fetch
方法发起网络请求,获取一个 EncodedImage
类型的数据源。然后调用 mImageDecoder.decodeFromEncodedImage
方法对下载的数据进行解码,返回一个 CloseableReference<CloseableImage>
类型的数据源。
五、动态图解码流程源码分析
5.1 解码器选择
Fresco 根据图片的格式选择合适的解码器进行解码。相关源码如下:
java
java
// ImageDecoder.java
@Override
public DataSource<CloseableReference<CloseableImage>> decodeFromEncodedImage(
DataSource<EncodedImage> encodedImageDataSource,
ImageRequest imageRequest,
Object callerContext) {
// 获取图片的解码配置
DecodeConfig decodeConfig = getDecodeConfig(imageRequest);
// 根据解码配置选择合适的解码器
ImageDecoder decoder = getDecoder(decodeConfig);
// 创建一个解码数据源,使用选择的解码器进行解码
return decoder.decode(encodedImageDataSource, imageRequest, callerContext);
}
private ImageDecoder getDecoder(DecodeConfig decodeConfig) {
switch (decodeConfig.getImageFormat()) {
case GIF:
return mGifDecoder;
case WEBP_ANIMATED:
return mWebpAnimatedDecoder;
default:
return mDefaultDecoder;
}
}
在 decodeFromEncodedImage
方法中,首先调用 getDecodeConfig
方法获取图片的解码配置,然后根据解码配置中的图片格式调用 getDecoder
方法选择合适的解码器。如果图片格式是 GIF,则使用 mGifDecoder
进行解码;如果是 WebP 动画,则使用 mWebpAnimatedDecoder
进行解码;否则使用默认解码器。
5.2 GIF 解码
以 GIF 解码为例,Fresco 使用 GifImage
类来处理 GIF 动画。相关源码如下:
java
java
// GifImage.java
public GifImage(
GifDecoder gifDecoder,
QualityInfo qualityInfo,
int sampleSize) {
super(qualityInfo, sampleSize);
// 初始化 GifDecoder
mGifDecoder = gifDecoder;
// 获取 GIF 图片的帧数量
mFrameCount = mGifDecoder.getFrameCount();
// 获取 GIF 图片的宽度和高度
mWidth = mGifDecoder.getWidth();
mHeight = mGifDecoder.getHeight();
// 初始化帧信息数组
mFrameInfos = new GifFrameInfo[mFrameCount];
for (int i = 0; i < mFrameCount; i++) {
mFrameInfos[i] = mGifDecoder.getFrameInfo(i);
}
}
@Override
public int getFrameCount() {
return mFrameCount;
}
@Override
public int getWidth() {
return mWidth;
}
@Override
public int getHeight() {
return mHeight;
}
@Override
public CloseableReference<Bitmap> getFrameBitmap(int frameIndex) {
// 获取指定帧的 Bitmap
return mGifDecoder.getFrameBitmap(frameIndex);
}
@Override
public int getFrameDuration(int frameIndex) {
// 获取指定帧的持续时间
return mGifDecoder.getFrameDuration(frameIndex);
}
GifImage
类封装了 GifDecoder
对象,提供了获取帧数量、宽度、高度、指定帧的 Bitmap
以及帧持续时间等方法。在构造函数中,初始化 GifDecoder
并获取相关信息。
5.3 WebP 动画解码
对于 WebP 动画,Fresco 使用 AnimatedWebPImage
类进行处理。相关源码如下:
java
java
// AnimatedWebPImage.java
public AnimatedWebPImage(
AnimatedWebPDecoder animatedWebPDecoder,
QualityInfo qualityInfo,
int sampleSize) {
super(qualityInfo, sampleSize);
// 初始化 AnimatedWebPDecoder
mAnimatedWebPDecoder = animatedWebPDecoder;
// 获取 WebP 动画的帧数量
mFrameCount = mAnimatedWebPDecoder.getFrameCount();
// 获取 WebP 动画的宽度和高度
mWidth = mAnimatedWebPDecoder.getWidth();
mHeight = mAnimatedWebPDecoder.getHeight();
// 初始化帧信息数组
mFrameInfos = new AnimatedWebPFrameInfo[mFrameCount];
for (int i = 0; i < mFrameCount; i++) {
mFrameInfos[i] = mAnimatedWebPDecoder.getFrameInfo(i);
}
}
@Override
public int getFrameCount() {
return mFrameCount;
}
@Override
public int getWidth() {
return mWidth;
}
@Override
public int getHeight() {
return mHeight;
}
@Override
public CloseableReference<Bitmap> getFrameBitmap(int frameIndex) {
// 获取指定帧的 Bitmap
return mAnimatedWebPDecoder.getFrameBitmap(frameIndex);
}
@Override
public int getFrameDuration(int frameIndex) {
// 获取指定帧的持续时间
return mAnimatedWebPDecoder.getFrameDuration(frameIndex);
}
AnimatedWebPImage
类与 GifImage
类类似,封装了 AnimatedWebPDecoder
对象,提供了获取帧信息的方法。
六、动态图显示流程源码分析
6.1 帧显示逻辑
Drawee
组件负责将解码后的动态图帧显示在界面上,实现动画效果。相关源码如下:
java
java
// DraweeController.java
@Override
public void setHierarchy(DraweeHierarchy hierarchy) {
mDraweeHierarchy = hierarchy;
if (mAnimatedDrawable != null) {
// 设置动画 Drawable 到 DraweeHierarchy 中
mDraweeHierarchy.setTopLevelDrawable(mAnimatedDrawable);
}
}
@Override
public void onFinalImageSet(
String id,
@Nullable CloseableReference<CloseableImage> imageReference,
@Nullable Animatable animatable) {
if (animatable != null) {
// 如果是动画图片,获取动画 Drawable
mAnimatedDrawable = (AnimatedDrawable) animatable;
if (mDraweeHierarchy != null) {
// 设置动画 Drawable 到 DraweeHierarchy 中
mDraweeHierarchy.setTopLevelDrawable(mAnimatedDrawable);
// 启动动画
mAnimatedDrawable.start();
}
} else if (imageReference != null) {
// 如果是静态图片,获取静态 Drawable
Drawable drawable = mDrawableFactory.createDrawable(imageReference);
if (mDraweeHierarchy != null) {
// 设置静态 Drawable 到 DraweeHierarchy 中
mDraweeHierarchy.setTopLevelDrawable(drawable);
}
}
}
在 setHierarchy
方法中,如果存在动画 Drawable
,则将其设置到 DraweeHierarchy
中。在 onFinalImageSet
方法中,如果图片是动画图片,获取动画 Drawable
并设置到 DraweeHierarchy
中,然后启动动画;如果是静态图片,则获取静态 Drawable
并设置到 DraweeHierarchy
中。
6.2 动画循环与控制
Fresco 支持动画的循环播放和控制。相关源码如下:
java
java
// AnimatedDrawable.java
@Override
public void start() {
if (isRunning()) {
return;
}
// 标记动画正在运行
mIsRunning = true;
// 重置帧索引
mCurrentFrameIndex = 0;
// 启动动画循环
scheduleNextFrame();
}
private void scheduleNextFrame() {
if (!mIsRunning) {
return;
}
// 获取当前帧的持续时间
int frameDuration = mAnimatedImage.getFrameDuration(mCurrentFrameIndex);
// 延迟指定时间后绘制下一帧
mHandler.postDelayed(mFrameUpdateRunnable, frameDuration);
}
private final Runnable mFrameUpdateRunnable = new Runnable() {
@Override
public void run() {
if (!mIsRunning) {
return;
}
// 绘制当前帧
invalidateSelf();
// 更新帧索引
mCurrentFrameIndex = (mCurrentFrameIndex + 1) % mAnimatedImage.getFrameCount();
// 调度下一帧
scheduleNextFrame();
}
};
在 start
方法中,标记动画正在运行,重置帧索引,并调用 scheduleNextFrame
方法启动动画循环。scheduleNextFrame
方法根据当前帧的持续时间,使用 Handler
延迟指定时间后调用 mFrameUpdateRunnable
绘制下一帧。mFrameUpdateRunnable
方法负责绘制当前帧,更新帧索引,并继续调度下一帧,实现动画的循环播放。
七、动态图缓存管理源码分析
7.1 内存缓存
Fresco 使用 BitmapMemoryCache
来管理动态图的内存缓存。相关源码如下:
java
java
// BitmapMemoryCache.java
@Override
public DataSource<CloseableReference<CloseableImage>> get(
ImageRequest imageRequest,
Object callerContext) {
// 根据图片请求生成缓存键
CacheKey cacheKey = mCacheKeyFactory.getBitmapCacheKey(imageRequest, callerContext);
// 从内存缓存中获取缓存项
CloseableReference<CloseableImage> cachedReference = mMemoryCache.get(cacheKey);
if (cachedReference != null) {
// 如果缓存中存在数据,创建一个数据源并返回
return DataSources.immediateDataSource(cachedReference);
}
return null;
}
@Override
public void put(
ImageRequest imageRequest,
CloseableReference<CloseableImage> image,
Object callerContext) {
// 根据图片请求生成缓存键
CacheKey cacheKey = mCacheKeyFactory.getBitmapCacheKey(imageRequest, callerContext);
// 将数据存入内存缓存
mMemoryCache.put(cacheKey, image);
}
在 get
方法中,根据图片请求生成缓存键,从内存缓存中获取缓存项。如果缓存中存在数据,创建一个数据源并返回。在 put
方法中,同样根据图片请求生成缓存键,将数据存入内存缓存。
7.2 磁盘缓存
Fresco 使用 DiskCache
来管理动态图的磁盘缓存。相关源码如下:
java
java
// DiskCache.java
@Override
public DataSource<EncodedImage> fetchFromDiskCache(ImageRequest imageRequest) {
// 根据图片请求生成缓存键
CacheKey cacheKey = mCacheKeyFactory.getEncodedCacheKey(imageRequest);
// 从磁盘缓存中获取数据
return mDiskStorageCache.get(cacheKey);
}
@Override
public DataSource<Void> writeToDiskCache(
EncodedImage encodedImage,
ImageRequest imageRequest) {
// 根据图片请求生成缓存键
CacheKey cacheKey = mCacheKeyFactory.getEncodedCacheKey(imageRequest);
// 将数据写入磁盘缓存
return mDiskStorageCache.put(cacheKey, encodedImage);
}
在 fetchFromDiskCache
方法中,根据图片请求生成缓存键,从磁盘缓存中获取数据。在 writeToDiskCache
方法中,同样根据图片请求生成缓存键,将数据写入磁盘缓存。
八、动态图支持模块的性能优化
8.1 内存优化
- 帧缓存策略 :Fresco 在解码动态图时,会对帧进行缓存,避免重复解码。例如,在
GifImage
类中,使用GifDecoder
来管理帧的缓存。
java
java
// GifImage.java
@Override
public CloseableReference<Bitmap> getFrameBitmap(int frameIndex) {
// 获取指定帧的 Bitmap
return mGifDecoder.getFrameBitmap(frameIndex);
}
- 及时释放资源 :在动态图不再使用时,及时释放相关资源,避免内存泄漏。例如,在
AnimatedDrawable
类的stop
方法中,停止动画并释放资源。
java
java
// AnimatedDrawable.java
@Override
public void stop() {
if (!isRunning()) {
return;
}
// 标记动画停止运行
mIsRunning = false;
// 移除所有待处理的消息
mHandler.removeCallbacks(mFrameUpdateRunnable);
// 释放资源
if (mAnimatedImage != null) {
mAnimatedImage.close();
}
}
8.2 性能优化
- 异步加载与解码 :Fresco 使用异步线程进行动态图的加载和解码,避免阻塞主线程。例如,在
ImagePipeline
中,网络请求和数据解码都是在异步线程中进行的。
java
java
// ImagePipeline.java
private <INFO> DataSource<CloseableReference<INFO>> fetchImageFromNetwork(
ImageRequest imageRequest,
Object callerContext) {
// 创建一个网络请求数据源
DataSource<EncodedImage> networkDataSource = mNetworkFetcher.fetch(imageRequest, callerContext);
// 创建一个解码数据源,用于对下载的数据进行解码
DataSource<CloseableReference<CloseableImage>> decodeDataSource =
mImageDecoder.decodeFromEncodedImage(networkDataSource, imageRequest, callerContext);
return (DataSource<CloseableReference<INFO>>) decodeDataSource;
}
- 优化解码算法:Fresco 采用了高效的解码算法,提高解码速度和性能。例如,在 GIF 解码和 WebP 动画解码中,使用了专门的解码器进行优化。
九、动态图支持模块的异常处理
9.1 网络异常处理
在动态图加载过程中,可能会出现网络异常,如网络连接失败、请求超时等。Fresco 会捕获这些异常并进行相应的处理。相关源码如下:
java
java
// ImagePipeline.java
private <INFO> DataSource<CloseableReference<INFO>> fetchImageFromNetwork(
ImageRequest imageRequest,
Object callerContext) {
try {
// 创建一个网络请求数据源
DataSource<EncodedImage> networkDataSource = mNetworkFetcher.fetch(imageRequest, callerContext);
// 创建一个解码数据源,用于对下载的数据进行解码
DataSource<CloseableReference<CloseableImage>> decodeDataSource =
mImageDecoder.decodeFromEncodedImage(networkDataSource, imageRequest, callerContext);
return (DataSource<CloseableReference<INFO>>) decodeDataSource;
} catch (Exception e) {
// 处理网络异常
return DataSources.immediateFailedDataSource(e);
}
}
在 fetchImageFromNetwork
方法中,使用 try-catch
块捕获网络请求过程中可能出现的异常。如果出现异常,返回一个失败的数据源。
9.2 解码异常处理
在动态图解码过程中,可能会出现解码异常,如图片格式不支持、数据损坏等。Fresco 会捕获这些异常并进行相应的处理。相关源码如下:
java
java
// ImageDecoder.java
@Override
public DataSource<CloseableReference<CloseableImage>> decodeFromEncodedImage(
DataSource<EncodedImage> encodedImageDataSource,
ImageRequest imageRequest,
Object callerContext) {
try {
// 获取图片的解码配置
DecodeConfig decodeConfig = getDecodeConfig(imageRequest);
// 根据解码配置选择合适的解码器
ImageDecoder decoder = getDecoder(decodeConfig);
// 创建一个解码数据源,使用选择的解码器进行解码
return decoder.decode(encodedImageDataSource, imageRequest, callerContext);
} catch (Exception e) {
// 处理解码异常
return DataSources.immediateFailedDataSource(e);
}
}
在 decodeFromEncodedImage
方法中,使用 try-catch
块捕获解码过程中可能出现的异常。如果出现异常,返回一个失败的数据源。
十、动态图支持模块的扩展与定制
10.1 自定义解码器
开发者可以通过实现 ImageDecoder
接口来创建自定义的解码器。例如,创建一个自定义的 GIF 解码器:
java
java
// CustomGifDecoder.java
public class CustomGifDecoder implements ImageDecoder {
@Override
public DataSource<CloseableReference<CloseableImage>> decode(
DataSource<EncodedImage> encodedImageDataSource,
ImageRequest imageRequest,
Object callerContext) {
// 实现自定义的 GIF 解码逻辑
try {
// 获取 EncodedImage 对象
CloseableReference<EncodedImage> encodedImageRef = encodedImageDataSource.getResult();
if (encodedImageRef != null) {
try {
EncodedImage encodedImage = encodedImageRef.get();
// 进行自定义的解码操作
CloseableReference<CloseableImage> decodedImageRef = customDecode(encodedImage);
if (decodedImageRef != null) {
return DataSources.immediateDataSource(decodedImageRef);
}
} finally {
CloseableReference.closeSafely(encodedImageRef);
}
}
} catch (Exception e) {
return DataSources.immediateFailedDataSource(e);
}
return DataSources.immediateFailedDataSource(new RuntimeException("自定义 GIF 解码失败"));
}
private CloseableReference<CloseableImage> customDecode(EncodedImage encodedImage) {
// 实现自定义的解码逻辑
// 这里可以使用自定义的算法或库进行解码
return null;
}
}
在 CustomGifDecoder
类中,实现了 ImageDecoder
接口的 decode
方法。在该方法中,获取 EncodedImage
对象,调用 customDecode
方法进行自定义的解码操作。如果解码成功,返回一个包含解码后图片的数据源;否则返回一个失败的数据源。
10.2 自定义缓存策略
开发者可以通过实现 MemoryCache
和 DiskCache
接口来创建自定义的缓存策略。例如,创建一个自定义的内存缓存:
java
java
// CustomMemoryCache.java
public class CustomMemoryCache implements MemoryCache<CacheKey, CloseableImage> {
private final LruCache<CacheKey, CloseableReference<CloseableImage>> mCache;
public CustomMemoryCache(int maxSize) {
mCache = new LruCache<CacheKey, CloseableReference<CloseableImage>>(maxSize) {
@Override
protected int sizeOf(CacheKey key, CloseableReference<CloseableImage> value) {
// 计算缓存项的大小
return value.get().getSizeInBytes();
}
@Override
protected void entryRemoved(
boolean evicted,
CacheKey key,
CloseableReference<CloseableImage> oldValue,
CloseableReference<CloseableImage> newValue) {
// 缓存项被移除时,释放资源
CloseableReference.closeSafely(oldValue);
}
分享