彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(二)缓存

Android高效图片异步加载实战:ListView、GridView与ViewPager集成 UIL的强大之处在于高度可定制。你可以根据业务需求精细调优每一个环节。你可能会问:UIL现在都不维护了,为什么还要花时间学它?答案很简单:思想永不过时。尽管Glide、Picasso等新框架在API易用性和功能丰富度上更胜一筹,但它们的核心理念——异步加载、三级缓存、内存控制、请求调度——全都源自UIL这类早期探索者。理解UIL,就像是学习计算机组成原理。你知道现在的CPU有多复杂,但只有搞懂了冯·诺依曼架构,才能真正明白计算机是怎么工作的。所以,下次当你轻松写下。 阅读详情

彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(一)基本使用

在UIL框架中,最重要的技术莫过于缓存技术的使用了,缓存技术的使用不仅仅体现在UIL中,其实在Gallery里面也有很多的使用;

  1. 首先我们从内存缓存MemoryCache 说起

在初始化ImageLoader的时候,我们可以通过如下代码配设置使用何种内存缓存,以下代码表示使用LruMemoryCache来进行内存缓存

config.memoryCache(new LruMemoryCache(10));

默认的UIL框架中,内存缓存是什么

在ImageLoaderConfiguration.java中,有如下代码

			if (memoryCache == null) {
				memoryCache = DefaultConfigurationFactory.createMemoryCache(context, memoryCacheSize);
			}
	public static MemoryCache createMemoryCache(Context context, int memoryCacheSize) {
		if (memoryCacheSize == 0) {
			ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
			int memoryClass = am.getMemoryClass();
			if (hasHoneycomb() && isLargeHeap(context)) {
				memoryClass = getLargeMemoryClass(am);
			}
			memoryCacheSize = 1024 * 1024 * memoryClass / 8;
		}
		return new LruMemoryCache(memoryCacheSize);
	}

因此UIL框架默认采用的内存缓存为LruMemoryCache

LruMemoryCache的核心是采用了最近最少使用算法(Lru),其大小为app可用内存的1/8, 实现了MemoryCache接口,那这个Lru是如何实现的,深拨下源码便可知晓

package com.nostra13.universalimageloader.cache.memory.impl;

import android.graphics.Bitmap;

import com.nostra13.universalimageloader.cache.memory.MemoryCache;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * A cache that holds strong references to a limited number of Bitmaps. Each time a Bitmap is accessed, it is moved to
 * the head of a queue. When a Bitmap is added to a full cache, the Bitmap at the end of that queue is evicted and may
 * become eligible for garbage collection.<br />
 * <br />
 * <b>NOTE:</b> This cache uses only strong references for stored Bitmaps.
 *
 * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
 * @since 1.8.1
 */
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 {@code 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 {@code Bitmap} for {@code 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 {@code 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 {@code Bitmap} in bytes.
	 * <p/>
	 * 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);
	}
}

LruMemoryCache.java核心作用是:

LruMemoryCache保存了限量的bitmap,并且每一张bitmap都保持着强引用关系;当把一个bitmap添加到满了的缓存的时候,在缓存末尾的图片会被删除,并且将被垃圾回收;而每一次访问一个bitmap的时候,会将该bitmap添加到队首;

LruMemoryCache.java核心成员是:

   . LinkedHashMap<String, Bitmap> map 实现Lru算法的关键
   . int maxSize 缓存的最大大小
   . int size 当前缓存的大小

