Skip to content

Commit e01325d

Browse files
committed
Merge pull request jimburton#35 from AlmasB/cache_branch
I've done some testing and this looks good. I'm going to use it as an example (with a namecheck of course) in a lecture on Android optimisation. Thanks.
2 parents be39928 + e6ecf88 commit e01325d

File tree

7 files changed

+238
-10
lines changed

7 files changed

+238
-10
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package uk.ac.brighton.ci360.bigarrow;
2+
3+
import java.util.LinkedList;
4+
import java.util.WeakHashMap;
5+
6+
/**
7+
* This class saves information about searches
8+
* so that same search instead of loading data over network
9+
* will read it from memory
10+
*
11+
* Copyright (c) 2013 The BigArrow authors (see the file AUTHORS).
12+
* See the file LICENSE for copying permission.
13+
*
14+
* @author Almas Baimagambetov (ab607)
15+
*
16+
*/
17+
public class Cache<V extends Cacheable> {
18+
19+
/**
20+
* Maximum number of key-value mappings that can be held in cache
21+
* This value is used by default when the limit is not specified
22+
*/
23+
private static final int DEFAULT_CACHE_SIZE_LIMIT = 30;
24+
25+
/**
26+
* Maximum number of key-value mappings that can be held in this cache
27+
*/
28+
private int sizeLimit;
29+
30+
/**
31+
* Formatter used to format cache keys
32+
*/
33+
private Formatter formatter;
34+
35+
/**
36+
* Actual key and value "holder"
37+
*/
38+
private WeakHashMap<String, V> cache;
39+
40+
/**
41+
* Provides means of identifying the least-recently accessed cache item
42+
*
43+
* First (head) element of this queue is the key
44+
* of the least-recently cached entry
45+
*/
46+
private LinkedList<String> queue = new LinkedList<String>();
47+
48+
/**
49+
* Constructs cache with given formatter
50+
* and default size limit
51+
*
52+
* @param formatter
53+
*/
54+
public Cache(Formatter formatter) {
55+
this(DEFAULT_CACHE_SIZE_LIMIT, formatter);
56+
}
57+
58+
/**
59+
* Constructs cache with given formatter
60+
* and given size limit, also taking into
61+
* account the default load factor of 7500 (0.75)
62+
*
63+
* @param limit
64+
* @param formatter
65+
*/
66+
public Cache(int limit, Formatter formatter) {
67+
sizeLimit = limit;
68+
this.formatter = formatter;
69+
cache = new WeakHashMap<String, V>((int)(sizeLimit/0.75) + 1);
70+
}
71+
72+
/**
73+
* Maps the specified key to the specified value
74+
*
75+
* @param key
76+
* @param value
77+
*/
78+
public void store(String key, V value) {
79+
key = formatter.formatKey(key);
80+
if (queue.size() == sizeLimit) {
81+
cache.remove(queue.poll());
82+
}
83+
84+
queue.add(key);
85+
cache.put(key, value);
86+
}
87+
88+
/**
89+
* Retrieve the value of the mapping with the specified key
90+
*
91+
* @param key
92+
* @return the value of the mapping with the specified key, or {@code null}
93+
* if no mapping for the specified key is found
94+
*/
95+
public V get(String key) {
96+
key = formatter.formatKey(key);
97+
queue.remove(key);
98+
queue.add(key);
99+
return cache.get(key);
100+
}
101+
102+
/**
103+
* Returns whether this cache contains the specified key
104+
*
105+
* @param key the key to search for
106+
* @return {@code true} if this map contains the specified key,
107+
* {@code false} otherwise
108+
*/
109+
public boolean contains(String key) {
110+
return cache.containsKey(formatter.formatKey(key));
111+
}
112+
113+
/**
114+
* @return number of items currently held in cache
115+
*/
116+
public int getSize() {
117+
return cache.size();
118+
}
119+
120+
/**
121+
* @return maximum number of items that can be held in this cache
122+
*/
123+
public int getLimit() {
124+
return sizeLimit;
125+
}
126+
127+
/**
128+
* Removes all data held in cache
129+
* Consequently on the first following GC sweep
130+
* GC will attempt to free up the memory used by old objects
131+
*/
132+
public void free() {
133+
queue.clear();
134+
cache.clear();
135+
}
136+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package uk.ac.brighton.ci360.bigarrow;
2+
3+
/**
4+
* Brings a little bit of structure to Cache
5+
*
6+
* Copyright (c) 2013 The BigArrow authors (see the file AUTHORS).
7+
* See the file LICENSE for copying permission.
8+
*
9+
* @author Almas Baimagambetov (ab607)
10+
*/
11+
public interface Cacheable {
12+
13+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package uk.ac.brighton.ci360.bigarrow;
2+
3+
/**
4+
* Users may use this interface to format cache
5+
* keys appropriately
6+
*
7+
* Copyright (c) 2013 The BigArrow authors (see the file AUTHORS).
8+
* See the file LICENSE for copying permission.
9+
*
10+
* @author Almas Baimagambetov (ab607)
11+
*/
12+
public interface Formatter {
13+
14+
/**
15+
* Used by {@code Cache} to format reference keys
16+
* to cached objects
17+
*
18+
* @param key
19+
* raw key
20+
* @return
21+
* formatted key
22+
*/
23+
public String formatKey(String key);
24+
}

src/uk/ac/brighton/ci360/bigarrow/PlacesAPISearch.java

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,37 @@ public class PlacesAPISearch {
8686
private CopyOnWriteArrayList<Bitmap> photoResults;
8787

8888
private ExecutorService executorService;
89+
90+
private Cache<PlacesList> placesListCache;
91+
private Cache<PlaceDetails> placeDetailsCache;
8992

9093
public PlacesAPISearch(PlaceSearchRequester requester) {
9194
this.requester = requester;
9295
Properties prop = new Properties();
96+
97+
placesListCache = new Cache<PlacesList>(new Formatter() {
98+
@Override
99+
public String formatKey(String queryString) {
100+
String location = queryString.substring(queryString.indexOf("location=")+9);
101+
location = location.substring(0, location.indexOf("&"));
102+
String lat = location.substring(0, location.indexOf(","));
103+
String lng = location.substring(location.indexOf(",") + 1);
104+
105+
lat = String.format("%.3f", Double.parseDouble(lat));
106+
lng = String.format("%.3f", Double.parseDouble(lng));
107+
108+
String type = queryString.substring(queryString.indexOf("types=")+6);
109+
// "type1,type2&latitude,longitude"
110+
return type + "&" + lat + "," + lng;
111+
}
112+
});
113+
114+
placeDetailsCache = new Cache<PlaceDetails>(new Formatter() {
115+
@Override
116+
public String formatKey(String key) {
117+
return key.substring(key.indexOf("reference=") + 10, key.indexOf("&sensor"));
118+
}
119+
});
93120

94121
try {
95122
prop.load(PlacesAPISearch.class.getClassLoader()
@@ -142,10 +169,23 @@ protected PlacesList doInBackground(String... urls) {
142169
request.getUrl().put("sensor", "true");
143170
if (estabType != null)
144171
request.getUrl().put("types", estabType.toLowerCase());
145-
Log.d(TAG, request.getUrl().toString());
146-
places = request.execute().parseAs(PlacesList.class);
172+
173+
String query = request.getUrl().toString();
174+
Log.d(TAG, query);
175+
176+
if (placesListCache.contains(query)) {
177+
places = placesListCache.get(query);
178+
Log.d(TAG, "retrieved from cache");
179+
}
180+
else {
181+
places = request.execute().parseAs(PlacesList.class);
182+
// we really should do some data validation before storing
183+
placesListCache.store(query, places);
184+
}
185+
147186
// Check log cat for places response status
148187
Log.d(TAG, "" + places.status);
188+
149189
return places;
150190
} catch (HttpResponseException e) {
151191
Log.e("Error:", e.getMessage());
@@ -197,10 +237,23 @@ protected PlaceDetails doInBackground(String... urls) {
197237
request.getUrl().put("key", apiKey);
198238
request.getUrl().put("reference", detailsReference);
199239
request.getUrl().put("sensor", "true");
200-
Log.d(TAG, request.getUrl().toString());
201-
details = request.execute().parseAs(PlaceDetails.class);
202-
// Check log cat for places response status
203-
Log.d(TAG, "" + details.status);
240+
241+
String query = request.getUrl().toString();
242+
Log.d(TAG, query);
243+
244+
if (placeDetailsCache.contains(query)) {
245+
details = placeDetailsCache.get(query);
246+
Log.d(TAG, "retrieved from cache");
247+
}
248+
else {
249+
details = request.execute().parseAs(PlaceDetails.class);
250+
// we really should do some data validation before storing
251+
placeDetailsCache.store(query, details);
252+
}
253+
254+
// Check log cat for places response status
255+
Log.d(TAG, "" + details.status);
256+
204257
return details;
205258
} catch (HttpResponseException e) {
206259
Log.e("Error:", e.getMessage());

src/uk/ac/brighton/ci360/bigarrow/places/Place.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
**/
1010

1111
import java.io.Serializable;
12-
import java.util.Calendar;
1312
import java.util.LinkedHashMap;
1413

1514
import android.annotation.SuppressLint;

src/uk/ac/brighton/ci360/bigarrow/places/PlaceDetails.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
**/
1010
import java.io.Serializable;
1111

12+
import uk.ac.brighton.ci360.bigarrow.Cacheable;
13+
1214
import com.google.api.client.util.Key;
1315

14-
public class PlaceDetails implements Serializable {
16+
public class PlaceDetails implements Serializable, Cacheable {
1517

1618
private static final long serialVersionUID = 1L;
1719

src/uk/ac/brighton/ci360/bigarrow/places/PlacesList.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
**/
1010
import java.io.Serializable;
1111
import java.util.List;
12+
13+
import uk.ac.brighton.ci360.bigarrow.Cacheable;
1214

1315
import com.google.api.client.util.Key;
1416

15-
public class PlacesList implements Serializable {
17+
public class PlacesList implements Serializable, Cacheable {
1618

1719
/**
1820
*
@@ -24,5 +26,4 @@ public class PlacesList implements Serializable {
2426

2527
@Key
2628
public List<Place> results;
27-
2829
}

0 commit comments

Comments
 (0)