菜单

Universal-Image-Loader(贰)

2019年5月2日 - 金沙编程资讯

期限删除

既然MemoryCache内部的数量是以MemoryCacheStore对象为单位进行保管,那么定期检查也很有希望是MemoryCacheStore对象内部的1种表现。

通过精心阅读源码,开采MemoryCacheStore的构造函数中调用了InitDisposableMembers()本条点子,该情势的代码如下:

private void InitDisposableMembers() {
    //_insertBlock是MemoryCacheStore的私有属性
    //_insertBlock的声明方式是:private ManualResetEvent _insertBlock;
    _insertBlock = new ManualResetEvent(true);
    //_expires是MemoryCacheStore的私有属性
    //_expires的声明方式是:private CacheExpires _expires;
    _expires.EnableExpirationTimer(true);
}

里头跟本章节研究的晚点机制有关的正是_expires其一性格。由于《.NET
reference
source》中并从未那几个CacheExpires类的有关源码,相当小概获悉具体的得以落成格局,由此从Mono项目中找到同名的章程深究该品种的有血有肉落到实处。

class CacheExpires : CacheEntryCollection
{

    public static TimeSpan MIN_UPDATE_DELTA = new TimeSpan (0, 0, 1);
    public static TimeSpan EXPIRATIONS_INTERVAL = new TimeSpan (0, 0, 20);
    public static CacheExpiresHelper helper = new CacheExpiresHelper ();

    Timer timer;

    public CacheExpires (MemoryCacheStore store)
        : base (store, helper)
    {
    }

    public new void Add (MemoryCacheEntry entry)
    {
        entry.ExpiresEntryRef = new ExpiresEntryRef ();
        base.Add (entry);
    }

    public new void Remove (MemoryCacheEntry entry)
    {
        base.Remove (entry);
        entry.ExpiresEntryRef = ExpiresEntryRef.INVALID;
    }

    public void UtcUpdate (MemoryCacheEntry entry, DateTime utcAbsExp)
    {
        base.Remove (entry);
        entry.UtcAbsExp = utcAbsExp;
        base.Add (entry);
    }

    public void EnableExpirationTimer (bool enable)
    {
        if (enable) {
            if (timer != null)
                return;

            var period = (int) EXPIRATIONS_INTERVAL.TotalMilliseconds;
            timer = new Timer ((o) => FlushExpiredItems (true), null, period, period);
        } else {
            timer.Dispose ();
            timer = null;
        }
    }

    public int FlushExpiredItems (bool blockInsert)
    {
        return base.FlushItems (DateTime.UtcNow, CacheEntryRemovedReason.Expired, blockInsert);
    }
}

因此Mono中的源代码能够看到,在CacheExpires内部使用了3个电火花计时器,通过放大计时器触发定时的检查。在触发时选取的是CacheEntryCollection类的FlushItems措施。该方法的完结如下;

protected int FlushItems (DateTime limit, CacheEntryRemovedReason reason, bool blockInsert, int count = int.MaxValue)
{
    var flushedItems = 0;
    if (blockInsert)
        store.BlockInsert ();

    lock (entries) {
        foreach (var entry in entries) {
            if (helper.GetDateTime (entry) > limit || flushedItems >= count)
                break;

            flushedItems++;
        }

        for (var f = 0; f < flushedItems; f++)
            store.Remove (entries.Min, null, reason);
    }

    if (blockInsert)
        store.UnblockInsert ();

    return flushedItems;
}

FlushItems(***)的逻辑中,通过遍历全数的缓存项并且比对了晚点时间,将开掘的超时缓存项推行Remove操作进行清理,完毕缓存项的期限删除操作。通过Mono项目中此类的效能猜度,在.net
framework中的完结应有也是有周边的作用,即每二个MemoryCache的实例都会有多个担任定时检查的天职,担当管理掉全体超时的缓存项。

总结

在应用上豪门依照实际情况,举行种种组合搭配。本篇谈的比较理论些,某个剧情细节没张开。

譬如说布满式缓存的应用,缓存置换计策及算法,缓存过期机制等。

***************************************************************

 

LruDiskCache

LruDiskCache是一向促成DiskCache的类,首先来看一下DiskCache接口的办法。
能够见到供给贯彻的措施并不多,首要关切get和save方法。get方法重返的是文件类,即缓存文件,要清楚地窥见到凡是磁盘缓存都是用文件来缓存数据的;save方法用于将数据存款和储蓄进与imageUri绝对应的文件,其中参数有四个InputStream,该InputStream是图形的输入流,通过该输入流将数据写入文件个中。

public interface DiskCache {
    /**
     * Returns root directory of disk cache
     */
    File getDirectory();

    /**
     * Returns file of cached image
     */
    File get(String imageUri);

    /**
     * Saves image stream in disk cache.
     * Incoming image stream shouldn't be closed in this method.
     *
     */
    boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException;

    boolean save(String imageUri, Bitmap bitmap) throws IOException;

    boolean remove(String imageUri);

    /** Closes disk cache, releases resources. */
    void close();

    void clear();
}

上面介绍LruDiskCache,绝对于DiskLruCache轻便大多,原因是内部采取DiskLruCache类作为成员,文件数量的写入和读取其实都是经过DiskLruCache落成的。上面是应用LruDiskCache要求关注的地点。

看来,其实LruDiskCache正是对DiskLruCache做了壹层封装,实际的数据的操作方法还是经过DiskLruCache来完毕的。

public class LruDiskCache implements DiskCache {
    /** {@value */
    public static final int DEFAULT_BUFFER_SIZE = 32 * 1024; // 32 Kb
    /** {@value */
    public static final Bitmap.CompressFormat DEFAULT_COMPRESS_FORMAT = Bitmap.CompressFormat.PNG;
    /** {@value */
    public static final int DEFAULT_COMPRESS_QUALITY = 100;

    private static final String ERROR_ARG_NULL = " argument must be not null";
    private static final String ERROR_ARG_NEGATIVE = " argument must be positive number";

    protected DiskLruCache cache;
    private File reserveCacheDir;

    //用于文件的命名,有Hash和MD5两种文件名
    protected final FileNameGenerator fileNameGenerator;

    protected int bufferSize = DEFAULT_BUFFER_SIZE;

    protected Bitmap.CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
    protected int compressQuality = DEFAULT_COMPRESS_QUALITY;

    public LruDiskCache(File cacheDir, FileNameGenerator fileNameGenerator, long cacheMaxSize) throws IOException {
        this(cacheDir, null, fileNameGenerator, cacheMaxSize, 0);
    }

    /**
     * @param cacheDir          Directory for file caching
     * @param reserveCacheDir   null-ok; Reserve directory for file caching. It's used when the primary directory isn't available.
     * @param fileNameGenerator {@linkplain com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator
     *                          Name generator} for cached files. Generated names must match the regex
     *                          <strong>[a-z0-9_-]{1,64}</strong>
     * @param cacheMaxSize      Max cache size in bytes. <b>0</b> means cache size is unlimited.
     * @param cacheMaxFileCount Max file count in cache. <b>0</b> means file count is unlimited.
     * @throws IOException if cache can't be initialized (e.g. "No space left on device")
     */
    public LruDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator, long cacheMaxSize,
            int cacheMaxFileCount) throws IOException {
        if (cacheDir == null) {
            throw new IllegalArgumentException("cacheDir" + ERROR_ARG_NULL);
        }
        if (cacheMaxSize < 0) {
            throw new IllegalArgumentException("cacheMaxSize" + ERROR_ARG_NEGATIVE);
        }
        if (cacheMaxFileCount < 0) {
            throw new IllegalArgumentException("cacheMaxFileCount" + ERROR_ARG_NEGATIVE);
        }
        if (fileNameGenerator == null) {
            throw new IllegalArgumentException("fileNameGenerator" + ERROR_ARG_NULL);
        }

        if (cacheMaxSize == 0) {
            cacheMaxSize = Long.MAX_VALUE;
        }
        if (cacheMaxFileCount == 0) {
            cacheMaxFileCount = Integer.MAX_VALUE;
        }

        this.reserveCacheDir = reserveCacheDir;
        this.fileNameGenerator = fileNameGenerator;
        initCache(cacheDir, reserveCacheDir, cacheMaxSize, cacheMaxFileCount);
    }

    private void initCache(File cacheDir, File reserveCacheDir, long cacheMaxSize, int cacheMaxFileCount)
            throws IOException {
        try {
            cache = DiskLruCache.open(cacheDir, 1, 1, cacheMaxSize, cacheMaxFileCount);
        } catch (IOException e) {
            L.e(e);
            if (reserveCacheDir != null) {
                initCache(reserveCacheDir, null, cacheMaxSize, cacheMaxFileCount);
            }
            if (cache == null) {
                throw e; //new RuntimeException("Can't initialize disk cache", e);
            }
        }
    }

    @Override
    public File getDirectory() {
        return cache.getDirectory();
    }

    @Override
    public File get(String imageUri) {
        DiskLruCache.Snapshot snapshot = null;
        try {
            snapshot = cache.get(getKey(imageUri));
            return snapshot == null ? null : snapshot.getFile(0);
        } catch (IOException e) {
            L.e(e);
            return null;
        } finally {
            if (snapshot != null) {
                snapshot.close();
            }
        }
    }

    @Override
    public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
        DiskLruCache.Editor editor = cache.edit(getKey(imageUri));
        if (editor == null) {
            return false;
        }

        OutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize);
        boolean copied = false;
        try {
            copied = IoUtils.copyStream(imageStream, os, listener, bufferSize);
        } finally {
            IoUtils.closeSilently(os);
            if (copied) {
                editor.commit();
            } else {
                editor.abort();
            }
        }
        return copied;
    }

    @Override
    public boolean save(String imageUri, Bitmap bitmap) throws IOException {
        DiskLruCache.Editor editor = cache.edit(getKey(imageUri));
        if (editor == null) {
            return false;
        }

        OutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize);
        boolean savedSuccessfully = false;
        try {
            savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os);
        } finally {
            IoUtils.closeSilently(os);
        }
        if (savedSuccessfully) {
            editor.commit();
        } else {
            editor.abort();
        }
        return savedSuccessfully;
    }

    @Override
    public boolean remove(String imageUri) {
        try {
            return cache.remove(getKey(imageUri));
        } catch (IOException e) {
            L.e(e);
            return false;
        }
    }

    @Override
    public void close() {
        try {
            cache.close();
        } catch (IOException e) {
            L.e(e);
        }
        cache = null;
    }

    @Override
    public void clear() {
        try {
            cache.delete();
        } catch (IOException e) {
            L.e(e);
        }
        try {
            initCache(cache.getDirectory(), reserveCacheDir, cache.getMaxSize(), cache.getMaxFileCount());
        } catch (IOException e) {
            L.e(e);
        }
    }

    private String getKey(String imageUri) {
        return fileNameGenerator.generate(imageUri);
    }

    public void setBufferSize(int bufferSize) {
        this.bufferSize = bufferSize;
    }

    public void setCompressFormat(Bitmap.CompressFormat compressFormat) {
        this.compressFormat = compressFormat;
    }

    public void setCompressQuality(int compressQuality) {
        this.compressQuality = compressQuality;
    }
}
#if OS_OBJECT_USE_OBJC  // iOS 6 之后 SDK 支持 GCD ARC, 不需要再 Dealloc 中 release
@property (nonatomic, strong) dispatch_queue_t concurrentQueue;
#else
@property (nonatomic, assign) dispatch_queue_t concurrentQueue;
#endif

何以从MemoryCahe中查询数据

从MemoryCache中获取数据经历了哪些进度吧?从全部来说,大约能够分成两类:获取数据和表达有效性。

以流程图的办法发挥上述手续如下:

图片 1

详尽的步骤是那样的:

  1. 校验查询参数RegionName和Key,实行实用判定
  2. 结构MemoryCacheKey对象,用于后续手续查询和比对现存数量
  3. 赢得MemoryCacheStore对象,裁减查询范围
  4. 从MemoryCacheStore的HashTable类型属性中领取MemoryCacheEntry对象,获得key对应的数码
  5. 推断MemoryCacheEntry对象的卓有成效,进行多少印证职业
  6. 拍卖MemoryCacheEntry的滑行超时时间等做客相关的逻辑

来看这里,不禁想起在此之前明白的别的缓存系统中的设计,就如历史有时候会有危言耸听的相似性,进行了美丽设计的缓存系统在一些时候看起来着实有不少貌似的位置。通过学习旁人的美好设计,从中能够学到繁多的事物,举个例子接下去的缓存超时机制。

逾期配置

经过设定最大过期时间来尽量制止冷数据常驻内部存款和储蓄器。

诸多状态种种数据缓存的岁月要求不平等的,所以须求再充实单个key的超时时间字段。

图片 2

图片 3

 private TimeSpan maxTime;
 public LRUCache(int maxKeySize,TimeSpan maxExpireTime){}

  //TrackValue增加创建时间和过期时间
 public readonly DateTime CreateTime;
 public readonly TimeSpan ExpireTime;

图片 4

图片 5

内部存款和储蓄器缓存小结

缓存是ImageLoader中的一个主导功用,由此须求深远的通晓,重新看一下MemoryCache的框架图,此时便会有了更加深的体会。

图片 6

MemoryCache传承机构图.PNG


  1. 对_dates依据 key 排序, 排序结果是时刻大的在头里, 比方20160拾一 在
    二〇一五1230前方; 之后实践数组倒转, 小的在前, 大的在后

MemoryCache线程安全部制

根据MSDN的描述:MemoryCache是线程安全的。那么注明,在操作MemoryCache中的缓存项时,MemoryCache保障程序的一举一动都是原子性的,而不会出现多少个线程共同操作变成的数目污染等主题材料。

那么,MemoryCache是什么样成功那或多或少的?

MemoryCache在中间使用加锁机制来保障数据项操作的原子性。该锁以每种MemoryCacheStore为单位,即同3个MemoryCacheStore内部的数额共享同多个锁,而各异MemoryCacheStore之间互不影响。

存在加锁逻辑的有如下场景:

  1. 遍历MemoryCache缓存项
  2. 向MemoryCache加多/更新缓存项
  3. 执行MemoryCache析构
  4. 移除MemoryCache中的缓存项

此外的场景都比较好理解,其中值得1提的正是场景一(遍历)的落到实处情势。在MemoryCache中,使用了锁加复制的点子来拍卖遍历的急需,保障在遍历进度中不会发出尤其。

在.net 四.五.第11中学的遍历的兑现格局是这么的:

protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator() {
    Dictionary<string, object> h = new Dictionary<string, object>();
    if (!IsDisposed) {
        foreach (MemoryCacheStore store in _stores) {
            store.CopyTo(h);
        }
    }
    return h.GetEnumerator();
}

其中store.CopyTo(h);的兑现情势是在MemoryCacheStore中定义的,也正是说,每种Store的加锁解锁都以独立的进度,减弱锁机制影响的限定也是升格品质的重中之重手段。CopyTo方法的重中之重逻辑是在锁机制调控下的简练的遍历:

internal void CopyTo(IDictionary h) {
    lock (_entriesLock) {
        if (_disposed == 0) {
            foreach (DictionaryEntry e in _entries) {
                MemoryCacheKey key = e.Key as MemoryCacheKey;
                MemoryCacheEntry entry = e.Value as MemoryCacheEntry;
                if (entry.UtcAbsExp > DateTime.UtcNow) {
                    h[key.Key] = entry.Value;
                }
            }
        }
    }
}

些微意料之外,在遍历MemoryCache的时候,为了兑现遍历进度中的线程安全,达成的办法依旧是将数据其余拷贝了一份。当然了,说是完全拷贝壹份也不尽然,若是缓存项本来正是援引类型,被拷贝的也只是个指针而已。可是看起来最佳照旧少用为妙,万一缓存的都是些基础项目,1旦数据量相当的大,在遍历进程中的内部存款和储蓄器压力就不是足以忽略的标题了。

单机Web意况下一般选用Run提姆eCache,那种境况下:

能够在运转事件之中刷新

void Application_Start(object sender, EventArgs e)
        {
            //刷新
        }

除此以外能够单写个刷新缓存页面,上线后手动刷新下或发表时自动调用刷新,再也许由用户自行触发。

BaseDiskCache

UIL中还有此外七个实际磁盘缓存类LimitedAgeDiskCache和UnlimitedDiskCache,它们的区别点只是三个还删除缓存文件的过时文件,3个不限量缓存大小。这里只介绍他们的共同父类:BaseDiskCache。

在BaseDiskCache中并不曾选用异乎平常的数据结构来存储数据,直接就是通过对文本类的操作来完成使用文件缓存的目的。注意该类并不曾范围缓存大小,上面就大致看一下BaseDiskCache的save和get方法。

public abstract class BaseDiskCache implements DiskCache {

    ...

        @Override
    public File get(String imageUri) {
        return getFile(imageUri);
    }

    protected File getFile(String imageUri) {
        String fileName = fileNameGenerator.generate(imageUri);
        File dir = cacheDir;
        if (!cacheDir.exists() && !cacheDir.mkdirs()) {
            if (reserveCacheDir != null && (reserveCacheDir.exists() || reserveCacheDir.mkdirs())) {
                dir = reserveCacheDir;
            }
        }
        return new File(dir, fileName);
    }

    @Override
    public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
        File imageFile = getFile(imageUri);
        File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);
        boolean loaded = false;
        try {
            OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
            try {
                loaded = IoUtils.copyStream(imageStream, os, listener, bufferSize);
            } finally {
                IoUtils.closeSilently(os);
            }
        } finally {
            if (loaded && !tmpFile.renameTo(imageFile)) {
                loaded = false;
            }
            if (!loaded) {
                tmpFile.delete();
            }
        }
        return loaded;
    }


}
- (BOOL)isTTLCache {
    BOOL isTTLCache;

    [self lock];
        isTTLCache = _ttlCache;
    [self unlock];

    return isTTLCache;
}

- (void)setTtlCache:(BOOL)ttlCache {
    [self lock];
        _ttlCache = ttlCache;
    [self unlock];
}

参考资料

缓存预热

上次有同学问过,在率先次加载时缓存都为空,怎么开始展览预热。

学到的文化

看源码的法子:先明白大概的流程,恐怕本身困惑职业流程,然后带着大局观去读书源码,那样方便保险思路的不可磨灭,而不致于在函数各个跳转的长河中迷失了,导致兴趣全无

#pragma mark - Protocol Method
- (void)setObject:(id)obj forKeyedSubscript:(NSString *)key {
    [self setObject:obj forKey:key withCost:0];
}

- (id)objectForKeyedSubscript:(NSString *)key {
    return [self objectForKey:key];
}

MemoryCache内部数据结构

在MemoryCache类内部,数据的团伙章程跟MemoryCacheStore、MemoryCacheKey和MemoryCacheEntry这三个类有关,它们的法力分别是:

MemoryCache和MemoryCacheStore的涉嫌大概如下图所示:

图片 7