LruMemoryCache.java代码分析
   第36行,构造LinkedHashMap,在构造的时候,将LinkedHashMap的参数accessOrder设置为true,其是Lru的关键所在,其表示map里面元素的排序顺序,为true的时候表示按照访问顺序来排序,为false的时候表示按照插入的顺序来访问;(LinkedHashMap这里不做特别分析,后面会在集合/多线程章节中详细分解)
  第44行,get方法通过传入的key,获得对应的bitmap
  第56行,put方法就是传入key和bitmap,放入到map中,放入的时候会做如下操作
    首先根据sizeOf方法获得该图片的具体的字节大小,计算当前缓存的当前总大小,即62行的方法的含义
    然后将该bitmap存入到map中,我们知道map的put方法中,key可以相同,但是value会覆盖前一个值,map的put方法返回的当前key的前一个对象,即63行的方法的含义
    如果通过map的put方法返回的对象已经存在,则我们需要重新计算当前缓存大小,即减去该图片的字节,即64,65行的方法的含义
    即将你存入一个对象,那么就得考虑是否超过了缓存的最大大小,顾通过trimToSize方法,删除最旧的对象,直到剩余总数等于或低于请求的大小。因为LinkedHashMap里面的数据是有序的,而且创建LinkedHashMap的时候,数据排序是以访问顺序来定的,即你只要使用了map的里面的元素(通过get方法)那么这个元素就会被排至末尾(Lru算法),顾在这个map中,排在对头的元素就是使用次数就是最少的;所以trimToSize方法使用了迭代器的方式,从头一个元素开始删除,直到大小小于等于maxSize
  第105行,根据key值移除map中的对象,并重新计算当前缓存大小;


  1. 我们在看下硬盘缓存DiskCache

在初始化ImageLoader的时候,我们可以通过如下代码配设置使用何种硬盘缓存

config.diskCache(... ...)

默认的UIL框架中,硬盘缓存使用的是什么类?硬盘缓存是如何保存图片的?

在ImageLoaderConfiguration.java中,有如下代码

			if (diskCache == null) {
				if (diskCacheFileNameGenerator == null) {
					diskCacheFileNameGenerator = DefaultConfigurationFactory.createFileNameGenerator();
				}
			}

在这段代码中,我们创建了FileNameGenerator的实例diskCacheFileNameGenerator,该类的作用是命名保存的图片,那在硬盘中一张图片的命名形式是怎么样的

	public static FileNameGenerator createFileNameGenerator() {
		return new HashCodeFileNameGenerator();
	}
	
public class HashCodeFileNameGenerator implements FileNameGenerator {
	@Override
	public String generate(String imageUri) {
		return String.valueOf(imageUri.hashCode());
	}
}

这个类比较简单,是根据图片的url的哈希值来进行命名的。有了文件命名方式,我们自然就可以很好的创建硬盘缓存了,接着看ImageLoaderConfiguration.java中的一段代码

diskCache = DefaultConfigurationFactory
						.createDiskCache(context, diskCacheFileNameGenerator, diskCacheSize, diskCacheFileCount);

	public static DiskCache createDiskCache(Context context, FileNameGenerator diskCacheFileNameGenerator,
			long diskCacheSize, int diskCacheFileCount) {
		File reserveCacheDir = createReserveDiskCacheDir(context);
		if (diskCacheSize > 0 || diskCacheFileCount > 0) {
			File individualCacheDir = StorageUtils.getIndividualCacheDirectory(context);
			try {
				return new LruDiskCache(individualCacheDir, reserveCacheDir, diskCacheFileNameGenerator, diskCacheSize,
						diskCacheFileCount);
			} catch (IOException e) {
				L.e(e);
				// continue and create unlimited cache
			}
		}
		File cacheDir = StorageUtils.getCacheDirectory(context);
		return new UnlimitedDiskCache(cacheDir, reserveCacheDir, diskCacheFileNameGenerator);
	}

可见我们会首先尝试创建LruDiskCache硬盘缓存,如果LruDiskCache创建失败,我们会创建UnlimitedDiskCache硬盘缓存;

    LruDiskCache实现了DiskCache接口,DiskCache是硬盘缓存的统一接口,LruDiskCache也是基于Lru算法实现的一个硬盘缓存,同时他的缓存是有最大限度的,在彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(一)基本使用中,我们通过如下方法设置了最大硬盘存储空间为50M

config.diskCacheSize(50 * 1024 * 1024);

    UnlimitedDiskCache继承自BaseDiskCache类,BaseDiskCache实现了DiskCache接口,他没有采用Lru算法,也没有限制最大缓存大小;针对这个类我们就不做单独分析了,有兴趣的自己看下源码

总结:在硬盘缓存中
   . 默认缓存到硬盘缓存的实现类是LruDiskCache.java,他采用了Lru算法保证按访问顺序排列,并限制了最大存储大小
   . 默认缓存到硬盘的图片的命名方式是以图片URL的哈希值来命名的

