After edit, instead of {@link #close()} the EditorOutputStream, the OutputStream need to 21 | * {@link #commit()} to write the change to cache, or {@link #abort()} to discard the change. 22 | *
All EditorOutputStream should be committed or aborted after use to prevent resource leak.
23 | */
24 | public final class EditorOutputStream extends FileOutputStream {
25 | private IgDiskCache mCache;
26 | private Entry mEntry;
27 | private boolean mHasErrors;
28 | private boolean mIsClosed;
29 |
30 | /* package */ EditorOutputStream(Entry entry, IgDiskCache cache) throws FileNotFoundException {
31 | super(entry.getDirtyFile());
32 | mCache = cache;
33 | mEntry = entry;
34 | mHasErrors = false;
35 | }
36 |
37 | /**
38 | * Commit change to disk cache.
39 | * @return true if the change is successfully committed to disk cache. In case of IOExceptions,
40 | * the method will return false instead of throwing out the IOExceptions.
41 | */
42 | public synchronized boolean commit() {
43 | checkNotClosedOrEditingConcurrently();
44 | close();
45 | mIsClosed = true;
46 | if (mHasErrors) {
47 | mCache.abortEdit(mEntry);
48 | mCache.remove(mEntry.getKey()); // Previous entry is stale.
49 | return false;
50 | } else {
51 | mCache.commitEdit(mEntry);
52 | return true;
53 | }
54 | }
55 |
56 | /**
57 | * Abort the change made to the EditorOutputStream.
58 | */
59 | public synchronized void abort() {
60 | checkNotClosedOrEditingConcurrently();
61 | close();
62 | mIsClosed = true;
63 | mCache.abortEdit(mEntry);
64 | }
65 |
66 | /**
67 | * Abort the change if it is not already committed. This is commonly used in the {@code finally}
68 | * block of error try-cache to make sure the EditorOutputStream is properly closed.
69 | */
70 | public synchronized void abortUnlessCommitted() {
71 | if (!mIsClosed) {
72 | abort();
73 | }
74 | }
75 |
76 | @Override
77 | public void write(byte[] buffer) {
78 | try {
79 | super.write(buffer);
80 | } catch (IOException e) {
81 | mHasErrors = true;
82 | }
83 | }
84 |
85 | @Override
86 | public void write(byte[] buffer, int byteOffset, int byteCount) {
87 | try {
88 | super.write(buffer, byteOffset, byteCount);
89 | } catch (IOException e) {
90 | mHasErrors = true;
91 | }
92 | }
93 |
94 | /**
95 | * Deprecated, should use {@link #commit()} or {@link #abort()} instead.
96 | */
97 | @Deprecated
98 | @Override
99 | public void close() {
100 | try {
101 | super.close();
102 | } catch (IOException e) {
103 | mHasErrors = true;
104 | }
105 | }
106 |
107 | @Override
108 | public void flush() {
109 | try {
110 | super.flush();
111 | } catch (IOException e) {
112 | mHasErrors = true;
113 | }
114 | }
115 |
116 | private void checkNotClosedOrEditingConcurrently() {
117 | if (mIsClosed) {
118 | throw new IllegalStateException(
119 | "Try to operate on an EditorOutputStream that is already closed");
120 | }
121 | if (mEntry.getCurrentEditorStream() != this) {
122 | throw new IllegalStateException(
123 | "Two editors trying to write to the same cached file");
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/instagram/igdiskcache/demo/cache/BitmapCache.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the license found in the
6 | * LICENSE-examples file in the root directory of this source tree.
7 | */
8 |
9 | package com.instagram.igdiskcache.demo.cache;
10 |
11 | import android.content.Context;
12 | import android.graphics.Bitmap;
13 | import android.graphics.BitmapFactory;
14 | import android.support.v4.util.LruCache;
15 | import android.util.Log;
16 |
17 | import com.instagram.igdiskcache.EditorOutputStream;
18 | import com.instagram.igdiskcache.IgDiskCache;
19 | import com.instagram.igdiskcache.OptionalStream;
20 | import com.instagram.igdiskcache.SnapshotInputStream;
21 |
22 | import java.io.File;
23 | import java.io.FileDescriptor;
24 | import java.io.IOException;
25 |
26 | /**
27 | * Bitmap cache for saving Bitmap in memory and on disk.
28 | */
29 | public class BitmapCache {
30 | private static final String TAG = BitmapCache.class.getSimpleName();
31 | private static final String DISK_CACHE_DIR = "bitmap";
32 | private static final int DEFAULT_MEM_CACHE_CAP = 10;
33 | private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 100; // 100MB
34 | private static final int DEFAULT_DISK_CACHE_SIZE_PERCENT = 10; // 10% of free disk space
35 |
36 | private Context mContext;
37 | private IgDiskCache mDiskCache;
38 | private LruCache Each line contains space-separated values: a state, a key, and a optional state-specific
43 | * value representing the length of the entry data in bytes.
44 | *
45 | * The journal file is appended to as cache operations occur. The journal may occasionally be
55 | * compacted when the number of lines inside the journal exceeds the rebuild threshold.
56 | * A temporary file named "journal.tmp" will be used during compaction; that file will be deleted
57 | * if it exists when the cache is re-opened.
58 | */
59 |
60 | /* package */ class Journal {
61 |
62 | static final String JOURNAL_FILE = "journal";
63 | static final String JOURNAL_FILE_TEMP = "journal.tmp";
64 | static final String JOURNAL_FILE_BACKUP = "journal.bkp";
65 | static final Charset US_ASCII = Charset.forName("US-ASCII");
66 |
67 | private static final String TAG = Journal.class.getSimpleName();
68 | private static final String CLEAN_ENTRY_PREFIX = "CLEAN";
69 | private static final String DIRTY_ENTRY_PREFIX = "DIRTY";
70 | private static final int JOURNAL_REBUILD_THRESHOLD = 1000;
71 |
72 | private final File mDirectory;
73 | private final File mJournalFile;
74 | private final File mJournalFileTmp;
75 | private final File mJournalFileBackup;
76 | private final IgDiskCache mCache;
77 | private final Executor mExecutor;
78 |
79 | private Writer mJournalWriter;
80 | private int mLineCount;
81 |
82 | @SuppressLint("EmptyCatchBlock")
83 | class WriteToJournalRunnable implements Runnable {
84 | final String mLine;
85 | public WriteToJournalRunnable(String line) {
86 | this.mLine = line;
87 | }
88 | @Override
89 | public void run() {
90 | try {
91 | if (mJournalWriter != null) {
92 | mJournalWriter.write(mLine);
93 | mJournalWriter.flush();
94 | mLineCount++;
95 | rebuildIfNeeded();
96 | }
97 | } catch (IOException ignored) {
98 | }
99 | }
100 | }
101 |
102 | /* package */ Journal(File directory, IgDiskCache cache, Executor executor) {
103 | mJournalFile = new File(directory, JOURNAL_FILE);
104 | mJournalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
105 | mJournalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
106 | mDirectory = directory;
107 | mCache = cache;
108 | mExecutor = executor;
109 | mLineCount = 0;
110 | }
111 |
112 | @SuppressLint("EmptyCatchBlock")
113 | /* package */ LinkedHashMap The cache stores its data in a directory on the filesystem. This directory must be exclusive
40 | * to the cache; the cache may delete or overwrite files from its directory. It is an error for
41 | * multiple processes to use the same cache directory at the same time.
42 | *
43 | * This cache limits the number of bytes, and the number of entries that it will store on
44 | * the filesystem. When the number of entries or stored bytes exceeds the limit, the cache will
45 | * remove entries in the background until the limits are satisfied. The limits are not strict:
46 | * the cache may temporarily exceed the limits while waiting for files to be deleted.
47 | *
48 | * Clients call {@link #get} to read a snapshot of an entry. The read will observe the value
49 | * at the time that {@link #get} was called. Updates and removals after the call do not impact
50 | * ongoing read. Reading from a disk cache entry:
51 | * Clients call {@link #edit} to create or update the values of an entry. An entry may have
65 | * only one editor at one time; if a value is not available to be edited then {@link #edit} will
66 | * return OptionalStream.absent().If an error occurs while writing a cache value, the edit will fail
67 | * silently without throwing IOExceptions.
68 | * The {@link EditorOutputStream} need to be either commit() or abort() after editing.
69 | * Writing to a disk cache entry:
70 | * This class will silently handle most of the IOExceptions. If files are missing from the
81 | * filesystem, the corresponding entries will be dropped from the cache.
82 | *
83 | * Note: IgDiskCache should never be initialized or closed from the UI Thread.
84 | */
85 | public final class IgDiskCache {
86 | private static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}";
87 | private static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN);
88 | private static final long DEFAULT_MAX_SIZE = 1024 * 1024 * 30; // maximum 30 megs in size
89 | private static final int DEFAULT_MAX_COUNT = 1000; // maximum 1000 files
90 | private static final ThreadPoolExecutor DISK_CACHE_EXECUTOR =
91 | new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue
36 | * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832
37 | * DIRTY 335c4c6028171cfddfbaae1a9c313c52
38 | * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934
39 | * DIRTY 3400330d1dfc7f3f7f4b8d4d803dfcf6
40 | *
41 | *
42 | *
53 | *
54 | *
52 | * {@code
53 | * OptionalStream
63 | *
64 | *
71 | * {@code
72 | * OptionalStream
79 | *
80 | *