安卓开发笔记——关于图片的三级缓存策略(内存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

先来看下效果图:

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

       

Log日志打印:

来看下代码实现:

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磁盘缓存类)

 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转换类

  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 = "https://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的介绍使用,这里就不再阐述了,网上资料很多,大家查阅下便是。

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

作者:李晨玮
出处:http://www.cnblogs.com/lichenwei/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、J Query实例大全 1)AjaxJavaScript资源 1.JS+CSS仿腾讯QQ首页搜索框无刷新换肤效果代码 2.js+css简单后台二级树形菜单demo示例 3.JS+CSS美化经典Select选项框插件 4.JS+CSS通用一个页面同时三个焦点图轮换效果完整实例 5.JS+CSS网页版模拟QQ登录框界面特效示例 6.JS+flash立柱广告代码仿百度MP3搜索右侧可伸缩的立柱广告效果 7.JS版本黑色超动感二级菜单导航模块代码 穿越火线游戏网 8.JS经典3款风格QQ客服在线浮动右侧与左侧代码 9.JS精简网页音乐播放器 浮动在页面顶部像工具栏 10.超强JS网页版泡泡龙游戏下载 11.兼容各浏览器JS+CSS水平和垂直无缝图片滚动效果代码 12.漂亮暖色调js+flash平滑过渡大屏图片切换的广告代码 13.实用国外JS内容选项卡DEMO下载 14.推荐通用文字图片JS不间断滚动封装类 代替Marquee 15.站长推荐全新兼容DIV+CSS版中国地图 JS+DIV+CSS支持热点效果的中国地图 16.支持4级树形 展开菜单插件dTree原版下载 2)JQuery Tab 1.jQuery.Switchable常见网页UI组件(整合Tabs、Slide、Scrollable等插件) 2.jQuery层拖拽插件之jquery仿QQ空间的模块拖动功能插件下载 3.jQuery动感图标切换网页Tab选项卡导航代码 4.jquery封装Tab标签选项卡,内含动画版选项卡及滑动门 5.jQuery实现flash动感切换选项卡TAB插件示例 6.jquery制作自动播放的TAB切换特效 7.基于jQuery简单的Tab滑动门菜单代码(jQuery MoveTab) 8.简单jquery选项卡插件下载(支持鼠标移上切换、点击切换、Ajax方式切换等) 9.漂亮Tab插件之jQuery自动切换轮播Tab选项卡示例代码 10.实用jquery+CSS实现流畅tab切换效果(Coda-Slider 1.1.1)插件 11.实用jquery标签Tab插件下载可切换不同的颜色 12.推荐jquery仿淘宝网商品排行榜展示切换效果,适合电子商务网站使用 13.一款jQuery黑金钢动感Tab选项卡代码 14.站长推荐jQuery动画翻转选项卡(flip风格) 3)表单验证 1.jquery+css美化select下拉菜单插件(Stylish Select v0.3)下载 2.jQuery+CSS实现多项选择文本框的插件下载 3.jQuery表单验证插件EasyValidator 2.0带TIP提示效果 4.jQuery导航插件下载之支持三级的Menu暖色、动画、折叠功能的导航菜单 5.jquery美化CheckBox的插件下载 6.jQuery美化表单漂亮插件jqtransform下载 7.jquery强大表单字符输入限制插件jQuery AlphaNumeric下载 8.jQuery实现表格带排序功能的源码 9.jquery实现动感隐藏登录入口代码 10.jQuery限制input或texteara字符输入限制字数控制插件下载 11.jQuery支持三级菜单的超酷华丽炫彩动感多级渐显菜单下载 12.jQuery制作登录按钮实现Ajax带状态提示 13.jvalidate无刷新表单验证插件示例下载 14.分享jquery无刷新多功能表单验证插件并漂亮美化表单及错误输入提示效果 15.简单jQuery+CSS实现验证表单红色提示效果 16.简单表单input输入框提示插件1.0下载 17.另种效果的jQuery EasyValidator表单验证插件demo示例下载 18.漂亮绿色多级动感仿flash效果的jquery网站导航菜单 19.实用jQuery列表多项选择插件 20.实用jquery实现鼠标滚轮控制改变文本框数字的插件 21.实用jquery使用ul模拟表单select列表效果 22.实用jQuery无限级导航菜单源码下载 23.适合于网站注册的jQuery用户注册条款插件下载(带特效) 24.提升用户体验jquery Ajax表单输入检测验证示例代码 25.推荐jQuery美化Select下拉单选框模拟插件V1.3.6版本下载 26.推荐jQuery美化select下拉框样式漂亮效果 27.下载jQuery网页表单美化修饰插件(jQtransForm美化表单) 28.一款jquery实现表单输入提示的Autobox插件(基于jquery.ui) 29.一款美化表单下拉列表、复选框等的jquery combobox插件实例 30.站长必备jquery实现combox自动筛选,高亮显示功能 4)菜单 1.jquery+CSS超炫丽横向多级滑动导航菜单 2.jQuery+CSS漂亮蓝色三级菜单下载 3.jquery+css实现简洁两级横向导航菜单,带动态效果 4.jQuery+CSS实现竖形动感导航菜单效果 5.jquery+div漂亮SmartMenu下拉菜单气泡透明效果 6.jQuery+div实现flash炫彩菜单插件下载 7.jquery+div实用漂亮精致的多级导航栏菜单插件代码示例 8.jQuery+ul实现漂亮竖直下拉菜单 9.jquery仿google首页图标动画菜单效果实现模拟鼠标动画菜单的代码 10.jquery仿卓越亚马逊网鼠标移到按钮弹出菜单的效果代码 11.jquery浮动变化的个性菜单插件floatmenu下载 12.jQuery黑色风格仿Flash版下滑菜单效果 13.jquery黑色循环滚动菜单特效插件下载 14.jquery灰色简洁风格横向三级网站导航菜单 15.jquery简洁动感支持三级的黑色导航菜单 16.jquery类似TAB一样的动感菜单下载 17.jquery绿色动感滑动下拉多级导航菜单代码下载 18.jQuery美化复选框Checkbox和radio单选框的插件hcheckbox示例 19.jQuery漂亮Flash卡通动感菜单示例 20.jQuery漂亮动感二级网站导航菜单源码下载 21.jquery漂亮苹果系统动画效果的导航菜单示例 22.jquery平滑效果黑色横向与纵向多级下拉菜单插件(支持5级) 23.jquery实现的动感菜单导航条源码 24.jquery实用滚动下拉菜单代码 25.jquery鼠标移动出现下拉菜单及提示特效代码 26.jquery外国滚动型多级展开菜单插件(jGlideMenu)下载 27.jquery自由滚动切换 标签式导航菜单示例 28.博客Blog横向jquery动态滑动菜单插件示例 29.打包50种实例的大型树形菜单控件JSTree(基于jQuery)下载 30.打包两款实用jquery+div横向滑动的下滑菜单代码(兼容性好)下载 31.非常帅jquery仿雅虎网超大菜单导航代码 32.分享jquery带阴影效果折叠菜单dropdown给大家 33.分享下载jquery+Css黑色风格版的手风琴菜单,带动感效果 34.分享一款jquery收缩展开的多级导航(附带手风琴效果示例) 35.国外漂亮动感jquery三级导航菜单插件(横向自动展开)下载 36.基于jquery美化表单效果的插件 37.兼容性好的jquery+div漂亮黑色动感下拉菜单 38.简单jquery背景滑动菜单插件示例 39.经典jQuery三级省、市、县级联菜单下载 40.漂亮jQuery+CSS橙黄色两级导航菜单示例下载 41.漂亮jQuery+CSS竖直菜单下拉展开菜单(手风琴)动画缓冲效果 42.漂亮jQuery四级滑出菜单插件带动画效果适合大多数网站 43.漂亮绿色jquery下拉菜单导航条源码下载 44.漂亮实用jquery绿色风格水平二级导航菜单 45.适合后台的jquery二级下滑菜单带展开与合拢功能 46.树形菜单jquery.popup支持文本框弹出层选择项插件 47.通用性强的jquery带箭头跟随的垂直菜单组(jquery vmenu)插件下载 48.推荐jQuery黑色多级横向菜单导航(带动画效果) 49.学习jQuery简单伸缩菜单代码实例 50.一款jquery仿flash滑动左侧菜单插件代码下载 51.一款jQuery实现伸缩型菜单源码下载 52.一款jQuery制作仿FLASH动感导航菜单效果(附PSD) 53.又一款jquery蓝色经典的三级动画网站菜单 54.又一款黑色风格jQuery支持多级的动感下拉菜单源码 55.增加用户体验之jQuery黑色下拉菜单插件下载 56.站长必备jQuery横向动感菜单代码下载 57.站长必备jQuery可展开与折叠的竖向手风琴内容展示菜单 58.支持3级的jquery天蓝色动画菜单下载,适合艺术类、儿童类网站使用 5)对话框 1.jquery仿div透明模态弹出窗插件下载 2.jquery实现多风格消息弹出框插件jGrowl下载 3.jQuery弹出层插件PopupDiv-v1.0下载(支持ajax、居中等效果) 4.jQuery超炫淡入淡出效果DIV渐变居中弹出框插件下载 5.jquery黑色+蓝色风格DIV提示框示例 6.一款jQuery+DIV居中淡入淡出信息提示框示例 7.一款jQuery可拖动提示窗插件(对话框Dialog插件) 8.一款基于jQuery实现的多功能弹出窗插件weebox及示例代码 9.一款支持拖拽的jQuery层弹出窗口(TipsWindown1.0版本)下载 10.分享一款jquery Ajax弹出对话框插件SimpleModal 11.下载jQuery对话框Dialog弹出层插件演示与使用说明 12.实用jQuery漂亮浮动层插件,精美Dialog代码 13.带关闭按钮jquery+div消息弹出层代码例子 14.推荐jQuery+CSS实现图片放大浮动层带关闭按钮 15.通用jQuery对话框dialog或popup弹出层或提示窗口插件下载 6)工具提示 1.jquery+div随屏幕滚动特效(支持设置参数滚动) 2.jQuery字符插件之适合留言板的jquery文本框输入字符限制插件下载 3.jquery实现Tooltip提示(文字、链接、地图锚点),自带5种不同样式 4.jQuery实现支付宝隐藏层提示信息内容的功能 5.jquery实现鼠标划过网址名称显示网站截图功能 6.jQuery带Tips动画提示通用表单验证插件下载(jquery validate),动画效果漂亮 7.jQuery鼠标移到下载地址滑出气泡提示效果的插件 8.又一款jquery实现链接Tip演示下载 9.常用jQuery弹出式链接提示效果Tooltip源码下载 10.相当牛的jQuery动感TipBox,鼠标移上去会动的提示框哦 11.简单实用jQuery用于登录弹出层例子代码(带动画的弹出层) 12.综合jquery实现表格隔行换色和鼠标经过提示效果源码 7)日历插件 1.jQuery简洁漂亮点阵数字时钟显示日期的插件 2.一款简单jQuery日历选择器代码 3.基于jquery实现倒计时钟插件下载,Flip-Down Clock特效效果 4.基于jquery实现多功能日历插件(weekcalendar)1.2版本下载 5.实用漂亮jQuery网页日历插件datePicker下载 6.简洁实用jquery日期选择插件之jQuery datePicker下载 8)图片展示 1.2款jQuery图片自动切换常用广告代码 2.jquery+css五屏焦点图淡入淡出+圆形按钮切换广告图片代码 3.jQuery+CSS实用图片收缩与放大效果插件 4.jquery+div实现同时滑动切换的图文展示特效插件下载 5.jquery+div带动画按钮图片手动与自动切换的特效代码 6.jquery一页多用的飞飞图片幻灯插件演示 7.jquery仿flash产品图片多角度展示特效代码 8.jquery仿flash图片放大相册的插件代码(Zoomer Gallery)下载 9.jquery仿flash的图片幻灯片播放特效实例完整版 10.jquery仿LightBox图片盒子单击逐渐放大的图片展示的插件下载 11.jquery仿Lightbox的插件jQuery UI.ariaLightbox点击图片放大显示插件下载 12.jQuery仿动感flash自动滚动图片切换广告插件 13.jQuery仿新浪新闻图片浏览器(支持鼠标左右滚动控制图片切换) 14.jquery仿苏宁易购商城产品图片全方位展示功能 15.jquery制作漂亮按钮示例打包 16.jQuery动态切换网页背景的大块图片的导航栏代码 17.jquery图文排行混合互相切换效果(仿Vancl效果) 18.jQuery图片插件之鼠标放在图片上出现动态的hover效果插件 19.jQuery图片自动轮播淡入淡出的幻灯插件iFadeSlide 20.jQuery大气banner带时间线自动播放的焦点图轮番切换代码 21.jquery定时自动切换banner广告图片动画插件示例 22.jquery实现LightBox图片点击放大效果的图片盒子插件 23.jQuery实现slider图片滚动,单个滚动,成组滚动示例 24.jQuery实现产品图片循坏旋转的代码 25.jQuery实现动态图文分组排序切换源码 26.jQuery实现图片3D旋转特效插件 v1.1版本下载 27.jQuery实现图片3D立体感的前后轮番展示特效 28.jQuery实现图片取景器仿相机拍照功能的插件photoShoot 29.jQuery实现图片变色特效插件与实例下载如jquery图片变灰色 30.jquery实现图片可拖动展示的实例下载 31.jQuery实现拖动滚动条的缩略图排列插件下载 32.jQuery实现焦点图片Flash自动平滑渐变效果 33.jQuery实现鼠标移到链接提示显示图片功能插件 34.jquery实现鼠标经过链接放大图片特效代码 35.jquery实用Banner大图片横向切换效果 36.jquery实用产品图片展示动感切换效果源码 37.jquery平滑交换真彩色的图片逐渐变为黑白图像的代码 38.jquery异步加载图片的插件jqGalScroll下载 39.jquery微型相册插件Micro Image Gallery下载 40.jQuery把图片放大及变亮特效插件下载 41.jquery拖动滚动条控制图片滚动及图片放大特效的示例 42.jquery旋转式图片切换并带图片放大功能 43.jQuery漂亮网页右上角双层撕角广告代码 44.jquery漂亮网页布局综合定时器、切换间隔、滚动时间、滚动图片个数等示例 45.jQuery演示Ajax加载并显示图片的相片画廊实例 46.jQuery版Sexy Lightbox 2.3内容修饰框插件下载(支持HTML,flash,图片,视频等) 47.jquery版自动滚动图片动画特效插件可处理图片JSON数据源 48.jQuery电子商务网站产品展示插件之仿苏宁易购商城产品图展示特效 49.jquery相册播放器插件实现无序图片列表转换成有序并播放 50.jQuery移动网页背景图浮云流水特效 51.jquery简单控制上下、左右四方向滚动的特效插件下载 52.jquery缩略图滚动特效之带小图的网页元素滚动轮播插件 53.jquery网站顶部滑动广告插件 54.jquery自动播放图片滚动漂浮式效果的示例 55.jquery贴图旋转及缩放插件下载 56.jquery门户网站首页全屏弹性伸缩至小屏的广告代码(非常实用) 57.jQuery黑色动感Ajax无刷新动态分组图片效果代码 58.jquery黑色风格左右带箭头的图片浏览控制插件下载 59.jQuery鼠标滑动图片显示标题与简介的滑动动感特效代码 60.jQuery鼠标移上小图显示大缩略图功能 61.js仿淘宝网鼠标经过缩略图放大图片效果的jQuery Fancy Hover Effect完整实例 62.Supersized jQuery全屏相册图片自动切换插件 63.[荐]jquery仿flash漂亮横向图片滚动效果完整版(兼容性非常好) 64.[荐]jQuery焦点图幻灯切换插件Tab选项卡(soChange 1.4)下载 65.一款jQuery仿flash放大图片的相册插件 66.一款jQuery仿苹果mac os系统经典菜单效果源码 67.一款jQuery动感左右滚动图片切换插件带缩图一起滚动 68.一款jQuery实现banner图片轮显、广告切换、图片幻灯插件EasySlide下载 69.一款jquery实现图片放大插件imgBox下载 70.一款jQuery实现漂亮精美相册插件源码 71.一款jQuery左右箭头控制大图滚动切换的代码 72.一款jquery常用产品图片放大效果插件下载 73.一款jQuery插件slide幻灯片切换图片宽高自适应 74.一款jQuery漂亮淡出淡入焦点大图切换源码 75.一款jquery缩略图商品切换放大展示功能插件 76.一款使用jQuery左右控制横向图片滚动的代码 77.一款基于jquery定时图片切换代码下载 78.一款实用经典jQuery图片切换展示插件下载 79.下载jQuery动感广告图片翻转插件(PictureRoll)示例版 80.下载JQuery淡入淡出效果插件InnerFade 81.下载jquery走马灯效果图片连续滚动的实例 82.几种简单实用jQuery焦点图片自动切换效果 83.分享jquery仿LightBox动感多样式图片放大插件zoomimage下载 84.分享jquery仿当当网店铺图片轮番切换(同时显示图片说明)代码下载 85.分享一款jQuery thumbnail惟美的图片Tip提示效果 86.分享一款jquery仿lightbox无刷新图片显示插件PrettyPhoto下载 87.分享多款jQuery图片预加载切换效果(上下滚动、淡入淡出渐变等) 88.动感十足jquery仿腾讯图片滚动浏览功能(带左右控制按钮)MovingBoxes插件下载 89.参考JQZOOM插件使用jQuery仿京东网产品展示切换及放大效果的代码 90.又一款jquery自动轮播焦点图+内容的广告代码 91.又一款非常不错的jQuery+div大气新闻图片切换插件代码(AnythingSlider)下载 92.基于jQuery带时间轴宽屏图片切换源码 93.基于jQuery的横向无缝图片滚动插件jcarousel 94.实用jquery焦点图源码2 95.常用jQuery新闻焦点图片切换效果插件 96.强大jQuery实现3D文字三维旋转效果代码下载 97.很帅的jquery焦点图切换源码可用于产品大图展示 98.很帅的jQuery鼠标移动预览图展示+简介内容展示特效代码 99.打包jQuery实用3个焦点图切换自动切换特效的代码 100.打包jquery拖动条拖动图片缩略图及放大图片效果插件(翻版thickbox插件) 101.打包基于jQuery对图片边框修饰插件示例下载(支持图片阴影效果、顶部banner效果、邮票边框、毛边相框、云朵包围效果) 102.推荐jquery仿Flash大banner图片切换播放特效,非常完美 103.推荐jQuery仿新浪QQ绝好图片轮播效果带左右控制插件 104.推荐jQuery兼容所有浏览器的自定义多样式图片幻灯片插件(KinSlideshow)下载 105.推荐jquery动画制作示例图片滚动和飞行乌鸦,车窗效果,非常强大 106.推荐jQuery实用缩略图广告效果插件下载 107.推荐jQuery模拟Windows视窗的效果实现相册图片拖动特效插件 108.推荐jQuery网站首页三幅banner切换大图广告自动滚动代码下载 109.推荐jQuery购物类产品图片放大插件jqzoom v2.0下载 110.推荐实用jquery图片截取代码下载 111.站长推荐jQuery产品展示专用的图片幻灯+缩略图、左右控制按钮实例下载 112.站长推荐一款非常精美的jQuery 1.4漂亮相册程序打包给大家 113.站长素材推荐jQuery带箭头控制左右图片滚动效果 114.第二款jQuery左右移动图片+内容展示插件代码 115.简单jQuery实现产品图片自动左右滚动插件下载 116.编辑推荐jQuery超帅模拟图片翻页的展示Flash特效效果 117.适合网站内容推广淡入淡出特效的jQuery焦点图结合文字导航切换特效(很精美) 118.非常不错jQuery放大镜实例包下载(可放大图片与区域放大的AnythingZoomer插件) 119.非常不错jQuery网页内容图片分类插件带特效 120.非常实用的jquery幻灯切换广告源码 121.非常帅的jQeruy图片切换拖影变清淅的神奇特效 122.非常漂亮jQuery左右切换三屏大幅焦点图广告代码下载 二、CSS + DIV 1)CSS网页模版 1.一款DIV绿色效果网页模板 2.一款浅蓝色DIV+CSS企业软件宣传全站模板 3.一款深蓝色DIV商务网页模板 4.一款潮流前线CSS网页模板 5.一款简洁漂亮棕褐色网页模板(DIV+CSS实现) 6.一款纯DIV+CSS商店模板下载 7.一款纯DIV+CSS灰色风格的网页模板 8.个性网页模板之用花装饰的DIV网站模板打包 9.公司类模板下载之专业公司DIV+CSS网页模板 10.公司类网页模板推荐大气的公司CSS模板下载 11.博客模板下载之DIV+CSS实现纸张效果博客模板 12.博客模板下载之灰色blog模板 13.商业模板下载之纯DIV+CSS制作深褐色商务网页模板 14.圣诞节日网页模板之欢乐圣诞纯DIV网站模板下载 15.圣诞节模板下载之圣诞礼物CSS网页模板 16.室内装饰类企业网页模板下载 17.工程类公司DIV网页模板源码下载 18.整套完整版纯DIV+CSS律师事务所类整站全套模板 19.深蓝色DIV公司类网页模板下载 20.深蓝色网页模板整理2 21.漂亮HTML网页模板纯DIV编写 22.漂亮模板推荐之水鸭色DIV+CSS网页模板 23.漂亮纯DIV+CSS绿色风格网页模板 24.漂亮网页模板下载之DIV+CSS木质性网站模板 25.漂亮英文DIV+CSS餐厅网站模板源码 26.简单纯DIV橙色网页模板 27.纯DIV+CSS制作的黑绿风格植物类网站模板 28.纯绿色博客类DIV网页模板 29.经典黑色DIV公司网页模板 30.绿色清新效果DIV网页模板 31.网页博客模板下载之DIV+CSS黄色博客模板 32.适合旅游类DIV+CSS网站模板源码 2)DIV+CSS实例 1.CSS导航菜单之颜色淡绿色水平导航菜单源码 2.CSS橙色一级导航菜单示例 3.CSS绿色网站导航菜单 4.css菜单系列之纯CSS灰色下拉菜单源码 5.css菜单系列之纯CSS黄色垂直左栏导航菜单 6.DIV仿支付宝导航菜单实例 7.jQuery+CSS实现蓝色垂直导航菜单带提示效果 8.jQuery实现车门滑动效果菜单源码,很帅哦 9.Jquery经典插件收集 10.使用jquery实现清新蓝色CSS二级菜单 11.兼容IEFF浏览器不规则TAB选项卡源码 12.实用网站快速导航CSS按钮菜单源码(漂亮CSS导航按钮菜单) 13.强烈推荐一款jQuery+CSS仿iPhone手机面板导航效果源码 14.推荐实用OA系统左侧DIV+CSS菜单源码(漂亮OA界面菜单源码下载) 15.推荐实用的jQuery+CSS水平二级导航菜单源码(黑蓝效果) 16.政府类网站支持二级纯CSS导航菜单源码 17.漂亮CSS小图标mini导航效果 18.漂亮滑动透明效果CSS水平导航菜单源码 19.简单实用CSS蓝色垂直导航菜单(UL、LI结合实现) 20.简单实用亮黑色纯CSS水平导航菜单 21.纯CSS+图片实现橙色网站水平菜单实例 22.纯CSS实现棕黄色水平导航菜单源码 23.纯CSS实现水平梯形CSS导航菜单(很简单) 24.纯CSS实现蓝色圆角水平网站导航菜单 25.纯CSS实现黑色水平CSS导航菜单源码 26.纯CSS菜单系列之橙色块状水平导航类CSS菜单 27.纯DIV+CSS灰色网站导航菜单 自适应长度菜单背景 28.非常漂亮2010猪八戒网站导航菜单源码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值