我们来看下LruDiskCache.java

LruDiskCache.java核心成员是:

   . DiskLruCache cache,真正的硬盘缓存类,LruDiskCache通过操作DiskLruCache来实现对象的增删改查操作
   . FileNameGenerator fileNameGenerator,如何命名缓存到硬盘中的图片,默认为图片URL的哈希值
   . Bitmap.CompressFormat compressFormat,图片的压缩格式,默认为png,因为png是无损压缩
   . int compressQuality ,图片的压缩质量,默认为100,compressQuality和compressFormat会被bitmap的compress方法所使用;bitmap的compress的圆形是

compress(CompressFormat format, int quality, OutputStream stream)

  其含义是按照format指定的图片格式以及quality指定的画质,将图片转换为stream输出流
举个例子,如果compressFormat为png,compressQuality的值为60,则这张图片会按照png的图片格式,保留60%的图像品质写入到输出流中,当然这种方式会使得图片失真;
  compress方法压缩的是存储大小,即你放到disk上的大小,他并没有改变图片占用内存的大小,不过我们可以通过BitmapFactory.Options options = new BitmapFactory.Options()的方式改变图片占用内存的大小

LruDiskCache.java核心方法:

  . 初始化

	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);
			}
		}
	}

通过调用DiskLruCache的全局静态方法完成DiskLruCache实例的创建cache

  . 根据图片URL获得存储的文件

	@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();
			}
		}
	}

可见硬盘缓存中在得到DiskLruCache实例之后,LruDiskCache变可以拿到DiskLruCache的快照类,通过快照类就可以找到对应的保存的文件

  . 根据图片URL将图片缓存起来

	@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;
	}

可见硬盘缓存中在得到DiskLruCache实例之后,LruDiskCache变可以拿到DiskLruCache的Editor类,通过Editor类就可以将对应的输入流写到DiskLruCache的Entry中

总结:
  . LruDiskCache是硬盘缓存对外暴露的实体类,其具体实现的硬盘缓存是DiskLruCache;
  . LruDiskCache默认压缩图片格式是PNG,默认压缩比率是100,即保持原画质
  . LruDiskCache默认硬盘缓存的文件名是图片URL的哈希值
  . LruDiskCache在初始化的时候会获得DiskLruCache实例
  . LruDiskCache通过DiskLruCache的快照类完成获取缓存的文件
  . LruDiskCache通过DiskLruCache的编辑类完成将流或者图片写入到DiskLruCache的Entry中,从而完成添加的动作
  . LruDiskCache通过DiskLruCache的remove方法根据传入的图片URL完成对一个缓存的删除操作


  1. 既然硬盘缓存的真正实现是DiskLruCache,那就让我们细看一下他的实现

  DiskLruCache.java概览
DiskLruCache缓存在文件系统中创建了一个有限的空间,在这个空间当中存储了一条条的数据,每一条的数据是由key,value来对应的;key需要满足[a-z0-9_-]{1,64}的正则表达,value是由字节组成的数据,可被流或者文件获取;每一条数据我们可以称呼为一个Entry

  DiskLruCache.java展示
在默认情况下,被缓存的文件的目录地址是/data/packagename/cache/uil-images目录,下图展示的是demo当中的被缓存的内容
在这里插入图片描述
  一个DiskLruCache最重要的部分有两个
. journal文件,例如上图中的journal
. 每一条cache entry,例如上图中的6cdg7wdkrtb8dmd05gdj8x87n0

打开journal文件你可以发现
在这里插入图片描述

  其中红色方框依次表示:libcore.io.DiskLruCache,DiskLruCache的版本,应用程序的版本,每个条目中保存值的个数
  其中绿色方框表示被缓存的内容的操作记录

  这里我们只要知道journal文件里面有哪些内容即可,就不详细了解journal文件的生成过程了,其过程在DiskLruCache.java的rebuildJournal方法中

  我们在打开6cdg7wdkrtb8dmd05gdj8x87n0文件看下
