转载:
Android图片加载框架最全解析(一),Glide的基本用法
Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程
Android图片加载框架最全解析(三),深入探究Glide的缓存机制
Android图片加载框架最全解析(四),玩转Glide的回调与监听
Android图片加载框架最全解析(五),Glide强大的图片变换功能
Android图片加载框架最全解析(六),探究Glide的自定义模块功能
一、Glide的执行流程
1. Glide.with(args) 得到RequestManager
public class Glide implements ComponentCallbacks2 {
@NonNull
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
}
参数有两种情况,传入Application类型的参数,和传入非Application类型的参数。
-
Application 类型参数
Glide的生命周期就相当于绑定了整 个应用,只要应用不退出,任何时候都能够加载,
也可以理解为不对Glide生 命周期进行管理。 -
非Application 类型参数(Activity、Fragment、View)
会向当前的Activity当中添加一个隐藏的Fragment,用于根据页面生命周期对Glide进行管理。
Glide # with 执行过程:
假设已经初始化完毕
2. RequestManager.load(Object)返回 RequestBuilder
public class RequestManager implements LifecycleListener, ModelTypes<RequestBuilder<Drawable>> {
@Override
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
}
public class RequestBuilder<TranscodeType> implements ... {
@Override
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
}
load() 方法 会把要加载的资源信息传入 RequestBuilder 中,返回 RequestBuilder对象
3. RequestBuilder.into(target)
RequestBuilder.into(target)执行过程:
- 1)该target 上次请求与本次是否相同(宽高、配置),相同则复用上次请求对象,
否则从对象池(容量150)获取 Request 用于请求 - 2)校验要获取的图片宽高
- 3)分别从 活动缓存<HashMap,弱引用存储> / 内存缓存<LruCache> / 磁盘缓存<DiskLruCache> 去取
- 4)无缓存,则通过 线程池 网络请求下载图片
- 5)下载完成后,解码
- 6)把图片添加到 活动缓存、磁盘缓存 中,
然后使用 引用计数,当图片不再使用时,从活动缓存删除并添加到内存缓存
(内存缓存使用了 LRU算法 淘汰缓存,正在使用图片放到活动缓存中,不在内存缓存,不会被LRU算法淘汰) - 7)回调 onResourceReady()
磁盘缓存线程池,默认只有一条线程
网络请求线程池,cpu核心 >= 4 则 设置为 2条线程,cpu核心 < 4 则设置为 1条线程
public class RequestBuilder<TranscodeType> implements ... {
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
return into(target, /*targetListener=*/ null);
}
@NonNull
@Synthetic <Y extends Target<TranscodeType>> Y into(...) {
return into(target, targetListener, getMutableOptions());
}
private <Y extends Target<TranscodeType>> Y into(...) {
//必须在主线程调用,否则会泡异常
Util.assertMainThread();
...
options = options.autoClone();
//构建本次请求
Request request = buildRequest(target, targetListener, options);
//获取上次请求
Request previous = target.getRequest();
if (request.isEquivalentTo(previous) && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
//如果本次请求于上次相同,并且options配置没有设置跳过内存缓存
//回收本次请求
request.recycle();
//如果 previous 已经请求完毕,会返回false
if (!Preconditions.checkNotNull(previous).isRunning()) {
//使用 previous 去请求,如果有缓存会使用缓存
previous.begin();
}
return target;
}
//这里会回收 target 的上次请求对象
requestManager.clear(target);
//给 target 设置本次请求的 request
target.setRequest(request);
//把请求塞入队列,根据页面是否在前台,来处理请求
requestManager.track(target, request);
return target;
}
}
public class RequestManager implements LifecycleListener, ... {
//把请求塞入队列,根据页面是否在前台,来处理请求
void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
}
public class RequestTracker {
//请求队列
private final Set<Request> requests = Collections.newSetFromMap(new WeakHashMap<Request, Boolean>());
public void runRequest(@NonNull Request request) {
//把请求塞入队列,
requests.add(request);
if (!isPaused) {//如果页面未暂停,直接调用 begin()开始请求
request.begin();
} else {
//页面暂停,请求塞入 pendingRequests 队列
pendingRequests.add(request);
}
}
}
public final class SingleRequest<R> implements Request, ... {
...
//图片加载引擎
private Engine engine;
//Request 默认有一个对象池
private static final Pools.Pool<SingleRequest<?>> POOL = FactoryPools.simple(150,
new FactoryPools.Factory<SingleRequest<?>>() {
@Override
public SingleRequest<?> create() {
return new SingleRequest<Object>();
}
});
@Override
public void begin() {
...
//图片尺寸有效
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
//这里是真正去请求
onSizeReady(overrideWidth, overrideHeight);
} else {
//这里会去获取图片尺寸,然后回调 onSizeReady()去加载图片
target.getSize(this);
}
...
}
@Override
public void onSizeReady(int width, int height) {
...
//调用 Engine.load()加载
loadStatus = engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this);
...
}
}
public class Engine implements EngineJobListener, ... {
/* 弱引用实现 活动缓存
* activeResources 是Glide通过引用计数实现 Bitmap复用的关键
*/
private final ActiveResources activeResources;
/* 内存缓存 LruCache 实现
* LruCache 内部是一个 LinkedHashMap,使用LRU 最近最少访问算法,淘汰缓存
*/
private final MemoryCache cache;
private final LazyDiskCacheProvider diskCacheProvider;//磁盘缓存
public <R> LoadStatus load(...) {
//到这里还是主线程
Util.assertMainThread();
/* 根据请求信息(包括load(model)方法的参数、图片宽高等),构建 key
* 这个key,也是用于 memoryCache diskCache 的key
*/
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
//从 活动缓存 activeResources 去拿图片
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
//从活动缓存中找到图片后,回调 onResourceReady() 方法
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
...
return null;
}
/* 活动缓存 activeResources 中没找到图片
* 去内存缓存 cache 中获取图片
*/
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
//从内存缓存中找到图片后,回调 onResourceReady() 方法
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
...
return null;
}
//根据key 获取 EngineJob
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
...
return new LoadStatus(cb, current);
}
//构建 EngineJob
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
//构建 DecodeJob
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
//根据key 缓存 EngineJob
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
...
return new LoadStatus(cb, engineJob);
}
}
class EngineJob<R> implements DecodeJob.Callback<R>, Poolable {
//EngineJob 对象池
private final Pools.Pool<EngineJob<?>> pool;
// 磁盘缓存线程池,默认只有一条核心线程、最大线程数也是一,闲置时间是0
private final GlideExecutor diskCacheExecutor;
//核心线程为 0,最大线程数为 Integer.MAX_VALUE,闲置时间 10秒
private final GlideExecutor sourceUnlimitedExecutor;
/* 根据cpu核心数定义线程数
* cpu核心 >= 4 则 设置为 2条线程,cpu核心 < 4 则设置为 1条线程
*/
private final GlideExecutor sourceExecutor;
/* 根据cpu核心数定义线程数
* cpu核心 >= 4 则 设置为 2条线程,cpu核心 < 4 则设置为 1条线程
*/
private final GlideExecutor animationExecutor;
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
//根据是否使用缓存、以及配置,获取线程池
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
//最终获取图片的行为在 DecodeJob.run() 中
executor.execute(decodeJob);
}
}
class DecodeJob<R> implements Runnable, ... {
//DecodeJob 对象池
private final Pools.Pool<DecodeJob<?>> pool;
@Override
public void run() {
...
}
}
二、Glide的缓存机制
活动缓存:HashMap value为弱引用,存放正在使用的图片。图片不再使用时会移除并添加到内存缓存
内存缓存:LruCache LRU算法淘汰,key为 EntryKey 与图片请求配置相关
磁盘缓存:实现 LRU 算法淘汰,文件名 为 EntryKey 使用SHA2算法得到,与图片请求配置相关
LruBitmapPool:Bitmap对象池,实现 LRU算法淘汰,Glide中主要用于图像变换后Bitmap对象的复用,避免频繁gc
Request 对象池:默认 150个,对象复用,避免频繁gc
DecodeJob 对象池:默认大小 150,对象复用,避免频繁gc
EngineJob 对象池:默认大小 150,对象复用,避免频繁gc
1、缓存 key
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);
内部实际上就是 new EngineKey(…),
EngineKey 类重写了 hashCode()、toString(),确保只有传入EngineKey的参数完全一直才能匹配。
2、活动缓存,存放正在使用的图片
采用 HashMap,value为图片 弱引用 缓存。
活动缓存通过引用计数,来表示图片是否在使用中。
如果计数为0,表示不再使用,图片会从 活动缓存 中删除,放到内存缓存中。
【由于内存缓存使用的 LRU算法 淘汰缓存,
正在使用的缓存放到活动缓存中,不在内存缓存,不会被LRU算法淘汰。】
1)活动缓存存储结构
final class ActiveResources {
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
}
2)活动缓存的存入
class EngineJob<R> implements DecodeJob.Callback<R>, Poolable {
private EngineResource<?> engineResource;
@Override
public void onResourceReady(Resource<R> resource, DataSource dataSource) {
this.resource = resource;
this.dataSource = dataSource;
MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}
private static class MainThreadCallback implements Handler.Callback {
@Override
public boolean handleMessage(Message message) {
EngineJob<?> job = (EngineJob<?>) message.obj;
switch (message.what) {
case MSG_COMPLETE:
job.handleResultOnMainThread();
break;
...
}
return true;
}
}
@Synthetic
void handleResultOnMainThread() {
...
engineResource = engineResourceFactory.build(resource, isCacheable);
...
//引用计数+1,表示EngineJob 正在使用该图片
engineResource.acquire();
//Engine.onEngineJobComplete()中,把图片存入 活动缓存 activeResources 中
listener.onEngineJobComplete(this, key, engineResource);
for (int i = 0, size = cbs.size(); i < size; i++) {
ResourceCallback cb = cbs.get(i);
if (!isInIgnoredCallbacks(cb)) {
//每一个地方使用到图片,引用计数都会 +1
engineResource.acquire();
cb.onResourceReady(engineResource, dataSource);
}
}
/* 引用计数-1,表示 EngineJob 对该图片使用已经结束
* 当引用计数 为0 时,表示图片不在使用中,会从 活动缓存 中删除。
*/
engineResource.release();
release(false /*isRemovedFromQueue*/);
}
}
3)活动缓存的删除
引用计数为 0 时,会调用 Engine.onResourceReleased()把图片从 活动缓存 删除,
并放入内存缓存。
3、内存缓存
采用 LruCache LRU最近最少使用 算法,实现内存缓存。
低内存时,会自动 减半 / 清空 内存缓存
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
//低内存时,会自动 减半/清空 内存缓存
@Override
public void trimMemory(int level) {
if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
clearMemory();
} else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
trimToSize(getMaxSize() / 2);
}
}
}
4、磁盘缓存
DiskCacheStrategy.NONE 表示 不缓存 任何内容
DiskCacheStrategy.DATA 表示只 缓存原始图片
DiskCacheStrategy.RESOURCE 表示 只缓存转换过后的图片
DiskCacheStrategy.ALL 表示 既缓存原始图片,也缓存转换过后的图片
DiskCacheStrategy.AUTOMATIC 表示让Glide根据图片资源 智能地选择 使用哪一种缓存策略(默认选项)
磁盘缓存也使用 LRU算法,DiskLruCache
com.bumptech.glide.disklrucache.DiskLruCache
内部也是维护 LinkedHashMap,实现 LRU算法淘汰超过上限的缓存。
1)磁盘缓存写入
图片从网络下载、解码成功后,会先写入 活动缓存,然后写入 磁盘缓存。
class DecodeJob<R> implements Runnable, ... {
//图片下载完成后回调
@Override
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey) {
...
decodeFromRetrievedData();
...
}
private void decodeFromRetrievedData() {
...
Resource<R> resource = null;
try {
//图片解码
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
...
}
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource);
} else {
runGenerators();
}
}
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
...
//这里会把图片添加到 活动缓存 中
notifyComplete(result, dataSource);
stage = Stage.ENCODE;
try {
if (deferredEncodeManager.hasResourceToEncode()) {
//这里会把图片写入磁盘
deferredEncodeManager.encode(diskCacheProvider, options);
}
} finally {
...
}
...
}
private static class DeferredEncodeManager<Z> {
private Key key;
private ResourceEncoder<Z> encoder;
private LockedResource<Z> toEncode;
...
//写入磁盘
void encode(DiskCacheProvider diskCacheProvider, Options options) {
TraceCompat.beginSection("DecodeJob.encode");
try {
//写入磁盘
diskCacheProvider.getDiskCache().put(key, new DataCacheWriter<>(encoder, toEncode, options));
} finally {
toEncode.unlock();
TraceCompat.endSection();
}
}
...
}
}
2)磁盘缓存 的加载
从前面工作流程中可知,图片的加载真正执行是在 DecodeJob.run() 中
class DecodeJob<R> implements Runnable, ... {
//判断是否读取磁盘缓存
boolean willDecodeFromCache() {
Stage firstStage = getNextStage(Stage.INITIALIZE);
return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
}
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE://默认情况
/* 从缓存策略的实现可知,除了 DiskCacheStrategy.NONE 之外
* 其他所有策略 decodeCachedResource()/decodeCachedData() 返回值都是 true
* 所以这里默认会返回 Stage.RESOURCE_CACHE/Stage.DATA_CACHE
*/
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
...
}
}
@Override
public void run() {
...
runWrapped();
...
}
private void runWrapped() {
switch (runReason) {// runReason 默认值是 RunReason.INITIALIZE;
case INITIALIZE:
//返回 Stage.RESOURCE_CACHE
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
...
}
}
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
...
}
}
}
class ResourceCacheGenerator implements ... {
@Override
public boolean startNext() {
...
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
//这里会调用 DiskLruCache 根据 key 读取磁盘缓存
cacheFile = helper.getDiskCache().get(currentKey);
...
return started;
}
}
5、LruBitmapPool
Bitmap 对象池,避免频繁的创建以及回收Bitmap对象,进而减少GC的出现。
内部实现类 LRU算法,用于缓存超过默认上限时,淘汰策略。
Bitmap类型的Resource,在回收时,会把图片放入 BitmapPool 对象池中,用于复用
从 Glide 内部的调用看,主要用于 BitmapTransformation 变换后的图片复用 Bitmap 对象
LruBitmapPool 容量:
分辨率宽 * 分辨率高 * 4字节(ARGB_8888一个像素点大小) * 比例(8.0以下是 4,8.0及以上是 1)
static final int BITMAP_POOL_TARGET_SCREENS =
Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 4 : 1;
@Synthetic float bitmapPoolScreens = BITMAP_POOL_TARGET_SCREENS;
int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;
int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);
Bitmap类型的Resource回收时,把 Bitmap 放入对象池
public class BitmapResource implements Resource<Bitmap>, ... {
@Override
public void recycle() {
//把转换后的图片,保存到 LruBitmapPool 中
bitmapPool.put(bitmap);
}
}
public class BitmapDrawableResource extends DrawableResource<BitmapDrawable> ... {
@Override
public void recycle() {
//把转换后的图片,保存到 LruBitmapPool 中
bitmapPool.put(drawable.getBitmap());
}
}
public class LruBitmapPool implements BitmapPool {
@Override
public synchronized void put(Bitmap bitmap) {
...
final int size = strategy.getSize(bitmap);
strategy.put(bitmap);
tracker.add(bitmap);
puts++;
currentSize += size;
...
//计算是否超出maxSize,超出则进行 LRU回收
evict();
}
private void evict() {
trimToSize(maxSize);
}
@Override
@NonNull
public Bitmap get(int width, int height, Bitmap.Config config) {
//从 BitmapPool 中根据尺寸和配置获取 Bitmap对象。
Bitmap result = getDirtyOrNull(width, height, config);
if (result != null) {
//找到匹配的Bitmap,擦除图片的像素值
result.eraseColor(Color.TRANSPARENT);
} else {
//没找到匹配的Bitmap,则创建新的Bitmap
result = createBitmap(width, height, config);
}
return result;
}
}
在调用LruBitmapPool.get方法获取到Bitmap后,
通过如下方法将获取到的 bitmap作为参数 传给Canvas,
在canvas中把 inBitmap 像素填充到进去,实现对象复用
public final class TransformationUtils {
private static void applyMatrix(@NonNull Bitmap inBitmap, @NonNull Bitmap targetBitmap, Matrix matrix) {
BITMAP_DRAWABLE_LOCK.lock();
try {
Canvas canvas = new Canvas(targetBitmap);
canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT);
clear(canvas);
} finally {
BITMAP_DRAWABLE_LOCK.unlock();
}
}
}
三、Glide图片转换 Transformation
Glide 图片转换库 glide-transformations:https://github.com/wasabeef/glide-transformations
1、图片转换时机
Glide 图片的变换会在 图片解码后 执行
class DecodeJob<R> implements Runnable, Poolable, ... {
<Z> Resource<Z> onResourceDecoded(DataSource dataSource, Resource<Z> decoded) {
...
Transformation<Z> appliedTransformation = null;
Resource<Z> transformed = decoded;
if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
//执行图片转换
transformed = appliedTransformation.transform(glideContext, decoded, width, height);
}
...
return result;
}
}
2、CenterCrop 图像转换的实现
public class CenterCrop extends BitmapTransformation {
...
/* 参数一:BitmapPool Bitmap缓存池,用于对Bitmap对象进行重用
* 否则每次图片变换都要重新创建Bitmap对象,会导致频繁 内存抖动/GC
*
* 参数二:toTransform 原始图片的Bitmap对象,我们就是要对它来进行图片变换。
*
* 参数三/四: 图片变换后的 宽度 和 高度
*/
@Override
protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
return TransformationUtils.centerCrop(pool, toTransform, outWidth, outHeight);
}
...
}
public final class TransformationUtils {
public static Bitmap centerCrop(BitmapPool pool, Bitmap inBitmap, int width, int height) {
if (inBitmap.getWidth() == width && inBitmap.getHeight() == height) {
return inBitmap;
}
//通过I mageView尺寸 与 原图 比例,进行缩放
final float scale;
final float dx;
final float dy;
Matrix m = new Matrix();
if (inBitmap.getWidth() * height > width * inBitmap.getHeight()) {
scale = (float) height / (float) inBitmap.getHeight();
dx = (width - inBitmap.getWidth() * scale) * 0.5f;
dy = 0;
} else {
scale = (float) width / (float) inBitmap.getWidth();
dx = 0;
dy = (height - inBitmap.getHeight() * scale) * 0.5f;
}
m.setScale(scale, scale);
m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
Bitmap result = pool.get(width, height, getNonNullConfig(inBitmap));
// We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given.
TransformationUtils.setAlpha(inBitmap, result);
applyMatrix(inBitmap, result, m);
return result;
}
/* inBitmap 一般是原图,一般会在 活动缓存/内存缓存中
* targetBitmap 是尝试从 BitmapPool 对象池中拿的 Bitmap,用于生成变换后图片
* 这里把 inBitmap 图像绘制到 targetBitmap上。
*/
private static void applyMatrix(Bitmap inBitmap, Bitmap targetBitmap, Matrix matrix) {
BITMAP_DRAWABLE_LOCK.lock();
try {
Canvas canvas = new Canvas(targetBitmap);
canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT);
clear(canvas);
} finally {
BITMAP_DRAWABLE_LOCK.unlock();
}
}
}
3、自定义图片转换
1)继承 BitmapTransformation
2)重写 transform() 方法
public class CircleCrop extends BitmapTransformation {
...
@Override
public String getId() {
return "com.example.test.CircleCrop";
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
int diameter = Math.min(toTransform.getWidth(), toTransform.getHeight());
//尝试从BitmapPool 中获取目标Bitmap对象
final Bitmap toReuse = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888);
final Bitmap result;
if (toReuse != null) {
result = toReuse;
} else {
//对象池中没找到,则创建一个新的 Bitmap
result = Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888);
}
//获取 裁剪后图片 相对与 原图 在 x轴、y轴,离原点的距离dx、dy
int dx = (toTransform.getWidth() - diameter) / 2;
int dy = (toTransform.getHeight() - diameter) / 2;
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
BitmapShader shader = new BitmapShader(toTransform, BitmapShader.TileMode.CLAMP,
BitmapShader.TileMode.CLAMP);
if (dx != 0 || dy != 0) {
Matrix matrix = new Matrix();
/* 把裁剪后的图片,移到动到原点
* 图片以图片更短的一遍为标准裁剪成正方形后,长的那边的原点已经不在原点上了。
*/
matrix.setTranslate(-dx, -dy);
shader.setLocalMatrix(matrix);
}
paint.setShader(shader);
paint.setAntiAlias(true);
//半径
float radius = diameter / 2f;
//画圆
canvas.drawCircle(radius, radius, radius, paint);
//画完后,尝试把复用的 Bitmap 放回 Bitmap缓存池
if (toReuse != null && !pool.put(toReuse)) {
//放入缓存池失败,回收
toReuse.recycle();
}
return result;
}
}
四、Glide加载回调
1、Glide 默认的回调 Target
public class RequestBuilder<TranscodeType> implements ... {
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
...
/* buildImageViewTarget()方法:根据 transcodeClass 的类型,构建一个 ViewTarget 对象
* BitmapImageViewTarget / DrawableImageViewTarget
*/
return into(glideContext.buildImageViewTarget(view, transcodeClass), null, requestOptions);
}
//这里会触发请求加载图片(活动缓存/内存缓存/磁盘缓存/网络)
private <Y extends Target<TranscodeType>> Y into(@NonNull Y target, ...) {
...
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
}
public class ImageViewTargetFactory {
//据传入的class参数,构建一个 ViewTarget 对象
public <Z> ViewTarget<ImageView, Z> buildTarget(ImageView view, Class<Z> clazz) {
if (Bitmap.class.equals(clazz)) {
return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
}
public final class SingleRequest<R> implements Request, ResourceCallback, ... {
private Target<R> target;
...
private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
...
if ((requestListener == null
|| !requestListener.onResourceReady(result, model, target, dataSource, isFirstResource))
&& (targetListener == null
|| !targetListener.onResourceReady(result, model, target, dataSource, isFirstResource))) {
Transition<? super R> animation = animationFactory.build(dataSource, isFirstResource);
/* 最终会触发 target.onResourceReady()
* BitmapImageViewTarget / DrawableImageViewTarget 中,
* 会调用view.setImageBitmap(resource) / view.setImageDrawable(resource)
* 把图片展示在控件上。
*/
target.onResourceReady(result, animation);
}
...
notifyLoadSuccess();
}
}
2、preload()方法,把图片预加载到【内存】中
GlideApp.with(config.getContext()).load(thumbnailUrl).preload();
通过 PreloadTarget 实现
public class RequestBuilder<TranscodeType> implements ... {
public Target<TranscodeType> preload(int width, int height) {
final PreloadTarget<TranscodeType> target = PreloadTarget.obtain(requestManager, width, height);
return into(target);
}
/**
*
*/
public Target<TranscodeType> preload() {
return preload(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
}
public Target<TranscodeType> preload(int width, int height) {
final PreloadTarget<TranscodeType> target = PreloadTarget.obtain(requestManager, width, height);
return into(target);
}
}
public final class PreloadTarget<Z> extends SimpleTarget<Z> {
private static final int MESSAGE_CLEAR = 1;
private static final Handler HANDLER = new Handler(Looper.getMainLooper(), new Callback() {
@Override
public boolean handleMessage(Message message) {
if (message.what == MESSAGE_CLEAR) {
((PreloadTarget<?>) message.obj).clear();
return true;
}
return false;
}
});
...
@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
HANDLER.obtainMessage(MESSAGE_CLEAR, this).sendToTarget();
}
@SuppressWarnings("WeakerAccess")
@Synthetic void clear() {
//图片下载完成后,移除 BaseTarget.request,释放资源
requestManager.clear(this);
}
}
3、downloadOnly()方法
仅仅下载图片,不加载到 内存
Glide.with(GlideManager.getApp())
.downloadOnly()
.load(url)
.into(new SimpleTarget<File>() {
@Override
public void onResourceReady(File resource, Transition<? super File> transition) {
//返回下载的文件 resource
}
@Override
public void onLoadFailed(Drawable errorDrawable) {
getter.onFail();
}
});
源码:
public class RequestBuilder<TranscodeType> implements ... {
protected static final RequestOptions DOWNLOAD_ONLY_OPTIONS =
new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.DATA)
.priority(Priority.LOW)
.skipMemoryCache(true);//不加载到内存,仅仅是通过这个配置而已
//只能在主线程调用
public <Y extends Target<File>> Y downloadOnly(@NonNull Y target) {
return getDownloadOnlyRequest().into(target);
}
/* 该方法内部调用 submit()的作用:
* 如果当前线程在子线程,会把下载的请求放到主线程去做,仅此而已。
* 在主线程也可以调用。
*/
public FutureTarget<File> downloadOnly(int width, int height) {
return getDownloadOnlyRequest().submit(width, height);
}
protected RequestBuilder<File> getDownloadOnlyRequest() {
return new RequestBuilder<>(File.class, this).apply(DOWNLOAD_ONLY_OPTIONS);
}
@NonNull
public FutureTarget<TranscodeType> submit(int width, int height) {
final RequestFutureTarget<TranscodeType> target =
new RequestFutureTarget<>(glideContext.getMainHandler(), width, height);
//如果调用线程不是主线程,则把请求塞到主线程的Handler 的 MessageQueue中。
if (Util.isOnBackgroundThread()) {
glideContext.getMainHandler().post(new Runnable() {
@Override
public void run() {
if (!target.isCancelled()) {
into(target, target);
}
}
});
} else {
into(target, target);
}
return target;
}
}
4、listener() 方法
Glide.with(this)
.load(url)
.listener(new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(...) {
/* 返回false就表示这个事件没有被处理,还会继续向下传递,
* 返回true就表示这个事件已经被处理掉了,从而不会再继续向下传递
*/
return false;
}
@Override
public boolean onResourceReady(...) {
/* 返回false就表示这个事件没有被处理,还会继续向下传递,
* 返回true就表示这个事件已经被处理掉了,从而不会再继续向下传递
*/
return false;
}
}).into(imageView);
源码:
public final classSingleRequest<R> implements Request, ... {
private RequestListener<R> requestListener;
private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
...
/* 这里会回调 requestListener.onResourceReady()
* 返回false的时候,才会继续调用Target的onResourceReady()方法
*/
if ((requestListener == null
|| !requestListener.onResourceReady(result, model, target, dataSource, isFirstResource))
&& (targetListener == null
|| !targetListener.onResourceReady(result, model, target, dataSource, isFirstResource))) {
Transition<? super R> animation =
animationFactory.build(dataSource, isFirstResource);
target.onResourceReady(result, animation);
}
...
notifyLoadSuccess();
}
}
五、Glide加载进度监听实现
思路:拦截器,获取请求返回的数据 ResponseBody,计算返回的数据百分比,回调到调用层
@GlideModule
public class UnsafeOkHttpGlideModule extends AppGlideModule {
...
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
//添加拦截器到Glide
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(new ProgressInterceptor());
OkHttpClient okHttpClient = builder.build();
//原来的是 new OkHttpUrlLoader.Factory();
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(okHttpClient));
}
@Override
public boolean isManifestParsingEnabled() {
return false;//false :不再解析AndroidMenifest文件
}
}
下载进度 拦截器
public class ProgressInterceptor implements Interceptor {
public static final Map<String, ProgressListener> LISTENER_MAP = new HashMap<>();
//入注册下载监听
public static void addListener(String url, ProgressListener listener) {
LISTENER_MAP.put(url, listener);
}
//取消注册下载监听
public static void removeListener(String url) {
LISTENER_MAP.remove(url);
}
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
String url = request.url().toString();
ResponseBody body = response.body();
Response newResponse = response.newBuilder().body(new ProgressResponseBody(url, body)).build();
return newResponse;
}
}
//下载进度计算与回调
public class ProgressResponseBody extends ResponseBody {
private static final String TAG = "XGlide";
private BufferedSource bufferedSource;
private ResponseBody responseBody;
private ProgressListener listener;
public ProgressResponseBody(String url, ResponseBody responseBody) {
this.responseBody = responseBody;
//从 拦截器中 传进来的 进度监听起
listener = ProgressInterceptor.LISTENER_MAP.get(url);
}
@Nullable
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(new ProgressSource(responseBody.source()));
}
return bufferedSource;
}
private class ProgressSource extends ForwardingSource {
long totalBytesRead = 0;
int currentProgress;
ProgressSource(Source source) {
super(source);
}
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
long fullLength = responseBody.contentLength();
if (bytesRead == -1) {
totalBytesRead = fullLength;
} else {
totalBytesRead += bytesRead;
}
int progress = (int) (100f * totalBytesRead / fullLength);
Log.d(TAG, "download progress is " + progress);
//下载进度回调
if (listener != null && progress != currentProgress) {
listener.onProgress(progress);
}
if (listener != null && totalBytesRead == fullLength) {
listener = null;
}
currentProgress = progress;
return bytesRead;
}
}
}
//进度监听回调
public interface ProgressListener {
void onProgress(int progress);
}
使用:
public void testProgressLoad() {
ProgressInterceptor.addListener(url, new ProgressListener() {
@Override
public void onProgress(int progress) {
//进度回调
}
});
RequestOptions options = new RequestOptions()
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
.load(url)
.apply(options)
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
//加载结束后,移除进度监听器,避免内存泄露
ProgressInterceptor.removeListener(url);
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
//加载结束后,移除进度监听器,避免内存泄露
ProgressInterceptor.removeListener(url);
return false;
}
}).into(img);
}
六、Glide 优先级使用
public enum Priority {
IMMEDIATE, //立即执行(最高优先级)
HIGH, //高优先级
NORMAL, //普通优先级
LOW, //低优先级
}
使用:
RequestOptions options = new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.DATA)
.priority(Priority.LOW);
七、关于 Matrix 坐标矩阵
1、用于处理图像平面的 缩放、平移、旋转等操作
使用Matrix类的时候,图片的移动和旋转,只能在控件上面。
控件本身没有动,控件里面的内容动了
2、 set, pre, post方法
- preXXX 表示 在队头插入一个方法
- postXXX 表示 在队尾插入一个方法
- setXXX 表示【把当前队列清空】,并且总是位于队列的最中间位置.
- 当执行了一次 setXXX 后:
preXXX 方法总是插入到 set前部的队列的最前面,
postXXX 方法总是插入到 set后部的队列的最后面。
1)setTranslate(float ds,float dy)
控制 Matrix 进行平移
2)setSkew(float kx ,float ky ,float px ,float py)
控制 Matrix 以 px、py 为轴心进行倾斜。kx、ky为X、Y方向上的倾斜距离。
3)setSkew(float kx,float ky)
控制Matrix进行倾斜。kx、ky为X、Y方向上的倾斜距离。
4)setRotate(float degrees)
控制Matrix进行旋转,degrees控制旋转的角度。
5)setRotate(float degrees,float px,float py)
设置以px、py为轴心进行旋转,degrees控制旋转的角度。
6)setScale(float sx,float sy)
设置Matrix进行缩放,sx、sy控制X、Y方向上的缩放比例。
7)setScale(float sx,float sy,float px,float py)
设置Matrix以px、py为轴心进行缩放,sx、sy控制X、Y方向上的缩放比例。
例一:
Matrix m = new Matrix();
m.setRotate(45);
m.setTranslate(80, 80);
只有m.setTranslate(80, 80)有效,因为m.setRotate(45);被清除.
例子二:
Matrix m = new Matrix();
m.preScale(2f,2f);
m.preTranslate(50f, 20f);
m.postScale(0.2f, 0.5f);
m.postTranslate(20f, 20f);
执行顺序:m.preTranslate(50f, 20f)–>m.preScale(2f,2f)–>m.postScale(0.2f, 0.5f)–>m.postTranslate(20f, 20f)
注意:m.preTranslate(50f, 20f)比m.preScale(2f,2f)先执行,因为它插到了队列的最前端.
例子五:
Matrix m = new Matrix();
m.postTranslate(20, 20);
m.preScale(0.2f, 0.5f);
m.setScale(0.8f, 0.8f);
m.postScale(3f, 3f);
m.preTranslate(0.5f, 0.5f);
执行顺序:m.preTranslate(0.5f, 0.5f)–>m.setScale(0.8f, 0.8f)–>m.postScale(3f, 3f)
注意:m.setScale(0.8f, 0.8f)清除了前面的m.postTranslate(20, 20)和m.preScale(0.2f, 0.5f);