15
15
16
16
/**
17
17
* Class for managing a file system directory (Context.getCacheDir) as an LRU cache.
18
- * This class is generally MT safe, except that the constructor must be called by the application's main
19
- * event loop thread, since it calls Context.getCacheDir, which doesn't look MT safe.
18
+ * This class is generally MT safe, except for getInstance(), which must be called by the application's main
19
+ * event loop thread; it calls Context.getCacheDir, which doesn't look MT safe.
20
20
*/
21
21
public class ExternalCacheManager {
22
22
private static final String TAG = "ExtenalCacheManager" ;
23
23
private final File mDir ;
24
24
private final Context mContext ;
25
+ private static final int MAX_CACHE_STALENESS_MS = 7200 * 1000 ; // 2h
25
26
26
27
// Cache key -> last access time
27
28
private HashMap <String , Long > mLastAccessTimes ;
28
29
29
30
// The path where a serialized mLastAccessTimes is saved serialized.
30
31
private static final String SUMMARY_PATH = "external_cache_summary" ;
31
-
32
- // TODO: add background purging
33
-
32
+
33
+ private static final HashMap <String , ExternalCacheManager > mInstances =
34
+ new HashMap <String , ExternalCacheManager >();
35
+
34
36
/**
37
+ * Create and return a process-wide cache instance. This method must be called by the
38
+ * main application event loop thread.
39
+ *
40
+ * @param context It should be the value of getApplicationContext().
35
41
*
36
42
* @param id The ID for this cache. It can be any string (it should be usable as a filename).
37
43
* It should be unique within the application, so that
38
44
* multiple instances of this class can partition the per-application cache space.
39
45
*/
40
- public ExternalCacheManager (Context context , String id ) {
46
+ public static ExternalCacheManager getInstance (Context context , String id ) {
47
+ ExternalCacheManager c = mInstances .get (id );
48
+ if (c == null ) {
49
+ c = new ExternalCacheManager (context , id );
50
+ mInstances .put (id , c );
51
+ }
52
+ return c ;
53
+ }
54
+
55
+ // TODO: add background purging
56
+
57
+ private ExternalCacheManager (Context context , String id ) {
41
58
mContext = context ;
42
59
mDir = new File (context .getCacheDir (), id );
43
60
mDir .mkdirs ();
@@ -75,6 +92,11 @@ public synchronized void clearAll() {
75
92
Log .d (TAG , SUMMARY_PATH + ": Failed to clear cache: " + e .toString ());
76
93
}
77
94
}
95
+
96
+ private static class CacheEntry implements Serializable {
97
+ long createMs ; // The time the entry was created. Millisec since the epoch
98
+ Serializable obj ;
99
+ }
78
100
79
101
/**
80
102
* Write the mapping key -> obj to the cache. Errors (e.g., IOError) are simply ignored.
@@ -83,12 +105,16 @@ public synchronized void clearAll() {
83
105
* chars such as '/'.
84
106
*/
85
107
public synchronized void write (String key , Serializable obj ) {
108
+ CacheEntry ent = new CacheEntry ();
109
+ ent .createMs = System .currentTimeMillis ();
110
+ ent .obj = obj ;
111
+
86
112
FileOutputStream data_out = null ;
87
113
try {
88
114
try {
89
115
File path = new File (mDir , key );
90
116
data_out = new FileOutputStream (path );
91
- new ObjectOutputStream (data_out ).writeObject (obj );
117
+ new ObjectOutputStream (data_out ).writeObject (ent );
92
118
mLastAccessTimes .put (key , System .currentTimeMillis ());
93
119
saveSummary ();
94
120
} finally {
@@ -103,25 +129,36 @@ public synchronized void write(String key, Serializable obj) {
103
129
* If object for "key" is in the cache, return it. Else, or in case of an error (e.g., IOError)
104
130
* return null.
105
131
*/
106
- public synchronized Object read (String key ) {
132
+ public static class ReadResult {
133
+ public Object obj ;
134
+ public boolean needRefresh ;
135
+ }
136
+
137
+ public synchronized ReadResult read (String key ) {
138
+ final long now = System .currentTimeMillis ();
139
+ ReadResult r = new ReadResult ();
140
+ r .obj = null ;
141
+ r .needRefresh = true ;
107
142
FileInputStream data_in = null ;
108
- Object obj = null ;
109
143
try {
110
144
try {
111
145
File path = new File (mDir , key );
112
146
data_in = new FileInputStream (path );
113
- obj = new ObjectInputStream (data_in ).readObject ();
114
- mLastAccessTimes .put (key , System .currentTimeMillis ());
147
+ CacheEntry ent = (CacheEntry )(new ObjectInputStream (data_in ).readObject ());
148
+ if (ent != null ) {
149
+ r .obj = ent .obj ;
150
+ r .needRefresh = (now - ent .createMs >= MAX_CACHE_STALENESS_MS );
151
+ }
152
+ mLastAccessTimes .put (key , now );
115
153
saveSummary ();
116
154
} finally {
117
155
if (data_in != null ) data_in .close ();
118
156
}
119
157
} catch (FileNotFoundException e ) {
120
- return null ;
121
158
} catch (Throwable e ) {
122
159
Log .d (TAG , SUMMARY_PATH + ": failed to write: " + e .toString ());
123
160
}
124
- return obj ;
161
+ return r ;
125
162
}
126
163
127
164
private void saveSummary () throws IOException {
0 commit comments