转载

安卓开发笔记——关于图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)

在开发安卓应用中避免不了要使用到网络图片,获取网络图片很简单,但是需要付出一定的代价——流量。对于少数的图片而言问题不大,但如果手机应用中包含大量的图片,这势必会耗费用户的一定流量,如果我们不加以处理,每次打开应用都去网络获取图片,那么用户可就不乐意了,这里的处理就是指今天要讲的缓存策略(缓存层分为三层:内存层,磁盘层,网络层)。

关于缓存层的工作,当我们第一次打开应用获取图片时,先到网络去下载图片,然后依次存入内存缓存,磁盘缓存,当我们再一次需要用到刚才下载的这张图片时,就不需要再重复的到网络上去下载,直接可以从内存缓存和磁盘缓存中找,由于内存缓存速度较快,我们优先到内存缓存中寻找该图片,如果找到则运用,如果没有找到(内存缓存大小有限),那么我们再到磁盘缓存中去找。只要我们合理的去协调这三层缓存运用,便可以提升应用性能和用户体验。

1、内存层:(手机内存)

内存缓存相对于磁盘缓存而言,速度要来的快很多,但缺点容量较小且会被系统回收,这里的实现我用到了LruCache。

LruCache这个类是Android3.1版本中提供的,如果你是在更早的Android版本中开发,则需要导入android-support-v4的jar包。

磁盘层:(SD卡)

相比内存缓存而言速度要来得慢很多,但容量很大,这里的实现我用到了DiskLruCache类。

DiskLruCache是非Google官方编写,但获得官方认证的硬盘缓存类,该类没有限定在Android内,所以理论上java应用也可以使用DiskLreCache来缓存。

这是DiskLruCache类的下载地址:http://pan.baidu.com/s/1hq0D53m

网络层:(移动网络,无线网络)

这个就没什么解释的了,就是我们上网用的流量。这里的网络访问实现我用到了开源框架Volley。

开源框架Volley是2013年Google I/O大会发布的,Volley是Android平台上的网络通信库,能使网络通信更快,更简单,更健壮。它的设计目标就是非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。

这是Volley的下载地址:http://pan.baidu.com/s/1hq1t2yo

先来看下效果图:

正常网络下:   断开网络,飞行模式下:

安卓开发笔记——关于图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley) 安卓开发笔记——关于图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)

Log日志打印:

安卓开发笔记——关于图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)

来看下代码实现:

1、由于应用中很多地方需要用到上下文对象,这里我自定义了一个全局的Application,用来提供上下文对象

 1 package com.lcw.rabbit.image.utils;  2   3 import android.app.Application;  4 /**  5  * Application类,提供全局上下文对象  6  * @author Rabbit_Lee  7  *  8  */  9 public class MyApplication extends Application { 10  11     public static String TAG; 12     public static MyApplication myApplication; 13  14     public static MyApplication newInstance() { 15         return myApplication; 16     } 17  18     @Override 19     public void onCreate() { 20         super.onCreate(); 21         TAG = this.getClass().getSimpleName(); 22         myApplication = this; 23  24     } 25 }

