图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)

转载自:图片的三级缓存策略(内存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

来看下代码实现:

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

[java] view plain copy

package com.lcw.rabbit.image.utils;  

import android.app.Application;  
/** 
 * Application类,提供全局上下文对象 
 * @author Rabbit_Lee 
 * 
 */  
public class MyApplication extends Application {  

    public static String TAG;  
    public static MyApplication myApplication;  

    public static MyApplication newInstance() {  
        return myApplication;  
    }  

    @Override  
    public void onCreate() {  
        super.onCreate();  
        TAG = this.getClass().getSimpleName();  
        myApplication = this;  

    }  
}  

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

[java] view plain copy

package com.lcw.rabbit.image;  

import com.android.volley.Request;  
import com.android.volley.RequestQueue;  
import com.android.volley.toolbox.Volley;  
import com.lcw.rabbit.image.utils.MyApplication;  

/** 
 * 请求队列处理类 
 * 获取RequestQueue对象 
 */  
public class VolleyRequestQueueManager {  
    // 获取请求队列类  
    public static RequestQueue mRequestQueue = Volley.newRequestQueue(MyApplication.newInstance());  

    //添加任务进任务队列  
    public static void addRequest(Request<?> request, Object tag) {  
        if (tag != null) {  
            request.setTag(tag);  
        }  
        mRequestQueue.add(request);  
    }  

    //取消任务  
    public static void cancelRequest(Object tag){  
        mRequestQueue.cancelAll(tag);  
    }  

}  

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

[java] view plain copy

package com.lcw.rabbit.image.utils;  

import java.math.BigInteger;  
import java.security.MessageDigest;  
import java.security.NoSuchAlgorithmException;  

public class MD5Utils {  
    /** 
     * 使用md5的算法进行加密 
     */  
    public static String md5(String plainText) {  
        byte[] secretBytes = null;  
        try {  
            secretBytes = MessageDigest.getInstance("md5").digest(  
                    plainText.getBytes());  
        } catch (NoSuchAlgorithmException e) {  
            throw new RuntimeException("没有md5这个算法!");  
        }  
        String md5code = new BigInteger(1, secretBytes).toString(16);// 16进制数字  
        // 如果生成数字未满32位,需要前面补0  
        for (int i = 0; i < 32 - md5code.length(); i++) {  
            md5code = "0" + md5code;  
        }  
        return md5code;  
    }  

}  

MD5转换类

[java] view plain copy

/* 
 * Copyright (C) 2011 The Android Open Source Project 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */  

package com.lcw.rabbit.image.utils;  

import java.io.BufferedInputStream;  
import java.io.BufferedWriter;  
import java.io.Closeable;  
import java.io.EOFException;  
import java.io.File;  
import java.io.FileInputStream;  
import java.io.FileNotFoundException;  
import java.io.FileOutputStream;  
import java.io.FileWriter;  
import java.io.FilterOutputStream;  
import java.io.IOException;  
import java.io.InputStream;  
import java.io.InputStreamReader;  
import java.io.OutputStream;  
import java.io.OutputStreamWriter;  
import java.io.Reader;  
import java.io.StringWriter;  
import java.io.Writer;  
import java.lang.reflect.Array;  
import java.nio.charset.Charset;  
import java.util.ArrayList;  
import java.util.Arrays;  
import java.util.Iterator;  
import java.util.LinkedHashMap;  
import java.util.Map;  
import java.util.concurrent.Callable;  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.LinkedBlockingQueue;  
import java.util.concurrent.ThreadPoolExecutor;  
import java.util.concurrent.TimeUnit;  

/** 
 ****************************************************************************** 
 * Taken from the JB source code, can be found in: 
 * libcore/luni/src/main/java/libcore/io/DiskLruCache.java 
 * or direct link: 
 * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java 
 ****************************************************************************** 
 * 
 * A cache that uses a bounded amount of space on a filesystem. Each cache 
 * entry has a string key and a fixed number of values. Values are byte 
 * sequences, accessible as streams or files. Each value must be between {@code 
 * 0} and {@code Integer.MAX_VALUE} bytes in length. 
 * 
 * <p>The cache stores its data in a directory on the filesystem. This 
 * directory must be exclusive to the cache; the cache may delete or overwrite 
 * files from its directory. It is an error for multiple processes to use the 
 * same cache directory at the same time. 
 * 
 * <p>This cache limits the number of bytes that it will store on the 
 * filesystem. When the number of stored bytes exceeds the limit, the cache will 
 * remove entries in the background until the limit is satisfied. The limit is 
 * not strict: the cache may temporarily exceed it while waiting for files to be 
 * deleted. The limit does not include filesystem overhead or the cache 
 * journal so space-sensitive applications should set a conservative limit. 
 * 
 * <p>Clients call {@link #edit} to create or update the values of an entry. An 
 * entry may have only one editor at one time; if a value is not available to be 
 * edited then {@link #edit} will return null. 
 * <ul> 
 *     <li>When an entry is being <strong>created</strong> it is necessary to 
 *         supply a full set of values; the empty value should be used as a 
 *         placeholder if necessary. 
 *     <li>When an entry is being <strong>edited</strong>, it is not necessary 
 *         to supply data for every value; values default to their previous 
 *         value. 
 * </ul> 
 * Every {@link #edit} call must be matched by a call to {@link Editor#commit} 
 * or {@link Editor#abort}. Committing is atomic: a read observes the full set 
 * of values as they were before or after the commit, but never a mix of values. 
 * 
 * <p>Clients call {@link #get} to read a snapshot of an entry. The read will 
 * observe the value at the time that {@link #get} was called. Updates and 
 * removals after the call do not impact ongoing reads. 
 * 
 * <p>This class is tolerant of some I/O errors. If files are missing from the 
 * filesystem, the corresponding entries will be dropped from the cache. If 
 * an error occurs while writing a cache value, the edit will fail silently. 
 * Callers should handle other problems by catching {@code IOException} and 
 * responding appropriately. 
 */  
public final class DiskLruCache implements Closeable {  
    static final String JOURNAL_FILE = "journal";  
    static final String JOURNAL_FILE_TMP = "journal.tmp";  
    static final String MAGIC = "libcore.io.DiskLruCache";  
    static final String VERSION_1 = "1";  
    static final long ANY_SEQUENCE_NUMBER = -1;  
    private static final String CLEAN = "CLEAN";  
    private static final String DIRTY = "DIRTY";  
    private static final String REMOVE = "REMOVE";  
    private static final String READ = "READ";  

    private static final Charset UTF_8 = Charset.forName("UTF-8");  
    private static final int IO_BUFFER_SIZE = 8 * 1024;  

    /* 
     * This cache uses a journal file named "journal". A typical journal file 
     * looks like this: 
     *     libcore.io.DiskLruCache 
     *     1 
     *     100 
     *     2 
     * 
     *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054832 21054 
     *     DIRTY 335c4c6028171cfddfbaae1a9c313c52 
     *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 
     *     REMOVE 335c4c6028171cfddfbaae1a9c313c52 
     *     DIRTY 1ab96a171faeeee38496d8b330771a7a 
     *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 
     *     READ 335c4c6028171cfddfbaae1a9c313c52 
     *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 
     * 
     * The first five lines of the journal form its header. They are the 
     * constant string "libcore.io.DiskLruCache", the disk cache's version, 
     * the application's version, the value count, and a blank line. 
     * 
     * Each of the subsequent lines in the file is a record of the state of a 
     * cache entry. Each line contains space-separated values: a state, a key, 
     * and optional state-specific values. 
     *   o DIRTY lines track that an entry is actively being created or updated. 
     *     Every successful DIRTY action should be followed by a CLEAN or REMOVE 
     *     action. DIRTY lines without a matching CLEAN or REMOVE indicate that 
     *     temporary files may need to be deleted. 
     *   o CLEAN lines track a cache entry that has been successfully published 
     *     and may be read. A publish line is followed by the lengths of each of 
     *     its values. 
     *   o READ lines track accesses for LRU. 
     *   o REMOVE lines track entries that have been deleted. 
     * 
     * The journal file is appended to as cache operations occur. The journal may 
     * occasionally be compacted by dropping redundant lines. A temporary file named 
     * "journal.tmp" will be used during compaction; that file should be deleted if 
     * it exists when the cache is opened. 
     */  

    private final File directory;  
    private final File journalFile;  
    private final File journalFileTmp;  
    private final int appVersion;  
    private final long maxSize;  
    private final int valueCount;  
    private long size = 0;  
    private Writer journalWriter;  
    private final LinkedHashMap<String, Entry> lruEntries  
            = new LinkedHashMap<String, Entry>(0, 0.75f, true);  
    private int redundantOpCount;  

    /** 
     * To differentiate between old and current snapshots, each entry is given 
     * a sequence number each time an edit is committed. A snapshot is stale if 
     * its sequence number is not equal to its entry's sequence number. 
     */  
    private long nextSequenceNumber = 0;  

    /* From java.util.Arrays */  
    @SuppressWarnings("unchecked")  
    private static <T> T[] copyOfRange(T[] original, int start, int end) {  
        final int originalLength = original.length; // For exception priority compatibility.  
        if (start > end) {  
            throw new IllegalArgumentException();  
        }  
        if (start < 0 || start > originalLength) {  
            throw new ArrayIndexOutOfBoundsException();  
        }  
        final int resultLength = end - start;  
        final int copyLength = Math.min(resultLength, originalLength - start);  
        final T[] result = (T[]) Array  
                .newInstance(original.getClass().getComponentType(), resultLength);  
        System.arraycopy(original, start, result, 0, copyLength);  
        return result;  
    }  

    /** 
     * Returns the remainder of 'reader' as a string, closing it when done. 
     */  
    public static String readFully(Reader reader) throws IOException {  
        try {  
            StringWriter writer = new StringWriter();  
            char[] buffer = new char[1024];  
            int count;  
            while ((count = reader.read(buffer)) != -1) {  
                writer.write(buffer, 0, count);  
            }  
            return writer.toString();  
        } finally {  
            reader.close();  
        }  
    }  

    /** 
     * Returns the ASCII characters up to but not including the next "\r\n", or 
     * "\n". 
     * 
     * @throws java.io.EOFException if the stream is exhausted before the next newline 
     *     character. 
     */  
    public static String readAsciiLine(InputStream in) throws IOException {  
        // TODO: support UTF-8 here instead  

        StringBuilder result = new StringBuilder(80);  
        while (true) {  
            int c = in.read();  
            if (c == -1) {  
                throw new EOFException();  
            } else if (c == '\n') {  
                break;  
            }  

            result.append((char) c);  
        }  
        int length = result.length();  
        if (length > 0 && result.charAt(length - 1) == '\r') {  
            result.setLength(length - 1);  
        }  
        return result.toString();  
    }  

    /** 
     * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null. 
     */  
    public static void closeQuietly(Closeable closeable) {  
        if (closeable != null) {  
            try {  
                closeable.close();  
            } catch (RuntimeException rethrown) {  
                throw rethrown;  
            } catch (Exception ignored) {  
            }  
        }  
    }  

    /** 
     * Recursively delete everything in {@code dir}. 
     */  
    // TODO: this should specify paths as Strings rather than as Files  
    public static void deleteContents(File dir) throws IOException {  
        File[] files = dir.listFiles();  
        if (files == null) {  
            throw new IllegalArgumentException("not a directory: " + dir);  
        }  
        for (File file : files) {  
            if (file.isDirectory()) {  
                deleteContents(file);  
            }  
            if (!file.delete()) {  
                throw new IOException("failed to delete file: " + file);  
            }  
        }  
    }  

    /** This cache uses a single background thread to evict entries. */  
    private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,  
            60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());  
    private final Callable<Void> cleanupCallable = new Callable<Void>() {  
        @Override public Void call() throws Exception {  
            synchronized (DiskLruCache.this) {  
                if (journalWriter == null) {  
                    return null; // closed  
                }  
                trimToSize();  
                if (journalRebuildRequired()) {  
                    rebuildJournal();  
                    redundantOpCount = 0;  
                }  
            }  
            return null;  
        }  
    };  

    private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {  
        this.directory = directory;  
        this.appVersion = appVersion;  
        this.journalFile = new File(directory, JOURNAL_FILE);  
        this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);  
        this.valueCount = valueCount;  
        this.maxSize = maxSize;  
    }  

    /** 
     * Opens the cache in {@code directory}, creating a cache if none exists 
     * there. 
     * 
     * @param directory a writable directory 
     * @param appVersion 
     * @param valueCount the number of values per cache entry. Must be positive. 
     * @param maxSize the maximum number of bytes this cache should use to store 
     * @throws java.io.IOException if reading or writing the cache directory fails 
     */  
    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)  
            throws IOException {  
        if (maxSize <= 0) {  
            throw new IllegalArgumentException("maxSize <= 0");  
        }  
        if (valueCount <= 0) {  
            throw new IllegalArgumentException("valueCount <= 0");  
        }  

        // prefer to pick up where we left off  
        DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);  
        if (cache.journalFile.exists()) {  
            try {  
                cache.readJournal();  
                cache.processJournal();  
                cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),  
                        IO_BUFFER_SIZE);  
                return cache;  
            } catch (IOException journalIsCorrupt) {  
//                System.logW("DiskLruCache " + directory + " is corrupt: "  
//                        + journalIsCorrupt.getMessage() + ", removing");  
                cache.delete();  
            }  
        }  

        // create a new empty cache  
        directory.mkdirs();  
        cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);  
        cache.rebuildJournal();  
        return cache;  
    }  

    private void readJournal() throws IOException {  
        InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE);  
        try {  
            String magic = readAsciiLine(in);  
            String version = readAsciiLine(in);  
            String appVersionString = readAsciiLine(in);  
            String valueCountString = readAsciiLine(in);  
            String blank = readAsciiLine(in);  
            if (!MAGIC.equals(magic)  
                    || !VERSION_1.equals(version)  
                    || !Integer.toString(appVersion).equals(appVersionString)  
                    || !Integer.toString(valueCount).equals(valueCountString)  
                    || !"".equals(blank)) {  
                throw new IOException("unexpected journal header: ["  
                        + magic + ", " + version + ", " + valueCountString + ", " + blank + "]");  
            }  

            while (true) {  
                try {  
                    readJournalLine(readAsciiLine(in));  
                } catch (EOFException endOfJournal) {  
                    break;  
                }  
            }  
        } finally {  
            closeQuietly(in);  
        }  
    }  

    private void readJournalLine(String line) throws IOException {  
        String[] parts = line.split(" ");  
        if (parts.length < 2) {  
            throw new IOException("unexpected journal line: " + line);  
        }  

        String key = parts[1];  
        if (parts[0].equals(REMOVE) && parts.length == 2) {  
            lruEntries.remove(key);  
            return;  
        }  

        Entry entry = lruEntries.get(key);  
        if (entry == null) {  
            entry = new Entry(key);  
            lruEntries.put(key, entry);  
        }  

        if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {  
            entry.readable = true;  
            entry.currentEditor = null;  
            entry.setLengths(copyOfRange(parts, 2, parts.length));  
        } else if (parts[0].equals(DIRTY) && parts.length == 2) {  
            entry.currentEditor = new Editor(entry);  
        } else if (parts[0].equals(READ) && parts.length == 2) {  
            // this work was already done by calling lruEntries.get()  
        } else {  
            throw new IOException("unexpected journal line: " + line);  
        }  
    }  

    /** 
     * Computes the initial size and collects garbage as a part of opening the 
     * cache. Dirty entries are assumed to be inconsistent and will be deleted. 
     */  
    private void processJournal() throws IOException {  
        deleteIfExists(journalFileTmp);  
        for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {  
            Entry entry = i.next();  
            if (entry.currentEditor == null) {  
                for (int t = 0; t < valueCount; t++) {  
                    size += entry.lengths[t];  
                }  
            } else {  
                entry.currentEditor = null;  
                for (int t = 0; t < valueCount; t++) {  
                    deleteIfExists(entry.getCleanFile(t));  
                    deleteIfExists(entry.getDirtyFile(t));  
                }  
                i.remove();  
            }  
        }  
    }  

    /** 
     * Creates a new journal that omits redundant information. This replaces the 
     * current journal if it exists. 
     */  
    private synchronized void rebuildJournal() throws IOException {  
        if (journalWriter != null) {  
            journalWriter.close();  
        }  

        Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);  
        writer.write(MAGIC);  
        writer.write("\n");  
        writer.write(VERSION_1);  
        writer.write("\n");  
        writer.write(Integer.toString(appVersion));  
        writer.write("\n");  
        writer.write(Integer.toString(valueCount));  
        writer.write("\n");  
        writer.write("\n");  

        for (Entry entry : lruEntries.values()) {  
            if (entry.currentEditor != null) {  
                writer.write(DIRTY + ' ' + entry.key + '\n');  
            } else {  
                writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');  
            }  
        }  

        writer.close();  
        journalFileTmp.renameTo(journalFile);  
        journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);  
    }  

    private static void deleteIfExists(File file) throws IOException {  
//        try {  
//            Libcore.os.remove(file.getPath());  
//        } catch (ErrnoException errnoException) {  
//            if (errnoException.errno != OsConstants.ENOENT) {  
//                throw errnoException.rethrowAsIOException();  
//            }  
//        }  
        if (file.exists() && !file.delete()) {  
            throw new IOException();  
        }  
    }  

    /** 
     * Returns a snapshot of the entry named {@code key}, or null if it doesn't 
     * exist is not currently readable. If a value is returned, it is moved to 
     * the head of the LRU queue. 
     */  
    public synchronized Snapshot get(String key) throws IOException {  
        checkNotClosed();  
        validateKey(key);  
        Entry entry = lruEntries.get(key);  
        if (entry == null) {  
            return null;  
        }  

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

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

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

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

    /** 
     * Returns an editor for the entry named {@code key}, or null if another 
     * edit is in progress. 
     */  
    public Editor edit(String key) throws IOException {  
        return edit(key, ANY_SEQUENCE_NUMBER);  
    }  

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

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

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

    /** 
     * Returns the directory where this cache stores its data. 
     */  
    public File getDirectory() {  
        return directory;  
    }  

    /** 
     * Returns the maximum number of bytes that this cache should use to store 
     * its data. 
     */  
    public long maxSize() {  
        return maxSize;  
    }  

    /** 
     * Returns the number of bytes currently being used to store the values in 
     * this cache. This may be greater than the max size if a background 
     * deletion is pending. 
     */  
    public synchronized long size() {  
        return size;  
    }  

    private synchronized void completeEdit(Editor editor, boolean success) throws IOException {  
        Entry entry = editor.entry;  
        if (entry.currentEditor != editor) {  
            throw new IllegalStateException();  
        }  

        // if this edit is creating the entry for the first time, every index must have a value  
        if (success && !entry.readable) {  
            for (int i = 0; i < valueCount; i++) {  
                if (!entry.getDirtyFile(i).exists()) {  
                    editor.abort();  
                    throw new IllegalStateException("edit didn't create file " + i);  
                }  
            }  
        }  

        for (int i = 0; i < valueCount; i++) {  
            File dirty = entry.getDirtyFile(i);  
            if (success) {  
                if (dirty.exists()) {  
                    File clean = entry.getCleanFile(i);  
                    dirty.renameTo(clean);  
                    long oldLength = entry.lengths[i];  
                    long newLength = clean.length();  
                    entry.lengths[i] = newLength;  
                    size = size - oldLength + newLength;  
                }  
            } else {  
                deleteIfExists(dirty);  
            }  
        }  

        redundantOpCount++;  
        entry.currentEditor = null;  
        if (entry.readable | success) {  
            entry.readable = true;  
            journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');  
            if (success) {  
                entry.sequenceNumber = nextSequenceNumber++;  
            }  
        } else {  
            lruEntries.remove(entry.key);  
            journalWriter.write(REMOVE + ' ' + entry.key + '\n');  
        }  

        if (size > maxSize || journalRebuildRequired()) {  
            executorService.submit(cleanupCallable);  
        }  
    }  

    /** 
     * We only rebuild the journal when it will halve the size of the journal 
     * and eliminate at least 2000 ops. 
     */  
    private boolean journalRebuildRequired() {  
        final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;  
        return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD  
                && redundantOpCount >= lruEntries.size();  
    }  

    /** 
     * Drops the entry for {@code key} if it exists and can be removed. Entries 
     * actively being edited cannot be removed. 
     * 
     * @return true if an entry was removed. 
     */  
    public synchronized boolean remove(String key) throws IOException {  
        checkNotClosed();  
        validateKey(key);  
        Entry entry = lruEntries.get(key);  
        if (entry == null || entry.currentEditor != null) {  
            return false;  
        }  

        for (int i = 0; i < valueCount; i++) {  
            File file = entry.getCleanFile(i);  
            if (!file.delete()) {  
                throw new IOException("failed to delete " + file);  
            }  
            size -= entry.lengths[i];  
            entry.lengths[i] = 0;  
        }  

        redundantOpCount++;  
        journalWriter.append(REMOVE + ' ' + key + '\n');  
        lruEntries.remove(key);  

        if (journalRebuildRequired()) {  
            executorService.submit(cleanupCallable);  
        }  

        return true;  
    }  

    /** 
     * Returns true if this cache has been closed. 
     */  
    public boolean isClosed() {  
        return journalWriter == null;  
    }  

    private void checkNotClosed() {  
        if (journalWriter == null) {  
            throw new IllegalStateException("cache is closed");  
        }  
    }  

    /** 
     * Force buffered operations to the filesystem. 
     */  
    public synchronized void flush() throws IOException {  
        checkNotClosed();  
        trimToSize();  
        journalWriter.flush();  
    }  

    /** 
     * Closes this cache. Stored values will remain on the filesystem. 
     */  
    public synchronized void close() throws IOException {  
        if (journalWriter == null) {  
            return; // already closed  
        }  
        for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {  
            if (entry.currentEditor != null) {  
                entry.currentEditor.abort();  
            }  
        }  
        trimToSize();  
        journalWriter.close();  
        journalWriter = null;  
    }  

    private void trimToSize() throws IOException {  
        while (size > maxSize) {  
//            Map.Entry<String, Entry> toEvict = lruEntries.eldest();  
            final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();  
            remove(toEvict.getKey());  
        }  
    }  

    /** 
     * Closes the cache and deletes all of its stored values. This will delete 
     * all files in the cache directory including files that weren't created by 
     * the cache. 
     */  
    public void delete() throws IOException {  
        close();  
        deleteContents(directory);  
    }  

    private void validateKey(String key) {  
        if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {  
            throw new IllegalArgumentException(  
                    "keys must not contain spaces or newlines: \"" + key + "\"");  
        }  
    }  

    private static String inputStreamToString(InputStream in) throws IOException {  
        return readFully(new InputStreamReader(in, UTF_8));  
    }  

    /** 
     * A snapshot of the values for an entry. 
     */  
    public final class Snapshot implements Closeable {  
        private final String key;  
        private final long sequenceNumber;  
        private final InputStream[] ins;  

        private Snapshot(String key, long sequenceNumber, InputStream[] ins) {  
            this.key = key;  
            this.sequenceNumber = sequenceNumber;  
            this.ins = ins;  
        }  

        /** 
         * Returns an editor for this snapshot's entry, or null if either the 
         * entry has changed since this snapshot was created or if another edit 
         * is in progress. 
         */  
        public Editor edit() throws IOException {  
            return DiskLruCache.this.edit(key, sequenceNumber);  
        }  

        /** 
         * Returns the unbuffered stream with the value for {@code index}. 
         */  
        public InputStream getInputStream(int index) {  
            return ins[index];  
        }  

        /** 
         * Returns the string value for {@code index}. 
         */  
        public String getString(int index) throws IOException {  
            return inputStreamToString(getInputStream(index));  
        }  

        @Override public void close() {  
            for (InputStream in : ins) {  
                closeQuietly(in);  
            }  
        }  
    }  

    /** 
     * Edits the values for an entry. 
     */  
    public final class Editor {  
        private final Entry entry;  
        private boolean hasErrors;  

        private Editor(Entry entry) {  
            this.entry = entry;  
        }  

        /** 
         * Returns an unbuffered input stream to read the last committed value, 
         * or null if no value has been committed. 
         */  
        public InputStream newInputStream(int index) throws IOException {  
            synchronized (DiskLruCache.this) {  
                if (entry.currentEditor != this) {  
                    throw new IllegalStateException();  
                }  
                if (!entry.readable) {  
                    return null;  
                }  
                return new FileInputStream(entry.getCleanFile(index));  
            }  
        }  

        /** 
         * Returns the last committed value as a string, or null if no value 
         * has been committed. 
         */  
        public String getString(int index) throws IOException {  
            InputStream in = newInputStream(index);  
            return in != null ? inputStreamToString(in) : null;  
        }  

        /** 
         * Returns a new unbuffered output stream to write the value at 
         * {@code index}. If the underlying output stream encounters errors 
         * when writing to the filesystem, this edit will be aborted when 
         * {@link #commit} is called. The returned output stream does not throw 
         * IOExceptions. 
         */  
        public OutputStream newOutputStream(int index) throws IOException {  
            synchronized (DiskLruCache.this) {  
                if (entry.currentEditor != this) {  
                    throw new IllegalStateException();  
                }  
                return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));  
            }  
        }  

        /** 
         * Sets the value at {@code index} to {@code value}. 
         */  
        public void set(int index, String value) throws IOException {  
            Writer writer = null;  
            try {  
                writer = new OutputStreamWriter(newOutputStream(index), UTF_8);  
                writer.write(value);  
            } finally {  
                closeQuietly(writer);  
            }  
        }  

        /** 
         * Commits this edit so it is visible to readers.  This releases the 
         * edit lock so another edit may be started on the same key. 
         */  
        public void commit() throws IOException {  
            if (hasErrors) {  
                completeEdit(this, false);  
                remove(entry.key); // the previous entry is stale  
            } else {  
                completeEdit(this, true);  
            }  
        }  

        /** 
         * Aborts this edit. This releases the edit lock so another edit may be 
         * started on the same key. 
         */  
        public void abort() throws IOException {  
            completeEdit(this, false);  
        }  

        private class FaultHidingOutputStream extends FilterOutputStream {  
            private FaultHidingOutputStream(OutputStream out) {  
                super(out);  
            }  

            @Override public void write(int oneByte) {  
                try {  
                    out.write(oneByte);  
                } catch (IOException e) {  
                    hasErrors = true;  
                }  
            }  

            @Override public void write(byte[] buffer, int offset, int length) {  
                try {  
                    out.write(buffer, offset, length);  
                } catch (IOException e) {  
                    hasErrors = true;  
                }  
            }  

            @Override public void close() {  
                try {  
                    out.close();  
                } catch (IOException e) {  
                    hasErrors = true;  
                }  
            }  

            @Override public void flush() {  
                try {  
                    out.flush();  
                } catch (IOException e) {  
                    hasErrors = true;  
                }  
            }  
        }  
    }  

    private final class Entry {  
        private final String key;  

        /** Lengths of this entry's files. */  
        private final long[] lengths;  

        /** True if this entry has ever been published */  
        private boolean readable;  

        /** The ongoing edit or null if this entry is not being edited. */  
        private Editor currentEditor;  

        /** The sequence number of the most recently committed edit to this entry. */  
        private long sequenceNumber;  

        private Entry(String key) {  
            this.key = key;  
            this.lengths = new long[valueCount];  
        }  

        public String getLengths() throws IOException {  
            StringBuilder result = new StringBuilder();  
            for (long size : lengths) {  
                result.append(' ').append(size);  
            }  
            return result.toString();  
        }  

        /** 
         * Set lengths using decimal numbers like "10123". 
         */  
        private void setLengths(String[] strings) throws IOException {  
            if (strings.length != valueCount) {  
                throw invalidLengths(strings);  
            }  

            try {  
                for (int i = 0; i < strings.length; i++) {  
                    lengths[i] = Long.parseLong(strings[i]);  
                }  
            } catch (NumberFormatException e) {  
                throw invalidLengths(strings);  
            }  
        }  

        private IOException invalidLengths(String[] strings) throws IOException {  
            throw new IOException("unexpected journal line: " + Arrays.toString(strings));  
        }  

        public File getCleanFile(int i) {  
            return new File(directory, key + "." + i);  
        }  

        public File getDirtyFile(int i) {  
            return new File(directory, key + "." + i + ".tmp");  
        }  
    }  
}  

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

