您的位置:首页 > 理论基础 > 计算机网络

universalimageloader-disk cache,缓存网络请求

2015-08-04 15:46 555 查看
看下DiskCache的组成:





转载请标明出处:/article/1908030.html

最顶层的基类是DiskCache,BasicDiskCache对DiskCache做了抽象实现,3个具体的实现类:LimitedAgeDiskCache,UnlimitedDiskCache,LruDiskCache。LruDiskCache比较另类,它内部把请求都代理给了LruDiskCache。看下源码:

public interface DiskCache {
	File getDirectory();
	File get(String imageUri);
	boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException;
	boolean save(String imageUri, Bitmap bitmap) throws IOException;
	boolean remove(String imageUri);
	void close();
	void clear();
}


重点看下LruDiskCache:

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;

	protected final FileNameGenerator fileNameGenerator;

	protected int bufferSize = DEFAULT_BUFFER_SIZE;

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

	/**
	 * @param cacheDir          Directory for file caching
	 * @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.
	 * @throws IOException if cache can't be initialized (e.g. "No space left on device")
	 */
	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);//这里的版本号给固定成了1,据说因此还导致会踩坑:http://blog.csdn.net/shaw1994/article/details/47223133
		} 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;
	}
}
对DiskLruCache做了简单的封装,使用更加方便了。

使用一些app的时候会发现,即使是没有网络的时候,页面也是能加载出来的,他们是怎么做到的呢?肯定是对网络请求做了硬盘缓存,这里的DiskCache就非常适合来干这个事情,下面我们就来试一下。

首先,模仿LimitedAgeDiskCache写一个LimitedAgeLruDiskCache,因为缓存的文件我们想让它可以自动过期:

public class LimitedAgeLruDiskCache extends LruDiskCache {

	/**过期时间,秒*/
	private final long maxFileAge;

	private final Map<File, Long> loadingDates = Collections.synchronizedMap(new HashMap<File, Long>());
	
	public LimitedAgeLruDiskCache(File cacheDir, long cacheMaxSize,int cacheMaxFileCount, long maxAge) throws IOException {
		super(cacheDir, null, new Md5FileNameGenerator(), cacheMaxSize, cacheMaxFileCount);
		this.maxFileAge = maxAge * 1000;
	}
	
	@Override
	public File get(String imageUri) {
		File file = super.get(imageUri);
		if (file != null && file.exists()) {
			boolean cached;
			Long loadingDate = loadingDates.get(file);
			if (loadingDate == null) {
				cached = false;
				loadingDate = file.lastModified();
			} else {
				cached = true;
			}
			if (System.currentTimeMillis() - loadingDate > maxFileAge) {
				super.remove(imageUri);
				loadingDates.remove(file);
			} else if (!cached) {
				loadingDates.put(file, loadingDate);
			}
		}
		return file;
	}
	
	
	@Override
	public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
		boolean saved = super.save(imageUri, imageStream, listener);
		rememberUsage(imageUri);
		return saved;
	}
	
	@Override
	public boolean remove(String imageUri) {
		loadingDates.remove(getFile(imageUri));
		return super.remove(imageUri);
	}

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

	private void rememberUsage(String imageUri) {
		File file = getFile(imageUri);
		long currentTime = System.currentTimeMillis();
		file.setLastModified(currentTime);
		loadingDates.put(file, currentTime);
	}
}
接下来我们做更进一步的封装,从缓存get()出来的是一个对象:

public class RequestCache {
	
	private static volatile RequestCache instance;
	
	private LimitedAgeLruDiskCache mLruCache;
	
	private RequestCache(){
	}
	
	/**
	 * 应用启动的时候调用
	 * */
	public static void init(Application context) {
		File individualCacheDir = StorageUtils.getIndividualCacheDirectory(context, "request-cache");
		try {
			getInstance().mLruCache = new LimitedAgeLruDiskCache(individualCacheDir, 10 * 1024 * 1024, 1024, 60);//缓存有效期默认60秒
		} catch (IOException e) {
			L.e(e);
			throw new RuntimeException("初始化DiskLruCache失败", e);
		}
	}

	public static RequestCache getInstance(){
		if(instance == null){
			synchronized (RequestCache.class) {
				if(instance == null){
					instance = new RequestCache();
				}
			}
		}
		return instance;
	}
	
	public static <T> T get(String url, Class<T> clazz){
		File file = getInstance().mLruCache.get(url);
		if(file != null && file.length() > 0){
			String jsondata = FileUtil.getContent(file, "UTF-8");
			JSONObject json = JSON.parseObject(jsondata);
			return JSON.toJavaObject(json, clazz);
		}else{
			return null;
		}
	}
	
	public static boolean put(String url, String string){
		try{
			ByteArrayInputStream in = new ByteArrayInputStream(string.getBytes("UTF-8"));
			return getInstance().mLruCache.save(url, in, null);
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}
	}
	
	public static boolean put(String url, Object obj){
		String str = JSON.toJSONString(obj);
		return put(url, str);
	}
	
	public static boolean remove(String url){
		return getInstance().mLruCache.remove(url);
	}
	
	public static void clear(){
		getInstance().mLruCache.clear();
	}
}
假如响应数据是json格式,我们用fastjson做json序列化。客户端在使用的时候:

@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		RequestCache.init(this.getApplication());
		
		Button upload = (Button) this.findViewById(R.id.upload);
		upload.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				String url = "http://172.16.28.250:8080/web/user.jsp?username=xjs&password=123456";
				doRequest(url);
			}
		});
	}
	public void doRequest(final String url){
		new AsyncTask<String,Integer,Person>(){
			@Override
			protected Person doInBackground(String... params) {
				Person p = RequestCache.get(url, Person.class);//先从cache中去获取
				if(p == null){
					 String s = HttpUtil.get(params[0]);//获取不到去网络获取
					 JSONObject json = JSON.parseObject(s);
					 p = JSON.toJavaObject(json, Person.class);
					 Log.e("test", "from network");
					 if(p != null){
						 RequestCache.put(url, p); //存到cache中
					 }
				}else{
					 Log.e("test", "from cache");
				}
				return p;
			}
			@Override
			protected void onPostExecute(Person result) {
				super.onPostExecute(result);
				Toast.makeText(MainActivity.this, result.toString(), Toast.LENGTH_SHORT).show();
			}
		}.execute(url);
	}


第一次点击按钮,log会输出:from network,接下来继续点击,就会输出:from cache。60秒以后再访问就又是from network了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: