多级缓存支持(最小化存储开销和解码次数)
默认情况下,Glide 会在请求图片之前检查以下多级缓存
- 活动资源 - 是否有另一个 View 正在展示这张图片
- 内存缓存 - 是否最近被加载过并仍存在于内存中
- 磁盘缓存 - 是否曾被解码、转换并写入过磁盘
- 文件缓存 - 资源是否曾被写入过文件缓存
在 Glide v4 里,所有缓存键都包含至少两个元素
- 请求加载的 Model,如果你使用自定义的 Model,它需要正确地实现 hashCode 和 equals
- 可选的签名(Signature),用来刷新缓存
另外,步骤1-3(活动资源,内存缓存,磁盘缓存)的缓存键还包含一些其他元素
- 宽度和高度
- 可选的变换(Transformation)
- 额外添加的任何选项(Options)
// Glide 提供一系列的 Options 选项,用于控制 Glide 缓存策略
Glide.with(fragment)
.load(url)
.onlyRetrieveFromCache(true) // 仅从缓存加载图片,省流量模式
.skipMemoryCache(true) // 跳过内存缓存
.diskCacheStrategy(DiskCacheStrategy.NONE) // 跳过磁盘缓存
.into(imageView);
资源重用(最小化昂贵的垃圾回收和堆碎片影响)
为决定某个资源是否正在被使用,以及什么时候可以安全地被重用,Glide 为每个资源保持了一个引用计数
- 每次调用 into 来加载一个资源,这个资源的引用计数会被加一。如果相同的资源被加载到两个不同的 Target,则在两个加载都完成后,它的引用计数将会为二
- 引用计数仅在调用者通过以下方式表示它们用完资源后会减少
- 在加载资源的 View 或 Target 上调用 clear()
- 在这个View 或 Target 上调用对另一个资源请求的 into 方法
- 当引用计数到达 0 时,这个资源会被释放并被返回给 Glide 以重用。当资源被返回给 Glide 以重用以后,继续使用它是不安全的,因此以下行为是不安全的
- 使用 getImageDrawable 来取回 ImageView 中加载的 Bitmap 或 Drawable,并使用某种方式展示它( setImageDrawable,动画,或 TransitionDrawable 或其他任何方式 )
- 使用 SimpleTarget 来将一个资源加载到 View,但没有实现 onLoadCleared 方法并在其中将资源从 View 中移除
- 对 Glide 加载的任何 Bitmap 调用 recycle 方法
支持缓存大小配置,包括内存缓存、Bitmap 池、磁盘缓存
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context)
.setMemoryCacheScreens(2)
.build();
builder.setMemoryCache(new LruResourceCache(calculator.getMemoryCacheSize()));
}
}
// 也可以直接覆写缓存大小:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
int memoryCacheSizeBytes = 1024 * 1024 * 20; // 20mb
builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
}
}
// 甚至可以提供自己的 MemoryCache 实现:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setMemoryCache(new YourAppMemoryCacheImpl());
}
}
// setBitmapPool 设置 Bitmap 池大小
// setDiskCache 设置磁盘缓存大小
支持自定义组件(Component)
- ModelLoader : 用于加载自定义的 Model,并返回 Data(InputStreams, FileDescriptors)
- Encoder:用于向 Glide 磁盘缓存写入 Data(InputStreams, FileDesciptors)
- ResourceDecoder : 用于对新的 Data 类型(InputStreams, FileDescriptors)进行解码
- ResourceEncoder : 用于向 Glide 磁盘缓存写入 Resources(BitmapResource, DrawableResource)
- ResourceTranscoder : 用于在不同的资源类型之间做转换,例如从 BitmapResource 转换为 DrawableResource
Glide 默认基于 HttpUrlConnection 网络栈,通过工具库或自定义 ModelLoader 可以实现 Model 到 Data 的定制
@GlideModule
public final class OkHttpLibraryGlideModule extends LibraryGlideModule {
@Override
public void registerComponents(
@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
}
Glide 对应支持组件排序,包括 prepend、append、replace
GlideExtension 注解用于标识一个扩展 Glide API 的类。任何扩展 Glide API 的类都必须使用这个注解来标记,否则其中被注解的方法就会被忽略。被 GlideExtention 注解的类有两种扩展方式
- GlideOption - 为 RequestOptions 添加一个自定义的选项,用于定义一个频繁使用的选项集合
- GlideType - 添加对新的资源类型的支持,被 GlideType 注解的静态方法用于扩展 RequestManager,被 GlideType 注解的方法允许你添加对新的资源类型的支持
多格式支持,包括图片、GIF 图
深度的生命周期集成
Glide 会确保优先处理活跃的 Activity 和 Fragment 请求,并有利于应用在必要时释放资源以避免在后台时被杀掉
class RequestManager implements ComponentCallbacks2, LifecycleListener {
@Override
public synchronized void onStart() {
resumeRequests();
targetTracker.onStart();
}
@Override
public synchronized void onStop() {
pauseRequests();
targetTracker.onStop();
}
@Override
public synchronized void onDestroy() {
targetTracker.onDestroy();
for (Target<?> target : targetTracker.getAll()) {
clear(target);
}
targetTracker.clear();
requestTracker.clearRequests();
lifecycle.removeListener(this);
lifecycle.removeListener(connectivityMonitor);
mainHandler.removeCallbacks(addSelfToLifecycle);
glide.unregisterRequestManager(this);
}
}
内存状态监控
通过实现 ComponentCallbacks2.onTrimMemory 回调,及时释放内存缓存
class RequestManager implements ComponentCallbacks2, LifecycleListener {
@Override
public void onTrimMemory(int level) {
if (level == TRIM_MEMORY_MODERATE && pauseAllRequestsOnTrimMemoryModerate) {
pauseAllRequestsRecursive();
}
}
}
网络状态监听,自动失败重试
class RequestManager {
private class RequestManagerConnectivityListener
implements ConnectivityMonitor.ConnectivityListener {
@Override
public void onConnectivityChanged(boolean isConnected) {
if (isConnected) {
synchronized (RequestManager.this) {
requestTracker.restartRequests();
}
}
}
}
}
占位符功能
Glide 支持 placeholder、error、fallback 选项,并对应提供了过渡效果(TransitionOptions)
Transition 允许你定义 Glide 如何从占位符到新加载的图片,或从缩略图到全尺寸图像过渡
在 Glide 中,图像可能从四个地方中的任何一个位置加载出来:
- Glide 的内存缓存
- Glide 的磁盘缓存
- 设备本地可用的一个源文件或 Uri
- 仅远程可用的一个源 Url 或 Uri
如果图像从 Glide 的内存缓存中加载出来,Glide 的内置过渡将不会执行。然而,在另外三种场景下,Glide 的内置过渡都会被执行
Android中的动画代价是比较大的,尤其是同时开始大量动画的时候。 交叉淡入和其他涉及 alpha 变化的动画显得尤其昂贵。 此外,动画通常比图片解码本身还要耗时。在列表和网格中滥用动画可能会让图像的加载显得缓慢而卡顿。为了提升性能,请在使用 Glide 向 RecyclerView 加载图片时考虑避免使用动画,尤其是大多数情况下,你希望图片被尽快缓存和加载的时候。作为替代方案,请考虑预加载,这样当用户滑动到具体的 item 的时候,图片已经在内存中了
占位符是异步加载的吗?
No。占位符是在主线程从Android Resources加载的。我们通常希望占位符比较小且容易被系统资源缓存机制缓存起来
变换是否会被应用到占位符上?
No。Transformation仅被应用于被请求的资源,而不会对任何占位符使用
支持自定义 Transform 转换
系统提供了一些默认的实现,包括CenterCrop、FitCenter、CircleCrop、RoundedCorners、Rotate 等
如果你只需要变换 Bitmap,最好是从继承 BitmapTransformation 开始。BitmapTransformation 为我们处理了一些基础的东西,如果你的变换返回了一个新修改的 Bitmap ,BitmapTransformation 将负责提取和回收原始的 Bitmap
public class FillSpace extends BitmapTransformation {
private static final String ID = "com.bumptech.glide.transformations.FillSpace";
private static final String ID_BYTES = ID.getBytes(STRING_CHARSET_NAME);
@Override
public Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
if (toTransform.getWidth() == outWidth && toTransform.getHeight() == outHeight) {
return toTransform;
}
return Bitmap.createScaledBitmap(toTransform, outWidth, outHeight, /*filter=*/ true);
}
@Override
public void equals(Object o) {
return o instanceof FillSpace;
}
@Override
public int hashCode() {
return ID.hashCode();
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest)
throws UnsupportedEncodingException {
messageDigest.update(ID_BYTES);
}
}
对于任何 Transformation 子类,包括 BitmapTransformation,有三个方法是必须实现的,以使得磁盘和内存缓存正确地工作:equals、hashCode、updateDiskCacheKey,如果你的 Transformation 没有参数,通常使用一个包含完整包限定名的 static final String 来作为一个 ID,它可以构成 hashCode 的基础,并可用于更新 updateDiskCacheKey 传入的 MessageDigest。如果你的 Transformation 需要参数而且它会影响到 Bitmap 被变换的方式,它们也必须被包含到这三个方法中
例如,Glide 的 RoundedCorners 变换接受一个 int,它决定了圆角的弧度。它的 equals、hashCode、updateDiskCacheKey 实现看起来像这样:
@Override
public boolean equals(Object o) {
if (o instanceof RoundedCorners) {
RoundedCorners other = (RoundedCorners) o;
return roundingRadius == other.roundingRadius;
}
return false;
}
@Override
public int hashCode() {
return Util.hashCode(ID.hashCode(),
Util.hashCode(roundingRadius));
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
messageDigest.update(ID_BYTES);
byte[] radiusData = ByteBuffer.allocate(4).putInt(roundingRadius).array();
messageDigest.update(radiusData);
}
支持自定义 Target
需要实现对应的生命周期回调,Glide 默认支持直接传入 ImageView,底层通过 setTag 和 getTag 来操作 Request
// 你可以注意到 into(Target) 和 into(ImageView) 都返回了一个 Target 实例。如果你重用
// 这个 Target 来在将来开始一个新的加载,则之前开始的任何请求都会被取消,它们使用的资源将被释放:
Target<Drawable> target =
Glide.with(fragment)
.load(url)
.into(new Target<Drawable>() {
...
});
...
// Some time in the future:
Glide.with(fragment)
.load(newUrl)
.into(target);
// 你也可以使用返回的 Target 来 clear 之前的加载,这将释放掉任何相关资源:
Target<Drawable> target =
Glide.with(fragment)
.load(url)
.into(new Target<Drawable>() {
...
});
...
// Some time in the future:
Glide.with(fragment).clear(target);
// Glide 的 ViewTarget 子类使用了 Android Framework 的 getTag() 和 setTag() 方法来
// 存储每个请求的相关信息,因此如果你在使用 ViewTarget 或在往 ImageView 中加载图片,你可以
// 直接重用或清理这个 View:
Glide.with(fragment)
.load(url)
.into(imageView);
// Some time in the future:
Glide.with(fragment).clear(imageView);
// Or:
Glide.with(fragment)
.load(newUrl)
.into(imageView);
// 此外,仅对ViewTarget而言,你可以在每次加载或清理调用时都传入一个新的实例,而
// Glide 仍然可以从 View 的 tag 中取回之前一次加载的信息:
Glide.with(fragment)
.load(url)
.into(new DrawableImageViewTarget(imageView));
// Some time in the future:
Glide.with(fragment)
.load(newUrl)
.into(new DrawableImageViewTarget(imageView));
// 注意,除非你的 Target 继承自 ViewTarget,或实现了 setRequest() 和 getRequest()并
// 允许你从新的 Target 实例中取回上一次加载的信息,否则这种使用方法将不奏效。
尺寸测量
ViewTarget 使用以下逻辑获取控件尺寸信息:
- 如果 View 的布局参数尺寸 > 0 且 > padding,则使用该布局参数
- 如果 View 尺寸 > 0 且 > padding,使用该实际尺寸
- 如果 View 布局参数为 wrap_content 且至少已发生一次 layout ,则打印一行警告日志,建议使用 Target.SIZE_ORIGINAL 或通过 override() 指定其他固定尺寸,并使用屏幕尺寸为该请求尺寸
- 其他情况下(布局参数为 match_parent, 0, 或 wrap_content 且没有发生过 layout ),则等待布局完成,然后回溯到步骤1
有时在使用 RecyclerView 时,View 可能被重用且保持了前一个位置的尺寸,但在当前位置会发生改变。为了处理这种场景,你可以创建一个新的 ViewTarget 并制定 waitForLayout = true:
@Override
public void onBindViewHolder(VH holder, int position) {
Glide.with(fragment)
.load(urls.get(position))
.into(new DrawableImageViewTarget(holder.imageView, /*waitForLayout=*/ true));
如果上诉策略无法获取真实的 View 大小,Glide 也通过 onPreDrawListener 提供了为 layout_weight,match_parent 和其他相对尺寸的完备鲁棒的支持。在任何情况下,如果 Glide 看起来获取了错误的 View 尺寸,你都可以手动覆盖来纠正它,你可以选择扩展 ViewTarget 实现你自己的逻辑,或者使用 RequestOption 里的 override() 方法
基本用法
/**
* 加载图片
*/
Glide.with(fragment)
.asBitmap()
// .asGif()
.transition(BitmapTransitionOptions.withCrossFade(20000)) // 占位符过渡策略
.load(myUrl)
.placeholder(placeholder)
.fitCenter()
.thumbnail(Glide.with(fragment).load(thumbnailUrl)) // 并行请求缩略图
.error(Glide.with(fragment).load(fallbackUrl)) // 主请求错误处理
.into(imageView);
/**
* 通过 RequestOptions 配置
*/
RequestOptions sharedOptions =
new RequestOptions()
.placeholder(placeholder)
.fitCenter();
Glide.with(fragment)
.load(myUrl)
.apply(sharedOptions)
.into(imageView);
/**
* 停止加载
* 在 Glide 框架中,我们不需要手动调用 clear 方法,
* 事实上,Glide 会在 Activity / Fragment 销毁的时候自动停止任务并回收资源
*/
Glide.with(fragment).clear(imageView);
对于 RecyclerView,Glide 用法和普通 View 一样,Glide 内部会处理资源复用逻辑
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
String url = urls.get(position);
Glide.with(fragment)
.load(url)
.into(holder.imageView);
}
Glide 唯一的要求是,对于任何可复用的 View 或 Target ,如果它们在之前的位置上用 Glide 进行过加载操作,那么我们可以执行一个新的 Glide 加载操作,或调用 clear 停止 Glide 的加载工作并自己设置 Drawable
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (isImagePosition(position)) {
String url = urls.get(position);
Glide.with(fragment)
.load(url)
.into(holder.imageView);
} else {
Glide.with(fragment).clear(holder.imageView);
holder.imageView.setImageDrawable(specialDrawable);
}
}
除了将 Bitmap 和 Drawable 加载到 View 中,我们也可以异步加载到自定义的 Target 中
Glide.with(context
.load(url)
.into(new CustomTarget<Drawable>() {
@Override
public void onResourceReady(Drawable resource, Transition<Drawable> transition) {
// Do something with the Drawable here.
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
// Remove the Drawable provided in onResourceReady from any Views and ensure
// no references to it remain.
}
});
直接在当前线程(一般是后台线程)加载图片
FutureTarget<Bitmap> futureTarget =
Glide.with(context)
.asBitmap()
.load(url)
.submit(width, height);
Bitmap bitmap = futureTarget.get(); // 阻塞方法
// Do something with the Bitmap and then when you're done with it:
Glide.with(context).clear(futureTarget);
解码格式
在 Glide v3, 默认的 DecodeFormat 是 DecodeFormat.PREFER_RGB_565,除非图片包含或可能包含透明像素。对于给定的图片尺寸,RGB_565 只使用 ARGB_8888 一半的内存,但对于特定的图片有明显的画质问题,包括条纹(banding)和着色(tinting)。为了避免 RGB_565 的画质问题,Glide 现在默认使用 ARGB_8888。结果是,图片质量变高了,但内存使用也增加了
要将 Glide v4 默认的 DecodeFormat 改回 DecodeFormat.PREFER_RGB_565,请在 AppGlideModule 中应用一个 RequestOption:
@GlideModule
public final class YourAppGlideModule extends GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDefaultRequestOptions(new RequestOptions().format(DecodeFormat.PREFER_RGB_565));
}
}
Glide 槽点
- 槽点一:Bitmap 引用数为 0 且内存缓存池满时,会主动 recycle 相应的 Bitmap
- 修复措施:我们一定要在 onLoadCleared 回调里释放在 onResourceReady 拿到的 Bitmap 的引用
- 建议:不要主动 recycle Bitmap,把 Bitmap 引用置空,剩余的交给 GC 去回收就好
- 槽点二:页面回收后,加载图片崩溃
- 修复措施:加载图片前,主动做下判断
- 建议:页面销毁后,加载图片不响应并且增加 warning 日志,没必要触发崩溃
-
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private static void assertNotDestroyed(@NonNull Activity activity) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) { throw new IllegalArgumentException( "You cannot start a load for a destroyed activity"); } }
- 槽点三:Glide 会默认把 Bitmap 放大,用以填充 Imageview,增加了内存占用,特别是长图,会导致内存极大增加
- 修复措施:加载小图到大的imageview,增加加载配置,避免图片被放大
- 建议:ImageView 如果比 Bitmap 大,默认不要放大 Bitmap,用原始 Bitmap 尺寸展示就好
-
// 配置方式1,增加override(Target.SIZE_ORIGINAL) Glide.with(this).override(Target.SIZE_ORIGINAL).load(imageUrl).into(target) // 配置方式2,采用其他DownsampleStrategy Glide.with(this).downsample(DownsampleStrategy.CENTER_INSIDE).load(imageUrl).into(target)
框架对比
CRITERION | | | |
Size | 121 Kb | 440 Kb | 500 kb |
Convenience of use | high | high | middle |
Web Download Speed | high | slightly lower due to lengthy caching processing | high |
Cache Download Speed | average | high | high |
Built-in Transformation Features | basic set of operations | basic set plus rounding | wide range of transformation capabilities, but with limitations |
Additional tools | Absent. | Loading a frame from video as a picture and GIF, using any type of model, a flexible API with the ability to connect any network stack. | Saving images not in Java Heap, but in ashmem heap, the ability to crop images around any point, resize JPEG using native resources, support for Progressive JPEG images. |
缓存策略
Picasso 提供两级缓存:LRU 内存缓存 以及 磁盘缓存。对应提供了 memoryPolicy 和 networkPolicy 两个方法
Picasso.get()
.load(imageUrl)
.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
.networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE)
.into(test_imageview)
Glide 提供四级缓存:
- 活动资源 - 是否有另一个 View 正在展示这张图片
- 内存缓存 - 是否最近被加载过并仍存在于内存中
- 磁盘缓存 - 是否曾被解码、转换并写入过磁盘
- 文件缓存 - 资源是否曾被写入过文件缓存
Glide.with(this)
.load(imageUrl)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.into(test_imageview)
和 Picasso 类似,Glide 也允许清除全部缓存或特定图片。但是 Glide 也可以通过 signature 方法来实现更精准控制
Fresco 提供三级缓存:
- 解码后的 Bitmap,用于 UI 展示或后续处理
- 内存中保存的原始压缩图片
- 磁盘中保存的原始压缩图片
val imagePipeline = Fresco.getImagePipeline()
// Check if image is in cache
val inMemoryCache = imagePipeline.isInBitmapMemoryCache(uri)
// Get cached image
imagePipeline.evictFromMemoryCache(uri)
imagePipeline.evictFromDiskCache(uri)
// Clear cache
imagePipeline.clearMemoryCaches()
imagePipeline.clearDiskCaches()
另外,Fresco 能够创建单独的缓存来存储小图像, 这对于提高应用程序性能很有用
图像变换
Picasso 提供了一系列内置的变换策略:resize、centerCrop、centerInside、rotate,你也可以通过继承 Transformation 来实现特定逻辑
class CircleTransformation : Transformation {
override fun transform(source: Bitmap): Bitmap {
val paint = Paint()
paint.setAntiAlias(true)
paint.setShader(BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP))
val output = Bitmap.createBitmap(source.width, source.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
canvas.drawCircle(source.width / 2, source.height / 2, source.width / 2, paint)
if (source != output) source.recycle()
return output
}
override fun key(): String {
return "circle"
}
}
Glide 的图像变换逻辑和 Picasso 类似,除了 Picasso 内置选项,Glide 还增加了其他选项:circleCrop、roundedCorners、granularRoundedCorners。Glide 也支持自定义,不过实现方式和 Picasso 不同,我们通过继承一系列 Transformation 来实现(DrawableTransformation、BitmapTransformation、GifDrawableTransformation、 MultiTransformation)
class GreyscaleTransformation : BitmapTransformation() {
override fun transform(pool: BitmapPool, source: Bitmap,
outWidth: Int, outHeight: Int): Bitmap {
val bitmap = Bitmap.createBitmap(source.width, source.height, Bitmap.Config.ARGB_8888)
val paint = Paint()
val greyMatrix = ColorMatrix()
greyMatrix.setSaturation(0.0f)
paint.colorFilter = ColorMatrixColorFilter(greyMatrix)
val canvas = Canvas(bitmap)
canvas.drawBitmap(source, 0f, 0f, paint)
return bitmap
}
override fun equals(other: Any?): Boolean {
return other is GreyscaleTransformation
}
override fun hashCode(): Int {
return Util.hashCode(ID.hashCode())
}
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(ID_BYTES)
}
companion object {
private val ID = "GreyscaleTransformation"
private val ID_BYTES = ID.toByteArray()
}
}
相比其他框架,Fresco 拥有最复杂的变换策略,大部分都可以在 Xml 中设置,不过在代码中设置就很困难,下面例子是设置圆角的功能,另外,一些标准 Transformation 会有一些限制,比如 resize 只能应用在 JPEG 格式
val roundingParams = RoundingParams.fromCornersRadius(7f)
fresco_imageview.setHierarchy(GenericDraweeHierarchyBuilder(resources)
.setRoundingParams(roundingParams)
.build())
另外,Fresco 主要关注于效率
- saving images not in Java Heap, but in ashmem heap, which saves memory for applications, reduces the risks of OutOfMemoryError and increases performance due to more rare calls of garbage collector
- cropping images around any point, not only in the center
- resizing JPEG using native resources, which also reduces the risks of OutOfMemoryError
- support for progressive JPEG images
Fresco 在 5.0 以前使用 inPurgeable 选项来解码 Bitmap,这样解码出来的 Bitmap 是在 Ashmem 内存中,GC 无法自动回收它。当该 Bitmap 在被使用时会被 pin 住,使用完之后就 unpin ,这样系统就可以在将来某一时间释放这部分内存。如果一个 unpinned 的 bitmap 在之后又要被使用,系统会运行时又将它重新 decode,但是这个 decode 操作是发生在 UI 线程中的有可能会造成掉帧现象,因此该做法已经被 Google 废弃掉,转为鼓励使用 inBitmap 来告知
为了让 inPurgeable 的 bitmap 不被自动 unpinned,可以通过使用 Jni 函数AndroidBitmap_lockPixels 来强制 pin 住 bitmap,这样我们就可以在 bitmap 被使用时不会被系统自动 unpinned,从而也就避免了 unpinned 的 bitmap 在重新被使用时又会被重新 decode 而引起的掉帧问题。这做后,Fresco 需要自己去管理这块内存区域,保证当这个 Bitmap 不再使用时,Ashmem 的内存空间能被 unpin,Fresco 选择在 Bitmap 离开屏幕可视范围时候(onDetachWindow等时候),通过调用 bitmap.recycle() 方法去做 unpin