在这里插入图片描述
可以发现里面多是一些字节信息

  顾我们可以简单的猜测下DiskLruCache的增删改查的过程
. 增加一个缓存:新建一个根据图片url的哈希值来命名的文件,将缓存以字节的形式存入并更新journal文件
. 删除一个缓存:在一个map,根据缓存图片的url来进行查找,从而删除缓存
. 查询一个缓存:在map中,科举图片url来查找具体的文件, 并读取文件保存的字节信息

  DiskLruCache.java核心成员
.LinkedHashMap<String, Entry> lruEntries:核心成员,使用Lru算法存储缓存的内容
.static final String JOURNAL_FILE = “journal”;:硬盘缓存内容操作镜像文件,通过这个文件可以知道缓存被进行了怎样的操作
.Callable cleanupCallable:硬盘缓存是有大小限制的,顾当缓存大小超过总大小的时候,就会启动cleanupCallable来执行清空任务,直到小于总缓存大小;这点和LruMemoryCache没有什么差别;这里我们注意到这是一个callable接口,他和runable接口一样,都是实现线程的接口,只不过callable接口实现了call方法,runable接口实现了run方法;callable接口可以有返回值,而runable接口没有返回值;
    注意点:
Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!

  DiskLruCache.java核心方法

    . 打开DiskLruCache

	public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize, int maxFileCount)
			throws IOException {
	/*省略部分代码*/
		// If a bkp file exists, use it instead.
		File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
		if (backupFile.exists()) {
			File journalFile = new File(directory, JOURNAL_FILE);
			// If journal file also exists just delete backup file.
			if (journalFile.exists()) {
				backupFile.delete();
			} else {
				renameTo(backupFile, journalFile, false);
			}
		}
/*以上都是对journal文件的操作*/
		// Prefer to pick up where we left off.
		DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount);
		if (cache.journalFile.exists()) {
			try {
				cache.readJournal();
				cache.processJournal();
				cache.journalWriter = new BufferedWriter(
						new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), Util.US_ASCII));
				return cache;
				/*以上都是对journal文件的操作*/
			} catch (IOException journalIsCorrupt) {
				System.out
						.println("DiskLruCache "
								+ directory
								+ " is corrupt: "
								+ journalIsCorrupt.getMessage()
								+ ", removing");
				cache.delete();
			}
		}

		// Create a new empty cache.
		directory.mkdirs();
		/*创建空的DiskLruCache*/
		cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount);
		/*在journal文件中写入固定内容*/
		cache.rebuildJournal();
		return cache;
	}

  . 存储一个缓存,主要有两个步骤
第一步是获得DiskLruCache类的Editor类,对应edit方法
第二步是将Edit类中的保存的字节信息写到到Entry中,对应commit方法

	private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
		checkNotClosed();
		validateKey(key);
		Entry entry = lruEntries.get(key);
		if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
				|| entry.sequenceNumber != expectedSequenceNumber)) {
			return null; // Snapshot is stale.
		}
		if (entry == null) {
			entry = new Entry(key);
			lruEntries.put(key, entry);
		} else if (entry.currentEditor != null) {
			return null; // Another edit is in progress.
		}

		Editor editor = new Editor(entry);
		entry.currentEditor = editor;

		// Flush the journal before creating files to prevent file leaks.
		journalWriter.write(DIRTY + ' ' + key + '\n');
		journalWriter.flush();
		return editor;
	}

  第4行,根据传入key,在lruEntriesmap中查找一个Entry entry,一个entry表示一个缓存的字节内容
  第11行,如果这个entry为空,则新建一个entry,并保存到lruEntries中
  第16行,创建DiskLruCache的Edit类

当创建完Edit类之后,LruDiskCache.java在save方法中

	@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;
	}

通过editor.newOutputStream(0)的方法来新建一个输出流,然后将需要被缓存的内容的流写入到输出流中,然后通过调用editor.commit()完成最后的刷新操作

  . 获得一个存储的缓存,这里也分为两个步骤
