「ピクチャーフレームGlideの3階層キャッシュ」を入手する記事
序文
Glide の内容は膨大すぎてまとめるのが耐えられないので、3 次キャッシュ リストを取り出して話しましょう。
まず第一に、第 3 レベルのキャッシュは Glide の非常に重要なキャッシュ メカニズムであり、画像の読み込み効率とパフォーマンスを非常に高くしているのはこのキャッシュ メカニズムです。
具体的な 3 レベル キャッシュの内容を見てみましょう
L3キャッシュからの読み取り
まず第一に、Glide の L3 キャッシュには何が含まれているのでしょうか? 一般的な 3 レベル キャッシュ (メモリ、ローカル、ネットワーク) とは異なり、
Glide の 3 レベル キャッシュは次の 3 レベルを表します。 (もちろん、ネットワーク キャッシュが存在する必要がありますが、Glide が言及する 3 レベル キャッシュには、ネットワークキャッシュ)
キャッシュのソースを取得します (最初の 2 つのレベル、アクティブ キャッシュとメモリ キャッシュ)
これは、Glide 画像読み込みの中心となるメソッドである Egine.load() メソッドで発生します。(このメソッドを知らない場合は、前の記事を読んでいないはずです)
Egine.load() 非常に重要な EngineJob の作成に加えて、DecodeJob はキャッシュ戦略です。
あ、ちなみに、EngineKey の生成もあります (追記: キャッシュ内の HashMap の Key キーとして、複数のパラメーターを使用して構築された EngineKey エンティティ)
public <R> Engine.LoadStatus load(GlideContext glideContext, Object model, Key signature, int width, int height, Class<?> resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<?>, Transformation<?>> transformations, boolean isTransformationRequired, boolean isScaleOnlyOrNoTransform, Options options, boolean isMemoryCacheable, boolean useUnlimitedSourceExecutorPool, boolean useAnimationPool, boolean onlyRetrieveFromCache, ResourceCallback cb, Executor callbackExecutor) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0L;
// 1、EngineKey 的生成
EngineKey key = this.keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);
EngineResource memoryResource;
synchronized(this) {
// 2、活动缓存和内存缓存的加载、获取(这里只是两级的)
memoryResource = this.loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
// 3、EngineJob和DecodeJob的生成(第三级磁盘缓存在DecodeJob中)
return this.waitForExistingOrStartNewJob(glideContext, model, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, options, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache, cb, callbackExecutor, key, startTime);
}
}
//回调 图片资源 (ps:当)
cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE, false);
return null;
}
loadFromMemory (ここではアクティブ キャッシュとメモリ キャッシュのみ) (ディスク キャッシュについては後述します)
@Nullable
private EngineResource<?> loadFromMemory(EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
} else {
// 1、先从活动缓存中获取
EngineResource<?> active = this.loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
} else {
// 2、从内存中获取
EngineResource<?> cached = this.loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
} else {
return null;
}
}
}
}
キャッシュのソースを取得します (第 3 レベル、ディスク キャッシュ)
前述したように、第 3 レベルのディスク キャッシュは DecodeJob にあります。なぜそれを別々に話すのでしょうか?ディスクキャッシュの取得はさらに奥に隠されているため
if (memoryResource == null) {
// 3、EngineJob和DecodeJob的生成(第三级磁盘缓存在DecodeJob中)
return this.waitForExistingOrStartNewJob(glideContext, model, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, options, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache, cb, callbackExecutor, key, startTime);
}
これを詳しく見てみましょう。
上記の最初の 2 レベルのキャッシュでは、memoryResource が取得されていません。このとき、waitForExistingOrStartNewJob が呼び出されますが、実際、ここにディスク キャッシュがあることは誰でも推測できます。ここでもう少し詳しく説明します。コードをあまり嫌いにならないでください。
waitForExistingOrStartNewJob
private <R> Engine.LoadStatus waitForExistingOrStartNewJob(GlideContext glideContext, Object model, Key signature, int width, int height, Class<?> resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<?>, Transformation<?>> transformations, boolean isTransformationRequired, boolean isScaleOnlyOrNoTransform, Options options, boolean isMemoryCacheable, boolean useUnlimitedSourceExecutorPool, boolean useAnimationPool, boolean onlyRetrieveFromCache, ResourceCallback cb, Executor callbackExecutor, EngineKey key, long startTime) {
.....
//获取缓的EngineJob
.....
//创建新的EngineJob、DecodeJob
EngineJob<R> engineJob = this.engineJobFactory.build(key, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache);
DecodeJob<R> decodeJob = this.decodeJobFactory.build(glideContext, model, key, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache, options, engineJob);
this.jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
//加载任务的启动
engineJob.start(decodeJob);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new Engine.LoadStatus(cb, engineJob);
}
}
EngineJob.start(decodeJob);
何も言うことはありませんので、decodeJobタスクを追加して実行します。
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache() ? this.diskCacheExecutor : this.getActiveSourceExecutor();
executor.execute(decodeJob);
}
どの実行スレッドが何を実行するのでしょうか? それは実行する必要があります! !!言うまでもなく(ディスクキャッシュのみを単純化して見てください)
public void run() {
.....
try {
if (!this.isCancelled) {
//主要方法
this.runWrapped();
return;
}
} catch (CallbackException var7) {
runWrapped()メソッド
では、ロードの 3 つの段階を表します (コードは注意深く読まれていないので、直接説明します)。
- フェーズ 1: 初期化フェーズでは、getNextStage() メソッドを呼び出して次のステージを取得し、getNextGenerator() メソッドを呼び出して次のジェネレーターを取得し、次に runGenerators() メソッドを呼び出してジェネレーターを実行します。
- フェーズ 2: ソース サービス フェーズに切り替え、runGenerators() メソッドを呼び出してジェネレーターを実行します。
- 3 つのフェーズ: データのデコード フェーズでは、取得したデータをデコードするために decodeFromRetrievedData() メソッドが呼び出されます。
private void runWrapped() {
switch(this.runReason) {
//1、一阶段
case INITIALIZE:
this.stage = this.getNextStage(DecodeJob.Stage.INITIALIZE);
this.currentGenerator = this.getNextGenerator(); //关注点一
this.runGenerators(); //关注点二
break;
//2、二阶段
case SWITCH_TO_SOURCE_SERVICE:
this.runGenerators();
break;
//3、三阶段
case DECODE_DATA:
this.decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + this.runReason);
}
}
これは画像を取得する段階である必要があります。初期化段階なので、焦点を当てます getNextGenerator()
private DataFetcherGenerator getNextGenerator() {
switch(this.stage) {
case RESOURCE_CACHE:
//磁盘缓存中获取修改后的图片
return new ResourceCacheGenerator(this.decodeHelper, this);
case DATA_CACHE:
//磁盘缓存中获取原始图片
return new DataCacheGenerator(this.decodeHelper, this);
case SOURCE:
//直接网络请求图片
return new SourceGenerator(this.decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + this.stage);
}
}
ここでは、3 つの異なるシナリオでリクエスト オブジェクトを作成するだけです。具体的な使用方法 (2 runGenerators() メソッドに関するもの)
private void runGenerators() {
........
while(!this.isCancelled && this.currentGenerator != null
&& !(isStarted = this.currentGenerator.startNext())) {
.........
}
......
}
次に、 currentGenerator.startNext()
ResourceCacheGenerator.startNext()を実行します。
public boolean startNext() {
...
currentKey =
new ResourceCacheKey
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions()); //创建图片资源的key
cacheFile = helper.getDiskCache().get(currentKey); //获取磁盘缓存
... //没有磁盘缓存,
return started;
}
まず、現在の画像のキーを作成して画像の一意性を識別します。次に、明らかに、helper.getDiskCache().get(currentKey) を呼び出すと、ディスク キャッシュから画像リソースが取得されます。これは、ここに startNext() があるためです。 ResourceCacheGenerator のリソースであるため、取得されたリソースは圧縮または変換されたイメージになります。
非圧縮および変換された画像を取得するにはどうすればよいですか? まず、RequestOptions の discCacheStrategy() を設定するときに設定した DiskCacheStrategy タイプは元のイメージをキャッシュできます (ALL、DATA、および AUTOMATIC は元のイメージをキャッシュできます)。その後、Glide は startNext() を呼び出すことで元のイメージを取得できます。 DataCacheGenerator のメソッドの画像。
アクティブキャッシュの詳細
- 原則: 弱参照された HashMap
Map<Key, ActiveResources.ResourceWeakReference> activeEngineResources;
- スコープ: 現在のアクティビティのライフサイクル内
- 機能: メモリ キャッシュのプレッシャーを共有し、適時にメモリを解放します。
- フェッチ優先度: 最初にアクティブなキャッシュから取得します
- ソースコード解析の証明
EngineResource<?>loadFromActiveResources(Key key)を取得
@Nullable
private EngineResource<?> loadFromActiveResources(Key key) {
EngineResource<?> active = this.activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
@Nullable
synchronized EngineResource<?> get(Key key) {
ActiveResources.ResourceWeakReference activeRef =
(ActiveResources.ResourceWeakReference)this.activeEngineResources.get(key);
if (activeRef == null) {
return null;
} else {
EngineResource<?> active = (EngineResource)activeRef.get();
if (active == null) {
this.cleanupActiveReference(activeRef);
}
return active;
}
}
弱参照Mapから取得されていることがわかります。(置き方は気にしないでください。後で説明します。ここではアクティブ キャッシュの概念についてのみ説明します)
詳細なメモリキャッシュ
- 原則: LruCache は LinkedHashMap の LRU によって管理され、Put や Remove などの MemoryCache インターフェース クラスのいくつかのメソッドを提供するためにパッケージ化されています。
public class LruCache<T, Y> {
private final Map<T, LruCache.Entry<Y>> cache = new LinkedHashMap(100, 0.75F, true);
- スコープ: 特定のアプリのスコープ内で、アプリケーションが完全に終了すると存在しません。
- 役割: データの読み取りを高速化し、ディスク IO 操作とネットワーク リクエストを削減します。
- 優先順位を付けます。アクティブなキャッシュがない場合は、メモリ キャッシュ内でそれを探します。
- ソース コードを解析すると、
EngineResource<?> がキャッシュされた = this.loadFromCache(key); が取得されることがわかります。
private EngineResource<?> loadFromCache(Key key) {
//1、步骤一:获取资源
EngineResource<?> cached = this.getEngineResourceFromCache(key);
if (cached != null) {
//2、步骤二:图片使用次数计数
cached.acquire();
//3、步骤三:给活动缓存
this.activeResources.activate(key, cached);
}
return cached;
}
ステップ 1: リソースの取得 getEngineResourceFromCache は
次の 2 つのステップに分かれています。
- キーを介して画像リソースを取得し、それを削除します (これは LruCache 管理の功績です。後で LRU アルゴリズムを手書きします!!!)
- EngineResourceをラップして返す
private EngineResource<?> getEngineResourceFromCache(Key key) {
//1、通过Key获取图片资源,并移除
Resource<?> cached = this.cache.remove(key);
EngineResource result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource)cached;
} else {
result = new EngineResource(cached, true, true, key, this);
}
return result;
}
ステップ 2: イメージの使用量をカウントする
ステップ 3: 弱参照を使用してイメージ キャッシュをパッケージ化し、管理のためにアクティブ キャッシュのマップに配置します。
synchronized void activate(Key key, EngineResource<?> resource) {
ActiveResources.ResourceWeakReference toPut = new ActiveResources.ResourceWeakReference(key, resource, this.resourceReferenceQueue, this.isActiveResourceRetentionAllowed);
ActiveResources.ResourceWeakReference removed = (ActiveResources.ResourceWeakReference)this.activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
わかりました!メモリキャッシュも完了し、アクティブキャッシュがどこで使用されるか、つまりメモリキャッシュ内で使用される場合、キャッシュは管理のためにアクティブキャッシュ領域に引き継がれることについても説明します(機能と設計のオリジナル)アクティブ キャッシュの目的については後で説明します)
ディスクキャッシュの詳しい説明
- 原則: DiskLruCache は LinkedHashMap の LRU によって管理され、DiskCache インターフェイスと DiskLruCacheWrapper ラッパー クラスによって使用されます。
public final class DiskLruCache implements Closeable {
private final LinkedHashMap<String, Entry> lruEntries =
new LinkedHashMap<String, Entry>(0, 0.75f, true);
現在管理されている DiskLruCache の LRU の LinkedHashMap のキーと値は次のとおりです。
キー: キャッシュ エントリのキー、つまり各キャッシュ エントリの一意の識別子 (URL から取得) を示します。
値: キャッシュ エントリの特定のコンテンツを示します。これは Entry オブジェクトであり、キャッシュ エントリのファイル パス、キャッシュ サイズ、キャッシュの読み取り/書き込みロックなどの情報が含まれます。
- 範囲: システム全体、データが削除されない限り、常に存在します。
- 機能: 永久保存用
- フェッチの優先順位: メモリ キャッシュがない場合は、ディスク キャッシュに移動して読み取ります。
- ソースコード証明:上記取得キャッシュソース(第3階層、ディスクキャッシュ)で詳しく説明しています。
public boolean startNext() {
//1、磁盘缓存中获取
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
while (!started && hasNextModelLoader()) {
...
// 2、网络加载
loadData = modelLoader.buildLoadData(
cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());
....
}
ディスクキャッシュが取得できない場合は、ネットワーク負荷が必要となります。
L3キャッシュへの書き込み
まずはネットワークから画像をリクエストしましょう。
ディスクキャッシュ書き込み
イメージはディスク キャッシュにないため、結果として生じるネットワーク リクエストはイメージを返します。
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders()); //进行网络请求
callback.onDataReady(result); //调用 SourceGenerator的onDataReady()方法
}
...
}
ここでは、InputStream ストリームを通じてネットワークから画像が取得され、onDataReady にコールバックが与えられます
。onDataReady は 2 つのことを行います。
- dataToCache オブジェクトの画像データを割り当て、cb.reschedule() をコールバックして、SourceGenerator の startNext() メソッドをトリガーして元の画像をキャッシュします。
- onDataFetcherReady に直接コールバックします (キャッシュが必要ない場合)
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
//如果请求返回的数据 data 不为空且需要缓存原始数据,就将 data 赋值给dataToCache,
//接着调用 cb.reschedule() 会再一次进入到 SourceGenerator 的 startNext() 方法
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
次に、ディスクの取得時に getNextGenerator() が SourceGenerator を返した場合、イメージを要求し、SourceGenerator の startNext() メソッドを入力する必要があることを示します。 (このソース コードは、キャッシュ ソースを取得するためのものです (「第 3 レベル))
case SOURCE:
//直接网络请求图片
return new SourceGenerator(this.decodeHelper, this);
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data); //在这里
}
...
}
それでは、cacheData が何をするのか見てみましょう
- エンコード(圧縮または変換)画像エンコーダ
- ディスクキャッシュへの書き込み(元のイメージ)
- DataCacheGenerator を作成して、圧縮およびエンコードされた画像をディスク キャッシュに書き込む
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
DataCacheWriter<Object> writer =
new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
// 写入磁盘缓存
helper.getDiskCache().put(originalKey, writer);
...
} finally {
loadData.fetcher.cleanup();
}
//
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
これにより、ネットワークから要求された画像がディスク キャッシュに書き込まれます。
アクティブなキャッシュ書き込み
それでは、メモリ キャッシュはいつ書き込まれるのでしょうか?
ディスクに書き込むときに、上記の onResourceReady メソッドを実行するためにディスクから画像を取得できます。
public void onResourceReady(Resource<R> resource, DataSource dataSource) {
this.resource = resource;
this.dataSource = dataSource;
MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}
handleResultOnMainThread() メソッドをコールバックします。
private static class MainThreadCallback implements Handler.Callback {
@Synthetic
@SuppressWarnings("WeakerAccess")
MainThreadCallback() {
}
@Override
public boolean handleMessage(Message message) {
EngineJob<?> job = (EngineJob<?>) message.obj;
switch (message.what) {
case MSG_COMPLETE:
job.handleResultOnMainThread();
......
}
return true;
}
}
次に、handleResultOnMainThread() メソッドに進み、何が行われたかを確認します。
- ステップ 1: count +1 を使用し、アクティブなキャッシュに入れる準備をします。
- ステップ 2: EngineJob へのコールバック
- ステップ 3: count -1 を使用し、アクティブ キャッシュに入れる準備をします。
void handleResultOnMainThread() {
...
engineResource = engineResourceFactory.build(resource, isCacheable);
hasResource = true;
//第一步:使用计数,为了放入活动缓存中准备
engineResource.acquire();
//第二步:回调给EngineJob
listener.onEngineJobComplete(this, key, engineResource);
for (int i = 0, size = cbs.size(); i < size; i++) {
ResourceCallback cb = cbs.get(i);
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource, dataSource);
}
}
//第三步:使用计数,为了放入活动缓存中准备
engineResource.release();
release(false /*isRemovedFromQueue*/);
}
まず 2 番目のステップを見てみましょう。コールバックは EngineJob に対して何をしたのでしょうか?
もちろん、リソースがエアコンの activeResources.activate() を使用していない場合、このメソッドはここでリソースを弱参照キャッシュに格納します。
public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
Util.assertMainThread();
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
activeResources.activate(key, resource);
}
}
jobs.removeIfCurrent(key, engineJob);
}
void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key,
resource,
getReferenceQueue(),
isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut); //传入弱引用缓存
if (removed != null) {
removed.reset();
}
}
ここまでで、弱参照キャッシュも書き込まれます。
メモリキャッシュ書き込み
それでは、メモリ キャッシュはいつ書き込まれるのでしょうか?
実は、これについては以前にも触れたことがあります。
EngineResource のacquire() メソッドと release() メソッド:
acquire() 每调用一次引用计数 acquired 加1
release() 方法每调用一次 acquired 减1
取得された参照カウントは、リソースを現在使用しているユーザーの数を示します。0 より大きい場合はリソースが使用中であることを示し、値 0 はユーザーが使用しておらず、現時点ではリソースをメモリ キャッシュに書き込む必要があることを示します。使用されたリソースはメモリ キャッシュに書き込まれますが、引き続き Engine の onResourceReleased() メソッドに戻ります。
private final MemoryCache cache;
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
Util.assertMainThread();
activeResources.deactivate(cacheKey); //先从弱引用缓存中移除
if (resource.isCacheable()) {
cache.put(cacheKey, resource); //写入内存缓存
} else {
resourceRecycler.recycle(resource);
}
}
このピクチャにはユーザーがいないため、activeResources.deactivate() を呼び出して最初に弱参照キャッシュからピクチャをクリアしてから、データをメモリ キャッシュに書き込みます。キャッシュは MemoryCache 型で、MemoryCache はその実装者が持つインターフェイス クラスです。 LruCacheです。
activeResources を使用して、使用中のピクチャが弱い参照として保存されていることがわかります。Glide は、ピクチャに対して取得された参照カウント変数を設定して、ピクチャへの現在の参照数をカウントします。取得された値が 0 の場合、ピクチャがユーザーがいないため、画像が保存されます。 弱参照キャッシュから削除され、LruCache に保存されます。つまり、メモリキャッシュ内です。
要約:
L3キャッシュからの読み込み、L3キャッシュの取得、L3キャッシュへの書き込みのソースを解析しました。
一言で言えば:
- キャッシュの読み取り順序:
アクティブ キャッシュ -> LruCache メモリ キャッシュ -> DiskLruCache ディスク キャッシュ - キャッシュ書き込み順序:
DiskLruCacheディスクキャッシュオリジナルイメージ→アクティブキャッシュ→LruCacheメモリキャッシュ(取得が0の場合に結合)
アクティブキャッシュ(弱参照キャッシュ)の役割
次の 3 つの機能を通じて、その機能を説明します。
機能 1:
まず、上記の知識により、アクティブ キャッシュに書き込むタイミングは 2 つあることがわかります。
- ネットワーク要求後、ディスク キャッシュに追加され、+1 カウントした後、engineResource.acquire(); によってアクティブ キャッシュに追加されます。
- キャッシュがメモリ キャッシュ LRU に取得された後、engineResource.acquire() によって使用された後、+1 カウントしてアクティブ キャッシュに追加されます。
EngineResource.release(); count -1 の後にのみメモリ キャッシュに追加されます。そうしないと、毎回メモリ キャッシュにロードするとメモリ キャッシュが過負荷になり、大きな負荷がかかります。!!!
機能 2:
弱参照オブジェクト参照の方法を使用します。GC リサイクル時にすぐにリサイクルされ、メモリ リークの発生が軽減されます。
機能 3:
LRU が現在使用中のオブジェクトを再利用しないようにする このアルゴリズムは、最近の使用頻度と最近の使用時間の原則に従います。
基本的な考え方は、長期間使用されていないページは最も最近使用されていないと考えられるため、最初に削除する必要があるということです。
大量の画像を頻繁に使用する場合、使用中の画像が再利用される場合があります。
要約:
- メモリキャッシュの負荷を軽減する
- メモリリークの問題を軽減する
- LRU が使用中のリソースを再利用しないようにする
キャッシュ構成では、
一部のキャッシュのサイズをカスタマイズする
@GlideModule
public final class CustomGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// 设置缓存大小为20mb
int memoryCacheSizeBytes = 1024 * 1024 * 20; // 20mb
// 设置内存缓存大小
builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
// 根据SD卡是否可用选择是在内部缓存还是SD卡缓存
if(SDCardUtils.isSDCardEnable()){
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context, "HYManagerImages", memoryCacheSizeBytes));
}else {
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, "HYManagerImages", memoryCacheSizeBytes));
}
}
// 针对V4用户可以提升速度
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}
Glide はメモリ キャッシュをスキップします
GlideApp.with(context)
.load(url)
.skipMemoryCache(true)//默认为false
.dontAnimate()
.centerCrop()
.into(imageView);
Glide ディスク キャッシュ ポリシーの構成
GlideApp.with(context)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.dontAnimate()
.centerCrop()
.into(imageView);
/*默认的策略是DiskCacheStrategy.AUTOMATIC
DiskCacheStrategy有五个常量:
DiskCacheStrategy.ALL 使用DATA和RESOURCE缓存远程数据,仅使用RESOURCE来缓存本地数据。
DiskCacheStrategy.NONE 不使用磁盘缓存
DiskCacheStrategy.DATA 在资源解码前就将原始数据写入磁盘缓存
DiskCacheStrategy.RESOURCE 在资源解码后将数据写入磁盘缓存,即经过缩放等转换后的图片资源。
DiskCacheStrategy.AUTOMATIC 根据原始图片数据和资源编码策略来自动选择磁盘缓存策略。*/
キャッシュのクリーニング
//磁盘缓存清理(子线程)
GlideApp.get(context).clearDiskCache();
//内存缓存清理(主线程)
GlideApp.get(context).clearMemory();
手書きの LRU アルゴリズム
AndroidのLRUアルゴリズムの解説
LRU アルゴリズムは、最近の使用頻度と最近の使用時間の原則に従います。基本的な考え方は、長期間使用されていないページは最も最近使用されていないと考えられるため、最初に削除する必要があるということです。
Android での LRU アルゴリズムの実装では、API の新しい LinkedHashMap が使用されます
private final Map<T, LruCache.Entry<Y>> cache = new LinkedHashMap(100, 0.75F, true);
彼の 3 つのパラメータについて説明します。
- パラメータ 1 (100): LinkedHashMap が保存できる要素の最大数を示す容量パラメータ。この数を超えると、LinkedHashMap は LRU アルゴリズムの原則に従って、最も最近使用されていない要素を自動的に削除します。
- パラメータ 2 (0.75F): 負荷係数。容量まで満たされた要素の割合を示します。このパーセンテージに達すると、LinkedHashMap は拡張操作を実行して、ハッシュ衝突の可能性を減らします。
- パラメーター 3 (true): アクセス順序パラメーター。このパラメーターが true に設定されている場合、LinkedHashMap はアクセス順序に従ってソートされます (最新のアクセスが最後に配置され、最も古いアクセスが先頭に配置されます)。 False に設定すると、LinkedHashMap は挿入順に並べ替えられます。
つまり、容量、負荷率、アクセス順序などのパラメーターを調整することで、キャッシュのサイズと削除戦略を制御し、最近頻繁に使用される要素への高速アクセスを最適化できます。
LRUアルゴリズムの実装
LRU アルゴリズムの実装を手動で記述してみましょう:
実装の前にいくつかの点を明確にする必要があります:
LRU キャッシュ メカニズムは、二重リンク リストによって補足されたハッシュ テーブルを通じて実装できます。ハッシュ テーブルと二重リンク リストを使用します。キャッシュに保存されているすべてのデータを維持するためのキーと値のペア。
- 二重リンク リストには、これらのキーと値のペアが使用される順序で格納されます。先頭に近いキーと値のペアが最も最近使用され、末尾に近いキーと値のペアが最も使用されていません。
- ハッシュ テーブルは通常のハッシュ マップ (HashMap) であり、キャッシュされたデータのキーを二重リンク リスト内の位置にマップします。
この方法では、まずハッシュ テーブルを使用して二重リンク リスト内のキャッシュ アイテムの位置を特定し、それを二重リンク リストの先頭に移動します。これは O(1)O で実行できます。 (1)O(1) get または put 操作が指定された時間内に完了しました。具体的な方法は以下の通りです。
- get 操作では、まずキーが存在するかどうかを確認します。
- キーが存在しない場合は、-1-1-1 を返します。
- キーが存在する場合、そのキーに対応するノードが最後に使用されたノードになります。ハッシュ テーブルを通じて二重リンク リスト内のノードの位置を特定し、それを二重リンク リストの先頭に移動し、最後にノードの値を返します。
- put 操作では、まずキーが存在するかどうかを確認します。
- キーが存在しない場合は、キーと値を使用して新しいノードを作成し、そのノードを二重リンク リストの先頭に追加して、キーとノードをハッシュ テーブルに追加します。次に、二重リンクリストのノード数が容量を超えているかどうかを判断し、容量を超えている場合は、二重リンクリストの末尾ノードを削除し、ハッシュテーブル内の対応する項目を削除します。
- キーが存在する場合、それは get 操作と似ており、最初にハッシュ テーブルを通じて検索し、次に対応するノードの値を value に更新し、そのノードを二重リンク リストの先頭に移動します。
上記の操作では、ハッシュ テーブルにアクセスする時間計算量は O(1)O(1)O(1)、二重リンク リストの先頭にノードを追加し、二重リンク リストの末尾にあるノードを削除する計算量は O(1)O(1)O(1) です。リンクリストも O(1)O(1)O(1).(1)O(1)O(1) です。
ノードを二重リンクリストの先頭に移動するには、「ノードの削除」と「二重リンクリストの先頭にノードを追加」の 2 つの手順に分けることができ、どちらも O(1)O で実行できます。 (1)O(1) 時間以内に完了しました。
public class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {
}
public DLinkedNode(int _key, int _value) {
key = _key; value = _value;}
}
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
--size;
}
}
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
要約する
来て!!!!