从图上能够直观的收看,叁个MemoryCache实例对象足以包蕴八个MemoryCacheStore对象,具体有几个须求取决于程序所在的硬件条件,跟CPU数目有关。在MemoryCache的当中,MemoryCacheStore对象就像是叁个个的小数据库同样,承载着各类数据。所以,要了解MemoryCache内部的数据结构,就必要先清楚MemoryCacheStore的身价和职能。

内部存款和储蓄器缓存

任由远程数据库读取,依旧缓存服务器读取。防止不了要跨进程,跨互连网通信,有的还跨机房。

而应用程序频仍读写,对Web、DB服务器都以个非常大的消耗,速度相较内部存款和储蓄器也慢的多。

代码上加锁、异步,以致加服务器在内,都不是多个很好的主意。因为加载速度,对用户体验拾叁分关键。


 所以在有供给的品类中采用本地内部存款和储蓄器做二级缓存,是十一分有须求的。目的即是1:抗并发,二:加速读取速度。

有个名牌的缓存伍分钟法则法则,正是说假如3个数量频仍被访问,那么就应当放内部存款和储蓄器中。

比方:  有拾0并发过来,加锁会变成前者9玖线程等候,这几个9玖线程等候着,其实是直接在费用Web服务器财富。不加便是缓存雪崩。

               
如果每秒钟拉取壹份缓存,缓存到内存,那样9九线程等候时间非常的大减弱。 

ImageLoader专门的学问流程

从地点能够看出外界使用ImageLoader只需求调用displayImage()就能够兑现图片的加载和呈现,所以商讨ImageLoader的劳作流程其实正是分析ImageLoader的displayImage方法。由于displayImage()中调用的方法会贯穿整个UIL包,加上前边仔细分析了重要类的专门的学问原理,由此上面只需分析入眼的一些,便于领悟整个UIL的行事流程。

public class ImageLoader
{
    ...

    //这些是工作时所需要的类成员
    private ImageLoaderConfiguration configuration;
    private ImageLoaderEngine engine;

    private ImageLoadingListener defaultListener = new SimpleImageLoadingListener();

    private volatile static ImageLoader instance;

    /**
     *  uri:图片的uri
     *  imageAware:显示图片的控件
     *  options:显示图片的参数配置
     *  targetSize:要显示的图片的大小
     *  listener:用于加载图片中的回调
     *  progressListener:也是用于图片加载时的回调
     */
    public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
            ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)
            {
                ...
                //将uri转换成key
                String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
                //从内存中获取相应key的图片
                Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
                //如果不为空说明从内存中获取到了图片
                if (bmp != null && !bmp.isRecycled()) {
                    //判断是否需要加工处理,如果是则封装图片的信息和需求成Info类,然后通过一个任务类ProcessAndDisplayImageTask将图片按照需求加载到控件中
                    if (options.shouldPostProcess()) {
                        ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                                options, listener, progressListener, engine.getLockForUri(uri));
                        ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
                                defineHandler(options));

                        //如果此时运行在子线程中就直接运行,否则使用线程池执行
                        if (options.isSyncLoading()) {
                            displayTask.run();
                        } else {
                            engine.submit(displayTask);
                        }
                    } 
                    //如果不需要经过处理,则直接通过displayer的display将图片显示在控件中
                    else {
                        options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
                        listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
                    }
                } 
                //为空说明需要内存缓存中不存在该图片,需要从磁盘和网络中加载该图片
                else {
                    //设置加载前的默认图片
                    if (options.shouldShowImageOnLoading()) {
                        imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
                    } else if (options.isResetViewBeforeLoading()) {
                        imageAware.setImageDrawable(null);
                    }

                    //封装图片加载信息类,然后通过LoadAndDisplayImageTask执行加载显示图片任务
                    ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                            options, listener, progressListener, engine.getLockForUri(uri));
                    LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
                            defineHandler(options));
                    if (options.isSyncLoading()) {
                        displayTask.run();
                    } else {
                        engine.submit(displayTask);
                    }
                }

            }

    ...
}

//同步加载图片并显示的任务
final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener
{
    ...

    @Override
    public void run()
    {
        ...
        //从内存中提取图片
        bmp = configuration.memoryCache.get(memoryCacheKey);
        if (bmp == null || bmp.isRecycled()) {
            //如果内存中为null则从磁盘网络中获取图片
            bmp = tryLoadBitmap();
            ...
            if (bmp != null && options.isCacheInMemory()) {
                //将图片存进内存缓存中
                configuration.memoryCache.put(memoryCacheKey, bmp);
            }
            ...
        }
        ...
        //前面加载完图片接着通过执行DisplayBitmapTask显示图片到控件中
        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
        runTask(displayBitmapTask, syncLoading, handler, engine);

    }

    //同步从磁盘、网络中加载图片
    private Bitmap tryLoadBitmap() throws TaskCancelledException
    {
        Bitmap bitmap = null;
        ...
        //从磁盘缓存中获取文件并解码成图片
        File imageFile = configuration.diskCache.get(uri);
        bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));

        if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) 
        {
            //如果磁盘中获取不到图片则通过tryCacheImageOnDisk()从网络中获取并存至磁盘中
            if (options.isCacheOnDisk() && tryCacheImageOnDisk()) 
            {
                ...
                //再次从磁盘中获取文件
                imageFile = configuration.diskCache.get(uri);
                if (imageFile != null) {
                    imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
                }
            }   
            //再次将文件解码成图片
            bitmap = decodeImage(imageUriForDecoding);
        }
        ...
        return bitmap;
    }

    //从网络中加载图片(通过ImageDownloader),并按照需求压缩(通过ImageDecoder),最后放入磁盘缓存中
    private boolean tryCacheImageOnDisk() throws TaskCancelledException 
    {
        boolean loaded;

        loaded = downloadImage();
        ...
        resizeAndSaveImage(width, height);
        ...

        return loaded;
    }

}

//异步显示图片的任务,里面主要是调用了图片在加载过程中的各种回调,最后通过displayer.display将图片显示到控件中,注意该任务要运行在主线程当中
final class DisplayBitmapTask implements Runnable {

    @Override
    public void run() {
        if (imageAware.isCollected()) {
            L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
            listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
        } else if (isViewWasReused()) {
            L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
            listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
        } else {
            L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);

            //最主要的是这步,显示图片
            displayer.display(bitmap, imageAware, loadedFrom);
            engine.cancelDisplayTaskFor(imageAware);
            listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
        }
    }

}

上面就是任何UIL的做事流程,大致上能够分成两步,同步加载图片和异步突显图片,首要关怀点在一同加载图片那一个进程。

以下是加载呈现图片的流水生产线,协理精晓和记念。

图片 8

UIL_Load&Display Task Flow.PNG


@required
/**
 *  下标脚本的取值操作, 实现该方法, 可以通过下标脚本获得存储的缓存值
 *  就像这样获得缓存值 id obj = cache[@"key"]
 *  @param key 缓存对象关联的 key
 *
 *  @return  指定 key 的缓存对象
 */
- (id)objectForKeyedSubscript:(NSString *)key;

/**
 *  下标脚本的设置值操作, 实现该方法可以通过下标脚本设置缓存
 *  像这样 cache[@"key"] = object
 *  @param obj 要缓存的对象
 *  @param key 缓存对象关联的 key
 */
- (void)setObject:(id)obj forKeyedSubscript:(NSString *)key;

/**
 *  以上两个方法应该确保线程安全
 */

MemoryCacheKey

MemoryCacheKey的类功能绝相比较较容易,首要用来封装缓存项的key及有关的常用方法。

上文提到了MemoryCacheStore中_entries的初叶化格局,在构造函数的参数是1个MemoryCacheEqualityComparer对象,那是个什么东西,又是起到何以效用的吧?

MemoryCacheEqualityComparer类完结了IEqualityComparer接口,个中便定义了哈希表中剖断值非凡的主意,来分析下源码:

internal class MemoryCacheEqualityComparer: IEqualityComparer {

    bool IEqualityComparer.Equals(Object x, Object y) {
        Dbg.Assert(x != null && x is MemoryCacheKey);
        Dbg.Assert(y != null && y is MemoryCacheKey);

        MemoryCacheKey a, b;
        a = (MemoryCacheKey)x;
        b = (MemoryCacheKey)y;
        //MemoryCacheKey的Key属性就是我们在获取和设置缓存时使用的key值
        return (String.Compare(a.Key, b.Key, StringComparison.Ordinal) == 0);
    }

    int IEqualityComparer.GetHashCode(Object obj) {
        MemoryCacheKey cacheKey = (MemoryCacheKey) obj;
        return cacheKey.Hash;
    }
}

从代码中能够看来,MemoryCacheEqualityComparer的真的意义正是概念MemoryCacheKey的可比艺术。决断三个八个MemoryCacheKey是或不是等于使用的就是MemoryCacheKey中的Key属性。由此大家在MemoryCache中获得和装置相关的始末时,使用的都是对此MemoryCacheKey的相关运算结果。

缓存穿透

先举个简单例子:一般网址平常会缓存用户搜索的结果,假如数据库查询不到,是不会做缓存的。但假使频仍查这么些空关键字,会造成每一遍请求都一向查询数据库了。

事例正是缓存穿透,请求绕过缓存间接查数据库,那也是日常提的缓存命中率难题。

图片 9

图片 10

  public object GetMemberSigninDays4()
        {
            const int cacheTime = 5;
            const string cacheKey = "mushroomsir";

            var cacheValue = CacheHelper.Get(cacheKey);
            if (cacheValue != null)
                return cacheValue;
            const string lockKey = cacheKey + "n(*≧▽≦*)n";

            lock (lockKey)
            {
                cacheValue = CacheHelper.Get(cacheKey);
                if (cacheValue != null)
                    return cacheValue;

                cacheValue = null; //数据库查询不到,为空。
                //if (cacheValue2 == null)
                //{
                //    return null;  //一般为空,不做缓存
                //}
                if (cacheValue == null)
                {
                    cacheValue = string.Empty; //如果发现为空,我设置个默认值,也缓存起来。
                }
                CacheHelper.Add(cacheKey, cacheValue, cacheTime);
            }
            return cacheValue;
        }

