了解 Glide 的内存缓存与加载 gif 动态图片,可以从 DiskCacheStrategy
作为突破口,DiskCacheStrategy
的结果就是将大小变换后的结果保存为图像。
在这个过程中,GIF 有以下过程。
下载 GIF -> 提取帧 -> 优化帧大小 -> 保存帧
磁盘缓存策略(Disk Cache Strategy)
DiskCacheStrategy
方法应用到每一个单独的请求。 目前支持的策略允许你阻止加载过程使用或写入磁盘缓存,选择性地仅缓存无修改的原生数据,或仅缓存变换过的缩略图,或是兼而有之。
默认的策略叫做 AUTOMATIC
,它会尝试对本地和远程图片使用最佳的策略。当你加载远程数据(比如,从URL下载)时,AUTOMATIC
策略仅会存储未被你的加载过程修改过(比如:变换,裁剪)的原始数据,因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC
策略则会仅存储变换过的缩略图,因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。
为此,GIF 将 DiskCacheStrategy 指定为 RESOURCE,并引导它在从原始 GIF 文件中提取帧后立即使用。对于 GIF,应该使用 RESOURCE。 对于其他格式,如果你重复查看图像或检索(本地或远程)大图像并在小视图中显示它们,就可以指定为其他的 DiskCacheStrategy 方式,如果你倾向于查看一次图像,或者只应用一个小的转换,会倾向于 RESOURCE。
默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:
- 活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片?
- 内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中?
- 资源类型(Resource) - 该图片是否之前曾被解码、转换并写入过磁盘缓存?
- 数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存?
前两步检查图片是否在内存中,如果是则直接返回图片。后两步则检查图片是否在磁盘上,以便快速但异步地返回图片。
如果四个步骤都未能找到图片,则Glide会返回到原始资源以取回数据(原始文件,Uri, Url等)。
Glide 的 GifDecoder 总是按设计将完整的 GIF 数据加载到内存中。它一次动态地解码每一个,但实际的 GIF 数据总是加载到内存中。
//Shared interface for GIF decoders.
public interface GifDecoder {
/** File read status: No errors. */
int STATUS_OK = 0;
/** File read status: Error decoding file (may be partially decoded). */
int STATUS_FORMAT_ERROR = 1;
/** File read status: Unable to open source. */
int STATUS_OPEN_ERROR = 2;
/** Unable to fully decode the current frame. */
int STATUS_PARTIAL_DECODE = 3;
/** The total iteration count which means repeat forever. */
int TOTAL_ITERATION_COUNT_FOREVER = 0;
/** Android Lint annotation for status codes that can be used with a GIF decoder. */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {STATUS_OK, STATUS_FORMAT_ERROR, STATUS_OPEN_ERROR, STATUS_PARTIAL_DECODE})
@interface GifDecodeStatus {
}
/**
* An interface that can be used to provide reused {@link android.graphics.Bitmap}s to avoid GCs
* from constantly allocating {@link android.graphics.Bitmap}s for every frame.
*/
interface BitmapProvider {
/**
* Returns an {@link Bitmap} with exactly the given dimensions and config.
*
* @param width The width in pixels of the desired {@link android.graphics.Bitmap}.
* @param height The height in pixels of the desired {@link android.graphics.Bitmap}.
* @param config The {@link android.graphics.Bitmap.Config} of the desired {@link
* android.graphics.Bitmap}.
*/
@NonNull
Bitmap obtain(int width, int height, @NonNull Bitmap.Config config);
/**
* Releases the given Bitmap back to the pool.
*/
void release(@NonNull Bitmap bitmap);
/**
* Returns a byte array used for decoding and generating the frame bitmap.
*
* @param size the size of the byte array to obtain
*/
@NonNull
byte[] obtainByteArray(int size);
/**
* Releases the given byte array back to the pool.
*/
void release(@NonNull byte[] bytes);
/**
* Returns an int array used for decoding/generating the frame bitmaps.
*/
@NonNull
int[] obtainIntArray(int size);
/**
* Release the given array back to the pool.
*/
void release(@NonNull int[] array);
}
int getWidth();
int getHeight();
@NonNull
ByteBuffer getData();
/**
* Returns the current status of the decoder.
*
* <p> Status will update per frame to allow the caller to tell whether or not the current frame
* was decoded successfully and/or completely. Format and open failures persist across frames.
* </p>
*/
@GifDecodeStatus
int getStatus();
/**
* Move the animation frame counter forward.
*/
void advance();
/**
* Gets display duration for specified frame.
*
* @param n int index of frame.
* @return delay in milliseconds.
*/
int getDelay(int n);
/**
* Gets display duration for the upcoming frame in ms.
*/
int getNextDelay();
/**
* Gets the number of frames read from file.
*
* @return frame count.
*/
int getFrameCount();
/**
* Gets the current index of the animation frame, or -1 if animation hasn't not yet started.
*
* @return frame index.
*/
int getCurrentFrameIndex();
/**
* Resets the frame pointer to before the 0th frame, as if we'd never used this decoder to
* decode any frames.
*/
void resetFrameIndex();
/**
* Gets the "Netscape" loop count, if any. A count of 0 means repeat indefinitely.
*
* @deprecated Use {@link #getNetscapeLoopCount()} instead.
* This method cannot distinguish whether the loop count is 1 or doesn't exist.
* @return loop count if one was specified, else 1.
*/
@Deprecated
int getLoopCount();
/**
* Gets the "Netscape" loop count, if any.
* A count of 0 ({@link GifHeader#NETSCAPE_LOOP_COUNT_FOREVER}) means repeat indefinitely.
* It must not be a negative value.
* <br>
* Use {@link #getTotalIterationCount()}
* to know how many times the animation sequence should be displayed.
*
* @return loop count if one was specified,
* else -1 ({@link GifHeader#NETSCAPE_LOOP_COUNT_DOES_NOT_EXIST}).
*/
int getNetscapeLoopCount();
/**
* Gets the total count
* which represents how many times the animation sequence should be displayed.
* A count of 0 ({@link #TOTAL_ITERATION_COUNT_FOREVER}) means repeat indefinitely.
* It must not be a negative value.
* <p>
* The total count is calculated as follows by using {@link #getNetscapeLoopCount()}.
* This behavior is the same as most web browsers.
* <table border='1'>
* <tr class='tableSubHeadingColor'><th>{@code getNetscapeLoopCount()}</th>
* <th>The total count</th></tr>
* <tr><td>{@link GifHeader#NETSCAPE_LOOP_COUNT_FOREVER}</td>
* <td>{@link #TOTAL_ITERATION_COUNT_FOREVER}</td></tr>
* <tr><td>{@link GifHeader#NETSCAPE_LOOP_COUNT_DOES_NOT_EXIST}</td>
* <td>{@code 1}</td></tr>
* <tr><td>{@code n (n > 0)}</td>
* <td>{@code n + 1}</td></tr>
* </table>
* </p>
*
* @see <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=592735#c5">Discussion about
* the iteration count of animated GIFs (Chromium Issue 592735)</a>
*
* @return total iteration count calculated from "Netscape" loop count.
*/
int getTotalIterationCount();
/**
* Returns an estimated byte size for this decoder based on the data provided to {@link
* #setData(GifHeader, byte[])}, as well as internal buffers.
*/
int getByteSize();
/**
* Get the next frame in the animation sequence.
*
* @return Bitmap representation of frame.
*/
@Nullable
Bitmap getNextFrame();
/**
* Reads GIF image from stream.
*
* @param is containing GIF file.
* @return read status code (0 = no errors).
*/
@GifDecodeStatus
int read(@Nullable InputStream is, int contentLength);
void clear();
void setData(@NonNull GifHeader header, @NonNull byte[] data);
void setData(@NonNull GifHeader header, @NonNull ByteBuffer buffer);
void setData(@NonNull GifHeader header, @NonNull ByteBuffer buffer, int sampleSize);
/**
* Reads GIF image from byte array.
*
* @param data containing GIF file.
* @return read status code (0 = no errors).
*/
@GifDecodeStatus
int read(@Nullable byte[] data);
/**
* Sets the default {@link android.graphics.Bitmap.Config} to use when decoding frames of a GIF.
*
* <p>Valid options are {@link android.graphics.Bitmap.Config#ARGB_8888} and
* {@link android.graphics.Bitmap.Config#RGB_565}.
* {@link android.graphics.Bitmap.Config#ARGB_8888} will produce higher quality frames, but will
* also use 2x the memory of {@link android.graphics.Bitmap.Config#RGB_565}.
*
* <p>Defaults to {@link android.graphics.Bitmap.Config#ARGB_8888}
*
* <p>This value is not a guarantee. For example if set to
* {@link android.graphics.Bitmap.Config#RGB_565} and the GIF contains transparent pixels,
* {@link android.graphics.Bitmap.Config#ARGB_8888} will be used anyway to support the
* transparency.
*/
void setDefaultBitmapConfig(@NonNull Bitmap.Config format);
}

