entry.executor.execute(new CallResourceReady(entry.cb));
}
//这里是通知发出上层删除活动资源数据
decrementPendingCallbacks();
}
…
}
//重要的就是这里了
@Synthetic
synchronized void decrementPendingCallbacks() {
if (decremented == 0) {
if (engineResource != null) {
//如果不为空,那么就调用内部 release 函数
engineResource.release();
}
}
}
复制代码
看一下 EngineResource 的 release 函数
class EngineResource implements Resource {
…
void release() {
synchronized (listener) {
synchronized (this) {
if (acquired <= 0) {
throw new IllegalStateException(“Cannot release a recycled or not yet acquired resource”);
}
//这里每次调用一次 release 内部引用计数法就会减一,到没有引用也就是为 0 的时候 ,就会通知上层
if (–acquired == 0) {
//回调出去,Engine 来接收
listener.onResourceReleased(key, this);
}
}
}
}
…
}
复制代码
根据注释,我们知道这里用了引用计数法,有点像 GC 回收的 引用计数法的影子。也就是说,当完全没有使用这样图片的时候,就会把活动资源清理掉,接着往下看,会调用 Engine 的 onResourceReleased 函数。
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
。。。
//接收来自 EngineResource 的调用回调
@Override
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
//1. 收到当前图片没有引用,清理图片资源
activeResources.deactivate(cacheKey);
//2. 如果开启了内存缓存
if (resource.isCacheable()) {
//3. 将缓存存储到内存缓存中。
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
。。。
}
复制代码
通过上面注释 1 可以知道,这里首先会将缓存图片从 activeResources 中移除,然后再将它 put 到 LruResourceCache 内存缓存当中。这样也就实现了正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用 LruCache 来进行缓存的功能,设计的真的很巧妙。
LruResourceCache 内存资源
获取内存资源
不知道有没有小伙伴注意到,其实在讲活动资源存储的时候已经涉及到了内存缓存的存储,下面我们在来看一下,具体代码如下:
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
…//忽略一些成员变量跟构造函数
public synchronized LoadStatus load(
…//忽略参数
) {
…
//1. 加载内存缓存
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
//如果内存缓存中有,就通知上层,最后在 SingleRequest 接收
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey(“Loaded resource from cache”, startTime, key);
}
return null;
}
}
…
}
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } //2\. 通过 getEngineResourceFromCache 获取内存资源 EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {//如果内存资源存在
//则引用计数 +1
cached.acquire();
//3. 将内存资源存入活动资源
activeResources.activate(key, cached);
}
return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) { //通过 Engine load 中传参可知,cache 就是 Lru 内存资源缓存 //2.1 这里是通过 remove 删除来拿到缓存资源 Resource<?> cached = cache.remove(key);
final EngineResource<?> result; if (cached == null) { result = null; } else if (cached instanceof EngineResource) { result = (EngineResource<?>) cached;
} else {
result = new EngineResource<>(cached, true /isMemoryCacheable/, true /isRecyclable/);
}
return result;
}
复制代码
通过注释1得知,这里是开始加载内存缓存中的资源;
通过注释2.1 可知,这里通过 Lru 内存缓存的 remove 来拿到内存缓存
最后将内存存储到活动缓存中,并缓存当前找到的内存缓存。
这里的活动缓存跟内存缓存紧密联系在一起,环环相扣,就像之前我们的疑问,为什么 Glide 会设计 2 个内存缓存的原因了。
存储内存资源
通过 EngineResource 的引用计数法机制 release 函数,只要没有引用那么就回调,请看下面代码:
class EngineJob implements DecodeJob.Callback,
Poolable {
…
@Override
public void onResourceReady(Resource resource, DataSource dataSource) {
synchronized (this) {
this.resource = resource;
this.dataSource = dataSource;
}
notifyCallbacksOfResult();
}
@Synthetic
void notifyCallbacksOfResult() {
…
//这里是通知发出上层删除活动资源数据
decrementPendingCallbacks();
}
…
}
//重要的就是这里了
@Synthetic
synchronized void decrementPendingCallbacks() {
if (decremented == 0) {
if (engineResource != null) {
//如果不为空,那么就调用内部 release 函数
engineResource.release();
}
}
}
void release() {
synchronized (listener) {
synchronized (this) {
//引用计数为 0 的时候,回调。
if (–acquired == 0) {
listener.onResourceReleased(key, this);
}
}
}
}
复制代码
最后会回调到 Engine 的 onResourceReleased
函数:
@Override
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
//1. 清理活动缓存
activeResources.deactivate(cacheKey);
//如果开启了内存缓存
if (resource.isCacheable()) {
//2. 清理出来的活动资源,添加进内存缓存
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
复制代码
到了这里我们知道,在清理活动缓存的时候添加进了内存缓存。
清理内存缓存
这里的清理是在获取内存缓存的时候通过 remove 清理掉的,详细可以看内存缓存获取的方式。
内存缓存小结
通过上面分析可知,内存缓存有活动缓存和内存资源缓存,下面看一个图来总结下它们相互之间是怎么配合交换数据的。
总结下步骤:
- 第一次加载没有获取到活动缓存。
- 接着加载内存资源缓存,先清理掉内存缓存,在添加进行活动缓存。
- 第二次加载活动缓存已经存在。
- 当前图片引用为 0 的时候,清理活动资源,并且添加进内存资源。
- 又回到了第一步,然后就这样环环相扣。
磁盘缓存
在介绍磁盘缓存之前先看一张表格
缓存表示 | 说明 |
---|---|
DiskCacheStrategy.NONE | 表示不开启磁盘缓存 |
DiskCacheStrategy.RESOURCE | 表示只缓存转换之后的图片。 |
DiskCacheStrategy.ALL | 表示既缓存原始图片,也缓存转换过后的图片。 |
DiskCacheStrategy.DATA | 表示只缓存原始图片 |
DiskCacheStrategy.AUTOMATIC | 根据数据源自动选择磁盘缓存策略(默认选择) |
上面这 4 中参数其实很好理解,这里有一个概念需要记住,就是当我们使用 Glide 去加载一张图片的时候,Glide 默认并不会将原始图片展示出来,而是会对图片进行压缩和转换,总之就是经过种种一系列操作之后得到的图片,就叫转换过后的图片。而 Glide 默认情况下在硬盘缓存的就是 DiskCacheStrategy.AUTOMATIC
以下面的代码来开启磁盘缓存:
Glide.
with(MainActivity.this.getApplication()).
//使用磁盘资源缓存功能
diskCacheStrategy(DiskCacheStrategy.RESOURCE).
into(imageView);
复制代码
知道了怎么开启,下面我们就来看一下磁盘缓存的的加载与存储。
这 2 个加载流程几乎一模一样,只是加载的数据源不同,下面我们具体来看一下
DiskCacheStrategy.RESOURCE 资源类型
获取资源数据
通过上一篇Glide 加载流程 我们知道,如果在活动缓存、内存缓存中没有找数据,那么就重新开启一个 GlideExecutor 线程池在 DecodeJob run 执行新的请求,下面我们就直接来看 DecodeJob run 函数,跟着它去找 资源数据的加载:
class DecodeJob implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable<DecodeJob<?>>,
Poolable {
…
@Override
public void run() {
…
try {
//如果取消就通知加载失败
if (isCancelled) {
notifyFailed();
return;
}
//1. 执行runWrapped
runWrapped();
} catch (CallbackException e) {
…
}
}
…
}
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
//2. 找到执行的状态
stage = getNextStage(Stage.INITIALIZE);
//3. 找到具体执行器
currentGenerator = getNextGenerator();
//4. 开始执行
runGenerators();
break;
…
}
}
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE: //3.1解码后的资源执行器
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE://原始数据执行器
return new DataCacheGenerator(decodeHelper, this);
case SOURCE://新的请求,http 执行器
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
复制代码
通过上面分析的代码跟注释,我们知道这里是在找具体的执行器,找完了之后注释 4 开始执行,现在我们直接看注释 4。先提一下 注释 3.1 因为外部我们配置的是 RESOURCE 磁盘资源缓存策略,所以直接找到的是 ResourceCacheGenerator
执行器。
private void runGenerators() {
//如果当前任务没有取消,执行器不为空,那么就执行 currentGenerator.startNext() 函数
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
…
}
复制代码
通过上面代码可知,主要是执行 currentGenerator.startNext()
就句代码,currentGerator 是一个接口,通过注释 3.1 我们知道这里它的实现类是 ResourceCacheGenerator
,那么我们具体看下 ResourceCacheGenerator
的 startNext 函数;
class ResourceCacheGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback {
…
@Override
public boolean startNext() {
…
while (modelLoaders == null || !hasNextModelLoader()) {
resourceClassIndex++;
…
//1. 拿到资源缓存 key
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
//2. 通过 key 获取到资源缓存
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
//3. 获取一个数据加载器
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
//3.1 为资源缓存文件,构建一个加载器,这是构建出来的是 ByteBufferFileLoader 的内部类 ByteBufferFetcher
loadData = modelLoader.buildLoadData(cacheFile,
helper.getWidth(), helper.getHeight(), helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
//3.2 利用 ByteBufferFetcher 加载,最后把结果会通过回调给 DecodeJob 的 onDataFetcherReady 函数
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
…
}
复制代码
通过上面注释可以得到几点信息
- 首先根据 资源 ID 等一些信息拿到资源缓存 Key
- 通过 key 拿到缓存文件
- 构建一个 ByteBufferFetcher 加载缓存文件
- 加载完成之后回调到 DecodeJob 中。
存储资源数据
先来看下面一段代码:
class DecodeJob implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable<DecodeJob<?>>,
Poolable {
…
private void notifyEncodeAndRelease(Resource resource, DataSource dataSource) {
…
stage = Stage.ENCODE;
try {
//1. 是否可以将转换后的图片缓存
if (deferredEncodeManager.hasResourceToEncode()) {
//1.1 缓存入口
deferredEncodeManager.encode(diskCacheProvider, options);
}
} finally {
…
}
onEncodeComplete();
}
}
void encode(DiskCacheProvider diskCacheProvider, Options options) {
GlideTrace.beginSection(“DecodeJob.encode”);
try {
//1.2 将 Bitmap 缓存到资源磁盘
diskCacheProvider.getDiskCache().put(key,
new DataCacheWriter<>(encoder, toEncode, options));
} finally {
toEncode.unlock();
GlideTrace.endSection();
}
}
复制代码
通过上面我们知道 http 请求到图片输入流之后经过一系列处理,转换得到目标 Bitmap 资源,最后通过回调到 DecodeJob 进行缓存起来。
清理资源缓存
- 用户主动通过系统来清理
- 卸载软件
- 调用 DisCache.clear();
DiskCacheStrategy.DATA 原始数据类型
获取原始数据
参考上小节DiskCacheStrategy.RESOURCE
获取资源,不同的是把 ResourceCacheGenerator 换成 DataCacheGenerator 加载了。
存储原始数据
这里既然存的是原始数据那么我们直接从 http 请求之后的响应数据开始查看,通过上一篇我们知道是在 HttpUrlFetcher 中请求网络,直接定位到目的地:
public class HttpUrlFetcher implements DataFetcher {
@Override
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
//1. 通过 loadDataWithRedirects 来进行http 请求,返回 InputStream
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
//2. 将请求之后的数据返回出去
callback.onDataReady(result);
} catch (IOException e) {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总)
面试成功其实是必然的,因为我做足了充分的准备工作,包括刷题啊,看一些Android核心的知识点,看一些面试的博客吸取大家面试的一些经验,下面这份PDF是我翻阅了差不多1个月左右一些Android大博主的博客从他们那里取其精华去其糟泊所整理出来的一些Android的核心知识点, 全部都是精华中的精华,我能面试到现在资深开发人员跟我整理的这本Android核心知识点有密不可分的关系,在这里本着共赢的心态分享给各位朋友。
这份PDF囊括了JVM,Java集合,Java多线程并发,Java基础,生命周期,微服务, 进程,Parcelable 接口,IPC,屏幕适配,线程异步,ART,架构,Jetpack,NDK开发,计算机网络基础,类加载器,Android 开源库源码分析,设计模式汇总,Gradle 知识点汇总…
由于篇幅有限,就不做过多的介绍,大家请自行脑补
oid扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总)
[外链图片转存中…(img-dRXq8XsO-1711878936384)]
面试成功其实是必然的,因为我做足了充分的准备工作,包括刷题啊,看一些Android核心的知识点,看一些面试的博客吸取大家面试的一些经验,下面这份PDF是我翻阅了差不多1个月左右一些Android大博主的博客从他们那里取其精华去其糟泊所整理出来的一些Android的核心知识点, 全部都是精华中的精华,我能面试到现在资深开发人员跟我整理的这本Android核心知识点有密不可分的关系,在这里本着共赢的心态分享给各位朋友。
[外链图片转存中…(img-xF4NItZZ-1711878936384)]
这份PDF囊括了JVM,Java集合,Java多线程并发,Java基础,生命周期,微服务, 进程,Parcelable 接口,IPC,屏幕适配,线程异步,ART,架构,Jetpack,NDK开发,计算机网络基础,类加载器,Android 开源库源码分析,设计模式汇总,Gradle 知识点汇总…
由于篇幅有限,就不做过多的介绍,大家请自行脑补