Glide使用了现在非常流行的流氏编码方式,方便了开发者的使用,简明、扼要。
接下来主要对上面这一段流氏操作做拆分。
Glide 主入口
这个类有点像门脸模式的统一代理入口,不过实际作用在4.0.0中很多功能都被放到后面的其他类中,此类关注的点就很少了。虽然整个libray的所有需要的组建都在这个类中,但是实际也只是一个统一初始化的地方。
)")RequestManager(Glide.with(…))
这个类主要用于管理和启动Glide的所有请求,可以使用activity,fragment或者连接生命周期的事件去智能的停止,启动,和重启请求。也可以检索或者通过实例化一个新的对象,或者使用静态的Glide去利用构建在Activity和Fragment生命周期处理中。它的方法跟你的Fragment和Activity的是同步的。
RequestBuilder
通用类,可以处理设置选项,并启动负载的通用资源类型。
在这个类中,主要是应用请求的很多选项(如下的选项从字面都能看出具体的用处,在ImageView控件中经常也能看到,另外之前版本可不是这么使用的):
public final class RequestOptions extends BaseRequestOptions {
private static RequestOptions skipMemoryCacheTrueOptions;
private static RequestOptions skipMemoryCacheFalseOptions;
private static RequestOptions fitCenterOptions;
private static RequestOptions centerCropOptions;
private static RequestOptions circleCropOptions;
private static RequestOptions noTransformOptions;
private static RequestOptions noAnimationOptions;
// …省略…
}
RequestBuilder transition(TransitionOptions transitionOptions){} 这个方法主要是用于加载对象从占位符(placeholder)或者缩略图(thumbnail)到真正对象加载完成的转场动画。
RequestBuilder load(…){}多太方法中,这里可以加载很多类型的数据对象,可以是String,Uri,File,resourceId,byte[]这些。当然这些后面对应的编码方式也是不一样的。
Target into(…){}这个方法是触发Request真正启动的地方,在上边的示例中最后就是调用这个方法发起请求。
不得不说的registry域,这个域挂载了很多元件,该注册器中囊括了模块加载器(ModelLoader)、编码器(Encoder)、资源解码器(ResourceDecoder)、资源编码器(ResourceEncoder)、数据回转器(DataRewinder)、转码器(Transcoder)。这些都是Glide在对资源编解码中既是基础又是核心功能。
代码结构
这里主要列举一下一些重要的组件以及他们的结构关系:
ModelLoader
DataFetcher
Target
Resource
ResourceTransformation
Pool
Cache
Decoder
Encoder
把这些组件代码结构列举出来主要是为了让读者和使用者一目了然的看到自己需要的一些功能。
执行流程
1、根据不同版本的Fragment创建RequestManagerFragment或者SupportRequestManagerFragment,并加入到对应的FragmentManager中。这两种Fragment是不带有任何界面的,主要是用于同步生命周期。具体实现如下:
public static RequestManager with(Context context) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}
// RequestManagerRetriever.get(…)
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public RequestManager get(Activity activity) {
if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(activity, fm, null);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
RequestManager fragmentGet(Context context, android.app.FragmentManager fm,
android.app.Fragment parentHint) {
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
requestManager =
new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
RequestManagerFragment getRequestManagerFragment(
final android.app.FragmentManager fm, android.app.Fragment parentHint) {
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
current = pendingRequestManagerFragments.get(fm);
if (current == null) {
current = new RequestManagerFragment();
current.setParentFragmentHint(parentHint);
pendingRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}
2、创建一个RequestBuilder,并添加一个DrawableTransitionOptions类型的转场动画
public RequestBuilder asDrawable() {
return as(Drawable.class).transition(new DrawableTransitionOptions());
}
3、加载对象(model域)
public RequestBuilder load(@Nullable Object model) {
return loadGeneric(model);
}
private RequestBuilder loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
4、装载对象(包含请求的发起点)。
public <Y extends Target> Y into(@NonNull Y target) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException(“You must call #load() before calling #into()”);
}
Request previous = target.getRequest();
if (previous != null) {
requestManager.clear(target);
}
requestOptions.lock();
Request request = buildRequest(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
一般而言,大部分使用者都是用来装载图片的,因此都会调用如下这个方法:
public Target into(ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
if (requestOptions.isLocked()) {
requestOptions = requestOptions.clone();
}
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions.optionalCenterCrop(context);
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
requestOptions.optionalFitCenter(context);
break;
//
C
A
S
E
S
−
O
M
I
T
T
E
D
CASES-OMITTED
CASES−OMITTED
default:
// Do nothing.
}
}
return into(context.buildImageViewTarget(view, transcodeClass));
}
这里针对ImageView的填充方式做了筛选并对应设置到requestOptions上。最终的是通过ImageView和转码类型(transcodeClass)创建不通过的Target(例如Bitmap对应的BitmapImageViewTarget和Drawable对应的DrawableImageViewTarget)
4.1 Request的创建buildRequest(target)。
在Request的创建中会针对是否有缩略图来创建不同尺寸的请求,缩略图方法可以使用RequestBuilder.thumbnail(…)方法来添加上。
Glide中的Request都是使用了SingleRequest类,当然缩略图采用的是ThumbnailRequestCoordinator类:
private Request obtainRequest(Target target,
BaseRequestOptions<?> requestOptions, RequestCoordinator requestCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions, Priority priority,
int overrideWidth, int overrideHeight) {
requestOptions.lock();
return SingleRequest.obtain(
context,
model,
transcodeClass,
requestOptions,
overrideWidth,
overrideHeight,
priority,
target,
requestListener,
requestCoordinator,
context.getEngine(),
transitionOptions.getTransitionFactory());
}
比较值得推崇的是SingleRequest.obtain写法,个人认为比new关键字更简洁明了吧。
target.setRequest(request)也是一个比较值得注意的地方,如果target是ViewTarget,那么request会被设置到View的tag上。这样其实是有一个好处,每一个View有一个自己的Request,如果有重复请求,那么都会先去拿到上一个已经绑定的Request,并且从RequestManager中清理回收掉。这应该是去重的功能。
4.2 requestManager.track(target, request)
这个方法非常的复杂,主要用于触发请求、编解码、装载和缓存这些功能。下面就一步一步来看吧:
4.2.1 缓存target,并启动Request
void track(Target<?> target, Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
/**
- Starts tracking the given request.
*/
public void runRequest(Request request) {
requests.add(request); //添加内存缓存
if (!isPaused) {
request.begin(); // 开始
} else {
pendingRequests.add(request); // 挂起请求
}
}
继续看一下SingleRequest中的begin方法:
@Override
public void begin() {
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
// 如果model空的,那么是不能执行的。 这里的model就是前面提到的RequestBuilder中的model
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
// Only log at more verbose log levels if the user has set a fallback drawable, because
// fallback Drawables indicate the user expects null models occasionally.
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
onLoadFailed(new GlideException(“Received null model”), logLevel);
return;
}
status = Status.WAITING_FOR_SIZE;
// 如果当前的View尺寸已经加载获取到了,那么就会进入真正的加载流程。
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
// 反之,当前View还没有画出来,那么是没有尺寸的。
// 这里会调用到ViewTreeObserver.addOnPreDrawListener。
// 等待View的尺寸都ok,才会继续
target.getSize(this);
}
// 如果等待和正在执行状态,那么当前会加载占位符Drawable
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
接下来是target.getSize(this)方法。这里主要说一下尺寸未加载出来的情况(ViewTarget.java):
void getSize(SizeReadyCallback cb) {
int currentWidth = getViewWidthOrParam();
int currentHeight = getViewHeightOrParam();
if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
cb.onSizeReady(currentWidth, currentHeight);
} else {
// We want to notify callbacks in the order they were added and we only expect one or two
// callbacks to
// be added a time, so a List is a reasonable choice.
if (!cbs.contains(cb)) {
cbs.add(cb);
}
if (layoutListener == null) {
final ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
// 绘画之前加入尺寸的监听。这一点我想大部分Android开发同学应该都知道。
// 接下来在看看系统触发该Listener时target又干了些什么。
observer.addOnPreDrawListener(layoutListener);
}
}
}
private static class SizeDeterminerLayoutListener implements ViewTreeObserver
.OnPreDrawListener {
// 注意这里是弱引用
private final WeakReference sizeDeterminerRef;
public SizeDeterminerLayoutListener(SizeDeterminer sizeDeterminer) {
sizeDeterminerRef = new WeakReference<>(sizeDeterminer);
}
@Override
public boolean onPreDraw() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, “OnGlobalLayoutListener called listener=” + this);
}
SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();
if (sizeDeterminer != null) {
// 通知SizeDeterminer去重新检查尺寸,并触发后续操作。
// SizeDeterminer有点像工具类,又作为尺寸回调的检测接口
sizeDeterminer.checkCurrentDimens();
}
return true;
}
}
ok,继续回到SingleRequest.onSizeReady方法,主要就是Engine发起load操作
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = Math.round(sizeMultiplier * width);
this.height = Math.round(sizeMultiplier * height);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
}
loadStatus = engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
this);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}
特别的,所有的操作都是来之唯一一个Engine,它的创建是来至于Glide的初始化。如果有需要修改缓存配置的同学可以继续看一下diskCacheFactory的创建:
if (engine == null) {
engine = new Engine(memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor);
}
继续看一下Engine.load的详细过程:
public LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass, Class transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map
// 创建key,这是给每次加载资源的唯一标示。
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
// 通过key查找缓存资源 (PS 这里的缓存主要是内存中的缓存,切记,可以查看MemoryCache)
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
// 如果有,那么直接利用当前缓存的资源。
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey(“Loaded resource from cache”, startTime, key);
}
return null;
}
// 这是一个二级内存的缓存引用,很简单用了一个Map<Key, WeakReference<EngineResource<?>>>装载起来的。 // 这个缓存主要是谁来放进去呢? 可以参考上面一级内存缓存loadFromCache方法。 EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey(“Loaded resource from active resources”, startTime, key);
}
return null;
}
// 根据key获取缓存的job。
EngineJob current = jobs.get(key);
if (current != null) {
current.addCallback(cb); // 给当前job添加上回调Callback
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey(“Added to existing load”, startTime, key);
}
return new LoadStatus(cb, current);
}
// 创建job
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob decodeJob = decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
// 放入线程池,执行
engineJob.start(decodeJob);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey(“Started new load”, startTime, key);
}
return new LoadStatus(cb, engineJob);
}
上面有一些值得注意的地方:
- 内存缓存:在Glide中默认是LruResourceCache。当然你也可以自定义;
- 为何要两级内存缓存(loadFromActiveResources)。个人理解是一级缓存采用LRU算法进行缓存,并不能保证全部能命中,添加二级缓存提高命中率之用;
- EngineJob和DecodeJob各自职责:EngineJob充当了管理和调度者,主要负责加载和各类回调通知;DecodeJob是真正干活的劳动者,这个类实现了Runnable接口。
下面来看看DecodeJob是如何执行的:
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
// 初始化 获取下一个阶段状态
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
// 运行
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
// 这里的阶段策略首先是从resource中寻找,然后再是data,,再是source
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
// 根据定义的缓存策略来回去下一个状态
// 缓存策略来之于RequestBuilder的requestOptions域
// 如果你有自定义的策略,可以调用RequestBuilder.apply方法即可
// 详细的可用缓存策略请参看DiskCacheStrategy.java
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
return Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
// 根据Stage找到数据抓取生成器。
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
// 产生含有降低采样/转换资源数据缓存文件的DataFetcher。
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
// 产生包含原始未修改的源数据缓存文件的DataFetcher。
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
// 生成使用注册的ModelLoader和加载时提供的Model获取源数据规定的DataFetcher。
// 根据不同的磁盘缓存策略,源数据可首先被写入到磁盘,然后从缓存文件中加载,而不是直接返回。
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
面试复习笔记
这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960页Android开发笔记》
《1307页Android开发面试宝典》
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960页Android开发笔记》
[外链图片转存中…(img-bAklgKyG-1712538853998)]
《1307页Android开发面试宝典》
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
[外链图片转存中…(img-2Gzyetge-1712538853998)]
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
[外链图片转存中…(img-Mm3zMwtT-1712538853998)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!