图片 11

图片 12

假若把询问不到的空结果,也给缓存起来,那样下次一样的请求就能够直接再次回到null了,即能够制止当查问的值为空时引起的缓存穿透。

能够独立设置个缓存区域存款和储蓄空值,对要询问的key进行先期校验,然后再放行给前面包车型地铁健康缓存管理逻辑。

小结UIL的关怀点

UIL首要关切以下几点:图片从网络加载的加载所选取的主意,内部存款和储蓄器缓存的主意,磁盘缓存的诀要以及全部UIL的行事流程。

网络加载:选拔HttpU猎豹CS陆LConnection进行互联网连接来获取数据
内部存款和储蓄器缓存:UIL中内部存款和储蓄器缓存有某个种用于内部存储器缓存的类,个中私下认可使用的是LruMemoryCache,它的兑现原理跟Android自带的LruCache大约,都以行使accessOrder为true的LinkedHashMap达成的,其余的还有LRULimitedMemoryCache,FIFOLimitedMemoryCache等等,它们的公家特点正是缓存大小有限定,差异点是在缓存超越限定的时候删除的规则不雷同
磁盘缓存:UIL中的磁盘缓存比内部存款和储蓄器缓存简单了不少,主要分为二种完毕,壹种是UIL中私下认可使用的LruDiskCache,它的最底层落成是平昔利用的DiskLruCache,只可是是做了壹层封装;另一种达成是直接对文本类File举行操作,在那之中有多少个实际贯彻,三个是有缓存大小限制的,另3个是绝非界定的
UIL专门的工作流程:便是内存缓存->磁盘缓存->网络二个流程,详细在前一节有介绍

优缺点
可取:比较老的框架,牢固,加载速度分外
症结:不扶助GIF图片加载,缓存机制未有和http的缓存很好的整合,完全是和谐的1套缓存机制

(贰).
假如将第二步也加锁,线程一实施到第二步,加锁,获得回调并试行,仍旧是当线程1实施到第二步时,
CPU
调治到线程二,此时线程贰执行意识线程1加锁操作,导致线程二等待,线程一有惊无险实行线程壹的回调,而回调是3个只要耗费时间10000s
的操作,导致线程贰必要拭目以俟一千0s, 功用低下;

MemoryCache的缓存过期战略

向MemoryCache实例中加多缓存项的时候,能够挑选二种过期宗旨:

  1. 无须超时
  2. 纯属超时
  3. 滑动超时

缓存攻略在缓存项增多/更新缓存时(无论是使用Add或然Set方法)钦定,通过在操作缓存时钦赐CacheItemPolicy目的来达成设置缓存超时战略的目标。

缓存超时攻略并无法随随意便的内定,在MemoryCache内部对此CacheItemPolicy目的有内置的检查体制。先看下源码:

private void ValidatePolicy(CacheItemPolicy policy) {
    //检查过期时间策略的组合设置
    if (policy.AbsoluteExpiration != ObjectCache.InfiniteAbsoluteExpiration
        && policy.SlidingExpiration != ObjectCache.NoSlidingExpiration) {
        throw new ArgumentException(R.Invalid_expiration_combination, "policy");
    }
    //检查滑动超时策略
    if (policy.SlidingExpiration < ObjectCache.NoSlidingExpiration || OneYear < policy.SlidingExpiration) {
        throw new ArgumentOutOfRangeException("policy", RH.Format(R.Argument_out_of_range, "SlidingExpiration", ObjectCache.NoSlidingExpiration, OneYear));
    }
    //检查CallBack设置
    if (policy.RemovedCallback != null
        && policy.UpdateCallback != null) {
        throw new ArgumentException(R.Invalid_callback_combination, "policy");
    }
    //检查优先级的设置
    if (policy.Priority != CacheItemPriority.Default && policy.Priority != CacheItemPriority.NotRemovable) {
        throw new ArgumentOutOfRangeException("policy", RH.Format(R.Argument_out_of_range, "Priority", CacheItemPriority.Default, CacheItemPriority.NotRemovable));
    }
}

总结下源码中的逻辑,超时攻略的设置有如下几个规则:

  1. 纯属超时和滑动超时不能而且设有(那是前文中说两者二选1的原故)
  2. 比方滑动超时时间小于0或然超越一年也10分
  3. RemovedCallbackUpdateCallback不能够而且安装
  4. 缓存的Priority属性不可能是高于枚举范围(Default和NotRemovable)

布满式缓存

依据内部存款和储蓄器缓存的redis、memcached等。

基于文件nosql的Casssandra、mongodb等。

redis、memcached是主流的布满式内存缓存,也是选择和DB中间最大的缓存层。

nosql那类的其实不单单只是做缓存用了,完全用在某个非大旨业务的DB层了。

12.ImageLoader

此前的章节主要介绍了ImageLoader在加载图片时入眼选取的1对类,比如配置类ImageLoaderConfiguration、线程池管理者以及首要职分施行者ImageLoaderEngine、加载和显示图片职分LoadAndDisplayImageDisk、图片加载展现配置类DisplayImageOptions、图片下载器ImageDownloader、图片解码器ImageDocoder、缓存内部存储器类MemoryCache和DiskCache等等。接下来分析ImageLoader的选拔和现实做事流程,由于根本要求的类的分析过了,由此分析起来大致了数不尽。

这里要说一下, 为何只在第二步中加锁,第贰步未有加锁;

摘要

MemoryCache是.Net Framework
四.0从头提供的内部存款和储蓄器缓存类,使用该项目能够一本万利的在先后内部缓存数据并对此数据的有用进行有益的军管,借助该品种能够落成ASP.NET中常用的Cache类的一般成效,并且能够适应尤其助长的应用处境。在利用MemoryCache时平时有种种疑问,数据是怎么组织的?有未有相当大概率用更迅捷的团组织和平运动用方式?数据超时如何支配?为了知其所以然,本文中对此MemoryCache的规律和兑现格局开始展览了一语道破剖析,同时在解析的经过中上学到了数不胜数产业界成熟组件的统一策画观念,为之后的做事张开了特别开阔的笔触

正文面向的是.net
四.伍.壹的本子,在承接的.net版本中MemoryCache有稍许的两样,招待补充

小说内容较长,估算阅读时间壹钟头左右


MemoryCache类承继自ObjectCache抽象类,并且完成了IEnumerableIDisposable接口。跟ASP.NET常用的Cache类落成了一般的功能,不过MemoryCache特别通用。使用它的时候不要依赖于System.Web类库,并且在同多少个进度中能够动用MemoryCache创设八个实例。

在利用MemoryCache的时候一般会稍稍难题,那一个类到底内部数据是怎样组织的?缓存项的过期是哪些管理的?它为啥宣传本身是线程安全的?为了回应这几个主题素材,接下去借助Reference
Source对于MemoryCache的中间贯彻壹探毕竟。

全局锁,实例锁

图片 13

图片 14

  public static object obj1 = new object();
        public object GetMemberSigninDays2()
        {
            const int cacheTime = 5;
            const string cacheKey = "mushroomsir";

            var cacheValue = CacheHelper.Get(cacheKey);

            if (cacheValue != null)
                return cacheValue;

            //lock (obj1)         //全局锁
            //{
            //    cacheValue = CacheHelper.Get(cacheKey);
            //    if (cacheValue != null)
            //        return cacheValue;
            //    cacheValue = "395"; //这里一般是 sql查询数据。 例:395 签到天数
            //    CacheHelper.Add(cacheKey, cacheValue, cacheTime);
            //}
            lock (this)
            {
                cacheValue = CacheHelper.Get(cacheKey);
                if (cacheValue != null)
                    return cacheValue;

                cacheValue = "395"; //这里一般是 sql查询数据。 例:395 签到天数
                CacheHelper.Add(cacheKey, cacheValue, cacheTime);
            }
            return cacheValue;
        }

图片 15

图片 16

首先种:lock
(obj一)  是大局锁能够满意,但要为各种函数都宣示二个obj,不然在A、B函数都锁obj一时,必然会让个中3个不通。

其次种:lock (this)
 那一个锁当前实例,对其他实例无效,那那几个锁就没怎么效果了,当然使用单例方式的目标能够锁。

           
在当前实例中:A函数锁当前实例,别的也锁当前实例的函数的读写,也被堵塞,那种做法也不可取。

0.ImageLoader中动用的设计形式

单例情势

public static ImageLoader getInstance() {
    if (instance == null) {
        synchronized (ImageLoader.class) {
            if (instance == null) {
                instance = new ImageLoader();
            }
        }
    }
    return instance;
}

建造者形式

在ImageLoaderConfiguration和DisplayImageOptions都以使用了建造者情势,原因是它们有众多可选的性质能够被设置,使用建造者方式能够使得代码更清晰和硬朗。

PINCache 实现了下标脚本设置和收获方式, 即通过 id obj = cache[@”key”]
得到缓存值, cache[@”key”] = object设置缓存值.

总结

