简单实现图片三级缓存
总阅读次
前言
现在的Android应用程序中,不可避免的都会使用到图片,如果每次加载图片的时候都要从网络重新拉取,这样不但很耗费用户的流量,而且图片加载的也会很慢,用户体验很不好。所以一个应用的图片缓存策略是很重要的。通常情况下,Android应用程序中图片的缓存策略采用“内存-本地-网络”三级缓存策略。
当然现在处理网络图片的时候,一般人都会选择UIL或Glide,它已经将网络缓存处理的相当好了,并且glide还能加载gif,面试的时候通常也会问到图片缓存是怎么做的,这里就来简单实现这个功能。
什么是三级缓存
- 网络缓存, 不优先加载, 速度慢,浪费流量
- 本地缓存, 次优先加载, 速度快
- 内存缓存, 优先加载, 速度最快
三级缓存原理
首次加载 Android App 时,肯定要通过网络交互来获取图片,之后我们可以将图片保存至本地SD卡和内存中
之后运行 App 时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中的图片
总之,只在初次访问新内容时,才通过网络获取图片资源
内存缓存(MemoryCache)
内存缓存说白了就是在内存中保存一份图片集合,首先会想到HashMap这种键值对的形式来进行保存,以url作为key,bitmap作为value。
- 通过
HashMap
键值对的方式保存图片,key为地址,value为图片对象,但因是强引用对象,很容易造成内存溢出,可以尝试SoftReference软引用对象
- 通过
HashMap>
SoftReference 为软引用对象(GC垃圾回收会自动回收软引用对象),但在Android2.3+后,系统会优先考虑回收弱引用对象,官方提出使用LruCache
通过
LruCache
least recentlly use 最少最近使用算法会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定
所以呢这里使用LruCache来进行:
这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859package com.losileeya.imageloader.ImageLoder;import android.graphics.Bitmap;import android.util.LruCache;/*** User: Losileeya (847457332@qq.com)* Date: 2016-08-31* Time: 11:08* 类描述:内存缓存** @version :*/public class MemoryCache {// private HashMap<String,Bitmap> mMemoryCache=new HashMap<>();//1.因为强引用,容易造成内存溢出,所以考虑使用下面弱引用的方法// private HashMap<String, SoftReference<Bitmap>> mMemoryCache = new HashMap<>();//2.因为在Android2.3+后,系统会优先考虑回收弱引用对象,官方提出使用LruCacheprivate LruCache <String,Bitmap> mMemoryCache=null;public MemoryCache(){long mCurrentMemory = Runtime.getRuntime().maxMemory();//得到手机最大允许内存long maxSize=mCurrentMemory/8;//得到手机最大允许内存的1/8,即超过指定内存,则开始回收mMemoryCache=new LruCache<String,Bitmap>((int)maxSize){protected int sizeOf(String key, Bitmap value) {return value.getRowBytes()*value.getHeight();}};}/*** 从内存中读图片** @param key*/public Bitmap getBitmapFormMemory(String key){//Bitmap bitmap = mMemoryCache.get(url);//1.强引用方法/*2.弱引用方法SoftReference<Bitmap> bitmapSoftReference = mMemoryCache.get(url);if (bitmapSoftReference != null) {Bitmap bitmap = bitmapSoftReference.get();return bitmap;}*/Bitmap bitmap=mMemoryCache.get(key);return bitmap;}/*** 往内存中写图片** @param key* @param bitmap*/public void putBitmapToMemory(String key,Bitmap bitmap){//mMemoryCache.put(url, bitmap);//1.强引用方法/*2.弱引用方法mMemoryCache.put(url, new SoftReference<>(bitmap));*/mMemoryCache.put(key,bitmap);}}本地缓存(保存到文件LocalCache)
从网络加载完图片之后,将图片保存到本地SD卡中。在加载图片的时候,判断一下SD卡中是否有图片缓存,如果有,就直接从SD卡加载图片。本地缓存的工具类中有两个公共的方法,分别是向本地SD卡设置网络图片,获取SD卡中的图片。设置图片的时候采用键值对的形式进行存储,将图片的url作为键,作为文件的名字,图片的Bitmap作位值来保存。由于url含有特殊字符,不能直接作为图片的名字来存储,故采用url的MD5值作为文件的名字。
在初次通过网络获取图片后,我们可以在本地SD卡中将图片保存起来
可以使用MD5加密图片的网络地址,来作为图片的名称保存
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364package com.losileeya.imageloader.ImageLoder;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Environment;import com.losileeya.imageloader.utils.Md5Encoder;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;/*** User: Losileeya (847457332@qq.com)* Date: 2016-08-31* Time: 10:49* 类描述:把bitmap保存到sd卡** @version :*/public class LocalCache {/*** 文件保存的路径*/private static final String CACHE_PATH= Environment.getExternalStorageDirectory().getAbsolutePath()+"/LosileeyaLocalCache";/*** 向本地SD卡写网络图片** @param url* @param bitmap*/public void setBitmapToLocal(String url, Bitmap bitmap){try {String fileName= Md5Encoder.encode(url);// 文件的名字File file=new File(CACHE_PATH,fileName);// 创建文件流,指向该路径,文件名叫做fileName//通过得到文件的父文件,判断父文件是否存在File parentFile = file.getParentFile();if (!parentFile.exists()){// 文件夹不存在parentFile.mkdirs();// 创建文件夹}//把图片保存至本地bitmap.compress(Bitmap.CompressFormat.JPEG,100,new FileOutputStream(file));} catch (Exception e) {e.printStackTrace();}}/*** 从本地SD卡获取网络图片,key是url的MD5值** @param url* @return*/public Bitmap getBitampFormLocal(String url){try {String fileName = Md5Encoder.encode(url);//把图片的url当做文件名,并进行MD5加密File file=new File(CACHE_PATH,fileName);Bitmap bitmap= BitmapFactory.decodeStream(new FileInputStream(file));return bitmap;}catch (Exception e){e.printStackTrace();}return null;}}md5
12345678910111213141516171819202122232425package com.losileeya.imageloader.utils;import java.security.MessageDigest;/*** User: Losileeya (847457332@qq.com)* Date: 2016-08-31* Time: 11:02* 类描述:** @version :*/public class Md5Encoder {public static String encode(String str) throws Exception {byte[] hash = MessageDigest.getInstance("MD5").digest(str.getBytes("UTF-8"));StringBuilder hex = new StringBuilder(hash.length * 2);for (byte b : hash) {if ((b & 0xFF) < 0x10) {hex.append("0");}hex.append(Integer.toHexString(b & 0xFF));}return hex.toString();}}网络缓存(NetCache)
网络拉取图片严格来讲不能称之为缓存,实质上就是下载url对应的图片,我们这里姑且把它看作是缓存的一种。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131package com.losileeya.imageloader.ImageLoder;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.AsyncTask;import android.widget.ImageView;import java.io.IOException;import java.net.HttpURLConnection;import java.net.URL;import java.util.concurrent.Executors;/*** User: Losileeya (847457332@qq.com)* Date: 2016-08-31* Time: 10:50* 类描述:网络下载图片** @version :*/public class NetCache {private LocalCache mLocalCache;private MemoryCache mMemoryCache;public NetCache(LocalCache localCache, MemoryCache memoryCache) {mLocalCache = localCache;mMemoryCache = memoryCache;}/*** 从网络下载图片** @param ivPic 显示图片的imageview* @param url 下载图片的网络地址*/public void getBitmapFromNet(ImageView ivPic, String url) {// new BitmapTask().execute(ivPic, url);//启动AsyncTasknew BitmapTask().executeOnExecutor(Executors.newFixedThreadPool(5),ivPic,url);//启动5个线程的线程池AsyncTask}/*** AsyncTask就是对handler和线程池的封装* 第一个泛型:参数类型* 第二个泛型:更新进度的泛型* 第三个泛型:onPostExecute的返回结果*/class BitmapTask extends AsyncTask<Object, Void, Bitmap> {private ImageView ivPic;private String url;/*** 后台耗时操作,存在于子线程中** @param params* @return*/protected Bitmap doInBackground(Object... params) {ivPic = (ImageView) params[0];url = (String) params[1];return downLoadBitmap(url);}/*** 更新进度,在主线程中** @param values*/protected void onProgressUpdate(Void[] values) {super.onProgressUpdate(values);}/*** 耗时方法结束后执行该方法,主线程中** @param result*/protected void onPostExecute(Bitmap result) {if (result != null) {ivPic.setImageBitmap(result);System.out.println("从网络缓存图片啦.....");//从网络获取图片后,保存至本地缓存mLocalCache.setBitmapToLocal(url, result);//保存至内存中mMemoryCache.putBitmapToMemory(url, result);//保存置硬盘disk// DiskCache.openCache(context);// try {// DiskCache.putBitmapToDisk( result,url);// DiskCache.closeCache();// } catch (Exception e) {// e.printStackTrace();// }}}}/*** 网络下载图片** @param url* @return*/private Bitmap downLoadBitmap(String url) {HttpURLConnection conn = null;try {conn = (HttpURLConnection) new URL(url).openConnection();conn.setConnectTimeout(5000);conn.setReadTimeout(5000);conn.setRequestMethod("GET");int responseCode = conn.getResponseCode();if (responseCode == 200) {//图片压缩BitmapFactory.Options options = new BitmapFactory.Options();options.inSampleSize = 2;//宽高压缩为原来的1/2options.inPreferredConfig = Bitmap.Config.ARGB_4444;Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream(), null, options);return bitmap;}} catch (IOException e) {e.printStackTrace();}finally {if(conn!=null){conn.disconnect();conn=null;}}return null;}}提供调用方法ImageLoder.disPlayImage
严格的来说需要提供一个门面来加载显示图片的,这里就简单的加载了:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566package com.losileeya.imageloader.ImageLoder;import android.content.Context;import android.graphics.Bitmap;import android.widget.ImageView;import com.losileeya.imageloader.R;/*** User: Losileeya (847457332@qq.com)* Date: 2016-08-31* Time: 10:49* 类描述:实现三级缓存,加载显示图片** @version :*/public class ImageLoder {private NetCache mNetCache;private LocalCache mLocalCache;private MemoryCache mMemoryCache;private Context context;public ImageLoder(Context context) {mLocalCache = new LocalCache();mMemoryCache = new MemoryCache();mNetCache = new NetCache(mLocalCache,mMemoryCache);}public void disPlayImage(ImageView pic, String url) {pic.setImageResource(R.mipmap.ic_launcher);Bitmap bitmap;//内存缓存bitmap=mMemoryCache.getBitmapFormMemory(url);if (bitmap!=null){pic.setImageBitmap(bitmap);System.out.println("从内存获取图片啦.....");return;}//本地缓存bitmap = mLocalCache.getBitampFormLocal(url);// DiskCache.openCache(context);// if(DiskCache.hasCache(url)){// try {// bitmap= DiskCache.getBitmapFormDisk(url);// } catch (Exception e) {// e.printStackTrace();// }// }if(bitmap !=null){pic.setImageBitmap(bitmap);System.out.println("从本地获取图片啦.....");//从本地获取图片后,保存至内存中mMemoryCache.putBitmapToMemory(url,bitmap);//从disk获取图片后,保存到内存// try {// DiskCache.putBitmapToDisk(bitmap,url);// DiskCache.closeCache();// } catch (Exception e) {// e.printStackTrace();// }return;}//网络缓存mNetCache.getBitmapFromNet(pic,url);}}从代码里我们可以看出,显示我们就是按顺序判断是否在内存中有bitmap,有就显示,没有就继续从本地找bitmap都没有去网络下载显示。
上述代码里面注释了DiskCache,因为自己写的一般保存到本地的方法都为存文件到sd卡,这里就顺带介绍下DiskLruCache。
DiskLruCache硬盘缓存
从各大图片缓存框架我们知道从网络上获取到之后都会存入到本地缓存中使用的是DiskLruCache,
由于DiskLruCache并不是由Google官方编写的,所以这个类并没有被包含在Android API当中,我们需要将这个类从网上下载下来,然后手动添加到项目当中。(代码有点多)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977package com.losileeya.imageloader.utils;/*** User: Losileeya (847457332@qq.com)* Date: 2016-08-31* Time: 10:51* 类描述:** @version :*//** Copyright (C) 2011 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/import java.io.BufferedInputStream;import java.io.BufferedWriter;import java.io.Closeable;import java.io.EOFException;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.FileWriter;import java.io.FilterOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.io.Reader;import java.io.StringWriter;import java.io.Writer;import java.lang.reflect.Array;import java.nio.charset.Charset;import java.util.ArrayList;import java.util.Arrays;import java.util.Iterator;import java.util.LinkedHashMap;import java.util.Map;import java.util.concurrent.Callable;import java.util.concurrent.ExecutorService;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;/*** ****************************************************************************** Taken from the JB source code, can be found in:* libcore/luni/src/main/java/libcore/io/DiskLruCache.java* or direct link:* https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java* ****************************************************************************** <p/>* A cache that uses a bounded amount of space on a filesystem. Each cache* entry has a string key and a fixed number of values. Values are byte* sequences, accessible as streams or files. Each value must be between {@code* 0} and {@code Integer.MAX_VALUE} bytes in length.* <p/>* <p>The cache stores its data in a directory on the filesystem. This* directory must be exclusive to the cache; the cache may delete or overwrite* files from its directory. It is an error for multiple processes to use the* same cache directory at the same time.* <p/>* <p>This cache limits the number of bytes that it will store on the* filesystem. When the number of stored bytes exceeds the limit, the cache will* remove entries in the background until the limit is satisfied. The limit is* not strict: the cache may temporarily exceed it while waiting for files to be* deleted. The limit does not include filesystem overhead or the cache* journal so space-sensitive applications should set a conservative limit.* <p/>* <p>Clients call {@link #edit} to create or update the values of an entry. An* entry may have only one editor at one time; if a value is not available to be* edited then {@link #edit} will return null.* <ul>* <li>When an entry is being <strong>created</strong> it is necessary to* supply a full set of values; the empty value should be used as a* placeholder if necessary.* <li>When an entry is being <strong>edited</strong>, it is not necessary* to supply data for every value; values default to their previous* value.* </ul>* Every {@link #edit} call must be matched by a call to {@link Editor#commit}* or {@link Editor#abort}. Committing is atomic: a read observes the full set* of values as they were before or after the commit, but never a mix of values.* <p/>* <p>Clients call {@link #get} to read a snapshot of an entry. The read will* observe the value at the time that {@link #get} was called. Updates and* removals after the call do not impact ongoing reads.* <p/>* <p>This class is tolerant of some I/O errors. If files are missing from the* filesystem, the corresponding entries will be dropped from the cache. If* an error occurs while writing a cache value, the edit will fail silently.* Callers should handle other problems by catching {@code IOException} and* responding appropriately.*/public final class DiskLruCache implements Closeable {static final String JOURNAL_FILE = "journal";static final String JOURNAL_FILE_TMP = "journal.tmp";static final String MAGIC = "com.losileeya.DiskLruCache";static final String VERSION_1 = "1";static final long ANY_SEQUENCE_NUMBER = -1;private static final String CLEAN = "CLEAN";private static final String DIRTY = "DIRTY";private static final String REMOVE = "REMOVE";private static final String READ = "READ";private static final Charset UTF_8 = Charset.forName("UTF-8");private static final int IO_BUFFER_SIZE = 8 * 1024;/** This cache uses a journal file named "journal". A typical journal file* looks like this:* libcore.io.DiskLruCache* 1* 100* 2** CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054* DIRTY 335c4c6028171cfddfbaae1a9c313c52* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342* REMOVE 335c4c6028171cfddfbaae1a9c313c52* DIRTY 1ab96a171faeeee38496d8b330771a7a* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234* READ 335c4c6028171cfddfbaae1a9c313c52* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6** The first five lines of the journal form its header. They are the* constant string "libcore.io.DiskLruCache", the disk cache's version,* the application's version, the value count, and a blank line.** Each of the subsequent lines in the file is a record of the state of a* cache entry. Each line contains space-separated values: a state, a key,* and optional state-specific values.* o DIRTY lines track that an entry is actively being created or updated.* Every successful DIRTY action should be followed by a CLEAN or REMOVE* action. DIRTY lines without a matching CLEAN or REMOVE indicate that* temporary files may need to be deleted.* o CLEAN lines track a cache entry that has been successfully published* and may be read. A publish line is followed by the lengths of each of* its values.* o READ lines track accesses for LRU.* o REMOVE lines track entries that have been deleted.** The journal file is appended to as cache operations occur. The journal may* occasionally be compacted by dropping redundant lines. A temporary file named* "journal.tmp" will be used during compaction; that file should be deleted if* it exists when the cache is opened.*/private final File directory;private final File journalFile;private final File journalFileTmp;private final int appVersion;private final long maxSize;private final int valueCount;private long size = 0;private Writer journalWriter;private final LinkedHashMap<String, Entry> lruEntries= new LinkedHashMap<String, Entry>(0, 0.75f, true);private int redundantOpCount;/*** To differentiate between old and current snapshots, each entry is given* a sequence number each time an edit is committed. A snapshot is stale if* its sequence number is not equal to its entry's sequence number.*/private long nextSequenceNumber = 0;/* From java.util.Arrays */"unchecked")(private static <T> T[] copyOfRange(T[] original, int start, int end) {final int originalLength = original.length; // For exception priority compatibility.if (start > end) {throw new IllegalArgumentException();}if (start < 0 || start > originalLength) {throw new ArrayIndexOutOfBoundsException();}final int resultLength = end - start;final int copyLength = Math.min(resultLength, originalLength - start);final T[] result = (T[]) Array.newInstance(original.getClass().getComponentType(), resultLength);System.arraycopy(original, start, result, 0, copyLength);return result;}/*** Returns the remainder of 'reader' as a string, closing it when done.*/public static String readFully(Reader reader) throws IOException {try {StringWriter writer = new StringWriter();char[] buffer = new char[1024];int count;while ((count = reader.read(buffer)) != -1) {writer.write(buffer, 0, count);}return writer.toString();} finally {reader.close();}}/*** Returns the ASCII characters up to but not including the next "\r\n", or* "\n".** @throws java.io.EOFException if the stream is exhausted before the next newline* character.*/public static String readAsciiLine(InputStream in) throws IOException {// TODO: support UTF-8 here insteadStringBuilder result = new StringBuilder(80);while (true) {int c = in.read();if (c == -1) {throw new EOFException();} else if (c == '\n') {break;}result.append((char) c);}int length = result.length();if (length > 0 && result.charAt(length - 1) == '\r') {result.setLength(length - 1);}return result.toString();}/*** Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.*/public static void closeQuietly(Closeable closeable) {if (closeable != null) {try {closeable.close();} catch (RuntimeException rethrown) {throw rethrown;} catch (Exception ignored) {}}}/*** Recursively delete everything in {@code dir}.*/// TODO: this should specify paths as Strings rather than as Filespublic static void deleteContents(File dir) throws IOException {File[] files = dir.listFiles();if (files == null) {throw new IllegalArgumentException("not a directory: " + dir);}for (File file : files) {if (file.isDirectory()) {deleteContents(file);}if (!file.delete()) {throw new IOException("failed to delete file: " + file);}}}/*** This cache uses a single background thread to evict entries.*/private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());private final Callable<Void> cleanupCallable = new Callable<Void>() {public Void call() throws Exception {synchronized (DiskLruCache.this) {if (journalWriter == null) {return null; // closed}trimToSize();if (journalRebuildRequired()) {rebuildJournal();redundantOpCount = 0;}}return null;}};private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {this.directory = directory;this.appVersion = appVersion;this.journalFile = new File(directory, JOURNAL_FILE);this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);this.valueCount = valueCount;this.maxSize = maxSize;}/*** Opens the cache in {@code directory}, creating a cache if none exists* there.** @param directory a writable directory* @param appVersion* @param valueCount the number of values per cache entry. Must be positive.* @param maxSize the maximum number of bytes this cache should use to store* @throws java.io.IOException if reading or writing the cache directory fails*/public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)throws IOException {if (maxSize <= 0) {throw new IllegalArgumentException("maxSize <= 0");}if (valueCount <= 0) {throw new IllegalArgumentException("valueCount <= 0");}// prefer to pick up where we left offDiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);if (cache.journalFile.exists()) {try {cache.readJournal();cache.processJournal();cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),IO_BUFFER_SIZE);return cache;} catch (IOException journalIsCorrupt) {// System.logW("DiskLruCache " + directory + " is corrupt: "// + journalIsCorrupt.getMessage() + ", removing");cache.delete();}}// create a new empty cachedirectory.mkdirs();cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);cache.rebuildJournal();return cache;}private void readJournal() throws IOException {InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE);try {String magic = readAsciiLine(in);String version = readAsciiLine(in);String appVersionString = readAsciiLine(in);String valueCountString = readAsciiLine(in);String blank = readAsciiLine(in);if (!MAGIC.equals(magic)|| !VERSION_1.equals(version)|| !Integer.toString(appVersion).equals(appVersionString)|| !Integer.toString(valueCount).equals(valueCountString)|| !"".equals(blank)) {throw new IOException("unexpected journal header: ["+ magic + ", " + version + ", " + valueCountString + ", " + blank + "]");}while (true) {try {readJournalLine(readAsciiLine(in));} catch (EOFException endOfJournal) {break;}}} finally {closeQuietly(in);}}private void readJournalLine(String line) throws IOException {String[] parts = line.split(" ");if (parts.length < 2) {throw new IOException("unexpected journal line: " + line);}String key = parts[1];if (parts[0].equals(REMOVE) && parts.length == 2) {lruEntries.remove(key);return;}Entry entry = lruEntries.get(key);if (entry == null) {entry = new Entry(key);lruEntries.put(key, entry);}if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {entry.readable = true;entry.currentEditor = null;entry.setLengths(copyOfRange(parts, 2, parts.length));} else if (parts[0].equals(DIRTY) && parts.length == 2) {entry.currentEditor = new Editor(entry);} else if (parts[0].equals(READ) && parts.length == 2) {// this work was already done by calling lruEntries.get()} else {throw new IOException("unexpected journal line: " + line);}}/*** Computes the initial size and collects garbage as a part of opening the* cache. Dirty entries are assumed to be inconsistent and will be deleted.*/private void processJournal() throws IOException {deleteIfExists(journalFileTmp);for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {Entry entry = i.next();if (entry.currentEditor == null) {for (int t = 0; t < valueCount; t++) {size += entry.lengths[t];}} else {entry.currentEditor = null;for (int t = 0; t < valueCount; t++) {deleteIfExists(entry.getCleanFile(t));deleteIfExists(entry.getDirtyFile(t));}i.remove();}}}/*** Creates a new journal that omits redundant information. This replaces the* current journal if it exists.*/private synchronized void rebuildJournal() throws IOException {if (journalWriter != null) {journalWriter.close();}Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);writer.write(MAGIC);writer.write("\n");writer.write(VERSION_1);writer.write("\n");writer.write(Integer.toString(appVersion));writer.write("\n");writer.write(Integer.toString(valueCount));writer.write("\n");writer.write("\n");for (Entry entry : lruEntries.values()) {if (entry.currentEditor != null) {writer.write(DIRTY + ' ' + entry.key + '\n');} else {writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');}}writer.close();journalFileTmp.renameTo(journalFile);journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);}private static void deleteIfExists(File file) throws IOException {// try {// Libcore.os.remove(file.getPath());// } catch (ErrnoException errnoException) {// if (errnoException.errno != OsConstants.ENOENT) {// throw errnoException.rethrowAsIOException();// }// }if (file.exists() && !file.delete()) {throw new IOException();}}/*** Returns a snapshot of the entry named {@code key}, or null if it doesn't* exist is not currently readable. If a value is returned, it is moved to* the head of the LRU queue.*/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.*/InputStream[] ins = new InputStream[valueCount];try {for (int i = 0; i < valueCount; i++) {ins[i] = new FileInputStream(entry.getCleanFile(i));}} catch (FileNotFoundException e) {// a file must have been deleted manually!return null;}redundantOpCount++;journalWriter.append(READ + ' ' + key + '\n');if (journalRebuildRequired()) {executorService.submit(cleanupCallable);}return new Snapshot(key, entry.sequenceNumber, ins);}/*** Returns an editor for the entry named {@code key}, or null if another* edit is in progress.*/public Editor edit(String key) throws IOException {return edit(key, ANY_SEQUENCE_NUMBER);}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 leaksjournalWriter.write(DIRTY + ' ' + key + '\n');journalWriter.flush();return editor;}/*** Returns the directory where this cache stores its data.*/public File getDirectory() {return directory;}/*** Returns the maximum number of bytes that this cache should use to store* its data.*/public long maxSize() {return maxSize;}/*** Returns the number of bytes currently being used to store the values in* this cache. This may be greater than the max size if a background* deletion is pending.*/public synchronized long size() {return size;}private synchronized void completeEdit(Editor editor, boolean success) throws IOException {Entry entry = editor.entry;if (entry.currentEditor != editor) {throw new IllegalStateException();}// if this edit is creating the entry for the first time, every index must have a valueif (success && !entry.readable) {for (int i = 0; i < valueCount; i++) {if (!entry.getDirtyFile(i).exists()) {editor.abort();throw new IllegalStateException("edit didn't create file " + i);}}}for (int i = 0; i < valueCount; i++) {File dirty = entry.getDirtyFile(i);if (success) {if (dirty.exists()) {File clean = entry.getCleanFile(i);dirty.renameTo(clean);long oldLength = entry.lengths[i];long newLength = clean.length();entry.lengths[i] = newLength;size = size - oldLength + newLength;}} else {deleteIfExists(dirty);}}redundantOpCount++;entry.currentEditor = null;if (entry.readable | success) {entry.readable = true;journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');if (success) {entry.sequenceNumber = nextSequenceNumber++;}} else {lruEntries.remove(entry.key);journalWriter.write(REMOVE + ' ' + entry.key + '\n');}if (size > maxSize || journalRebuildRequired()) {executorService.submit(cleanupCallable);}}/*** We only rebuild the journal when it will halve the size of the journal* and eliminate at least 2000 ops.*/private boolean journalRebuildRequired() {final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD&& redundantOpCount >= lruEntries.size();}/*** Drops the entry for {@code key} if it exists and can be removed. Entries* actively being edited cannot be removed.** @return true if an entry was removed.*/public synchronized boolean remove(String key) throws IOException {checkNotClosed();validateKey(key);Entry entry = lruEntries.get(key);if (entry == null || entry.currentEditor != null) {return false;}for (int i = 0; i < valueCount; i++) {File file = entry.getCleanFile(i);if (!file.delete()) {throw new IOException("failed to delete " + file);}size -= entry.lengths[i];entry.lengths[i] = 0;}redundantOpCount++;journalWriter.append(REMOVE + ' ' + key + '\n');lruEntries.remove(key);if (journalRebuildRequired()) {executorService.submit(cleanupCallable);}return true;}/*** Returns true if this cache has been closed.*/public boolean isClosed() {return journalWriter == null;}private void checkNotClosed() {if (journalWriter == null) {throw new IllegalStateException("cache is closed");}}/*** Force buffered operations to the filesystem.*/public synchronized void flush() throws IOException {checkNotClosed();trimToSize();journalWriter.flush();}/*** Closes this cache. Stored values will remain on the filesystem.*/public synchronized void close() throws IOException {if (journalWriter == null) {return; // already closed}for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {if (entry.currentEditor != null) {entry.currentEditor.abort();}}trimToSize();journalWriter.close();journalWriter = null;}private void trimToSize() throws IOException {while (size > maxSize) {// Map.Entry<String, Entry> toEvict = lruEntries.eldest();final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();remove(toEvict.getKey());}}/*** Closes the cache and deletes all of its stored values. This will delete* all files in the cache directory including files that weren't created by* the cache.*/public void delete() throws IOException {close();deleteContents(directory);}private void validateKey(String key) {if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {throw new IllegalArgumentException("keys must not contain spaces or newlines: \"" + key + "\"");}}private static String inputStreamToString(InputStream in) throws IOException {return readFully(new InputStreamReader(in, UTF_8));}/*** A snapshot of the values for an entry.*/public final class Snapshot implements Closeable {private final String key;private final long sequenceNumber;private final InputStream[] ins;private Snapshot(String key, long sequenceNumber, InputStream[] ins) {this.key = key;this.sequenceNumber = sequenceNumber;this.ins = ins;}/*** Returns an editor for this snapshot's entry, or null if either the* entry has changed since this snapshot was created or if another edit* is in progress.*/public Editor edit() throws IOException {return DiskLruCache.this.edit(key, sequenceNumber);}/*** Returns the unbuffered stream with the value for {@code index}.*/public InputStream getInputStream(int index) {return ins[index];}/*** Returns the string value for {@code index}.*/public String getString(int index) throws IOException {return inputStreamToString(getInputStream(index));}public void close() {for (InputStream in : ins) {closeQuietly(in);}}}/*** Edits the values for an entry.*/public final class Editor {private final Entry entry;private boolean hasErrors;private Editor(Entry entry) {this.entry = entry;}/*** Returns an unbuffered input stream to read the last committed value,* or null if no value has been committed.*/public InputStream newInputStream(int index) throws IOException {synchronized (DiskLruCache.this) {if (entry.currentEditor != this) {throw new IllegalStateException();}if (!entry.readable) {return null;}return new FileInputStream(entry.getCleanFile(index));}}/*** Returns the last committed value as a string, or null if no value* has been committed.*/public String getString(int index) throws IOException {InputStream in = newInputStream(index);return in != null ? inputStreamToString(in) : null;}/*** Returns a new unbuffered output stream to write the value at* {@code index}. If the underlying output stream encounters errors* when writing to the filesystem, this edit will be aborted when* {@link #commit} is called. The returned output stream does not throw* IOExceptions.*/public OutputStream newOutputStream(int index) throws IOException {synchronized (DiskLruCache.this) {if (entry.currentEditor != this) {throw new IllegalStateException();}return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));}}/*** Sets the value at {@code index} to {@code value}.*/public void set(int index, String value) throws IOException {Writer writer = null;try {writer = new OutputStreamWriter(newOutputStream(index), UTF_8);writer.write(value);} finally {closeQuietly(writer);}}/*** Commits this edit so it is visible to readers. This releases the* edit lock so another edit may be started on the same key.*/public void commit() throws IOException {if (hasErrors) {completeEdit(this, false);remove(entry.key); // the previous entry is stale} else {completeEdit(this, true);}}/*** Aborts this edit. This releases the edit lock so another edit may be* started on the same key.*/public void abort() throws IOException {completeEdit(this, false);}private class FaultHidingOutputStream extends FilterOutputStream {private FaultHidingOutputStream(OutputStream out) {super(out);}public void write(int oneByte) {try {out.write(oneByte);} catch (IOException e) {hasErrors = true;}}public void write(byte[] buffer, int offset, int length) {try {out.write(buffer, offset, length);} catch (IOException e) {hasErrors = true;}}public void close() {try {out.close();} catch (IOException e) {hasErrors = true;}}public void flush() {try {out.flush();} catch (IOException e) {hasErrors = true;}}}}private final class Entry {private final String key;/*** Lengths of this entry's files.*/private final long[] lengths;/*** True if this entry has ever been published*/private boolean readable;/*** The ongoing edit or null if this entry is not being edited.*/private Editor currentEditor;/*** The sequence number of the most recently committed edit to this entry.*/private long sequenceNumber;private Entry(String key) {this.key = key;this.lengths = new long[valueCount];}public String getLengths() throws IOException {StringBuilder result = new StringBuilder();for (long size : lengths) {result.append(' ').append(size);}return result.toString();}/*** Set lengths using decimal numbers like "10123".*/private void setLengths(String[] strings) throws IOException {if (strings.length != valueCount) {throw invalidLengths(strings);}try {for (int i = 0; i < strings.length; i++) {lengths[i] = Long.parseLong(strings[i]);}} catch (NumberFormatException e) {throw invalidLengths(strings);}}private IOException invalidLengths(String[] strings) throws IOException {throw new IOException("unexpected journal line: " + Arrays.toString(strings));}public File getCleanFile(int i) {return new File(directory, key + "." + i);}public File getDirtyFile(int i) {return new File(directory, key + "." + i + ".tmp");}}}
首先你要知道,DiskLruCache是不能new出实例的,如果我们要创建一个DiskLruCache的实例,则需要调用它的open()方法,接口如下所示:
1public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)DiskCache(DiskLruCache )
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110package com.losileeya.imageloader.ImageLoder;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Environment;import com.losileeya.imageloader.utils.DiskLruCache;import com.losileeya.imageloader.utils.Md5Encoder;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;/*** User: Losileeya (847457332@qq.com)* Date: 2016-08-31* Time: 10:50* 类描述:DiskLruCache** @version :*/public class DiskCache {private static DiskLruCache mCache;/*** 打开DiskLruCache。*/public static void openCache(Context context) {try {if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())|| !Environment.isExternalStorageRemovable()) {mCache = DiskLruCache.open(context.getExternalCacheDir(), 0, 1, 10 * 1024 * 1024);} else {mCache = DiskLruCache.open(context.getCacheDir(), 0, 1, 10 * 1024 * 1024);}} catch (IOException e) {e.printStackTrace();}}/*** 把图片写入disk内存*/public static void putBitmapToDisk(Bitmap bitmap, String keyCache) throws Exception {if (mCache == null) throw new IllegalStateException("Must call openCache() first!");DiskLruCache.Editor editor = mCache.edit(Md5Encoder.encode(keyCache));if (editor != null) {OutputStream outputStream = editor.newOutputStream(0);boolean success = bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);if (success) {editor.commit();} else {editor.abort();}}}/*** 从disk中获取图片*/public static Bitmap getBitmapFormDisk(String keyCache) throws Exception {if (mCache == null) throw new IllegalStateException("Must call openCache() first!");DiskLruCache.Snapshot snapshot = mCache.get(Md5Encoder.encode(keyCache));if (snapshot != null) {InputStream inputStream = snapshot.getInputStream(0);Bitmap bitmap = BitmapFactory.decodeStream(inputStream);return bitmap;}return null;}/*** 检查缓存是否存在。*/public static boolean hasCache(String keyCache) {try {return mCache.get(Md5Encoder.encode(keyCache)) != null;}catch (Exception e) {e.printStackTrace();}return false;}/*** 同步日志。*/public static void syncLog() {try {mCache.flush();} catch (IOException e) {e.printStackTrace();}}/*** 关闭DiskLruCache。*/public static void closeCache() {syncLog();}}好了缓存代码都手把手的写完了,使用很简单
1234567891011121314151617181920212223package com.losileeya.imageloader;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.RelativeLayout;import com.losileeya.imageloader.ImageLoder.ImageLoder;public class MainActivity extends AppCompatActivity {protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);RelativeLayout rl= (RelativeLayout) findViewById(R.id.bg);ImageView iv=new ImageView(this);iv.setLayoutParams(new LinearLayout.LayoutParams(getWindow().getAttributes().width,getWindow().getAttributes().height));rl.addView(iv);new ImageLoder(this).disPlayImage(iv,"http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg");}}就一句话ImageLoder(this).disPlayImage(iv,”http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg“);
好了,demo就不上传了,所有的代码附上面了,看下效果吧:
当然别忘了添加所需权限:
12345<uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
未经许可不得转载,转载请注明zilianliuxue的blog,本人保留所有版权。