第一步:根据key查找对应的快照,对应get方法
第二步:根据Snapshot的snapshot.getFile(0)方法返回这个缓存的文件

	public synchronized Snapshot get(String key) throws IOException {
		checkNotClosed();
		validateKey(key);
		Entry entry = lruEntries.get(key);
		if (entry == null) {
			return null;
		}

		if (!entry.readable) {
			return null;
		}

		// Open all streams eagerly to guarantee that we see a single published
		// snapshot. If we opened streams lazily then the streams could come
		// from different edits.
		File[] files = new File[valueCount];
		InputStream[] ins = new InputStream[valueCount];
		try {
			File file;
			for (int i = 0; i < valueCount; i++) {
				file = entry.getCleanFile(i);
				files[i] = file;
				ins[i] = new FileInputStream(file);
			}
		} catch (FileNotFoundException e) {
			// A file must have been deleted manually!
			for (int i = 0; i < valueCount; i++) {
				if (ins[i] != null) {
					Util.closeQuietly(ins[i]);
				} else {
					break;
				}
			}
			return null;
		}

		redundantOpCount++;
		journalWriter.append(READ + ' ' + key + '\n');
		if (journalRebuildRequired()) {
			executorService.submit(cleanupCallable);
		}

		return new Snapshot(key, entry.sequenceNumber, files, ins, entry.lengths);
	}

这里删除和关闭方法也不做分析,比较简单

总结:
. DiskLruCache.java通过LinkedHashMap的方式来实现Lru算法
. DiskLruCache.java 硬盘缓存是有大小限制的,顾当缓存大小超过总大小的时候,就会启动 cleanupCallable来执行清空任务,知道小于总缓存大小
. LinkedHashMap<String, Entry> lruEntries里面的key对应图片URL的哈希值,其Entry保存了该key,
  那么当新增一个缓存的时候,我们会创建一个Entry,这个Entry练保存了缓存的key(为以后查找做准备),然后创建Editor类,将缓存的字节信息输出到输出流中,即目标缓存文件中;
  当查找一个缓存的时候,我们会根据key在lruEntries查找这样Entry,找到对应的Entry之后,创建snapshot类,通过Entry找到对应的文件并获取文件的输入流,获取里面的字节信息,在调用snapshot的getfile方法返回这个缓存
  当清空一个缓存的时候,删除掉这个key对应的所有缓存文件,然后重新计算当前缓存的大小,然后从lruEntries中删除对应的key


  1. 最后看下硬盘缓存UnlimitedDiskCache

  .UnlimitedDiskCache.java概述
UnlimitedDiskCache也是硬盘缓存的一种,在创建LruDiskCache失败的时候,就会创建UnlimitedDiskCache,他缓存大小没有限制,也不采用Lru来控制缓存;UnlimitedDiskCache继承自BaseDiskCache类,BaseDiskCache实现了DiskCache接口;而LruDiskCache只实现了DiskCache接口;
UnlimitedDiskCache针对缓存的增删改查比较简单,就是通过缓存的url地址,查找到对应的文件,然后通过对流的操作来实现增删改查的操作

似乎这样UnlimitedDiskCache的实现方式比较简单,那为什么LruDiskCache不按照这样的方式去做了?那还不是因为LruDiskCache支持Lru算法,并且缓存大小有限制

软件缺陷(定义+表现形式+优先级+信息+产生原因),看完这篇文章就懂了 软件缺陷软件缺陷的定义软件缺陷的表现形式软件缺陷产生的原因软件缺陷的根源软件缺陷的费用软件缺陷的信息 软件缺陷的定义 IEEE 1983 of IEEE Stardard 729中软件缺陷做了一个标准的定义: 从产品内部看,软件缺陷是软件产品开发或维护过程中所存在的错误、毛病等各种问题;从外部看,软件缺陷是系统所需要实现的某种功能的失效或违背。 因此软件缺陷就是软件产品中所存在的问题,最终表现为用户所需要的功能没有完全实现,没有满足用户的需求。 缺陷不是bug,缺陷包含bug,缺陷的范围更大。 软件缺陷 阅读详情

相关推荐

STM32 USART高级功能全解析:IrDA、DMA、硬件流控与低功耗唤醒

