欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > Android Fresco 框架动态图支持模块源码深度剖析(七)

Android Fresco 框架动态图支持模块源码深度剖析(七)

2025/4/17 9:13:32 来源:https://blog.csdn.net/qq_19641309/article/details/146996576  浏览:    关键词:Android Fresco 框架动态图支持模块源码深度剖析(七)

上一期 Android Fresco 框架兼容模块源码深度剖析(六)

本人掘金号,欢迎点击关注:https://juejin.cn/user/4406498335701950

一、引言

在 Android 开发中,高效处理和展示动态图(如 GIF、WebP 动画等)是一个常见需求。Fresco 作为 Facebook 开源的强大图片加载和显示库,其动态图支持模块为开发者提供了便捷且高性能的解决方案。本文将深入探讨 Fresco 框架的动态图支持模块,从源码层面进行细致分析,帮助开发者更好地理解和使用该模块。

二、Fresco 框架基础概述

2.1 Fresco 简介

Fresco 是一个功能丰富的 Android 图片加载库,具有内存管理优化、支持多种图片格式、强大的缓存机制等特点。它将图片加载和显示的各个环节进行了抽象和封装,使得开发者可以更轻松地处理图片相关的任务。

2.2 核心组件

Fresco 的核心组件包括 ImagePipelineDrawee 等。ImagePipeline 负责图片的加载、解码和缓存管理,而 Drawee 则负责图片的显示和交互。

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 的动态图支持模块主要分为以下几个层次:

  1. 接口层:定义了动态图加载、解码和显示的接口,为上层调用提供统一的抽象。
  2. 实现层:实现了接口层定义的接口,完成具体的动态图处理逻辑,如 GIF 解码、WebP 动画解码等。
  3. 缓存层:负责动态图数据的缓存管理,提高加载效率和减少网络请求。
  4. 显示层:将解码后的动态图帧显示在界面上,实现动画效果。

3.2 模块交互流程

当需要加载一张动态图时,整体的交互流程如下:

  1. 发起请求:开发者通过 SimpleDraweeView 或其他方式发起动态图加载请求,指定图片的 URI。
  2. 缓存检查ImagePipeline 首先检查内存缓存和磁盘缓存中是否存在该动态图数据。如果存在,则直接从缓存中获取;否则,发起网络请求下载数据。
  3. 数据下载:如果缓存中没有数据,ImagePipeline 会通过网络请求下载动态图数据。
  4. 解码处理:下载完成后,将数据传递给相应的解码器进行解码,如 GIF 解码器或 WebP 解码器。
  5. 帧处理:解码器将动态图数据解码为一系列帧,并提供帧的相关信息(如帧数量、帧间隔时间等)。
  6. 显示动画Drawee 组件根据帧信息,依次显示每一帧,实现动态图的动画效果。

四、动态图加载流程源码分析

4.1 请求发起

当调用 SimpleDraweeView.setImageURI(uri) 方法时,会触发动态图加载请求。具体源码如下:

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

// 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

// 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

// 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

// GifImage.java
public GifImage(GifDecoder gifDecoder,QualityInfo qualityInfo,int sampleSize) {super(qualityInfo, sampleSize);// 初始化 GifDecodermGifDecoder = 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) {// 获取指定帧的 Bitmapreturn mGifDecoder.getFrameBitmap(frameIndex);
}@Override
public int getFrameDuration(int frameIndex) {// 获取指定帧的持续时间return mGifDecoder.getFrameDuration(frameIndex);
}

GifImage 类封装了 GifDecoder 对象,提供了获取帧数量、宽度、高度、指定帧的 Bitmap 以及帧持续时间等方法。在构造函数中,初始化 GifDecoder 并获取相关信息。

5.3 WebP 动画解码

对于 WebP 动画,Fresco 使用 AnimatedWebPImage 类进行处理。相关源码如下:

java