在本文中以MemoryCache对于数据的集团管制和采用为轴线,深刻的解析了MemoryCache对于一些普普通通使用有直接关联的效能的兑现方式。MemoryCache通过多个MemoryCacheStore对象将数据分散到不一致的HastTable中,并且接纳加锁的诀要在每一个Store内部保证操作是线程安全的,同时那种逻辑也在自然水准上更上一层楼了大局锁的品质难点。为了促成对于缓存项超时的军管,MemoryCache选用了二种分化的保管艺术,迥然差异,有效确定保证了缓存项的逾期管理的实惠,并在逾期后当即移除相关的缓存以释放内部存款和储蓄器财富。通过对于这个功用的解析,驾驭了MemoryCache内部的数据结构和数量查询格局,为后来的办事驾驭了过多有指点性意义的经验。

正文还会有再三再四的文章,敬请期待~~

总结

本篇参考了redis、Orleans的相关兑现。

倘使继续完善下去就是内部存款和储蓄器数据库的雏形,类似redis,比方增添删除key的打招呼回调,匡助越多的数据类型存款和储蓄。

 

 

磁盘缓存小结

相对于内部存款和储蓄器缓存,UIL中的磁盘缓存的框架轻巧的多,差不离分为三个磁盘缓存计策,两个是行使DiskLruCache作为底层完成的LruDiskCache,另三个是平素对文本类File进行操作。


  1. 万一钦点了当收到内部存储器警告时清理缓存,实施 removeAllObject 方法
  2. 加锁获得当前线程钦定的_didReceiveMemoryWaringBlock 回调
  3. 试行回调

惰性删除

除此而外按时删除以外,MemoryCache还得以达成了惰性删除的效果,那项成效的贯彻相对于定期删除轻松的多,而且12分的实用。

惰性删除是何许看头啊?简单的说正是在选拔缓存项的时等候法庭判决断缓存项是不是应该被删除,而不用等到被专用的清理职务清理。

前文描述过MemoryCache中数据的团协会措施,既然是在动用时接触的逻辑,由此惰性删除必然与MemoryCacheStore获取缓存的艺术有关。来看下它的Get情势的内部逻辑:

internal MemoryCacheEntry Get(MemoryCacheKey key) {
    MemoryCacheEntry entry = _entries[key] as MemoryCacheEntry;
    // 判断是否超时
    if (entry != null && entry.UtcAbsExp <= DateTime.UtcNow) {
        Remove(key, entry, CacheEntryRemovedReason.Expired);
        entry = null;
    }
    // 更新滑动超时的时间和相关的计数器
    UpdateExpAndUsage(entry);
    return entry;
}

从代码中得以见到,MemoryCacheStore查找到相关的key对应的缓存项现在,并不曾一贯回到,而是先反省了缓存项目标晚点时间。借使缓存项超时,则删除该项并赶回null。那正是MemoryCache中惰性删除的落到实处格局。

字符串锁

既然锁对象尤其,利用字符串的特色,直接锁缓存的key呢

图片 17

图片 18

    public object GetMemberSigninDays3()
        {
            const int cacheTime = 5;
            const string cacheKey = "mushroomsir";

            var cacheValue = CacheHelper.Get(cacheKey);
            if (cacheValue != null)
                return cacheValue;
            const string lockKey = cacheKey + "n(*≧▽≦*)n";

            //lock (cacheKey)
            //{
            //    cacheValue = CacheHelper.Get(cacheKey);
            //    if (cacheValue != null)
            //        return cacheValue;
            //    cacheValue = "395"; //这里一般是 sql查询数据。 例:395 签到天数
            //    CacheHelper.Add(cacheKey, cacheValue, cacheTime);
            //}
            lock (lockKey)
            {
                cacheValue = CacheHelper.Get(cacheKey);
                if (cacheValue != null)
                    return cacheValue;
                cacheValue = "395"; //这里一般是 sql查询数据。 例:395 签到天数
                CacheHelper.Add(cacheKey, cacheValue, cacheTime);
            }
            return cacheValue;
        }

图片 19

图片 20

先是种:lock
(cacheName)  有标题,因为字符串也是共享的,会卡住其余应用这些字符串的操作行为。
 

       
 具体请参见以前的博文 c#语言-二十四线程中的锁系统(1)。 

         因为字符串被集体语言运转库
(CLKuga)暂留,那表示全部程序中别的给定字符串都只有一个实例,所以才会用上面第贰种办法。

第二种:lock (lockKey)
 能够满意。其目的正是为着保障锁的粒度最小并且全局唯一性,只锁当前缓存的询问行为。

11.DiskCache

ImageLoader中另三个缓存是磁盘缓存,首先依旧先想起一下从前学过的ImageLoader的DiskLruCache的劳作原理是什么。

DiskLruCache工作原理:内部选取了accessOrder为true的LinkedHashMap作为数据的目录,因为DiskLruCache是以文件的款式储存数据的,由此LinkedHaspMap里面并不持有Bitmap对象,实际上持有的是贰个Entry内部类对象,该目的指向的是3个缓存文件,DiskLruCache对缓存数据的增进和获得其实就是对该缓存文件的写入和读取。DiskLruCache对缓存文件的写入和读取分别是透过中间类对象艾德itor和Snotshot的OutputStream和InputStream来促成多少的写入和读取。

接下去大家从源码中打听DiskCache整个的框架,跟MemoryCache同样,先从框架图动手调整全体承接结构。
专注图中还有1个DiskLruCache不属于传承结构,其实DiskLruCache正是前边学过的ImageLoader里面包车型客车DiskLruCache,然而UIL中也加多了此类,原因是LruDiskCache在里边选拔了DiskLruCache,简单的话正是在DiskLruCache外封装了一层。在ImageLoaderConfiguration中暗许使用的LruDiskCache

图片 21

DiskCache承袭结构图.PNG

/**
 *  递归检查并清除超过规定时间的缓存对象, TTL缓存操作
 */
- (void)trimToAgeLimitRecursively {

    Lock(_lock);
    NSTimeInterval ageLimit = _ageLimit;
    BOOL ttlCache = _ttlCache;
    Unlock(_lock);

    if (ageLimit == 0.0 || !ttlCache) {
        return ;
    }
    // 从当前时间开始, 往前推移 ageLimit(内存缓存对象允许存在的最大时间)
    NSDate *trimDate = [NSDate dateWithTimeIntervalSinceNow:-ageLimit];
    // 将计算得来的时间点之前的数据清除, 确保每个对象最大存在 ageLimit 时间
    [self trimMemoryToDate:trimDate];

    // ageLimit 之后在递归执行
    __weak typeof(self)weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(ageLimit * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        __strong typeof(weakSelf)strongSelf = weakSelf;
        [strongSelf trimToAgeLimitRecursively];
    });
}

MemoryCache超时机制

MemoryCache在装置缓存项时能够选用永世缓存只怕在逾期后自行消失。在那之中缓存计策能够挑选稳固超时时间和滑动超时时间的专擅1种(注意那三种超时战略只可以贰选1,下文中会解释为啥有诸如此类的平整)。

缓存项的过期管理机制是缓存系统(比方Redis和MemCached)的必需成效,Redis中有积极性检查和被动触及三种,MemCached选用的是颓唐触及检查,那么内部存款和储蓄器缓存MemoryCache内部是何许保管缓存项的晚点机制?

MemoryCache对于缓存项的逾期处理机制与Redis类似,也是有二种:定期删除和惰性删除。

O(1)LRU实现

概念个LRUCache<TValue>类,构造参数maxKeySize 来支配缓存最大额。

运用ConcurrentDictionary来作为大家的缓存容器,并能保障线程安全。

图片 22

图片 23

 public class LRUCache<TValue> : IEnumerable<KeyValuePair<string, TValue>>
    {
        private long ageToDiscard = 0;  //淘汰的年龄起点
        private long currentAge = 0;        //当前缓存最新年龄
        private int maxSize = 0;          //缓存最大容量
        private readonly ConcurrentDictionary<string, TrackValue> cache;
        public LRUCache(int maxKeySize)
        {
            cache = new ConcurrentDictionary<string, TrackValue>();
            maxSize = maxKeySize;
        }
    }

图片 24

图片 25

上面定义了 ageToDiscard、currentAge 那个自增值参数,功效是标记缓存列表中各样key的新旧程度。

兑现步骤如下:

每一回增添key时,currentAge自增并将currentAge值分配给这些缓存值的age,currentAge一向自增。

图片 26

图片 27

 public void Add(string key, TValue value)
        {
            Adjust(key);
            var result = new TrackValue(this, value);
            cache.AddOrUpdate(key, result, (k, o) => result);
        }
        public class TrackValue
        {
            public readonly TValue Value;
            public long Age;
            public TrackValue(LRUCache<TValue> lv, TValue tv)
            {
                Age = Interlocked.Increment(ref lv.currentAge);
                Value = tv;
            }
        }

图片 28

图片 29

在增多时,如超越最大数据,检查字典里是或不是有ageToDiscard年龄的key,如未有循环自增检查,有则删除、增添成功。

其ageToDiscard+maxSize= currentAge ,那样设计就能够在O(壹)下保证能够淘汰旧数据,而不是行使链表移动。 

图片 30

图片 31

  public void Adjust(string key)
        {
            while (cache.Count >= maxSize)
            {
                long ageToDelete = Interlocked.Increment(ref ageToDiscard);
                var toDiscard =
                      cache.FirstOrDefault(p => p.Value.Age == ageToDelete);
                if (toDiscard.Key == null)
                    continue;
                TrackValue old;
                cache.TryRemove(toDiscard.Key, out old);
            }
        }

图片 32

图片 33

赢得key的时候表示它又被人拜访,将流行的currentAge赋值给它,扩充它的年华:

图片 34

图片 35

  public TValue Get(string key)
        {
            TrackValue value=null;
            if (cache.TryGetValue(key, out value))
            {
                value.Age = Interlocked.Increment(ref currentAge);
            }
            return value.Value;
        }

图片 36