[java] view plain copy
package com.lcw.rabbit.image.utils;  

import java.io.File;  
import java.io.IOException;  
import java.io.OutputStream;  

import android.content.Context;  
import android.content.pm.PackageInfo;  
import android.content.pm.PackageManager.NameNotFoundException;  
import android.graphics.Bitmap;  
import android.graphics.Bitmap.CompressFormat;  
import android.graphics.BitmapFactory;  
import android.os.Environment;  
import android.support.v4.util.LruCache;  
import android.util.Log;  

import com.android.volley.toolbox.ImageLoader.ImageCache;  
import com.lcw.rabbit.image.utils.DiskLruCache.Snapshot;  

/** 
 * 图片缓存帮助类 
 *  
 * 包含内存缓存LruCache和磁盘缓存DiskLruCache 
 *  
 * @author Rabbit_Lee 
 *  
 */  
public class ImageCacheUtil implements ImageCache {  

    private String TAG=ImageCacheUtil.this.getClass().getSimpleName();  

    //缓存类  
    private static LruCache<String, Bitmap> mLruCache;  
    private static DiskLruCache mDiskLruCache;  

    //磁盘缓存大小  
    private static final int DISKMAXSIZE = 10 * 1024 * 1024;  

    public ImageCacheUtil() {  
        // 获取应用可占内存的1/8作为缓存  
        int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);  
        // 实例化LruCaceh对象  
        mLruCache = new LruCache<String, Bitmap>(maxSize) {  
            @Override  
            protected int sizeOf(String key, Bitmap bitmap) {  
                return bitmap.getRowBytes() * bitmap.getHeight();  
            }  
        };  
        try {  
            // 获取DiskLruCahce对象  
            mDiskLruCache = DiskLruCache.open(getDiskCacheDir(MyApplication.newInstance(), "Rabbit"), getAppVersion(MyApplication.newInstance()), 1, DISKMAXSIZE);  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  

    /** 
     * 从缓存(内存缓存,磁盘缓存)中获取Bitmap 
     */  
    @Override  
    public Bitmap getBitmap(String url) {  
        if (mLruCache.get(url) != null) {  
            // 从LruCache缓存中取  
            Log.i(TAG,"从LruCahce获取");  
            return mLruCache.get(url);  
        } else {  
            String key = MD5Utils.md5(url);  
            try {  
                if (mDiskLruCache.get(key) != null) {  
                    // 从DiskLruCahce取  
                    Snapshot snapshot = mDiskLruCache.get(key);  
                    Bitmap bitmap = null;  
                    if (snapshot != null) {  
                        bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));  
                        // 存入LruCache缓存  
                        mLruCache.put(url, bitmap);  
                        Log.i(TAG,"从DiskLruCahce获取");  
                    }  
                    return bitmap;  
                }  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
        return null;  
    }  

    /** 
     * 存入缓存(内存缓存,磁盘缓存) 
     */  
    @Override  
    public void putBitmap(String url, Bitmap bitmap) {  
        // 存入LruCache缓存  
        mLruCache.put(url, bitmap);  
        // 判断是否存在DiskLruCache缓存,若没有存入  
        String key = MD5Utils.md5(url);  
        try {  
            if (mDiskLruCache.get(key) == null) {  
                DiskLruCache.Editor editor = mDiskLruCache.edit(key);  
                if (editor != null) {  
                    OutputStream outputStream = editor.newOutputStream(0);  
                    if (bitmap.compress(CompressFormat.JPEG, 100, outputStream)) {  
                        editor.commit();  
                    } else {  
                        editor.abort();  
                    }  
                }  
                mDiskLruCache.flush();  
            }  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  

    }  

    /** 
     * 该方法会判断当前sd卡是否存在,然后选择缓存地址 
     *  
     * @param context 
     * @param uniqueName 
     * @return 
     */  
    public static File getDiskCacheDir(Context context, String uniqueName) {  
        String cachePath;  
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {  
            cachePath = context.getExternalCacheDir().getPath();  
        } else {  
            cachePath = context.getCacheDir().getPath();  
        }  
        return new File(cachePath + File.separator + uniqueName);  
    }  

    /** 
     * 获取应用版本号 
     *  
     * @param context 
     * @return 
     */  
    public int getAppVersion(Context context) {  
        try {  
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);  
            return info.versionCode;  
        } catch (NameNotFoundException e) {  
            e.printStackTrace();  
        }  
        return 1;  
    }  

}  

5、图片缓存管理类

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

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

[java] view plain copy

package com.lcw.rabbit.image;  

import android.graphics.Bitmap;  
import android.widget.ImageView;  

import com.android.volley.VolleyError;  
import com.android.volley.toolbox.ImageLoader;  
import com.android.volley.toolbox.ImageLoader.ImageCache;  
import com.android.volley.toolbox.ImageLoader.ImageContainer;  
import com.android.volley.toolbox.ImageLoader.ImageListener;  
import com.lcw.rabbit.image.utils.ImageCacheUtil;  

/** 
 * 图片缓存管理类 获取ImageLoader对象 
 *  
 * @author Rabbit_Lee 
 *  
 */  
public class ImageCacheManager {  