USART是嵌入式系统中最基础的串行通信外设,其核心原理在于同步/异步时序控制与数据帧收发机制。现代MCU如STM32通过深度集成IrDA调制解调、DMA直传通道、RS232/RS485硬件流控及Stop模式唤醒等扩展能力,显著提升通信可靠性、吞吐效率与能效比。其中,IrDA SIR采用RZI编码实现抗干扰红外传输,DMA通过TXE/RXNE事件驱动实现零CPU干预的连续数据流,而硬件流控(RTS/CTS/DE)和低功耗唤醒(WUF+UESM)则分别保障工业现场鲁棒性与电池级超长待机。本文围绕STM32平台

weixin_28922227的博客 211

android universal 缓存目录,Android Universal Image Loader 磁盘缓存分析

前言图片加载方面UIL的确很好用,所有有空就看了看UIL的源码,方面自己了解原理并能更熟练的使用它。源码版本:Android-Universal-Image-Loader-1.9.5项目地址:Universal-Image-Loader注意点:此版本discCache过时,用diskCache替换磁盘缓存分析DisplayImageOptions开启磁盘缓存options.cacheOnDisk(...

weixin_39654823的博客 609

【验证技能树】UVM 源码解读01 -- UVM本质上就是软件工程方法论在芯片验证领域的应用

文章摘要: UVM(通用验证方法学)是基于SystemVerilog的芯片验证框架,采用组件化、面向对象和事务级抽象设计。其核心在于职责分离与数据流,通过Driver、Monitor、Sequencer等模块协作实现高效验证。UVM强调复用性、可扩展性和可维护性,类似软件工程的模块化与低耦合原则。理解其体系结构比记忆API更重要,有助于灵活组合组件并提升验证效率。UVM不仅是工具,更融合了软件工程思维,推动验证工作向自动化、系统化发展。掌握UVM底层逻辑能优化验证环境设计,适用于从IP到SoC的多层级验证场

qq_45869788的博客 1040

Android-Universal-Image-Loader(UIL)使用分析

Android-Universal-Image-Loader(UIL) 是android端开源的异步图片下载、缓存

dxyoo7的专栏 8413

非华为电脑也能用!华为电脑管家完美安装教程来了

GitHub 上有一个开源项目 ​****​,专门解决“非华为设备无法安装华为电脑管家”的问题。它通过智能模拟 + DLL 劫持技术,绕过官方对硬件型号的检测,让你在联想、戴尔、华硕甚至 DIY 主机上也能流畅使用华为电脑管家!⭐ 项目已获 ​700+ Stars​,社区活跃,持续更新,兼容性优秀!科技不该有围墙。如果你成功在非华为电脑上跑起了 PCManager,欢迎在评论区晒出你的设备型号 + 使用体验!也别忘了把这篇推文分享给同样“眼馋”华为生态的朋友 ~📱 ​关注我们。

程序员朱永胜 4928

EB Tresos激活失败?3种实用解决方案帮你搞定(含离线激活步骤)

本文针对EB Tresos软件激活失败问题,提供了三种实用的解决方案。详细分析了激活失败的常见根源,并系统阐述了在线环境下的排查步骤、无网环境下的离线激活全流程,以及高级许可证环境修复方法,帮助汽车电子工程师快速恢复开发环境。

milk8的博客 609

Android Image开源框架之ImageLoader(二)

一.特点可配置度高。支持任务线程池、下载器、解码器、内存及磁盘缓存、显示选项等等的配置。包含内存缓存和磁盘缓存两级缓存。支持多线程,支持异步和同步加载。支持多种缓存算法、下载进度监听、ListView 图片错乱解决等。1.1.总体设计图 上面是 UIL 的总体设计图。整个库分为 ImageLoaderEngine , Cache 及 ImageDownloader , ImageDecoder ,

进击的Sence 1136

解决VMware与Hyper-V/Device Guard冲突的实用指南

本文详细解析了VMware与Hyper-V/Device Guard冲突的根源,并提供了三种实用的解决方案:彻底关闭相关功能、通过启动配置实现选择性引导,以及使用VMware Workstation Pro的Hyper-V兼容模式。指南包含清晰的诊断步骤、操作命令和避坑建议,帮助用户根据自身需求灵活选择,实现虚拟化环境的稳定运行或高效共存。

weixin_29306317的博客 684

ImageLoader: java.io.FileNotFoundException: /storage/sdcard0/Android/data/[...]/cache/uil-images/jo

插拔SD卡出现:01-23 15:48:00.512 2385 2385 E ImageLoader: /storage/sdcard0/Android/data/com.android.customerservice/cache/uil-images/journal.tmp: open failed: EIO (I/O error) 01-23 15:48:00.512 2385 2385

wei_zhi的博客 6831

从符号到基元:Token的技术演化史与“词元”汉化的术语学

中文摘要:Token概念演化与"词元"译名的确立 本文系统梳理了"Token"概念从信息论符号到AI基元的完整演化史,揭示了"词元"这一中文译名确立的必然性。概念发展经历了三个阶段:早期作为编译原理中的词法单元(1960s),统计NLP时期作为概率空间的原子(1990s-2000s),以及神经时代成为动态语义载体(2010s后)。随着Transformer架构兴起,Token从静态符号转变为上下文相关的语义基元,其定义彻底突破"词&quo

AllenLV的博客 1222

海康视觉算法平台VisionMaster 4.3.0 C# 二次开发01 加载方案并获取结果

第一次使用海康视觉算法平台VisionMaster 4.3.0,项目中要使用这个平台进行视觉处理并获取结果。

StevenChen的博客 7875

【UIL框架】Universal-Image-Loader完全解析(一)之介绍与使用详解

(一).前言:              已经半个月没有更新文章了,一方面是最近工作比较忙,另一方面我也在选择博客的题材。这次我主要讲解平时用的比较多的图片加载框架ImageLoader。虽然这个框架前不久作者已经停止更新了,不过这个图片加载框架我已经用了很久了。我深知框架使用简便是一方面,但是如果能够不仅会使用而且能够深知其中实现的原理才是最重要的。所以我把ImageLoader项目所有的代码阅

江清清的专栏 1万+

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

彻底掌握如何有效处理高清大图:如何更高效更快速的加载图片

彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(一)基本使用 彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(二)缓存 彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(三)下载模块,解码模块和显示模块   在之前的框架中,当硬盘中有图片缓存的时...

yi_master的博客 1385

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(四)URL全流程总述

彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(一)基本使用 彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(二)缓存 彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(三)下载模块,解码模块和显示模块 前面三篇文章系统的介绍了UIL框架使用的核...

yi_master的博客 295

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(一)基本使用

为什么要写这章 在最近的项目中,有一个高清大图片处理任务,自己尝试进行了一些封装,虽然使用起来还算顺利,但中间还是遇到一些槽点,如不当调用bitmap的recycle方法导致app crash,图片缓存策略设计不够合理,框架只能适应用于较少的任务…顾方才有了此系列 Android-Universal-Image-Loader(Android通过图片加载框架)基本使用 实现效果如下:   在...

yi_master的博客 636

彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(三)下载模块,解码模块和显示模块

彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(一)基本使用 彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(二)缓存 在UIL框架中,下载,解码和显示三个模块也是比较核心的内容;这篇我们将逐一分析 下载模块 在这个模块当中,所有的下载类都实现了ImageDownloader接口,其默认下...

yi_master的博客 450

Universal-Image-Loader,android-Volley,Picasso、Fresco和Glide五大Android开源组件加载网络图片的优缺点比较(2)

总结:这个库有着对加载网络图片的吵详细配置。可以根据个人的喜欢,进行一些配置等等。并且可以实现图片下载过程的监听。且可以支持图片下载完成后,显示圆形或者圆角矩形的图片。4.Fresco关于 FrescoFacebook最近推出了一款用于Android应用中展示图片的强大图片库Fresco,它能够从网络、本地存储和本地资源中加载图片。而且,为了节省数据和CPU,它拥有三级缓存。Fresco 是一个强大的图片加载组件。Fresco 中设计有一个叫做 image pipeline 的模块。

2401_84545339的博客 512

Unity项目发布与性能优化实战:从构建到上线的完整指南

在游戏开发与软件工程领域,性能优化与资源管理是确保产品最终用户体验的关键技术环节。其核心原理在于通过系统性的分析与调优,平衡运行时效率与资源占用,从而提升应用的整体流畅度与稳定性。这项技术的价值在于能将开发环境中的原型,转化为在不同硬件平台上都能高效运行的成熟产品,直接影响用户留存与商业成功。典型的应用场景包括移动端游戏的帧率优化、安装包体积压缩、多平台适配以及内存泄漏防治。本文聚焦于Unity引擎,深入探讨了如何通过分析**绘制调用(Draw Call)**与实施**资源分包**等具体策略,系统解决发布过

weixin_30448603的博客 294

使用UIL(Universal-Image-Loader)异步加载图片

概要: Android-Universal-Image-Loader是一个开源的UI组件程序,该项目的目的是实现可重复使用的异步图像加载、缓存和显示。所以,如果你的程序里需要这个功能的话,使用它,因为已经封装好了一些类和方法。其实,写一个这方面的程序还是比较麻烦的,要考虑多线程,缓存,内存溢出等很多方面。      功能介绍:           A:多线程图片加载;          B:灵...

weixin_33939843的博客 151

(universal Image Loader)UIL 使用 (1)

universal Image Loader (UIL)简单使用 第一天

CoderLi 802

UIL(单例框架)Android-Universal-Image-Loader (图片加载框架)

对于早期使用的图片加载框架进行回顾,对UIL进行简单总结,学而时习之,不亦说乎? UIL(单例框架) 1、概述 Android-Universal-Image-Loader是一个开源的UI组件程序,该项目的目的是提供一个可重复使用的仪器为异步图像加载,缓存和显示。 2、特点 图片来源任意,可以是本地,网络,assets目录和drawble目录 自定义配置信息(线程池大小,缓存策略【...

宝弟弟的博客 1140

开源项目源码解析-Android Universal Image Loader 源码分析

Android Universal Image Loader 源码分析 本文为 Android 开源项目源码解析 中 Android Universal Image Loader 部分 项目地址:Android-Universal-Image-Loader,分析的版本:eb794c3,Demo 地址:UIL Demo 分析者:huxian99,校对者:Grumoon、Trinea,校

第四维空间的专栏 8297

Android Universal Image Loader 磁盘缓存分析

前言 图片加载方面UIL的确很好用,所有有空就看了看UIL的源码,方面自己了解原理并能更熟练的使用它。 源码版本:Android-Universal-Image-Loader-1.9.5 项目地址:Universal-Image-Loader 注意点:此版本discCache过时,用diskCache替换 磁盘缓存分析 DisplayImageOptions开启磁盘缓存 op

禁地 2709

京东金融 App 收集隐私?开源库程序员不背锅!

「大数据时代,人人都在“裸奔”」。本是一句玩笑话,无奈现实却一次又一次地扯掉了遮挡在用户隐私面前的遮羞布。近日,京东金融 App 成为众矢之的,原因是其被网友控诉,在用户...

优快云资讯 5774

UniversalImageLoader源码解析之 DiskCache

转载请注明出处:http://blog.youkuaiyun.com/crazy1235/article/details/70472306 FileNameGenerator DiskCache tryLoadBitmap UnlimitedDiskCache BaseDiskCache LimitedAgeDiskCache LruDiskCache get save remove clear上上一篇介绍了

crazy_jack 2389

Android 获取创建各种存储路径工具类(内置权限获取)

外部存储和内部存储前言最近工作比较轻,从项目中总结抽象出一些工具类,利人利己,欢迎交流完善~概念介绍1、内部存储 Internal Storage:注意内部存储不是内存。内部存储位于系统中很特殊的一个位置,如果你想将文件存储于内部存储中,那么文件默认只能被你的应用访问到,且一个应用所创建的所有文件都在和应用包名相同的目录下。也就是说应用创建于内部存储的文件,与这个应用是关联起来的。当一个应用卸载之后

Google、在奋斗 5126
上一篇: 彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(一)基本使用
下一篇: 彻底掌握如何有效处理高清大图:Android-Universal-Image-Loader框架解析(三)下载模块,解码模块和显示模块
yi_master
博客等级 码龄10年 283粉丝 104原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值