图片 37

10.MemoryCache

MemoryCache是落到实处内部存款和储蓄器缓存的类,不管是内部存储器缓存依然磁盘缓存,对于ImageLoader来讲都以基本成效,因为关乎着图片的加载速度,由此要深切精晓UIL中缓存的职业规律。

纪念一下,从前学过的ImageLoader的缓存落成,在在此以前的兑现个中,利用的是LruCache来落成的,而LruCache又是透过accessOrder为true的LinkedHashMap来完成LRU算法的。

在UIL中,不光光有基于LRU算法的LRUMemoryCache,还有FIFOLimitedMemoryCache、LargestLimitedMemoryCache、LimitedAgeMemoryCache、LRULimitedMemoryCache、UsingFreqLimitedMemoryCache和WeakMemoryCache基于各样缓存规则的MemoryCache,它们达成的主干正是Map,一般LRU算法的都以基于accessOrder为true的LinkedHashMap,其余用的是HashMap,当中WeakMemoryCache的值用的是WeakReference。它们的保证同步方法是透过Collections.synchronizedList(Map…)获取到联合队列。UIL中暗中认可配置的是LruMemoryCache.

MemoryCache还有多少个基础抽象落成类BaseMemoryCache和LimitedMemoryCache,而LimitedMemoryCache又是三番五次于BaseMemoryCache,全部项目标MemoryCache类都以得以达成MemoryCache接口也许接二连三于LimitedMemoryCache和BaseMrmoryCache那三者的,大概能够分为是绝非缓存大小限制和有缓存大小限制的,两者之间的区分正是,在增添新数据时若是缓存的尺寸当先大小限制阈值时是还是不是删除Map中的数据;而怎么着删除数据的平整又将有缓存大小限制的MemoryCache分为多少个类。上面是有着品类MemoryCache的归类表格。

MemoryCache子类 实现接口 Or 父类 有无大小限制 删除规则
LruMemoryCache MemoryCache LRU最近最少使用
LimitedAgeMemoryCache MemoryCache 存在超过限制时间的
FuzzyKeyMemoryCache MemoryCache put的时候有等价key的
LRULimitedMemoryCache LimitedMemoryCache LRU最近最少使用
FIFOLimitedMemoryCache LlimitedMemoryCache FIFO先入先出
LargestLimitedMemoryCache LimitedMemoryCache Largest最大的
UsingFreqLimitedMemoryCache LimitedMemoryCache 使用次数最少的
WeakMemoryCache BaseMemoryCache

下边是MemoryCache承袭结构图,能够支持我们知道整个MemoryCache的框架

图片 38

MemoryCache承继结构图.PNG

上面就来详细介绍多少个常用的MemoryCache以及它们的行事流程。

内部存款和储蓄器缓存大家要用个字典来存放数据,用个字典存放条数据的体量,用个字典来存放在每条数据的终极的修改时间

MemoryCacheEntry

此类型是缓存项在内部存款和储蓄器中真正的存在情势。它继续自MemoryCacheKey类型,并在此基础上加码了成百上千的属性和章程,举例推断是或不是过期等。

先来看下该类的欧洲经济共同体情况:

图片 39

看来,MemoryCacheEntry中的属性和办法重要为3类:

  1. 缓存的剧情有关,如Key、Value
  2. 缓存内容的情状相关,如State、HasExpiration方法等
  3. 缓存内容的相干事件有关,如CallCacheEntryRemovedCallback方法、CallNotifyOnChanged方法等

明白了MemoryCache中数据的集体章程后,可以协理了然数据是怎么样从MemoryCache中被一步步询问获得的。

总结

文中说的梗塞其余函数指的是,并发情况下锁同一对象,例如1个函数锁A对象,其它的函数就不能够不等待A对象的锁释放后技巧重新进锁。

有关创新缓存,能够单开3个线程去专门跑缓存更新,图方便的话扔线程池里面就可以。

实则项目中,缓存层框架的包装往往要复杂的多,假使并发量极小,那样写反而会增添代码的复杂度,具体要依据真实情形来选用。
   

 

**************************************************************

 

LruMemoryCache

先是是具有项目MemoryCache的接口MemoryCache.java。
它的成效至关心重视借使向外提供接口,外界入眼通过该接口增加、获取数据,不爱护内部的现实性完成。
接口异常粗略,就几其中央的增删查方法。

public interface MemoryCache {

    boolean put(String key, Bitmap value);

    Bitmap get(String key);

    Bitmap remove(String key);

    Collection<String> keys();

    void clear();
}

然后介绍首要完结MemoryCache接口的大4的MemoryCache类。

率先是UIL暗许使用的LruMemoryCache。
能够看出来,完毕的规律跟LruCache是十二分相似的。都以应用了accessOrder为true的LinkedHashMap来实现LRU算法,在超越体积之后将去除Map中近日至少使用的数额。其余的操作大多数都以经过LinkedHashMap的同名操作达成的。

此处提前分析一下,既然都有体积限制,都以LRU算法,那么LruMemoryCache和LRULimitedMemoryCache有如何界别?
答:原理上是同样的,只不过删除的次第不雷同:LruMemoryCache在历次的put之后才调用了trimToSize()保证数据不当先限制大小;LRULimitedMemoryCache是承接保险数据不当先限制大小之后才加多进LinkedHashMap当中。
背后再详细分析LRULimitedMemoryCache的现实性达成原理,来探视落成和LruMemoryCache是有如何分别。

public class LruMemoryCache implements MemoryCache {

    private final LinkedHashMap<String, Bitmap> map;

    private final int maxSize;
    /** Size of this cache in bytes */
    private int size;

    /** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */
    public LruMemoryCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
    }

    /**
     * Returns the Bitmap for key if it exists in the cache. If a Bitmap was returned, it is moved to the head
     * of the queue. This returns null if a Bitmap is not cached.
     */
    @Override
    public final Bitmap get(String key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        synchronized (this) {
            return map.get(key);
        }
    }

    /** Caches Bitmap for key. The Bitmap is moved to the head of the queue. */
    @Override
    public final boolean put(String key, Bitmap value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        synchronized (this) {
            size += sizeOf(key, value);
            Bitmap previous = map.put(key, value);
            if (previous != null) {
                size -= sizeOf(key, previous);
            }
        }

        trimToSize(maxSize);
        return true;
    }

    /**
     * Remove the eldest entries until the total of remaining entries is at or below the requested size.
     *
     * @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements.
     */
    private void trimToSize(int maxSize) {
        while (true) {
            String key;
            Bitmap value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize || map.isEmpty()) {
                    break;
                }

                Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= sizeOf(key, value);
            }
        }
    }

    /** Removes the entry for key if it exists. */
    @Override
    public final Bitmap remove(String key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        synchronized (this) {
            Bitmap previous = map.remove(key);
            if (previous != null) {
                size -= sizeOf(key, previous);
            }
            return previous;
        }
    }

    @Override
    public Collection<String> keys() {
        synchronized (this) {
            return new HashSet<String>(map.keySet());
        }
    }

    @Override
    public void clear() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }

    /**
     * Returns the size Bitmap in bytes.
     * 
     * An entry's size must not change while it is in the cache.
     */
    private int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }

    @Override
    public synchronized final String toString() {
        return String.format("LruCache[maxSize=%d]", maxSize);
    }
}

MemoryCache 中实际贯彻

MemoryCacheStore

该项目是MemoryCache内部真正用于承载数据的容器。它从来管制着程序的内部存款和储蓄器缓存项,既然要承载数据,那么该项目中必将有些属性与数码存款和储蓄有关。其具体表现是:MemoryCache中有多少个门类为HashTable的村办属性_entries,在该属性中贮存了它所管理的全数缓存项。

Hashtable _entries = new Hashtable(new MemoryCacheEqualityComparer());

当须要去MemoryCache中获取数据的时候,MemoryCache所做的首先步正是寻觅存储被搜寻key的MemoryCacheStore对象,而并非是咱们想象中的直接去有些Dictionary品种只怕HashTable体系的对象中央直机关接寻找结果。

在MemoryCache中检索MemoryCacheStore的艺术也挺风趣,首要的逻辑在MemoryCache的GetStore方法中,源码如下(为了知道方便扩张了壹部分注释):

internal MemoryCacheStore GetStore(MemoryCacheKey cacheKey) {
    int hashCode = cacheKey.Hash;//获取key有关的hashCode值
    if (hashCode < 0) {
        //避免出现负数
        hashCode = (hashCode == Int32.MinValue) ? 0 : -hashCode;
    }
    int idx = hashCode & _storeMask;
    //_storeMask跟CPU的数目一致,通过&进行按位与计算获取到对应的Store
    //本处代码是.NET 4.5的样子,在.NET Framework 4.7.2版本已经改成了使用%进行取余计算,对于正整数来说实际结果是一样的。
    return _stores[idx];
}

既然如此只怕存在八个MemoryCacheStore对象,那么就供给有一定的平整来决定每种Store中贮存的始末。从源码中能够看出,MemoryCache使用的是CPU的核数作为掩码,并选拔该掩码和key的hashcode来计量缓存项的着落地,确实是大约而敏捷。

DB缓存

那1层DB首假如缓存由原本数据测算出的结果,从而制止由Web程序通过SQL或在接纳中一贯总结。

本来也足以把计算好的数量,存款和储蓄到redis中当缓存。

图片 40

LRULimitedMemoryCache

直白完成MemoryCache接口的只要求精通LruMemoryCache就丰硕了,本节主要介绍承继与LimitedMemoryCache的LRULimitedMemoryCache和FIFOLimitedMemoryCache。