//Set of available caching strategies for media.
public abstract class DiskCacheStrategy {
...省略
//Writes resources to disk after they've been decoded.
public static final DiskCacheStrategy RESOURCE =
new DiskCacheStrategy() {
@Override
public boolean isDataCacheable(DataSource dataSource) {
return false;
}
@Override
public boolean isResourceCacheable(
boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
return dataSource != DataSource.RESOURCE_DISK_CACHE
&& dataSource != DataSource.MEMORY_CACHE;
}
@Override
public boolean decodeCachedResource() {
return true;
}
@Override
public boolean decodeCachedData() {
return false;
}
};
}
如果加载一个很大的 gif 文件,可以先 zip 该文件。GifHeaderParser.parseHeader() 就是 gif 的解析器.
/**
* A class responsible for creating {@link com.bumptech.glide.gifdecoder.GifHeader}s from data
* representing animated GIFs.
*
* @see <a href="https://www.w3.org/Graphics/GIF/spec-gif89a.txt">GIF 89a Specification</a>
*/
public class GifHeaderParser {
...省略
public GifHeader parseHeader() {
if (rawData == null) {
throw new IllegalStateException("You must call setData() before parseHeader()");
}
if (err()) {
return header;
}
readHeader();
if (!err()) {
readContents();
if (header.frameCount < 0) {
header.status = STATUS_FORMAT_ERROR;
}
}
return header;
}
}
//An animated Drawable that plays the frames of an animated GIF.
class GifDrawable extends Drawable implements GifFrameLoader.FrameCallback, Animatable
, Animatable2Compat
缓存的刷新
因为磁盘缓存使用的是哈希键,所以并没有一个比较好的方式来简单地删除某个特定url或文件路径对应的所有缓存文件。如果你只允许加载或缓存原始图片的话,问题可能会变得更简单,但因为Glide还会缓存缩略图和提供多种变换(transformation),它们中的任何一个都会导致在缓存中创建一个新的文件,而要跟踪和删除一个图片的所有版本无疑是困难的。
在实践中,使缓存文件无效的最佳方式是在内容发生变化时(url,uri,文件路径等)更改你的标识符。
网友评论