// AnimatedWebPImage.java
public AnimatedWebPImage(AnimatedWebPDecoder animatedWebPDecoder,QualityInfo qualityInfo,int sampleSize) {super(qualityInfo, sampleSize);// 初始化 AnimatedWebPDecodermAnimatedWebPDecoder = 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) {// 获取指定帧的 Bitmapreturn mAnimatedWebPDecoder.getFrameBitmap(frameIndex);
}@Override
public int getFrameDuration(int frameIndex) {// 获取指定帧的持续时间return mAnimatedWebPDecoder.getFrameDuration(frameIndex);
}

AnimatedWebPImage 类与 GifImage 类类似,封装了 AnimatedWebPDecoder 对象,提供了获取帧信息的方法。

六、动态图显示流程源码分析

6.1 帧显示逻辑

Drawee 组件负责将解码后的动态图帧显示在界面上,实现动画效果。相关源码如下:

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) {// 如果是动画图片,获取动画 DrawablemAnimatedDrawable = (AnimatedDrawable) animatable;if (mDraweeHierarchy != null) {// 设置动画 Drawable 到 DraweeHierarchy 中mDraweeHierarchy.setTopLevelDrawable(mAnimatedDrawable);// 启动动画mAnimatedDrawable.start();}} else if (imageReference != null) {// 如果是静态图片,获取静态 DrawableDrawable drawable = mDrawableFactory.createDrawable(imageReference);if (mDraweeHierarchy != null) {// 设置静态 Drawable 到 DraweeHierarchy 中mDraweeHierarchy.setTopLevelDrawable(drawable);}}
}

setHierarchy 方法中,如果存在动画 Drawable,则将其设置到 DraweeHierarchy 中。在 onFinalImageSet 方法中,如果图片是动画图片,获取动画 Drawable 并设置到 DraweeHierarchy 中,然后启动动画;如果是静态图片,则获取静态 Drawable 并设置到 DraweeHierarchy 中。

6.2 动画循环与控制

Fresco 支持动画的循环播放和控制。相关源码如下:

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() {@Overridepublic void run() {if (!mIsRunning) {return;}// 绘制当前帧invalidateSelf();// 更新帧索引mCurrentFrameIndex = (mCurrentFrameIndex + 1) % mAnimatedImage.getFrameCount();// 调度下一帧scheduleNextFrame();}
};

start 方法中,标记动画正在运行,重置帧索引,并调用 scheduleNextFrame 方法启动动画循环。scheduleNextFrame 方法根据当前帧的持续时间,使用 Handler 延迟指定时间后调用 mFrameUpdateRunnable 绘制下一帧。mFrameUpdateRunnable 方法负责绘制当前帧,更新帧索引,并继续调度下一帧,实现动画的循环播放。

七、动态图缓存管理源码分析

7.1 内存缓存

Fresco 使用 BitmapMemoryCache 来管理动态图的内存缓存。相关源码如下:

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

// 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

// GifImage.java
@Override
public CloseableReference<Bitmap> getFrameBitmap(int frameIndex) {// 获取指定帧的 Bitmapreturn mGifDecoder.getFrameBitmap(frameIndex);
}
  • 及时释放资源:在动态图不再使用时,及时释放相关资源,避免内存泄漏。例如,在 AnimatedDrawable 类的 stop 方法中,停止动画并释放资源。

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

// 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

// 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

// 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

// CustomGifDecoder.java
public class CustomGifDecoder implements ImageDecoder {@Overridepublic 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 自定义缓存策略

开发者可以通过实现 MemoryCacheDiskCache 接口来创建自定义的缓存策略。例如,创建一个自定义的内存缓存:

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) {@Overrideprotected int sizeOf(CacheKey key, CloseableReference<CloseableImage> value) {// 计算缓存项的大小return value.get().getSizeInBytes();}@Overrideprotected void entryRemoved(boolean evicted,CacheKey key,CloseableReference<CloseableImage> oldValue,CloseableReference<CloseableImage> newValue) {// 缓存项被移除时,释放资源CloseableReference.closeSafely(oldValue);}

分享

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词