那边LRULimitedMemoryCache的后续结构有三层(除去MemoryCache接口),每层结构都有本身的效果,由此未曾知道通晓承接结构的话会促成思维混乱,不能清楚LRULimitedMemoryCache的办事原理,所以先来看一下无冕结构。

图片 41

LimitedMemoryCache承袭结构图.PNG

从图中可以见见,从BaseMemoryCache到LRULimitedMemoryCache壹共有3层,所以难点就出去了,为啥要如此多层,像LruMemoryCache这样直接达成MemoryCache不可以呢?
本来能够,然而那样写就不符合类的单壹任务供给,而且LRULimitedMemoryCache和FIFOLimitedMemoryCache那五个类只是只有删除规则不等同,借使LRULimitedMemoryCache直接促成MemoryCache的话,那么FIFOLimitedMemoryCache也要落到实处该类,而且会有大多的代码和LRU是如出1辙的,由此将一齐的一些虚幻出来,使得各样类的职分单一,降低耦合。

在那三层继承结构中,每壹层的干活是:
BaseMemoryCache:有1个Map的里边类,该Map的效应是提供最尾部的缓存功效,最后存款和储蓄数据和获取数据实际都以BaseMemoryCache完成的,注意该Map的value并不是四个Bitmap类,而是三个Bitmap的Reference类,日常会传入Bitmap的WeakReference,那样的法力是Map中的value随时都能够GC回收;
LimitedMemoryCache:该类的成效是确定保证缓存大小不超过阈值。类内部有三个List容器类用于强引用存款和储蓄Bitmap,通过该List类来保管科学提供容量限制的效劳,即首先在put方法中判定当前囤积的多少是不是抢先阈值,要是是则调用抽象方法removeNext()依据一定规则删除,再利用List来删除的Bitmap,假设List删除成功则证实缓存过该Bitmap,然后再转移缓存大小;
LRULimitedMemoryCache和FIFOLimitedMemoryCache等别的类的LimitedMemoryCache:用于提供删除规则即落到实处LimitedMemoryCache的removeNext(),内部有用于落成各种规则的数据结构,比方LRU利用accessOrder为true的LinkedHashMap,FIFO利用的是二个LinkedList。

上面分别介绍那三层组织。
1.BaseMemoryCache
先是先介绍BaseMemoryCache,因为LimitedMemoryCache承袭于它,下边是它的实现源码。

关心的重要有:

public abstract class BaseMemoryCache implements MemoryCache {

    /** Stores not strong references to objects */
    private final Map<String, Reference<Bitmap>> softMap = Collections.synchronizedMap(new HashMap<String, Reference<Bitmap>>());

    @Override
    public Bitmap get(String key) {
        Bitmap result = null;
        Reference<Bitmap> reference = softMap.get(key);
        if (reference != null) {
            result = reference.get();
        }
        return result;
    }

    @Override
    public boolean put(String key, Bitmap value) {
        softMap.put(key, createReference(value));
        return true;
    }

    @Override
    public Bitmap remove(String key) {
        Reference<Bitmap> bmpRef = softMap.remove(key);
        return bmpRef == null ? null : bmpRef.get();
    }

    @Override
    public Collection<String> keys() {
        synchronized (softMap) {
            return new HashSet<String>(softMap.keySet());
        }
    }

    @Override
    public void clear() {
        softMap.clear();
    }

    /** Creates {@linkplain Reference not strong} reference of value */
    protected abstract Reference<Bitmap> createReference(Bitmap value);
}

2.LimitedMemoryCache

此前早已说过了LimitedMemoryCache的成效正是贯彻幸免缓存当先阈值的轻重,所以关怀此类的关怀点在什么促成限制缓存大小。

由源码能够见见:

public abstract class LimitedMemoryCache extends BaseMemoryCache {

    private static final int MAX_NORMAL_CACHE_SIZE_IN_MB = 16;
    private static final int MAX_NORMAL_CACHE_SIZE = MAX_NORMAL_CACHE_SIZE_IN_MB * 1024 * 1024;

    private final int sizeLimit;

    private final AtomicInteger cacheSize;

    /**
     * Contains strong references to stored objects. Each next object is added last. If hard cache size will exceed
     * limit then first object is deleted (but it continue exist at {@link #softMap} and can be collected by GC at any
     * time)
     */
    private final List<Bitmap> hardCache = Collections.synchronizedList(new LinkedList<Bitmap>());

    /** @param sizeLimit Maximum size for cache (in bytes) */
    public LimitedMemoryCache(int sizeLimit) {
        this.sizeLimit = sizeLimit;
        cacheSize = new AtomicInteger();
        if (sizeLimit > MAX_NORMAL_CACHE_SIZE) {
            L.w("You set too large memory cache size (more than %1$d Mb)", MAX_NORMAL_CACHE_SIZE_IN_MB);
        }
    }

    /**
     * 实现缓存大小限制的关键
     */
    @Override
    public boolean put(String key, Bitmap value) {
        boolean putSuccessfully = false;
        // Try to add value to hard cache
        int valueSize = getSize(value);
        int sizeLimit = getSizeLimit();
        int curCacheSize = cacheSize.get();
        if (valueSize < sizeLimit) {
            // 在添加数据后大于限制大小时则删除removeNext()返回的Bitmap
            while (curCacheSize + valueSize > sizeLimit) {
                Bitmap removedValue = removeNext();
                if (hardCache.remove(removedValue)) {
                    curCacheSize = cacheSize.addAndGet(-getSize(removedValue));
                }
            }
            hardCache.add(value);
            cacheSize.addAndGet(valueSize);

            putSuccessfully = true;
        }
        // 最后一定要将数据存到sofeMap中
        super.put(key, value);
        return putSuccessfully;
    }

    @Override
    public Bitmap remove(String key) {
        Bitmap value = super.get(key);
        if (value != null) {
            if (hardCache.remove(value)) {
                cacheSize.addAndGet(-getSize(value));
            }
        }
        return super.remove(key);
    }

    @Override
    public void clear() {
        hardCache.clear();
        cacheSize.set(0);
        super.clear();
    }

    protected int getSizeLimit() {
        return sizeLimit;
    }

    protected abstract int getSize(Bitmap value);

    protected abstract Bitmap removeNext();
}

3.LRULimitedMemoryCache

由地点可知,数据的存取获取以及缓存大小的限量在后面两层结构已经得以完结了,此时LRULimitedMemoryCache以及FIFOLimitedMemoryCache等类的做事就大致大多了,只必要制订相应的删除规则removeNext()就行了,而删除钦赐的多少驱动类本人也急需用三个数据结构存款和储蓄数据,究竟你未有多少怎么显明要刨除的数量是哪些啊是啊,上面是源码。

关心的首要:

public class LRULimitedMemoryCache extends LimitedMemoryCache {

    private static final int INITIAL_CAPACITY = 10;
    private static final float LOAD_FACTOR = 1.1f;