2、Volley请求队列处理类,用来管理Rquest请求对象操作

 1 package com.lcw.rabbit.image;  2   3 import com.android.volley.Request;  4 import com.android.volley.RequestQueue;  5 import com.android.volley.toolbox.Volley;  6 import com.lcw.rabbit.image.utils.MyApplication;  7   8 /**  9  * 请求队列处理类 10  * 获取RequestQueue对象 11  */ 12 public class VolleyRequestQueueManager { 13     // 获取请求队列类 14     public static RequestQueue mRequestQueue = Volley.newRequestQueue(MyApplication.newInstance()); 15  16     //添加任务进任务队列 17     public static void addRequest(Request<?> request, Object tag) { 18         if (tag != null) { 19             request.setTag(tag); 20         } 21         mRequestQueue.add(request); 22     } 23      24     //取消任务 25     public static void cancelRequest(Object tag){ 26         mRequestQueue.cancelAll(tag); 27     } 28      29      30  31 }

3、这里附上2个工具类(生成MD5序列帮助类,DiskLruCache磁盘缓存类)

安卓开发笔记——关于图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)
 1 package com.lcw.rabbit.image.utils;  2   3 import java.math.BigInteger;  4 import java.security.MessageDigest;  5 import java.security.NoSuchAlgorithmException;  6   7 public class MD5Utils {  8     /**  9      * 使用md5的算法进行加密 10      */ 11     public static String md5(String plainText) { 12         byte[] secretBytes = null; 13         try { 14             secretBytes = MessageDigest.getInstance("md5").digest( 15                     plainText.getBytes()); 16         } catch (NoSuchAlgorithmException e) { 17             throw new RuntimeException("没有md5这个算法!"); 18         } 19         String md5code = new BigInteger(1, secretBytes).toString(16);// 16进制数字 20         // 如果生成数字未满32位,需要前面补0 21         for (int i = 0; i < 32 - md5code.length(); i++) { 22             md5code = "0" + md5code; 23         } 24         return md5code; 25     } 26  27 }
MD5转换类
安卓开发笔记——关于图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)
  1 /*   2  * Copyright (C) 2011 The Android Open Source Project   3  *   4  * Licensed under the Apache License, Version 2.0 (the "License");   5  * you may not use this file except in compliance with the License.   6  * You may obtain a copy of the License at   7  *   8  *      http://www.apache.org/licenses/LICENSE-2.0   9  *  10  * Unless required by applicable law or agreed to in writing, software  11  * distributed under the License is distributed on an "AS IS" BASIS,  12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  13  * See the License for the specific language governing permissions and  14  * limitations under the License.  15  */  16   17 package com.lcw.rabbit.image.utils;  18   19 import java.io.BufferedInputStream;  20 import java.io.BufferedWriter;  21 import java.io.Closeable;  22 import java.io.EOFException;  23 import java.io.File;  24 import java.io.FileInputStream;  25 import java.io.FileNotFoundException;  26 import java.io.FileOutputStream;  27 import java.io.FileWriter;  28 import java.io.FilterOutputStream;  29 import java.io.IOException;  30 import java.io.InputStream;  31 import java.io.InputStreamReader;  32 import java.io.OutputStream;  33 import java.io.OutputStreamWriter;  34 import java.io.Reader;  35 import java.io.StringWriter;  36 import java.io.Writer;  37 import java.lang.reflect.Array;  38 import java.nio.charset.Charset;  39 import java.util.ArrayList;  40 import java.util.Arrays;  41 import java.util.Iterator;  42 import java.util.LinkedHashMap;  43 import java.util.Map;  44 import java.util.concurrent.Callable;  45 import java.util.concurrent.ExecutorService;  46 import java.util.concurrent.LinkedBlockingQueue;  47 import java.util.concurrent.ThreadPoolExecutor;  48 import java.util.concurrent.TimeUnit;  49   50 /**  51  ******************************************************************************  52  * Taken from the JB source code, can be found in:  53  * libcore/luni/src/main/java/libcore/io/DiskLruCache.java  54  * or direct link:  55  * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java  56  ******************************************************************************  57  *  58  * A cache that uses a bounded amount of space on a filesystem. Each cache  59  * entry has a string key and a fixed number of values. Values are byte  60  * sequences, accessible as streams or files. Each value must be between {@code  61  * 0} and {@code Integer.MAX_VALUE} bytes in length.  62  *  63  * <p>The cache stores its data in a directory on the filesystem. This  64  * directory must be exclusive to the cache; the cache may delete or overwrite  65  * files from its directory. It is an error for multiple processes to use the  66  * same cache directory at the same time.  67  *  68  * <p>This cache limits the number of bytes that it will store on the  69  * filesystem. When the number of stored bytes exceeds the limit, the cache will  70  * remove entries in the background until the limit is satisfied. The limit is  71  * not strict: the cache may temporarily exceed it while waiting for files to be  72  * deleted. The limit does not include filesystem overhead or the cache  73  * journal so space-sensitive applications should set a conservative limit.  74  *  75  * <p>Clients call {@link #edit} to create or update the values of an entry. An  76  * entry may have only one editor at one time; if a value is not available to be  77  * edited then {@link #edit} will return null.  78  * <ul>  79  *     <li>When an entry is being <strong>created</strong> it is necessary to  80  *         supply a full set of values; the empty value should be used as a  81  *         placeholder if necessary.  82  *     <li>When an entry is being <strong>edited</strong>, it is not necessary  83  *         to supply data for every value; values default to their previous  84  *         value.  85  * </ul>  86  * Every {@link #edit} call must be matched by a call to {@link Editor#commit}  87  * or {@link Editor#abort}. Committing is atomic: a read observes the full set  88  * of values as they were before or after the commit, but never a mix of values.  89  *  90  * <p>Clients call {@link #get} to read a snapshot of an entry. The read will  91  * observe the value at the time that {@link #get} was called. Updates and  92  * removals after the call do not impact ongoing reads.  93  *  94  * <p>This class is tolerant of some I/O errors. If files are missing from the  95  * filesystem, the corresponding entries will be dropped from the cache. If  96  * an error occurs while writing a cache value, the edit will fail silently.  97  * Callers should handle other problems by catching {@code IOException} and  98  * responding appropriately.  99  */ 100 public final class DiskLruCache implements Closeable { 101     static final String JOURNAL_FILE = "journal"; 102     static final String JOURNAL_FILE_TMP = "journal.tmp"; 103     static final String MAGIC = "libcore.io.DiskLruCache"; 104     static final String VERSION_1 = "1"; 105     static final long ANY_SEQUENCE_NUMBER = -1; 106     private static final String CLEAN = "CLEAN"; 107     private static final String DIRTY = "DIRTY"; 108     private static final String REMOVE = "REMOVE"; 109     private static final String READ = "READ"; 110  111     private static final Charset UTF_8 = Charset.forName("UTF-8"); 112     private static final int IO_BUFFER_SIZE = 8 * 1024; 113  114     /* 115      * This cache uses a journal file named "journal". A typical journal file 116      * looks like this: 117      *     libcore.io.DiskLruCache 118      *     1 119      *     100 120      *     2 121      * 122      *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 123      *     DIRTY 335c4c6028171cfddfbaae1a9c313c52 124      *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 125      *     REMOVE 335c4c6028171cfddfbaae1a9c313c52 126      *     DIRTY 1ab96a171faeeee38496d8b330771a7a 127      *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 128      *     READ 335c4c6028171cfddfbaae1a9c313c52 129      *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 130      * 131      * The first five lines of the journal form its header. They are the 132      * constant string "libcore.io.DiskLruCache", the disk cache's version, 133      * the application's version, the value count, and a blank line. 134      * 135      * Each of the subsequent lines in the file is a record of the state of a 136      * cache entry. Each line contains space-separated values: a state, a key, 137      * and optional state-specific values. 138      *   o DIRTY lines track that an entry is actively being created or updated. 139      *     Every successful DIRTY action should be followed by a CLEAN or REMOVE 140      *     action. DIRTY lines without a matching CLEAN or REMOVE indicate that 141      *     temporary files may need to be deleted. 142      *   o CLEAN lines track a cache entry that has been successfully published 143      *     and may be read. A publish line is followed by the lengths of each of 144      *     its values. 145      *   o READ lines track accesses for LRU. 146      *   o REMOVE lines track entries that have been deleted. 147      * 148      * The journal file is appended to as cache operations occur. The journal may 149      * occasionally be compacted by dropping redundant lines. A temporary file named 150      * "journal.tmp" will be used during compaction; that file should be deleted if 151      * it exists when the cache is opened. 152      */ 153  154     private final File directory; 155     private final File journalFile; 156     private final File journalFileTmp; 157     private final int appVersion; 158     private final long maxSize; 159     private final int valueCount; 160     private long size = 0; 161     private Writer journalWriter; 162     private final LinkedHashMap<String, Entry> lruEntries 163             = new LinkedHashMap<String, Entry>(0, 0.75f, true); 164     private int redundantOpCount; 165  166     /** 167      * To differentiate between old and current snapshots, each entry is given 168      * a sequence number each time an edit is committed. A snapshot is stale if 169      * its sequence number is not equal to its entry's sequence number. 170      */ 171     private long nextSequenceNumber = 0; 172  173     /* From java.util.Arrays */ 174     @SuppressWarnings("unchecked") 175     private static <T> T[] copyOfRange(T[] original, int start, int end) { 176         final int originalLength = original.length; // For exception priority compatibility. 177         if (start > end) { 178             throw new IllegalArgumentException(); 179         } 180         if (start < 0 || start > originalLength) { 181             throw new ArrayIndexOutOfBoundsException(); 182         } 183         final int resultLength = end - start; 184         final int copyLength = Math.min(resultLength, originalLength - start); 185         final T[] result = (T[]) Array 186                 .newInstance(original.getClass().getComponentType(), resultLength); 187         System.arraycopy(original, start, result, 0, copyLength); 188         return result; 189     } 190  191     /** 192      * Returns the remainder of 'reader' as a string, closing it when done. 193      */ 194     public static String readFully(Reader reader) throws IOException { 195         try { 196             StringWriter writer = new StringWriter(); 197             char[] buffer = new char[1024]; 198             int count; 199             while ((count = reader.read(buffer)) != -1) { 200                 writer.write(buffer, 0, count); 201             } 202             return writer.toString(); 203         } finally { 204             reader.close(); 205         } 206     } 207  208     /** 209      * Returns the ASCII characters up to but not including the next "/r/n", or 210      * "/n". 211      * 212      * @throws java.io.EOFException if the stream is exhausted before the next newline 213      *     character. 214      */ 215     public static String readAsciiLine(InputStream in) throws IOException { 216         // TODO: support UTF-8 here instead 217  218         StringBuilder result = new StringBuilder(80); 219         while (true) { 220             int c = in.read(); 221             if (c == -1) { 222                 throw new EOFException(); 223             } else if (c == '/n') { 224                 break; 225             } 226  227             result.append((char) c); 228         } 229         int length = result.length(); 230         if (length > 0 && result.charAt(length - 1) == '/r') { 231             result.setLength(length - 1); 232         } 233         return result.toString(); 234     } 235  236     /** 237      * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null. 238      */ 239     public static void closeQuietly(Closeable closeable) { 240         if (closeable != null) { 241             try { 242                 closeable.close(); 243             } catch (RuntimeException rethrown) { 244                 throw rethrown; 245             } catch (Exception ignored) { 246             } 247         } 248     } 249  250     /** 251      * Recursively delete everything in {@code dir}. 252      */ 253     // TODO: this should specify paths as Strings rather than as Files 254     public static void deleteContents(File dir) throws IOException { 255         File[] files = dir.listFiles(); 256         if (files == null) { 257             throw new IllegalArgumentException("not a directory: " + dir); 258         } 259         for (File file : files) { 260             if (file.isDirectory()) { 261                 deleteContents(file); 262             } 263             if (!file.delete()) { 264                 throw new IOException("failed to delete file: " + file); 265             } 266         } 267     } 268  269     /** This cache uses a single background thread to evict entries. */ 270     private final ExecutorService executorService = new ThreadPoolExecutor(0, 1, 271             60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); 272     private final Callable<Void> cleanupCallable = new Callable<Void>() { 273         @Override public Void call() throws Exception { 274             synchronized (DiskLruCache.this) { 275                 if (journalWriter == null) { 276                     return null; // closed 277                 } 278                 trimToSize(); 279                 if (journalRebuildRequired()) { 280                     rebuildJournal(); 281                     redundantOpCount = 0; 282                 } 283             } 284             return null; 285         } 286     }; 287  288     private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) { 289         this.directory = directory; 290         this.appVersion = appVersion; 291         this.journalFile = new File(directory, JOURNAL_FILE); 292         this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP); 293         this.valueCount = valueCount; 294         this.maxSize = maxSize; 295     } 296  297     /** 298      * Opens the cache in {@code directory}, creating a cache if none exists 299      * there. 300      * 301      * @param directory a writable directory 302      * @param appVersion 303      * @param valueCount the number of values per cache entry. Must be positive. 304      * @param maxSize the maximum number of bytes this cache should use to store 305      * @throws java.io.IOException if reading or writing the cache directory fails 306      */ 307     public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) 308             throws IOException { 309         if (maxSize <= 0) { 310             throw new IllegalArgumentException("maxSize <= 0"); 311         } 312         if (valueCount <= 0) { 313             throw new IllegalArgumentException("valueCount <= 0"); 314         } 315  316         // prefer to pick up where we left off 317         DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); 318         if (cache.journalFile.exists()) { 319             try { 320                 cache.readJournal(); 321                 cache.processJournal(); 322                 cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true), 323                         IO_BUFFER_SIZE); 324                 return cache; 325             } catch (IOException journalIsCorrupt) { 326 //                System.logW("DiskLruCache " + directory + " is corrupt: " 327 //                        + journalIsCorrupt.getMessage() + ", removing"); 328                 cache.delete(); 329             } 330         } 331  332         // create a new empty cache 333         directory.mkdirs(); 334         cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); 335         cache.rebuildJournal(); 336         return cache; 337     } 338  339     private void readJournal() throws IOException { 340         InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE); 341         try { 342             String magic = readAsciiLine(in); 343             String version = readAsciiLine(in); 344             String appVersionString = readAsciiLine(in); 345             String valueCountString = readAsciiLine(in); 346             String blank = readAsciiLine(in); 347             if (!MAGIC.equals(magic) 348                     || !VERSION_1.equals(version) 349                     || !Integer.toString(appVersion).equals(appVersionString) 350                     || !Integer.toString(valueCount).equals(valueCountString) 351                     || !"".equals(blank)) { 352                 throw new IOException("unexpected journal header: [" 353                         + magic + ", " + version + ", " + valueCountString + ", " + blank + "]"); 354             } 355  356             while (true) { 357                 try { 358                     readJournalLine(readAsciiLine(in)); 359                 } catch (EOFException endOfJournal) { 360                     break; 361                 } 362             } 363         } finally { 364             closeQuietly(in); 365         } 366     } 367  368     private void readJournalLine(String line) throws IOException { 369         String[] parts = line.split(" "); 370         if (parts.length < 2) { 371             throw new IOException("unexpected journal line: " + line); 372         } 373  374         String key = parts[1]; 375         if (parts[0].equals(REMOVE) && parts.length == 2) { 376             lruEntries.remove(key); 377             return; 378         } 379  380         Entry entry = lruEntries.get(key); 381         if (entry == null) { 382             entry = new Entry(key); 383             lruEntries.put(key, entry); 384         } 385  386         if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) { 387             entry.readable = true; 388             entry.currentEditor = null; 389             entry.setLengths(copyOfRange(parts, 2, parts.length)); 390         } else if (parts[0].equals(DIRTY) && parts.length == 2) { 391             entry.currentEditor = new Editor(entry); 392         } else if (parts[0].equals(READ) && parts.length == 2) { 393             // this work was already done by calling lruEntries.get() 394         } else { 395             throw new IOException("unexpected journal line: " + line); 396         } 397     } 398  399     /** 400      * Computes the initial size and collects garbage as a part of opening the 401      * cache. Dirty entries are assumed to be inconsistent and will be deleted. 402      */ 403     private void processJournal() throws IOException { 404         deleteIfExists(journalFileTmp); 405         for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) { 406             Entry entry = i.next(); 407             if (entry.currentEditor == null) { 408                 for (int t = 0; t < valueCount; t++) { 409                     size += entry.lengths[t]; 410                 } 411             } else { 412                 entry.currentEditor = null; 413                 for (int t = 0; t < valueCount; t++) { 414                     deleteIfExists(entry.getCleanFile(t)); 415                     deleteIfExists(entry.getDirtyFile(t)); 416                 } 417                 i.remove(); 418             } 419         } 420     } 421  422     /** 423      * Creates a new journal that omits redundant information. This replaces the 424      * current journal if it exists. 425      */ 426     private synchronized void rebuildJournal() throws IOException { 427         if (journalWriter != null) { 428             journalWriter.close(); 429         } 430  431         Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE); 432         writer.write(MAGIC); 433         writer.write("/n"); 434         writer.write(VERSION_1); 435         writer.write("/n"); 436         writer.write(Integer.toString(appVersion)); 437         writer.write("/n"); 438         writer.write(Integer.toString(valueCount)); 439         writer.write("/n"); 440         writer.write("/n"); 441  442         for (Entry entry : lruEntries.values()) { 443             if (entry.currentEditor != null) { 444                 writer.write(DIRTY + ' ' + entry.key + '/n'); 445             } else { 446                 writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '/n'); 447             } 448         } 449  450         writer.close(); 451         journalFileTmp.renameTo(journalFile); 452         journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE); 453     } 454  455     private static void deleteIfExists(File file) throws IOException { 456 //        try { 457 //            Libcore.os.remove(file.getPath()); 458 //        } catch (ErrnoException errnoException) { 459 //            if (errnoException.errno != OsConstants.ENOENT) { 460 //                throw errnoException.rethrowAsIOException(); 461 //            } 462 //        } 463         if (file.exists() && !file.delete()) { 464             throw new IOException(); 465         } 466     } 467  468     /** 469      * Returns a snapshot of the entry named {@code key}, or null if it doesn't 470      * exist is not currently readable. If a value is returned, it is moved to 471      * the head of the LRU queue. 472      */ 473     public synchronized Snapshot get(String key) throws IOException { 474         checkNotClosed(); 475         validateKey(key); 476         Entry entry = lruEntries.get(key); 477         if (entry == null) { 478             return null; 479         } 480  481         if (!entry.readable) { 482             return null; 483         } 484  485         /* 486          * Open all streams eagerly to guarantee that we see a single published 487          * snapshot. If we opened streams lazily then the streams could come 488          * from different edits. 489          */ 490         InputStream[] ins = new InputStream[valueCount]; 491         try { 492             for (int i = 0; i < valueCount; i++) { 493                 ins[i] = new FileInputStream(entry.getCleanFile(i)); 494             } 495         } catch (FileNotFoundException e) { 496             // a file must have been deleted manually! 497             return null; 498         } 499  500         redundantOpCount++; 501         journalWriter.append(READ + ' ' + key + '/n'); 502         if (journalRebuildRequired()) { 503             executorService.submit(cleanupCallable); 504         } 505  506         return new Snapshot(key, entry.sequenceNumber, ins); 507     } 508  509     /** 510      * Returns an editor for the entry named {@code key}, or null if another 511      * edit is in progress. 512      */ 513     public Editor edit(String key) throws IOException { 514         return edit(key, ANY_SEQUENCE_NUMBER); 515     } 516  517     private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { 518         checkNotClosed(); 519         validateKey(key); 520         Entry entry = lruEntries.get(key); 521         if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER 522                 && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) { 523             return null; // snapshot is stale 524         } 525         if (entry == null) { 526             entry = new Entry(key); 527             lruEntries.put(key, entry); 528         } else if (entry.currentEditor != null) { 529             return null; // another edit is in progress 530         } 531  532         Editor editor = new Editor(entry); 533         entry.currentEditor = editor; 534  535         // flush the journal before creating files to prevent file leaks 536         journalWriter.write(DIRTY + ' ' + key + '/n'); 537         journalWriter.flush(); 538         return editor; 539     } 540  541     /** 542      * Returns the directory where this cache stores its data. 543      */ 544     public File getDirectory() { 545         return directory; 546     } 547  548     /** 549      * Returns the maximum number of bytes that this cache should use to store 550      * its data. 551      */ 552     public long maxSize() { 553         return maxSize; 554     } 555  556     /** 557      * Returns the number of bytes currently being used to store the values in 558      * this cache. This may be greater than the max size if a background 559      * deletion is pending. 560      */ 561     public synchronized long size() { 562         return size; 563     } 564  565     private synchronized void completeEdit(Editor editor, boolean success) throws IOException { 566         Entry entry = editor.entry; 567         if (entry.currentEditor != editor) { 568             throw new IllegalStateException(); 569         } 570  571         // if this edit is creating the entry for the first time, every index must have a value 572         if (success && !entry.readable) { 573             for (int i = 0; i < valueCount; i++) { 574                 if (!entry.getDirtyFile(i).exists()) { 575                     editor.abort(); 576                     throw new IllegalStateException("edit didn't create file " + i); 577                 } 578             } 579         } 580  581         for (int i = 0; i < valueCount; i++) { 582             File dirty = entry.getDirtyFile(i); 583             if (success) { 584                 if (dirty.exists()) { 585                     File clean = entry.getCleanFile(i); 586                     dirty.renameTo(clean); 587                     long oldLength = entry.lengths[i]; 588                     long newLength = clean.length(); 589                     entry.lengths[i] = newLength; 590                     size = size - oldLength + newLength; 591                 } 592             } else { 593                 deleteIfExists(dirty); 594             } 595         } 596  597         redundantOpCount++; 598         entry.currentEditor = null; 599         if (entry.readable | success) { 600             entry.readable = true; 601             journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '/n'); 602             if (success) { 603                 entry.sequenceNumber = nextSequenceNumber++; 604             } 605         } else { 606             lruEntries.remove(entry.key); 607             journalWriter.write(REMOVE + ' ' + entry.key + '/n'); 608         } 609  610         if (size > maxSize || journalRebuildRequired()) { 611             executorService.submit(cleanupCallable); 612         } 613     } 614  615     /** 616      * We only rebuild the journal when it will halve the size of the journal 617      * and eliminate at least 2000 ops. 618      */ 619     private boolean journalRebuildRequired() { 620         final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000; 621         return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD 622                 && redundantOpCount >= lruEntries.size(); 623     } 624  625     /** 626      * Drops the entry for {@code key} if it exists and can be removed. Entries 627      * actively being edited cannot be removed. 628      * 629      * @return true if an entry was removed. 630      */ 631     public synchronized boolean remove(String key) throws IOException { 632         checkNotClosed(); 633         validateKey(key); 634         Entry entry = lruEntries.get(key); 635         if (entry == null || entry.currentEditor != null) { 636             return false; 637         } 638  639         for (int i = 0; i < valueCount; i++) { 640             File file = entry.getCleanFile(i); 641             if (!file.delete()) { 642                 throw new IOException("failed to delete " + file); 643             } 644             size -= entry.lengths[i]; 645             entry.lengths[i] = 0; 646         } 647  648         redundantOpCount++; 649         journalWriter.append(REMOVE + ' ' + key + '/n'); 650         lruEntries.remove(key); 651  652         if (journalRebuildRequired()) { 653             executorService.submit(cleanupCallable); 654         } 655  656         return true; 657     } 658  659     /** 660      * Returns true if this cache has been closed. 661      */ 662     public boolean isClosed() { 663         return journalWriter == null; 664     } 665  666     private void checkNotClosed() { 667         if (journalWriter == null) { 668             throw new IllegalStateException("cache is closed"); 669         } 670     } 671  672     /** 673      * Force buffered operations to the filesystem. 674      */ 675     public synchronized void flush() throws IOException { 676         checkNotClosed(); 677         trimToSize(); 678         journalWriter.flush(); 679     } 680  681     /** 682      * Closes this cache. Stored values will remain on the filesystem. 683      */ 684     public synchronized void close() throws IOException { 685         if (journalWriter == null) { 686             return; // already closed 687         } 688         for (Entry entry : new ArrayList<Entry>(lruEntries.values())) { 689             if (entry.currentEditor != null) { 690                 entry.currentEditor.abort(); 691             } 692         } 693         trimToSize(); 694         journalWriter.close(); 695         journalWriter = null; 696     } 697  698     private void trimToSize() throws IOException { 699         while (size > maxSize) { 700 //            Map.Entry<String, Entry> toEvict = lruEntries.eldest(); 701             final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next(); 702             remove(toEvict.getKey()); 703         } 704     } 705  706     /** 707      * Closes the cache and deletes all of its stored values. This will delete 708      * all files in the cache directory including files that weren't created by 709      * the cache. 710      */ 711     public void delete() throws IOException { 712         close(); 713         deleteContents(directory); 714     } 715  716     private void validateKey(String key) { 717         if (key.contains(" ") || key.contains("/n") || key.contains("/r")) { 718             throw new IllegalArgumentException( 719                     "keys must not contain spaces or newlines: /"" + key + "/""); 720         } 721     } 722  723     private static String inputStreamToString(InputStream in) throws IOException { 724         return readFully(new InputStreamReader(in, UTF_8)); 725     } 726  727     /** 728      * A snapshot of the values for an entry. 729      */ 730     public final class Snapshot implements Closeable { 731         private final String key; 732         private final long sequenceNumber; 733         private final InputStream[] ins; 734  735         private Snapshot(String key, long sequenceNumber, InputStream[] ins) { 736             this.key = key; 737             this.sequenceNumber = sequenceNumber; 738             this.ins = ins; 739         } 740  741         /** 742          * Returns an editor for this snapshot's entry, or null if either the 743          * entry has changed since this snapshot was created or if another edit 744          * is in progress. 745          */ 746         public Editor edit() throws IOException { 747             return DiskLruCache.this.edit(key, sequenceNumber); 748         } 749  750         /** 751          * Returns the unbuffered stream with the value for {@code index}. 752          */ 753         public InputStream getInputStream(int index) { 754             return ins[index]; 755         } 756  757         /** 758          * Returns the string value for {@code index}. 759          */ 760         public String getString(int index) throws IOException { 761             return inputStreamToString(getInputStream(index)); 762         } 763  764         @Override public void close() { 765             for (InputStream in : ins) { 766                 closeQuietly(in); 767             } 768         } 769     } 770  771     /** 772      * Edits the values for an entry. 773      */ 774     public final class Editor { 775         private final Entry entry; 776         private boolean hasErrors; 777  778         private Editor(Entry entry) { 779             this.entry = entry; 780         } 781  782         /** 783          * Returns an unbuffered input stream to read the last committed value, 784          * or null if no value has been committed. 785          */ 786         public InputStream newInputStream(int index) throws IOException { 787             synchronized (DiskLruCache.this) { 788                 if (entry.currentEditor != this) { 789                     throw new IllegalStateException(); 790                 } 791                 if (!entry.readable) { 792                     return null; 793                 } 794                 return new FileInputStream(entry.getCleanFile(index)); 795             } 796         } 797  798         /** 799          * Returns the last committed value as a string, or null if no value 800          * has been committed. 801          */ 802         public String getString(int index) throws IOException { 803             InputStream in = newInputStream(index); 804             return in != null ? inputStreamToString(in) : null; 805         } 806  807         /** 808          * Returns a new unbuffered output stream to write the value at 809          * {@code index}. If the underlying output stream encounters errors 810          * when writing to the filesystem, this edit will be aborted when 811          * {@link #commit} is called. The returned output stream does not throw 812          * IOExceptions. 813          */ 814         public OutputStream newOutputStream(int index) throws IOException { 815             synchronized (DiskLruCache.this) { 816                 if (entry.currentEditor != this) { 817                     throw new IllegalStateException(); 818                 } 819                 return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index))); 820             } 821         } 822  823         /** 824          * Sets the value at {@code index} to {@code value}. 825          */ 826         public void set(int index, String value) throws IOException { 827             Writer writer = null; 828             try { 829                 writer = new OutputStreamWriter(newOutputStream(index), UTF_8); 830                 writer.write(value); 831             } finally { 832                 closeQuietly(writer); 833             } 834         } 835  836         /** 837          * Commits this edit so it is visible to readers.  This releases the 838          * edit lock so another edit may be started on the same key. 839          */ 840         public void commit() throws IOException { 841             if (hasErrors) { 842                 completeEdit(this, false); 843                 remove(entry.key); // the previous entry is stale 844             } else { 845                 completeEdit(this, true); 846             } 847         } 848  849         /** 850          * Aborts this edit. This releases the edit lock so another edit may be 851          * started on the same key. 852          */ 853         public void abort() throws IOException { 854             completeEdit(this, false); 855         } 856  857         private class FaultHidingOutputStream extends FilterOutputStream { 858             private FaultHidingOutputStream(OutputStream out) { 859                 super(out); 860             } 861  862             @Override public void write(int oneByte) { 863                 try { 864                     out.write(oneByte); 865                 } catch (IOException e) { 866                     hasErrors = true; 867                 } 868             } 869  870             @Override public void write(byte[] buffer, int offset, int length) { 871                 try { 872                     out.write(buffer, offset, length); 873                 } catch (IOException e) { 874                     hasErrors = true; 875                 } 876             } 877  878             @Override public void close() { 879                 try { 880                     out.close(); 881                 } catch (IOException e) { 882                     hasErrors = true; 883                 } 884             } 885  886             @Override public void flush() { 887                 try { 888                     out.flush(); 889                 } catch (IOException e) { 890                     hasErrors = true; 891                 } 892             } 893         } 894     } 895  896     private final class Entry { 897         private final String key; 898  899         /** Lengths of this entry's files. */ 900         private final long[] lengths; 901  902         /** True if this entry has ever been published */ 903         private boolean readable; 904  905         /** The ongoing edit or null if this entry is not being edited. */ 906         private Editor currentEditor; 907  908         /** The sequence number of the most recently committed edit to this entry. */ 909         private long sequenceNumber; 910  911         private Entry(String key) { 912             this.key = key; 913             this.lengths = new long[valueCount]; 914         } 915  916         public String getLengths() throws IOException { 917             StringBuilder result = new StringBuilder(); 918             for (long size : lengths) { 919                 result.append(' ').append(size); 920             } 921             return result.toString(); 922         } 923  924         /** 925          * Set lengths using decimal numbers like "10123". 926          */ 927         private void setLengths(String[] strings) throws IOException { 928             if (strings.length != valueCount) { 929                 throw invalidLengths(strings); 930             } 931  932             try { 933                 for (int i = 0; i < strings.length; i++) { 934                     lengths[i] = Long.parseLong(strings[i]); 935                 } 936             } catch (NumberFormatException e) { 937                 throw invalidLengths(strings); 938             } 939         } 940  941         private IOException invalidLengths(String[] strings) throws IOException { 942             throw new IOException("unexpected journal line: " + Arrays.toString(strings)); 943         } 944  945         public File getCleanFile(int i) { 946             return new File(directory, key + "." + i); 947         } 948  949         public File getDirtyFile(int i) { 950             return new File(directory, key + "." + i + ".tmp"); 951         } 952     } 953 }
DiskLruCache磁盘缓存类

4、图片缓存类,包含(LruCache内存缓存,DiskLruCache磁盘缓存)

  1 package com.lcw.rabbit.image.utils;   2    3 import java.io.File;   4 import java.io.IOException;   5 import java.io.OutputStream;   6    7 import android.content.Context;   8 import android.content.pm.PackageInfo;   9 import android.content.pm.PackageManager.NameNotFoundException;  10 import android.graphics.Bitmap;  11 import android.graphics.Bitmap.CompressFormat;  12 import android.graphics.BitmapFactory;  13 import android.os.Environment;  14 import android.support.v4.util.LruCache;  15 import android.util.Log;  16   17 import com.android.volley.toolbox.ImageLoader.ImageCache;  18 import com.lcw.rabbit.image.utils.DiskLruCache.Snapshot;  19   20 /**  21  * 图片缓存帮助类  22  *   23  * 包含内存缓存LruCache和磁盘缓存DiskLruCache  24  *   25  * @author Rabbit_Lee  26  *   27  */  28 public class ImageCacheUtil implements ImageCache {  29       30     private String TAG=ImageCacheUtil.this.getClass().getSimpleName();  31   32     //缓存类  33     private static LruCache<String, Bitmap> mLruCache;  34     private static DiskLruCache mDiskLruCache;  35   36     //磁盘缓存大小  37     private static final int DISKMAXSIZE = 10 * 1024 * 1024;  38   39     public ImageCacheUtil() {  40         // 获取应用可占内存的1/8作为缓存  41         int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);  42         // 实例化LruCaceh对象  43         mLruCache = new LruCache<String, Bitmap>(maxSize) {  44             @Override  45             protected int sizeOf(String key, Bitmap bitmap) {  46                 return bitmap.getRowBytes() * bitmap.getHeight();  47             }  48         };  49         try {  50             // 获取DiskLruCahce对象  51             mDiskLruCache = DiskLruCache.open(getDiskCacheDir(MyApplication.newInstance(), "Rabbit"), getAppVersion(MyApplication.newInstance()), 1, DISKMAXSIZE);  52         } catch (IOException e) {  53             e.printStackTrace();  54         }  55     }  56   57     /**  58      * 从缓存(内存缓存,磁盘缓存)中获取Bitmap  59      */  60     @Override  61     public Bitmap getBitmap(String url) {  62         if (mLruCache.get(url) != null) {  63             // 从LruCache缓存中取  64             Log.i(TAG,"从LruCahce获取");  65             return mLruCache.get(url);  66         } else {  67             String key = MD5Utils.md5(url);  68             try {  69                 if (mDiskLruCache.get(key) != null) {  70                     // 从DiskLruCahce取  71                     Snapshot snapshot = mDiskLruCache.get(key);  72                     Bitmap bitmap = null;  73                     if (snapshot != null) {  74                         bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));  75                         // 存入LruCache缓存  76                         mLruCache.put(url, bitmap);  77                         Log.i(TAG,"从DiskLruCahce获取");  78                     }  79                     return bitmap;  80                 }  81             } catch (IOException e) {  82                 e.printStackTrace();  83             }  84         }  85         return null;  86     }  87   88     /**  89      * 存入缓存(内存缓存,磁盘缓存)  90      */  91     @Override  92     public void putBitmap(String url, Bitmap bitmap) {  93         // 存入LruCache缓存  94         mLruCache.put(url, bitmap);  95         // 判断是否存在DiskLruCache缓存,若没有存入  96         String key = MD5Utils.md5(url);  97         try {  98             if (mDiskLruCache.get(key) == null) {  99                 DiskLruCache.Editor editor = mDiskLruCache.edit(key); 100                 if (editor != null) { 101                     OutputStream outputStream = editor.newOutputStream(0); 102                     if (bitmap.compress(CompressFormat.JPEG, 100, outputStream)) { 103                         editor.commit(); 104                     } else { 105                         editor.abort(); 106                     } 107                 } 108                 mDiskLruCache.flush(); 109             } 110         } catch (IOException e) { 111             e.printStackTrace(); 112         } 113  114     } 115  116     /** 117      * 该方法会判断当前sd卡是否存在,然后选择缓存地址 118      *  119      * @param context 120      * @param uniqueName 121      * @return 122      */ 123     public static File getDiskCacheDir(Context context, String uniqueName) { 124         String cachePath; 125         if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) { 126             cachePath = context.getExternalCacheDir().getPath(); 127         } else { 128             cachePath = context.getCacheDir().getPath(); 129         } 130         return new File(cachePath + File.separator + uniqueName); 131     } 132  133     /** 134      * 获取应用版本号 135      *  136      * @param context 137      * @return 138      */ 139     public int getAppVersion(Context context) { 140         try { 141             PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); 142             return info.versionCode; 143         } catch (NameNotFoundException e) { 144             e.printStackTrace(); 145         } 146         return 1; 147     } 148  149 }

5、图片缓存管理类

这里的图片加载运用到了Volley自带的ImageLoader,在它的构造方法中需要一个ImageCache对象,在上面的图片缓存类已实现了该接口。

这里向外部提供了一个loadImage的重载方法,一个传入加载图片的宽高,一个默认加载原图,使得外部不再需要关注任何关于缓存的操作。

 1 package com.lcw.rabbit.image;  2   3 import android.graphics.Bitmap;  4 import android.widget.ImageView;  5   6 import com.android.volley.VolleyError;  7 import com.android.volley.toolbox.ImageLoader;  8 import com.android.volley.toolbox.ImageLoader.ImageCache;  9 import com.android.volley.toolbox.ImageLoader.ImageContainer; 10 import com.android.volley.toolbox.ImageLoader.ImageListener; 11 import com.lcw.rabbit.image.utils.ImageCacheUtil; 12  13 /** 14  * 图片缓存管理类 获取ImageLoader对象 15  *  16  * @author Rabbit_Lee 17  *  18  */ 19 public class ImageCacheManager { 20  21     private static String TAG = ImageCacheManager.class.getSimpleName(); 22  23     // 获取图片缓存类对象 24     private static ImageCache mImageCache = new ImageCacheUtil(); 25     // 获取ImageLoader对象 26     public static ImageLoader mImageLoader = new ImageLoader(VolleyRequestQueueManager.mRequestQueue, mImageCache); 27  28     /** 29      * 获取ImageListener 30      *  31      * @param view 32      * @param defaultImage 33      * @param errorImage 34      * @return 35      */ 36     public static ImageListener getImageListener(final ImageView view, final Bitmap defaultImage, final Bitmap errorImage) { 37  38         return new ImageListener() { 39  40             @Override 41             public void onErrorResponse(VolleyError error) { 42                 // 回调失败 43                 if (errorImage != null) { 44                     view.setImageBitmap(errorImage); 45                 } 46             } 47  48             @Override 49             public void onResponse(ImageContainer response, boolean isImmediate) { 50                 // 回调成功 51                 if (response.getBitmap() != null) { 52                     view.setImageBitmap(response.getBitmap()); 53                 } else if (defaultImage != null) { 54                     view.setImageBitmap(defaultImage); 55                 } 56             } 57         }; 58  59     } 60  61     /** 62      * 提供给外部调用方法 63      *  64      * @param url 65      * @param view 66      * @param defaultImage 67      * @param errorImage 68      */ 69     public static void loadImage(String url, ImageView view, Bitmap defaultImage, Bitmap errorImage) { 70         mImageLoader.get(url, ImageCacheManager.getImageListener(view, defaultImage, errorImage), 0, 0); 71     } 72  73     /** 74      * 提供给外部调用方法 75      *  76      * @param url 77      * @param view 78      * @param defaultImage 79      * @param errorImage 80      */ 81     public static void loadImage(String url, ImageView view, Bitmap defaultImage, Bitmap errorImage, int maxWidth, int maxHeight) { 82         mImageLoader.get(url, ImageCacheManager.getImageListener(view, defaultImage, errorImage), maxWidth, maxHeight); 83     } 84 }

6、MainActivity类

 1 package com.lcw.rabbit.image;  2   3 import android.app.Activity;  4 import android.content.res.Resources;  5 import android.graphics.Bitmap;  6 import android.graphics.BitmapFactory;  7 import android.os.Bundle;  8 import android.view.View;  9 import android.view.View.OnClickListener; 10 import android.widget.Button; 11 import android.widget.ImageView; 12  13 public class MainActivity extends Activity { 14  15     private Button mButton; 16     private ImageView mImageView; 17  18     @Override 19     protected void onCreate(Bundle savedInstanceState) { 20         super.onCreate(savedInstanceState); 21         setContentView(R.layout.activity_main); 22         mButton = (Button) findViewById(R.id.button); 23         mImageView= (ImageView) findViewById(R.id.image); 24          25  26         mButton.setOnClickListener(new OnClickListener() { 27  28             @Override 29             public void onClick(View v) { 30                 String url = "http://img.blog.csdn.net/20130702124537953?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdDEyeDM0NTY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast"; 31                 ImageCacheManager.loadImage(url, mImageView, getBitmapFromRes(R.drawable.ic_launcher), getBitmapFromRes(R.drawable.ic_launcher)); 32  33             } 34         }); 35     } 36  37     public Bitmap getBitmapFromRes(int resId) { 38         Resources res = this.getResources(); 39         return BitmapFactory.decodeResource(res, resId); 40  41     } 42  43 }

到这里代码就结束了,由于主要是讲关于缓存层的运用,关于Volley,LruCache,DiskCache的介绍使用,这里就不再阐述了,网上资料很多,大家查阅下便是。

有任何疑问或者建议,大家可以在文章评论给我留言,一起交流!

安卓开发笔记——关于图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)

作者:李晨玮

出处: http://www.cnblogs.com/lichenwei/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

正在看本人博客的这位童鞋,我看你气度不凡,谈吐间隐隐有王者之气,日后必有一番作为!旁边有“推荐”二字,你就顺手把它点了吧,相得准,我分文不收;相不准,你也好回来找我!

正文到此结束
Loading...