这几天一直研究在安卓开发中图片应该如何处理,在网上翻了好多资料,这里做点小总结,如果朋友们有更好的解决方案,可以留言一起交流下。
?
内存缓存技术
在我们开发程序中要在界面上加载一张图片是件非常容易的事情,但如果是加载一堆图片呢?比如ListView,GridView这类的控件,随着屏幕滑动,图片加载也会越来越多,应用程序所可以使用的内存毕竟是有限的,如果一味的去加载图片,很容易导致OOM(Out Of Memory)内存溢出,导致程序崩溃。
这里我们一般的做法是将显示在屏幕之外的图片进行内存回收,此时的垃圾回收器会认为应用对这些图片不再持有引用,从而进行GC操作。但现实还需要考虑到问题是,如果用户又滑动屏幕回到之前我们已经回收掉的图片位置,这时候该怎么办?重新去加载一张图片肯定是不可取的,这样既浪费了时间,又浪费了用户的流量。
这里我们就会想到利用内存缓存来解决这个问题,利用内存缓存可以让应用快速的加载和处理图片,从而提高流畅性。
内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法,其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
对于LruCache类不熟悉的朋友可以看看这篇文章《Android高效加载大图、多图解决方案,有效避免程序OOM》。
?
磁盘缓存技术
对于内存缓存LruCache只是管理了内存中图片的存储与释放,如果图片从内存中被移除的话,那么又需要从网络上重新加载一次图片,这显然非常耗时。对此,Google又提供了一套硬盘缓存的解决方案:DiskLruCache(非Google官方编写,但获得官方认证)。
关于磁盘缓存DisLruCache类不熟悉的朋友可以看看这篇文章的介绍《Android DiskLruCache完全解析,硬盘缓存的最佳方案》
?
?
?
完美结合LruCache+DiskLruCache
首先先来看下实现效果:
 ??
?
这是一个很简单的布局,大布局是GridView,小布局ImageView嵌套在大布局里,贴上代码再做分析吧,其实注释也挺全的。
既然要完成磁盘存储,那么必不可少的就是DisLruCache类的,先引进项目里再说,再来就是图片资源集合了。
图片资源类:


1 package com.example.photoswall;
2
3
4 /**
5 * 图片资源类
6 * @author Balla_兔子
7 *
8 */
9 public class Images {
10
11 final static String[] imageThumbUrls = new String[] {
12 "http://img.my.csdn.net/uploads/201407/26/1406383299_1976.jpg",13 "http://img.my.csdn.net/uploads/201407/26/1406383291_6518.jpg"14 "http://img.my.csdn.net/uploads/201407/26/1406383291_8239.jpg"15 "http://img.my.csdn.net/uploads/201407/26/1406383290_9329.jpg"16 "http://img.my.csdn.net/uploads/201407/26/1406383290_1042.jpg"17 "http://img.my.csdn.net/uploads/201407/26/1406383275_3977.jpg"18 "http://img.my.csdn.net/uploads/201407/26/1406383265_8550.jpg"19 "http://img.my.csdn.net/uploads/201407/26/1406383264_3954.jpg"20 "http://img.my.csdn.net/uploads/201407/26/1406383264_4787.jpg"21 "http://img.my.csdn.net/uploads/201407/26/1406383264_8243.jpg"22 "http://img.my.csdn.net/uploads/201407/26/1406383248_3693.jpg"23 "http://img.my.csdn.net/uploads/201407/26/1406383243_5120.jpg"24 "http://img.my.csdn.net/uploads/201407/26/1406383242_3127.jpg"25 "http://img.my.csdn.net/uploads/201407/26/1406383242_9576.jpg"26 "http://img.my.csdn.net/uploads/201407/26/1406383242_1721.jpg"27 "http://img.my.csdn.net/uploads/201407/26/1406383219_5806.jpg"28 "http://img.my.csdn.net/uploads/201407/26/1406383214_7794.jpg"29 "http://img.my.csdn.net/uploads/201407/26/1406383213_4418.jpg"30 "http://img.my.csdn.net/uploads/201407/26/1406383213_3557.jpg"31 "http://img.my.csdn.net/uploads/201407/26/1406383210_8779.jpg"32 "http://img.my.csdn.net/uploads/201407/26/1406383172_4577.jpg"33 "http://img.my.csdn.net/uploads/201407/26/1406383166_3407.jpg"34 "http://img.my.csdn.net/uploads/201407/26/1406383166_2224.jpg"35 "http://img.my.csdn.net/uploads/201407/26/1406383166_7301.jpg"36 "http://img.my.csdn.net/uploads/201407/26/1406383165_7197.jpg"37 "http://img.my.csdn.net/uploads/201407/26/1406383150_8410.jpg"38 "http://img.my.csdn.net/uploads/201407/26/1406383131_3736.jpg"39 "http://img.my.csdn.net/uploads/201407/26/1406383130_5094.jpg"40 "http://img.my.csdn.net/uploads/201407/26/1406383130_7393.jpg"41 "http://img.my.csdn.net/uploads/201407/26/1406383129_8813.jpg"42 "http://img.my.csdn.net/uploads/201407/26/1406383100_3554.jpg"43 "http://img.my.csdn.net/uploads/201407/26/1406383093_7894.jpg"44 "http://img.my.csdn.net/uploads/201407/26/1406383092_2432.jpg"45 "http://img.my.csdn.net/uploads/201407/26/1406383092_3071.jpg"46 "http://img.my.csdn.net/uploads/201407/26/1406383091_3119.jpg"47 "http://img.my.csdn.net/uploads/201407/26/1406383059_6589.jpg"48 "http://img.my.csdn.net/uploads/201407/26/1406383059_8814.jpg"49 "http://img.my.csdn.net/uploads/201407/26/1406383059_2237.jpg"50 "http://img.my.csdn.net/uploads/201407/26/1406383058_4330.jpg"51 "http://img.my.csdn.net/uploads/201407/26/1406383038_3602.jpg"52 "http://img.my.csdn.net/uploads/201407/26/1406382942_3079.jpg"53 "http://img.my.csdn.net/uploads/201407/26/1406382942_8125.jpg"54 "http://img.my.csdn.net/uploads/201407/26/1406382942_4881.jpg"55 "http://img.my.csdn.net/uploads/201407/26/1406382941_4559.jpg"56 "http://img.my.csdn.net/uploads/201407/26/1406382941_3845.jpg"57 "http://img.my.csdn.net/uploads/201407/26/1406382924_8955.jpg"58 "http://img.my.csdn.net/uploads/201407/26/1406382923_2141.jpg"59 "http://img.my.csdn.net/uploads/201407/26/1406382923_8437.jpg"60 "http://img.my.csdn.net/uploads/201407/26/1406382922_6166.jpg"61 "http://img.my.csdn.net/uploads/201407/26/1406382922_4843.jpg"62 "http://img.my.csdn.net/uploads/201407/26/1406382905_5804.jpg"63 "http://img.my.csdn.net/uploads/201407/26/1406382904_3362.jpg"64 "http://img.my.csdn.net/uploads/201407/26/1406382904_2312.jpg"65 "http://img.my.csdn.net/uploads/201407/26/1406382904_4960.jpg"66 "http://img.my.csdn.net/uploads/201407/26/1406382900_2418.jpg"67 "http://img.my.csdn.net/uploads/201407/26/1406382881_4490.jpg"68 "http://img.my.csdn.net/uploads/201407/26/1406382881_5935.jpg"69 "http://img.my.csdn.net/uploads/201407/26/1406382880_3865.jpg"70 "http://img.my.csdn.net/uploads/201407/26/1406382880_4662.jpg"71 "http://img.my.csdn.net/uploads/201407/26/1406382879_2553.jpg"72 "http://img.my.csdn.net/uploads/201407/26/1406382862_5375.jpg"73 "http://img.my.csdn.net/uploads/201407/26/1406382862_1748.jpg"74 "http://img.my.csdn.net/uploads/201407/26/1406382861_7618.jpg"75 "http://img.my.csdn.net/uploads/201407/26/1406382861_8606.jpg"76 "http://img.my.csdn.net/uploads/201407/26/1406382861_8949.jpg"77 "http://img.my.csdn.net/uploads/201407/26/1406382841_9821.jpg"78 "http://img.my.csdn.net/uploads/201407/26/1406382840_6603.jpg"79 "http://img.my.csdn.net/uploads/201407/26/1406382840_2405.jpg"80 "http://img.my.csdn.net/uploads/201407/26/1406382840_6354.jpg"81 "http://img.my.csdn.net/uploads/201407/26/1406382839_5779.jpg"82 "http://img.my.csdn.net/uploads/201407/26/1406382810_7578.jpg"83 "http://img.my.csdn.net/uploads/201407/26/1406382810_2436.jpg"84 "http://img.my.csdn.net/uploads/201407/26/1406382809_3883.jpg"85 "http://img.my.csdn.net/uploads/201407/26/1406382809_6269.jpg"86 "http://img.my.csdn.net/uploads/201407/26/1406382808_4179.jpg"87 "http://img.my.csdn.net/uploads/201407/26/1406382790_8326.jpg"88 "http://img.my.csdn.net/uploads/201407/26/1406382789_7174.jpg"89 "http://img.my.csdn.net/uploads/201407/26/1406382789_5170.jpg"90 "http://img.my.csdn.net/uploads/201407/26/1406382789_4118.jpg"91 "http://img.my.csdn.net/uploads/201407/26/1406382788_9532.jpg"92 "http://img.my.csdn.net/uploads/201407/26/1406382767_3184.jpg"93 "http://img.my.csdn.net/uploads/201407/26/1406382767_4772.jpg"94 "http://img.my.csdn.net/uploads/201407/26/1406382766_4924.jpg"95 "http://img.my.csdn.net/uploads/201407/26/1406382766_5762.jpg"96 "http://img.my.csdn.net/uploads/201407/26/1406382765_7341.jpg"
97 };
98 }@H_198_301@
View Code
DiskLruCache(磁盘缓存类):


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,1)"> 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 18
19 import java.io.BufferedInputStream;
20 java.io.BufferedWriter;
21 java.io.Closeable;
22 java.io.EOFException;
23 java.io.File;
24 java.io.FileInputStream;
25 java.io.FileNotFoundException;
26 java.io.FileOutputStream;
27 java.io.FileWriter;
28 java.io.FilterOutputStream;
29 java.io.IOException;
30 java.io.InputStream;
31 java.io.InputStreamReader;
32 java.io.OutputStream;
33 java.io.OutputStreamWriter;
34 java.io.Reader;
35 java.io.StringWriter;
36 java.io.Writer;
37 java.lang.reflect.Array;
38 java.nio.charset.Charset;
39 java.util.ArrayList;
40 java.util.Arrays;
41 java.util.Iterator;
42 java.util.LinkedHashMap;
43 java.util.Map;
44 java.util.concurrent.Callable;
45 java.util.concurrent.ExecutorService;
46 java.util.concurrent.LinkedBlockingQueue;
47 java.util.concurrent.ThreadPoolExecutor;
48 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 { #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 { #edit} call must be matched by a call to { Editor#commit}
87 * or { 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 #get} to read a snapshot of an entry. The read will
91 * observe the value at the time that { #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 { IOException} and
98 * responding appropriately.
99 100 class DiskLruCache implements Closeable {
101 static final String JOURNAL_FILE = "journal";
102 final String JOURNAL_FILE_TMP = "journal.tmp"103 final String MAGIC = "libcore.io.DiskLruCache"104 final String VERSION_1 = "1"105 long ANY_SEQUENCE_NUMBER = -1106 private final String CLEAN = "CLEAN"107 final String DIRTY = "DIRTY"108 final String REMOVE = "REMOVE"109 final String READ = "READ"110
111 final Charset UTF_8 = Charset.forName("UTF-8");
112 int IO_BUFFER_SIZE = 8 * 1024113
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,1)">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,1)">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 final File directory;
155 File journalFile;
156 File journalFileTmp;
157 int appVersion;
158 long maxSize;
159 valueCount;
160 long size = 0161 private Writer journalWriter;
162 final LinkedHashMap<String,Entry> lruEntries
163 = new LinkedHashMap<String,Entry>(0,0.75f,true164 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 long nextSequenceNumber = 0172
173 /* From java.util.Arrays 174 @SuppressWarnings("unchecked")
175 static <T> T[] copyOfRange(T[] original,1)">int start,1)"> end) {
176 int originalLength = original.length; // For exception priority compatibility.
177 if (start >178 throw IllegalArgumentException();
179 }
180 if (start < 0 || start > originalLength) {
181 ArrayIndexOutOfBoundsException();
182 183 int resultLength = end - start;
184 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,0188 return result;
189 }
190
191 192 * Returns the remainder of 'reader' as a string,closing it when done.
193 194 static String readFully(Reader reader) throws IOException {
195 try {
196 StringWriter writer = StringWriter();
197 char[] buffer = new char[1024];
198 count;
199 while ((count = reader.read(buffer)) != -1) {
200 writer.write(buffer,count);
201 }
202 writer.toString();
203 } finally204 reader.close();
205 206 207
208 209 * Returns the ASCII characters up to but not including the next "rn",or
210 * "n".
211 212 * @throws java.io.EOFException if the stream is exhausted before the next newline
213 * character.
214 215 static String readAsciiLine(InputStream in) 216 TODO: support UTF-8 here instead
217
218 StringBuilder result = new StringBuilder(80219 while (220 int c = in.read();
221 if (c == -1222 EOFException();
223 } else if (c == 'n'224 break225 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 - 1232 233 result.toString();
234 235
236 237 * Closes 'closeable',ignoring any checked exceptions. Does nothing if 'closeable' is null.
238 239 void closeQuietly(Closeable closeable) {
240 if (closeable != null241 242 closeable.close();
243 } catch (RuntimeException rethrown) {
244 throw rethrown;
245 } (Exception ignored) {
246 247 248 249
250 251 * Recursively delete everything in { dir}.
252 253 TODO: this should specify paths as Strings rather than as Files
254 void deleteContents(File dir) 255 File[] files = dir.listFiles();
256 if (files == 257 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 new IOException("Failed to delete file: " + file);
265 266 267 268
269 /** This cache uses a single background thread to evict entries. 270 final ExecutorService executorService = new ThreadPoolExecutor(0,1271 60L,TimeUnit.SECONDS,1)">new LinkedBlockingQueue<Runnable>());
272 final Callable<Void> cleanupCallable = new Callable<Void>() {
273 @Override public Void call() Exception {
274 synchronized (DiskLruCache.this275 if (journalWriter == 276 return null; closed
277 }
278 trimToSize();
279 (journalRebuildrequired()) {
280 rebuildJournal();
281 redundantOpCount = 0282 283 284 285 286 287
288 private DiskLruCache(File directory,1)">int appVersion,1)">int valueCount,1)"> maxSize) {
289 this.directory = directory;
290 this.appVersion =291 this.journalFile = File(directory,JOURNAL_FILE);
292 this.journalFileTmp = 293 this.valueCount =294 this.maxSize =295 296
297 298 * Opens the cache in { directory},creating a cache if none exists
299 * there.
300 301 @param directory a writable directory
302 appVersion
303 valueCount the number of values per cache entry. Must be positive.
304 maxSize the maximum number of bytes this cache should use to store
305 java.io.IOException if reading or writing the cache directory fails
306 307 static DiskLruCache open(File directory,1)"> maxSize)
308 309 if (maxSize <= 0310 new IllegalArgumentException("maxSize <= 0"311 312 if (valueCount <= 0313 new IllegalArgumentException("valueCount <= 0"314 315
316 prefer to pick up where we left off
317 DiskLruCache cache = DiskLruCache(directory,appVersion,valueCount,maxSize);
318 (cache.journalFile.exists()) {
319 320 cache.readJournal();
321 cache.processJournal();
322 cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile,1)">),1)">323 IO_BUFFER_SIZE);
324 cache;
325 } (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 = 335 cache.rebuildJournal();
336 337 338
339 void readJournal() 340 InputStream in = new BufferedInputStream( FileInputStream(journalFile),IO_BUFFER_SIZE);
341 342 String magic = readAsciiLine(in);
343 String version =344 String appVersionString =345 String valueCountString =346 String blank =347 MAGIC.equals(magic)
348 || !VERSION_1.equals(version)
349 || !Integer.toString(appVersion).equals(appVersionString)
350 || !Integer.toString(valueCount).equals(valueCountString)
351 || !"".equals(blank)) {
352 new IOException("unexpected journal header: ["
353 + magic + "," + version + "," + valueCountString + "," + blank + "]"354 355
356 357 358 readJournalLine(readAsciiLine(in));
359 } (EOFException endOfJournal) {
360 361 362 363 } 364 closeQuietly(in);
365 366 367
368 void readJournalLine(String line) 369 String[] parts = line.split(" "370 if (parts.length < 2371 new IOException("unexpected journal line: " + line);
372 373
374 String key = parts[1375 if (parts[0].equals(REMOVE) && parts.length == 2376 lruEntries.remove(key);
377 378 379
380 Entry entry = lruEntries.get(key);
381 if (entry == 382 entry = Entry(key);
383 lruEntries.put(key,entry);
384 385
386 if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {
387 entry.readable = 388 entry.currentEditor = 389 entry.setLengths(copyOfRange(parts,2390 } if (parts[0].equals(DIRTY) && parts.length == 2391 entry.currentEditor = Editor(entry);
392 } if (parts[0].equals(READ) && parts.length == 2393 this work was already done by calling lruEntries.get()
394 } else395 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 void processJournal() 404 deleteIfExists(journalFileTmp);
405 for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
406 Entry entry = i.next();
407 if (entry.currentEditor == 408 for (int t = 0; t < valueCount; t++409 size += entry.lengths[t];
410 411 } 412 entry.currentEditor = 413 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 synchronized void rebuildJournal() 427 if (journalWriter != 428 journalWriter.close();
429 430
431 Writer writer = FileWriter(journalFileTmp),1)">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 (Entry entry : lruEntries.values()) {
443 if (entry.currentEditor != 444 writer.write(DIRTY + ' ' + entry.key + 'n'445 } 446 writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + 'n'447 448 449
450 writer.close();
451 journalFileTmp.renameTo(journalFile);
452 journalWriter = new FileWriter(journalFile,1)">453 454
455 void deleteIfExists(File file) 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() && !464 IOException();
465 466 467
468 469 * Returns a snapshot of the entry named { 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 synchronized Snapshot get(String key) 474 checkNotClosed();
475 validateKey(key);
476 Entry entry =477 478 479 480
481 entry.readable) {
482 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 = InputStream[valueCount];
491 492 int i = 0; i < valueCount; i++493 ins[i] = FileInputStream(entry.getCleanFile(i));
494 495 } (FileNotFoundException e) {
496 a file must have been deleted manually!
497 498 499
500 redundantOpCount++501 journalWriter.append(READ + ' ' + key + 'n'502 503 executorService.submit(cleanupCallable);
504 505
506 Snapshot(key,entry.sequenceNumber,ins);
507 508
509 510 * Returns an editor for the entry named {511 * edit is in progress.
512 513 public Editor edit(String key) 514 edit(key,ANY_SEQUENCE_NUMBER);
515 516
517 synchronized Editor edit(String key,1)">long expectedSequenceNumber) 518 519 520 Entry entry =521 if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
522 && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
523 snapshot is stale
524 525 526 entry = 527 528 } 529 another edit is in progress
530 531
532 Editor editor = 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 539 540
541 542 * Returns the directory where this cache stores its data.
543 544 public File getDirectory() {
545 546 547
548 549 * Returns the maximum number of bytes that this cache should use to store
550 * its data.
551 552 maxSize() {
553 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 size() {
562 size;
563 564
565 void completeEdit(Editor editor,1)">boolean success) 566 Entry entry = editor.entry;
567 if (entry.currentEditor != editor) {
568 IllegalStateException();
569 570
571 if this edit is creating the entry for the first time,every index must have a value
572 if (success && !573 574 entry.getDirtyFile(i).exists()) {
575 editor.abort();
576 new IllegalStateException("edit didn't create file " + i);
577 578 579 580
581 582 File dirty = entry.getDirtyFile(i);
583 (success) {
584 (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 +591 592 } 593 deleteIfExists(dirty);
594 595 596
597 redundantOpCount++598 entry.currentEditor = 599 if (entry.readable | success) {
600 entry.readable = 601 journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + 'n'602 603 entry.sequenceNumber = nextSequenceNumber++604 605 } 606 lruEntries.remove(entry.key);
607 journalWriter.write(REMOVE + ' ' + entry.key + 'n'608 609
610 if (size > maxSize || journalRebuildrequired()) {
611 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 boolean journalRebuildrequired() {
620 int REDUNDANT_OP_COMPACT_THRESHOLD = 2000621 return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
622 && redundantOpCount >= lruEntries.size();
623 624
625 626 * Drops the entry for { 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 boolean remove(String key) 632 633 634 Entry entry =635 null || entry.currentEditor != 636 false637 638
639 640 File file =641 642 new IOException("Failed to delete " +643 644 size -=645 entry.lengths[i] = 0646 647
648 redundantOpCount++649 journalWriter.append(REMOVE + ' ' + key + 'n'650 lruEntries.remove(key);
651
652 653 654 655
656 657 658
659 660 * Returns true if this cache has been closed.
661 662 isClosed() {
663 return journalWriter == 664 665
666 checkNotClosed() {
667 668 new IllegalStateException("cache is closed"669 670 671
672 673 * Force buffered operations to the filesystem.
674 675 void flush() 676 677 trimToSize();
678 679 680
681 682 * Closes this cache. Stored values will remain on the filesystem.
683 684 void close() 685 686 return; already closed
687 688 for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
689 690 entry.currentEditor.abort();
691 692 693 694 journalWriter.close();
695 journalWriter = 696 697
698 void trimToSize() 699 while (size >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 void delete() 712 close();
713 deleteContents(directory);
714 715
716 validateKey(String key) {
717 if (key.contains(" ") || key.contains("n") || key.contains("r")) {
718 IllegalArgumentException(
719 "keys must not contain spaces or newlines: "" + key + """720 721 722
723 static String inputStreamToString(InputStream in) 724 return readFully( InputStreamReader(in,UTF_8));
725 726
727 728 * A snapshot of the values for an entry.
729 730 class Snapshot 731 String key;
732 sequenceNumber;
733 InputStream[] ins;
734
735 private Snapshot(String key,1)"> sequenceNumber,InputStream[] ins) {
736 this.key = key;
737 this.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() 747 return DiskLruCache..edit(key,sequenceNumber);
748 749
750 751 * Returns the unbuffered stream with the value for { index}.
752 753 public InputStream getInputStream( index) {
754 ins[index];
755 756
757 758 * Returns the string value for {759 760 public String getString(int index) 761 inputStreamToString(getInputStream(index));
762 763
764 @Override close() {
765 (InputStream in : ins) {
766 closeQuietly(in);
767 768 769 770
771 772 * Edits the values for an entry.
773 774 Editor {
775 Entry entry;
776 hasErrors;
777
778 Editor(Entry entry) {
779 this.entry = entry;
780 781
782 783 * Returns an unbuffered input stream to read the last committed value,1)">784 * or null if no value has been committed.
785 786 public InputStream newInputStream(787 788 789 790 791 792 793 794 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 803 InputStream in = newInputStream(index);
804 return in != null ? inputStreamToString(in) : 805 806
807 808 * Returns a new unbuffered output stream to write the value at
809 * { index}. If the underlying output stream encounters errors
810 * when writing to the filesystem,this edit will be aborted when
811 #commit} is called. The returned output stream does not throw
812 * IOExceptions.
813 814 public OutputStream newOutputStream(815 816 817 818 819 new FaultHidingOutputStream( FileOutputStream(entry.getDirtyFile(index)));
820 821 822
823 824 * Sets the value at { index} to { value}.
825 826 void set(int index,String value) 827 Writer writer = 828 829 writer = OutputStreamWriter(newOutputStream(index),UTF_8);
830 writer.write(value);
831 } 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 void commit() 841 (hasErrors) {
842 completeEdit(this,1)">843 remove(entry.key); the prevIoUs entry is stale
844 } 845 completeEdit(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 void abort() 854 completeEdit(855 856
857 class FaultHidingOutputStream extends FilterOutputStream {
858 FaultHidingOutputStream(OutputStream out) {
859 super(out);
860 861
862 @Override void write( oneByte) {
863 864 out.write(oneByte);
865 } (IOException e) {
866 hasErrors = 867 868 869
870 @Override byte[] buffer,1)">int offset,1)"> length) {
871 872 out.write(buffer,offset,length);
873 } 874 hasErrors = 875 876 877
878 @Override 879 880 out.close();
881 } 882 hasErrors = 883 884 885
886 @Override flush() {
887 888 out.flush();
889 } 890 hasErrors = 891 892 893 894 895
896 Entry {
897 898
899 Lengths of this entry's files. 900 [] lengths;
901
902 True if this entry has ever been published 903 readable;
904
905 The ongoing edit or null if this entry is not being edited. 906 Editor currentEditor;
907
908 The sequence number of the most recently committed edit to this entry. 909 910
911 Entry(String key) {
912 913 this.lengths = [valueCount];
914 915
916 public String getLengths() 917 StringBuilder result = StringBuilder();
918 size : lengths) {
919 result.append(' ').append(size);
920 921 922 923
924 925 * Set lengths using decimal numbers like "10123".
926 927 void setLengths(String[] strings) 928 if (strings.length !=929 invalidLengths(strings);
930 931
932 933 int i = 0; i < strings.length; i++934 lengths[i] = Long.parseLong(strings[i]);
935 936 } (NumberFormatException e) {
937 938 939 940
941 private IOException invalidLengths(String[] strings) 942 Arrays.toString(strings));
943 944
945 public File getCleanFile( i) {
946 new File(directory,key + "." +947 948
949 public File getDirtyFile(950 951 952 953 }@H_198_301@
View Code
MD5Utils(MD5转换工具类):


3 java.math.BigInteger;
java.security.MessageDigest;
java.security.NoSuchAlgorithmException;
6
MD5Utils {
8 * 使用md5的算法进行加密
10 static String md5(String plainText) {
12 byte[] secretBytes = 13 14 secretBytes = MessageDigest.getInstance("md5").digest(
15 plainText.getBytes());
16 } (NoSuchAlgorithmException e) {
17 new RuntimeException("没有md5这个算法!"18 19 String md5code = new BigInteger(1,secretBytes).toString(16); 16进制数字
20 如果生成数字未满32位,需要前面补0
21 int i = 0; i < 32 - md5code.length(); i++22 md5code = "0" + md5code;
23 24 25 26
27 }@H_198_301@
View Code
上面三个类直接引入项目就行了,接下来说说核心实现代码了。
PhotoWallAdapter(GridView适配器类):
说下思路:由于我们的图片源是单纯的字符串(网址),这里给GridView适配的Adpter采用ArrayAdapter,当然如果你想用BaseAdatper也是可以的,思路不变。
1、继承ArrayAdatper,在构造函数传入必要参数后,需要进行2个操作:1、对内存缓存类的初始化 2、对磁盘缓存的初始化
2、然后在getView方法中来设置图片源,首先先设置成一张默认的图片,然后根据图片的URL去缓存中找是否有相关联的资源,如果没找到在磁盘缓存中找,如果还是没找到再去网络上下载,然后保存在磁盘缓存里,在以上的任一环节(磁盘,网络)里,只要我们找到了相对的图片资源,我们就把它添加到内存缓存中,以便下一次的引用。为了避免异步下载图片造成的图片错位现象,我们在每一个ImageView里设置了一个标识符Tag,Tag为图片的唯一标志:URL地址。
3、由于磁盘缓存属于I/O操作,网络属于下载操作都是属于耗时性的工作,这里我们开启了一个内部类Async异步类去完成,把所有的耗时操作都安排在doInBackground里执行(这里选择Async而不选择直接new Thread的原因是,Async运用了线程池的概念,会比单开子线程会更省资源,而且所有的任务会按照队列的顺序去执行)
?由于这里的图片都是比较小的,在实际开发中,大家可以对利用BitmapFactory的Options类对图片进行压缩再展示。
2
java.io.BufferedOutputStream;
java.net.HttpURLConnection;
java.net.URL;
java.util.HashSet;
java.util.Set;
12
android.content.Context;
android.content.pm.PackageInfo;
15 android.content.pm.PackageManager.NameNotFoundException;
16 android.graphics.Bitmap;
android.graphics.BitmapFactory;
18 android.os.AsyncTask;
android.os.Environment;
android.support.v4.util.LruCache;
android.util.Log;
android.view.LayoutInflater;
android.view.View;
android.view.ViewGroup;
android.widget.ArrayAdapter;
android.widget.GridView;
android.widget.ImageView;
28
com.example.photoswall.DiskLruCache.Snapshot;
30
class PhotoWallAdapter extends ArrayAdapter<String> 32
33 声明LruCache缓存对象
34 private LruCache<String,Bitmap> lruCache;
35 声明DiskLruCache硬盘缓存对象
36 DiskLruCache diskLruCache;
37 任务队列
38 private Set<LoadImageAsync> tasks;
39 声明GridView对象
40 GridView gridView;
41
42 public PhotoWallAdapter(Context context,1)"> textViewResourceId,String[] objects,GridView gridView) {
43 (context,textViewResourceId,objects);
44 this.gridView = gridView;
45 tasks = new HashSet<LoadImageAsync>();
46 * 初始化内存缓存LruCache
48 49 获取应用程序最大可占内存值
50 int maxMemory = () Runtime.getRuntime().maxMemory();
51 设置最大内存的八分之一作为缓存大小
52 int lruMemory = maxMemory / 8 53 lruCache = new LruCache<String,1)">(lruMemory) {
@Override
55 protected sizeOf(String key,Bitmap bitmap) {
56 返回Bitmap对象所占大小,单位:kb
57 bitmap.getByteCount();
59
};
61 * 初始化硬盘缓存DiskLruCahce
63 64 获取硬盘缓存路径,参数二为所在缓存路径的文件夹名称
65 File directory = getDiskCacheDir(getContext(),"bitmap" 66 directory.exists()) {
67 若文件夹不存在,建立文件夹
directory.mkdirs();
70 int appVersion = getAppVersion(getContext());
71 72 参数1:缓存文件路径,参数2:系统版本号,参数3:一个缓存路径对于几个文件,参数4:缓存空间大小:字节
73 diskLruCache = DiskLruCache.open(directory,1,1024 * 1024 * 10 74 } e.printStackTrace();
77
79
80 context
uniqueName
@return
* 当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取缓存路径,否则就调用getCacheDir
* ()方法来获取缓存路径。 前者获取到的就是 /sdcard/Android/data/<application
* package>/cache 这个路径 而后者获取到的是 datadata/application package>/cache
* 这个路径。
88 89 File getDiskCacheDir(Context context,String uniqueName) {
String cachePath;
91 if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
92 cachePath = context.getExternalCacheDir().getPath();
93 } 94 cachePath = context.getCacheDir().getPath();
96 new File(cachePath + File.separator + uniqueName);
98
99 101 获取系统版本号
102 getAppVersion(Context context) {
104 105 PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),1)">106 info.versionCode;
107 } (NameNotFoundException e) {
108 109 110 return 1111 112
113 @Override
public View getView( position,View convertView,ViewGroup parent) {
115 获取图片资源URL地址
116 String path = getItem(position);
117 View view = 118 if (convertView == 119 view = LayoutInflater.from(getContext()).inflate(R.layout.gridview_item,1)">120 } 121 view = convertView;
123 获取控件实例
124 ImageView imageView = (ImageView) view.findViewById(R.id.iv_photo);
125 设置一个唯一标识符,避免异步加载图片时错位
imageView.setTag(path);
127 设置默认图片
imageView.setImageResource(R.drawable.ic_launcher);
129 根据图片URL到缓存中去找图片资源并设置
setImageFromLruCache(path,imageView);
131 view;
133
134 * 根据图片URL地址获取缓存中图片,若不存在去磁盘缓存中查找=》网络下载
*
path
imageView
139 140 setImageFromLruCache(String path,ImageView imageView) {
141 Bitmap bitmap = lruCache.get(path);
142 if (bitmap != 143 缓存存在,取出设置图片
144 Log.i("PhotoWallAdapter","在内存缓存中找到" imageView.setImageBitmap(bitmap);
146 } 147 缓存不存在,先找硬盘缓存,还不存在,就去网络下载(开启异步任务)
148 LoadImageAsync loadImageAsync = LoadImageAsync();
loadImageAsync.execute(path);
150 添加任务到任务队列
tasks.add(loadImageAsync);
152 153 154
156 * 取消队列中准备下载和正在下载的任务
157 cancelTask() {
159 (LoadImageAsync task : tasks) {
160 task.cancel(161 162 163
165 * 同步内存操作到journal文件
166 167 flushCache() {
168 if (diskLruCache != 169 170 diskLruCache.flush();
171 } 172 e.printStackTrace();
173 174 175
176 177
178 class LoadImageAsync extends AsyncTask<String,Void,1)">179 图片资源URL
180 String path = 181
@Override
protected Bitmap doInBackground(String... params) {
184 图片下载地址
185 this.path = params[0186 Snapshot snapshot = 187 OutputStream outputStream = 188 Bitmap bitmap = 189 String pathMd5 = MD5Utils.md5(path);
190 根据图片url(md5)查找图片资源是否存在于硬盘缓存
191 192 snapshot = diskLruCache.get(pathMd5);
193 if (snapshot == 194 在磁盘缓存中没有找到图片资源
195 获取一个DiskLruCache写入对象
196 DiskLruCache.Editor editor = diskLruCache.edit(pathMd5);
197 if (editor != 198 outputStream = editor.newOutputStream(0199 开启异步网络任务获取图片,并存入磁盘缓存
200 (downloadUrlToStream(path,outputStream)) {
201 下载成功
202 Log.i("PhotoWallAdapter","下载文件成功"203 editor.commit();
204 } editor.abort();
}
207 }
208 209 图片写入磁盘缓存后,再一次的查找磁盘缓存
210 snapshot =211 if (snapshot != 212 若查找到,获取图片,并把图片资源写入内存缓存
213 bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));
214 Log.i("PhotoWallAdapter","在磁盘缓存中找到"215 216 217 将Bitmap对象添加到内存缓存当中
218 lruCache.put(path,bitmap);
219 220 bitmap;
221 } 222 if (outputStream != 225 226 outputStream.close();
227 } e.printStackTrace();
229 230 231 232 233 234
235 236 onPostExecute(Bitmap bitmap) {
237 .onPostExecute(bitmap);
238 根据Tag获取控件对象并设置图片
239 ImageView imageView = (ImageView) gridView.findViewWithTag(path);
240 if (imageView != null && bitmap != 241 加载图片
imageView.setImageBitmap(bitmap);
243 244 tasks.remove(245
247
248 249 * 根据图片URL地址下载图片,成功返回true,失败false
250 *
* urlString
252 outputStream
253 @return
254 255 downloadUrlToStream(String urlString,OutputStream outputStream) {
256 HttpURLConnection urlConnection = 257 BufferedOutputStream out = 258 BufferedInputStream in = 259 260 final URL url = URL(urlString);
261 urlConnection = (HttpURLConnection) url.openConnection();
262 in = new BufferedInputStream(urlConnection.getInputStream(),8 * 1024263 out = new BufferedOutputStream(outputStream,1)"> b;
265 while ((b = in.read()) != -1 out.write(b);
268 269 } catch ( IOException e) {
270 271 } 272 if (urlConnection != 273 urlConnection.disconnect();
274 if (out != out.close();
279 if (in != in.close();
281 282 } e.printStackTrace();
284 286 287 288
289 290
291 }@H_198_301@
?MainActivity类:
相比之下这个类就简单许多了,这里需要注意的两点:
1、在onPause方法里进行flush操作(更新磁盘缓存操作日志,磁盘缓存之所以能够被读取取决于日志文件)
2、在Activity销毁之前取消所有正在下载和准备下载的任务。
android.app.Activity;
android.os.Bundle;
class MainActivity Activity {
8
9 GridView gv_photo;
10 PhotoWallAdapter adapter;
11
12 13 onCreate(Bundle savedInstanceState) {
14 .onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
16 gv_photo = (GridView) findViewById(R.id.gv_photo);
17 adapter = new PhotoWallAdapter(MainActivity. gv_photo.setAdapter(adapter);
19
20 21
22 23 onPause() {
.onPause();
adapter.flushCache();
26 27
28 29 onDestroy() {
30 .onDestroy();
31 adapter.cancelTask();
32 33
34 }@H_198_301@
?
剩下的一些细节,代码注释的很详细,还有不清楚的可以看看上文提到附带的2篇文章或者评论给我留言。
?
作者:Balla_兔子 出处:http://www.cnblogs.com/lichenwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。 正在看本人博客的这位童鞋,我看你气度不凡,谈吐间隐隐有王者之气,日后必有一番作为!旁边有“推荐”二字,你就顺手把它点了吧,相得准,我分文不收;相不准,你也好回来找我! (编辑:北几岛)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|