    private static String TAG = ImageCacheManager.class.getSimpleName();  

    // 获取图片缓存类对象  
    private static ImageCache mImageCache = new ImageCacheUtil();  
    // 获取ImageLoader对象  
    public static ImageLoader mImageLoader = new ImageLoader(VolleyRequestQueueManager.mRequestQueue, mImageCache);  

    /** 
     * 获取ImageListener 
     *  
     * @param view 
     * @param defaultImage 
     * @param errorImage 
     * @return 
     */  
    public static ImageListener getImageListener(final ImageView view, final Bitmap defaultImage, final Bitmap errorImage) {  

        return new ImageListener() {  

            @Override  
            public void onErrorResponse(VolleyError error) {  
                // 回调失败  
                if (errorImage != null) {  
                    view.setImageBitmap(errorImage);  
                }  
            }  

            @Override  
            public void onResponse(ImageContainer response, boolean isImmediate) {  
                // 回调成功  
                if (response.getBitmap() != null) {  
                    view.setImageBitmap(response.getBitmap());  
                } else if (defaultImage != null) {  
                    view.setImageBitmap(defaultImage);  
                }  
            }  
        };  

    }  

    /** 
     * 提供给外部调用方法 
     *  
     * @param url 
     * @param view 
     * @param defaultImage 
     * @param errorImage 
     */  
    public static void loadImage(String url, ImageView view, Bitmap defaultImage, Bitmap errorImage) {  
        mImageLoader.get(url, ImageCacheManager.getImageListener(view, defaultImage, errorImage), 0, 0);  
    }  

