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

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

一、引言

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

二、Fresco 框架基础概述

2.1 Fresco 简介

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

2.2 核心组件

Fresco 的核心组件包括 ImagePipelineDrawee 等。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 的动态图支持模块主要分为以下几个层次:

  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

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 自定义缓存策略

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

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);
            }

分享

相关推荐
保护我方三四5 分钟前
ART | GC Configuration
android
weixin_391336901 小时前
gdb 调试mysql
android·mysql·adb
ufo00l2 小时前
Android buildToolsVersion 会影响什么
android
技术蔡蔡2 小时前
Android项目如何添加Flutter Module
android·flutter
dora2 小时前
Android数据缓存框架 - 内存缓存与数据的发布订阅
android·开源·github
_一条咸鱼_2 小时前
Android Compose 框架的列表与集合模块之懒加载列表深入剖析(四十五)
android
十六ᵛᵃᵉ4 小时前
day3_Flink基础
android·java·flink
C_V_Better5 小时前
!!!谷歌停止开源安卓
android·开源
前行的小黑炭7 小时前
Kotlin的委托是什么?在看源码的时候不知道他的作用是什么,为什么使用,那么你看看这篇文章。
android·kotlin
前行的小黑炭7 小时前
Kotlin的扩展函数:给任何类添加你想要的功能,即使是自带类,第三方类。
android·kotlin