    /** Cache providing Least-Recently-Used logic */
    private final Map<String, Bitmap> lruCache = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(INITIAL_CAPACITY, LOAD_FACTOR, true));

    /** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */
    public LRULimitedMemoryCache(int maxSize) {
        super(maxSize);
    }

    @Override
    public boolean put(String key, Bitmap value) {
        if (super.put(key, value)) {
            lruCache.put(key, value);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public Bitmap get(String key) {
        lruCache.get(key); // call "get" for LRU logic
        return super.get(key);
    }

    @Override
    public Bitmap remove(String key) {
        lruCache.remove(key);
        return super.remove(key);
    }

    @Override
    public void clear() {
        lruCache.clear();
        super.clear();
    }

    @Override
    protected int getSize(Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }

    @Override
    protected Bitmap removeNext() {
        Bitmap mostLongUsedValue = null;
        synchronized (lruCache) {
            Iterator<Entry<String, Bitmap>> it = lruCache.entrySet().iterator();
            if (it.hasNext()) {
                Entry<String, Bitmap> entry = it.next();
                mostLongUsedValue = entry.getValue();
                it.remove();
            }
        }
        return mostLongUsedValue;
    }

    @Override
    protected Reference<Bitmap> createReference(Bitmap value) {
        return new WeakReference<Bitmap>(value);
    }
}

4.FIFOLimitedMemoryCache

看完下面之后,FIFOLimiedMemoryCache更便于明白了,只是removeNext()重回的数据跟LRU不雷同而已,具体看下边包车型大巴源码

public class FIFOLimitedMemoryCache extends LimitedMemoryCache {

    private final List<Bitmap> queue = Collections.synchronizedList(new LinkedList<Bitmap>());

    public FIFOLimitedMemoryCache(int sizeLimit) {
        super(sizeLimit);
    }

    @Override
    public boolean put(String key, Bitmap value) {
        if (super.put(key, value)) {
            queue.add(value);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public Bitmap remove(String key) {
        Bitmap value = super.get(key);
        if (value != null) {
            queue.remove(value);
        }
        return super.remove(key);
    }

    @Override
    public void clear() {
        queue.clear();
        super.clear();
    }

    @Override
    protected int getSize(Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }

    @Override
    protected Bitmap removeNext() {
        return queue.remove(0);
    }

    @Override
    protected Reference<Bitmap> createReference(Bitmap value) {
        return new WeakReference<Bitmap>(value);
    }
}
/**
 *  收到内存警告操作
 */
- (void)didReceiveMemoryWarningNotification:(NSNotification *)notify {
    1.
    if (self.removeAllObjectOnMemoryWoring) {
        [self removeAllObject:nil];
    }

    __weak typeof(self)weakSelf = self;
    AsyncOption(
                __strong typeof(weakSelf)strongSelf = weakSelf;
                if (!strongSelf) return ;
                2.
                Lock(_lock);
                WNMemoryCacheBlcok didReceiveMemoryWarningBlock = strongSelf->_didReceiveMemoryWarningBlock;
                Unlock(_lock);
                3.
                if (didReceiveMemoryWarningBlock) {
                    didReceiveMemoryWarningBlock(strongSelf);
                }
    );
}

遍布式缓存(Redis、Memcached)情状下:

譬如说在几10台服务器缓存时,单刷满缓存都急需广大学一年级段时间。

这种预热就千头万绪一些,有的会单写个应用程序去跑,也有些会单写套框架机制去管理(更智能化)。

其目标是在系统上线从前,全部的缓存都预先加载落成。

ImageLoader使用

ImageLoader的非常粗大略,首要分为三步

  1. 布局加载图片的参数类ImageLoaderConfiguration并创制Imageloader对象
  2. 配备展现图片用的参数类DisplayImageOptions
  3. 运用displayImage()呈现图片

先是步,配置并创建ImageLoader对象
末端注释的是Configuration的可选项和局地私下认可项,能够见到某些加载图片的必不可缺暗中同意项已经能够让ImageLoader平常专门的学业了,比方taskExecutor、diskCache、memoryCache、downloader、decoder和defaultDisplayImageOptions等等。

//Set the ImageLoaderConfigutation
ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(getApplicationContext())
        .threadPriority(Thread.NORM_PRIORITY - 2)
        .denyCacheImageMultipleSizesInMemory()
        .diskCacheFileNameGenerator(new Md5FileNameGenerator())
        .diskCacheSize(50 * 1024 * 1024)
        .tasksProcessingOrder(QueueProcessingType.LIFO)
        .writeDebugLogs()
        .build();

//Initial ImageLoader with ImageLoaderConfiguration
ImageLoader.getInstance().init(configuration);

/*
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
    .memoryCacheExtraOptions(480, 800) // default = device screen dimensions
    .diskCacheExtraOptions(480, 800, null)
    .taskExecutor(...)// default
    .taskExecutorForCachedImages(...)
    .threadPoolSize(3) // default
    .threadPriority(Thread.NORM_PRIORITY - 2) // default
    .tasksProcessingOrder(QueueProcessingType.FIFO) // default
    .denyCacheImageMultipleSizesInMemory()
    .memoryCache(new LruMemoryCache(2 * 1024 * 1024))
    .memoryCacheSize(2 * 1024 * 1024)
    .memoryCacheSizePercentage(13) // default
    .diskCache(new UnlimitedDiskCache(cacheDir)) // default
    .diskCacheSize(50 * 1024 * 1024)
    .diskCacheFileCount(100)
    .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
    .imageDownloader(new BaseImageDownloader(context)) // default
    .imageDecoder(new BaseImageDecoder()) // default
    .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
    .writeDebugLogs()
    .build();
/**/

其次步,配置展现图片用的参数类DislayImageOptions
从类名就能够料定出该类的法力是让ImageLoader按须要显得图片,跟Configuration相同,Options也有那个可选的布局参数,并且有个别彰显图片的不能缺少参数已经被早先化了,比方displayer用于控件展现图片和handler传回主线程操作。
但值得注意的是,在Options中,cacheInMemory和cacheOnDisk私下认可是false,由此很有不可缺少在先后中手动将它们将设置成true,如上边代码所示。

DisplayImageOptions options = new DisplayImageOptions.Builder()
        .cacheInMemory(true)    //缓存的这两步很有必要
        .cacheOnDisk(true)
        .considerExifParams(true)
        .displayer(new CircleBitmapDisplayer(Color.WHITE, 5))
        .build();
/*
DisplayImageOptions options = new DisplayImageOptions.Builder()
    .showImageOnLoading(R.drawable.ic_stub) // resource or drawable
    .showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable
    .showImageOnFail(R.drawable.ic_error) // resource or drawable
    .resetViewBeforeLoading(false)  // default
    .delayBeforeLoading(1000)
    .cacheInMemory(false) // default
    .cacheOnDisk(false) // default
    .preProcessor(...)
    .postProcessor(...)
    .extraForDownloader(...)
    .considerExifParams(false) // default
    .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default
    .bitmapConfig(Bitmap.Config.ARGB_8888) // default
    .decodingOptions(...)
    .displayer(new SimpleBitmapDisplayer()) // default
    .handler(new Handler()) // default
    .build();      
/**/    

其三步,使用displayImage()呈现图片
调用displayImage()以前要拿走到ImageLoader的实例,因为ImageLoader接纳单例格局,由此ImageLoader的实例是经过IamgeLoader的静态方法getInstance()获取的。
displayImage()有很两种重载方法,这里只呈现二个,前面包车型地铁解说是具有displayImage()的版本。
由displayImage的参数中得以看看最重大的三个参数正是imageUri和imageView,也正是要体现的图形的uri地址和展示图片的控件,这里也反映出了ImageLoader的最本色的行事,那正是将图纸从uri中加载到控件中。

注:不像后面Configuration和Optins配置1遍就够了,displayImage()方法在每趟加载展现图片时都应当调用三次,由此普通该格局运用在ListView的艾达pter的getView个中,因为getView中能够取获得目前要显得图片的控件,并且列表滑动就能够触发getView方法,因而只要求在getView中查对应的ImageView传送给displayImage就足以了

ImageLoader.getInstance().displayImage(imageUri, imageView, options, imageLoadingListener);

/*
//所有displayImage的方法,前面一部分是针对ImageAware的,后面一部分是针对ImageView的,也就是我们在开发中所使用到的,其实实现是利用了前面的方法

displayImage(String uri, ImageAware imageAware)
displayImage(String uri, ImageAware imageAware, ImageLoadingListener listener)
displayImage(String uri, ImageAware imageAware, DisplayImageOptions options)
displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
        ImageLoadingListener listener) 
displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
        ImageLoadingListener listener, ImageLoadingProgressListener progressListener)
displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
        ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)

displayImage(String uri, ImageView imageView)
displayImage(String uri, ImageView imageView, ImageSize targetImageSize)
displayImage(String uri, ImageView imageView, DisplayImageOptions options)
displayImage(String uri, ImageView imageView, ImageLoadingListener listener)
displayImage(String uri, ImageView imageView, DisplayImageOptions options,
        ImageLoadingListener listener)
displayImage(String uri, ImageView imageView, DisplayImageOptions options,
        ImageLoadingListener listener, ImageLoadingProgressListener progressListener)
/**/

到此ImageLoader的使用方法就得了了,是否很轻便,只必要3步,以至壹旦运用的都以暗中认可的安排,那只必要开头化ImageLoader并调用displayImage就可以了。下边具体分析ImageLoader的职业流程。

- (instancetype)init {
    if (self = [super init]) {
        1.
        NSString *queueName = [NSString stringWithFormat:@"%@.%p",WannaMemoryCachePrefix,self];
        // 以指定的名称, 创建并发队列, 用于异步缓存数据
        _concurrentQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_CONCURRENT);

        2.
        _removeAllObjectOnMemoryWoring = YES;
        _removeAllObjectOnEnteringBackground = YES;

        3.
        _dictionary = [NSMutableDictionary dictionary];
        _dates = [NSMutableDictionary dictionary];
        _costs = [NSMutableDictionary dictionary];

        4. 
        _willAddObjectBlock = nil;
        _willRemoveObjectBlock = nil;
        _willRemoveAllObjectsBlock = nil;

        _didAddObjectBlock = nil;
        _didRemoveObjectBlock = nil;
        _didRemoveAllObjectsBlock = nil;

        _didReceiveMemoryWarningBlock = nil;
        _didEnterBackgroundBlock = nil;

        5. 
        _ageLimit = 0.0;
        _costLimit = 0;
        _totalCost = 0;

        6.
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0 && !TARGET_OS_WATCH
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(didReceiveEnterBackgroundNotification:)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(didReceiveMemoryWarningNotification:)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];

#endif
    }
    return self;
}

基本写法

为了方便演示,这里运用Runtime.Cache做缓存容器,并定义个简易操作类。如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class CacheHelper
   {
       public static object Get(string cacheKey)
       {
           return HttpRuntime.Cache[cacheKey];
       }
       public static void Add(string cacheKey, object obj, int cacheMinute)
       {
           HttpRuntime.Cache.Insert(cacheKey, obj, null, DateTime.Now.AddMinutes(cacheMinute),
               Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
       }
   }

 简单读取:

1
2
3
4
5
6
7
8
9
10
11
12
13
public object GetMemberSigninDays1()
    {
        const int cacheTime = 5;
        const string cacheKey = "mushroomsir";
 
        var cacheValue = CacheHelper.Get(cacheKey);
        if (cacheValue != null)
            return cacheValue;
 
        cacheValue = "395"//这里一般是 sql查询数据。 例:395 签到天数
        CacheHelper.Add(cacheKey, cacheValue, cacheTime);
        return cacheValue;
    }

在档期的顺序中,有众多那样写法,那样写并没错,但在并发量上来后就便于出难点。

/**
 *  程序进入后台操作
 */
- (void)didReceiveEnterBackgroundNotification:(NSNotification *)notify {
    if (self.removeAllObjectOnEnteringBackground) {
        [self removeAllObject:nil];
    }
    __weak typeof(self)weakSelf = self;
    AsyncOption(
               __strong typeof(weakSelf)strongSelf = weakSelf;
               if (!strongSelf) return ;
               Lock(_lock);
               WNMemoryCacheBlcok didEnterBackgroundBlock = strongSelf->_didEnterBackgroundBlock;
               Unlock(_lock);
               if (didEnterBackgroundBlock) {
                   didEnterBackgroundBlock(strongSelf);
               }
    );

}

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图