    /** 
     * 提供给外部调用方法 
     *  
     * @param url 
     * @param view 
     * @param defaultImage 
     * @param errorImage 
     */  
    public static void loadImage(String url, ImageView view, Bitmap defaultImage, Bitmap errorImage, int maxWidth, int maxHeight) {  
        mImageLoader.get(url, ImageCacheManager.getImageListener(view, defaultImage, errorImage), maxWidth, maxHeight);  
    }  
}  

6、MainActivity类

[java] view plain copy

package com.lcw.rabbit.image;  

import android.app.Activity;  
import android.content.res.Resources;  
import android.graphics.Bitmap;  
import android.graphics.BitmapFactory;  
import android.os.Bundle;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.widget.Button;  
import android.widget.ImageView;  

public class MainActivity extends Activity {  

    private Button mButton;  
    private ImageView mImageView;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        mButton = (Button) findViewById(R.id.button);  
        mImageView= (ImageView) findViewById(R.id.image);  


        mButton.setOnClickListener(new OnClickListener() {  

            @Override  
            public void onClick(View v) {  
                String url = "https://img-blog.csdn.net/20130702124537953?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdDEyeDM0NTY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast";  
                ImageCacheManager.loadImage(url, mImageView, getBitmapFromRes(R.drawable.ic_launcher), getBitmapFromRes(R.drawable.ic_launcher));  

            }  
        });  
    }  

    public Bitmap getBitmapFromRes(int resId) {  
        Resources res = this.getResources();  
        return BitmapFactory.decodeResource(res, resId);  

    }  

}  

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

一、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、付费专栏及课程。

余额充值