This class is used for buffered reading of lines. For purposes of this class, a line ends
31 | * with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
32 | * line at end of input is invalid and will be ignored, the caller may use {@code
33 | * hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
34 | *
35 | *
This class is intended for reading input that strictly consists of lines, such as line-based
36 | * cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
37 | * with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
38 | * end-of-input reporting and a more restrictive definition of a line.
39 | *
40 | *
This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
41 | * and 10, respectively, and the representation of no other character contains these values.
42 | * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
43 | * The default charset is US_ASCII.
44 | */
45 | class StrictLineReader implements Closeable {
46 | private static final byte CR = (byte) '\r';
47 | private static final byte LF = (byte) '\n';
48 |
49 | private final InputStream in;
50 | private final Charset charset;
51 |
52 | /*
53 | * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
54 | * and the data in the range [pos, end) is buffered for reading. At end of input, if there is
55 | * an unterminated line, we set end == -1, otherwise end == pos. If the underlying
56 | * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
57 | */
58 | private byte[] buf;
59 | private int pos;
60 | private int end;
61 |
62 | /**
63 | * Constructs a new {@code LineReader} with the specified charset and the default capacity.
64 | *
65 | * @param in the {@code InputStream} to read data from.
66 | * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
67 | * supported.
68 | * @throws NullPointerException if {@code in} or {@code charset} is null.
69 | * @throws IllegalArgumentException if the specified charset is not supported.
70 | */
71 | public StrictLineReader(InputStream in, Charset charset) {
72 | this(in, 8192, charset);
73 | }
74 |
75 | /**
76 | * Constructs a new {@code LineReader} with the specified capacity and charset.
77 | *
78 | * @param in the {@code InputStream} to read data from.
79 | * @param capacity the capacity of the buffer.
80 | * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
81 | * supported.
82 | * @throws NullPointerException if {@code in} or {@code charset} is null.
83 | * @throws IllegalArgumentException if {@code capacity} is negative or zero
84 | * or the specified charset is not supported.
85 | */
86 | public StrictLineReader(InputStream in, int capacity, Charset charset) {
87 | if (in == null || charset == null) {
88 | throw new NullPointerException();
89 | }
90 | if (capacity < 0) {
91 | throw new IllegalArgumentException("capacity <= 0");
92 | }
93 | if (!(charset.equals(Util.US_ASCII))) {
94 | throw new IllegalArgumentException("Unsupported encoding");
95 | }
96 |
97 | this.in = in;
98 | this.charset = charset;
99 | buf = new byte[capacity];
100 | }
101 |
102 | /**
103 | * Closes the reader by closing the underlying {@code InputStream} and
104 | * marking this reader as closed.
105 | *
106 | * @throws java.io.IOException for errors when closing the underlying {@code InputStream}.
107 | */
108 | public void close() throws IOException {
109 | synchronized (in) {
110 | if (buf != null) {
111 | buf = null;
112 | in.close();
113 | }
114 | }
115 | }
116 |
117 | /**
118 | * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
119 | * this end of line marker is not included in the result.
120 | *
121 | * @return the next line from the input.
122 | * @throws java.io.IOException for underlying {@code InputStream} errors.
123 | * @throws java.io.EOFException for the end of source stream.
124 | */
125 | public String readLine() throws IOException {
126 | synchronized (in) {
127 | if (buf == null) {
128 | throw new IOException("LineReader is closed");
129 | }
130 |
131 | // Read more data if we are at the end of the buffered data.
132 | // Though it's an error to read after an exception, we will let {@code fillBuf()}
133 | // throw again if that happens; thus we need to handle end == -1 as well as end == pos.
134 | if (pos >= end) {
135 | fillBuf();
136 | }
137 | // Try to find LF in the buffered data and return the line if successful.
138 | for (int i = pos; i != end; ++i) {
139 | if (buf[i] == LF) {
140 | int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
141 | String res = new String(buf, pos, lineEnd - pos, charset.name());
142 | pos = i + 1;
143 | return res;
144 | }
145 | }
146 |
147 | // Let's anticipate up to 80 characters on top of those already read.
148 | ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
149 | @Override
150 | public String toString() {
151 | int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
152 | try {
153 | return new String(buf, 0, length, charset.name());
154 | } catch (UnsupportedEncodingException e) {
155 | throw new AssertionError(e); // Since we control the charset this will never happen.
156 | }
157 | }
158 | };
159 |
160 | while (true) {
161 | out.write(buf, pos, end - pos);
162 | // Mark unterminated line in case fillBuf throws EOFException or IOException.
163 | end = -1;
164 | fillBuf();
165 | // Try to find LF in the buffered data and return the line if successful.
166 | for (int i = pos; i != end; ++i) {
167 | if (buf[i] == LF) {
168 | if (i != pos) {
169 | out.write(buf, pos, i - pos);
170 | }
171 | pos = i + 1;
172 | return out.toString();
173 | }
174 | }
175 | }
176 | }
177 | }
178 |
179 | public boolean hasUnterminatedLine() {
180 | return end == -1;
181 | }
182 |
183 | /**
184 | * Reads new input data into the buffer. Call only with pos == end or end == -1,
185 | * depending on the desired outcome if the function throws.
186 | */
187 | private void fillBuf() throws IOException {
188 | int result = in.read(buf, 0, buf.length);
189 | if (result == -1) {
190 | throw new EOFException();
191 | }
192 | pos = 0;
193 | end = result;
194 | }
195 | }
196 |
197 |
--------------------------------------------------------------------------------
/XDroidCacheExample/src/com/xdroid/cache/example/fragment/CharsetFragment.java:
--------------------------------------------------------------------------------
1 | package com.xdroid.cache.example.fragment;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONException;
5 | import org.json.JSONObject;
6 |
7 | import com.xdroid.cache.SecondLevelCacheKit;
8 | import com.xdroid.cache.example.R;
9 | import com.xdroid.cache.interfaces.TimeUnit;
10 |
11 | import android.os.Bundle;
12 | import android.support.v4.app.Fragment;
13 | import android.text.TextUtils;
14 | import android.view.LayoutInflater;
15 | import android.view.View;
16 | import android.view.View.OnClickListener;
17 | import android.view.ViewGroup;
18 | import android.widget.EditText;
19 | import android.widget.TextView;
20 | import android.widget.Toast;
21 |
22 | public class CharsetFragment extends Fragment {
23 |
24 | private EditText mCacheValueEditText;
25 | private EditText mCacheTimeEditText;
26 | private TextView mTipsTextView;
27 |
28 | @Override
29 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
30 | View view = inflater.inflate(R.layout.fragment_charset, container, false);
31 | init(view);
32 | return view;
33 |
34 | }
35 |
36 | private void init(View view) {
37 | initView(view);
38 | }
39 |
40 | private void initView(View view) {
41 | mCacheValueEditText = (EditText) view.findViewById(R.id.et_cache_value);
42 | mCacheTimeEditText = (EditText) view.findViewById(R.id.et_cache_time);
43 | mTipsTextView = (TextView) view.findViewById(R.id.tv_tips);
44 |
45 | initStringCache(view);
46 | initJsonObjectCache(view);
47 | initJsonArrayCache(view);
48 | initByteArrayCache(view);
49 | }
50 |
51 | /**
52 | * String 缓存
53 | * @param view
54 | */
55 | private void initStringCache(View view) {
56 | view.findViewById(R.id.btn_string_save).setOnClickListener(new OnClickListener() {
57 |
58 | @Override
59 | public void onClick(View v) {
60 | String cacheValue = mCacheValueEditText.getText().toString().trim();
61 | int cacheTime = 0;
62 | if (!TextUtils.isEmpty(cacheValue)) {
63 | //如果缓存时间为空,那么不设置缓存时间(永久缓存)
64 | if (!TextUtils.isEmpty(mCacheTimeEditText.getText().toString().trim())) {
65 | cacheTime = Integer.parseInt(mCacheTimeEditText.getText().toString().trim());
66 | //存储key为”key_string“,值为EdiText输入内容
67 | SecondLevelCacheKit.getInstance(getActivity()).put("key_string", cacheValue, cacheTime, TimeUnit.SECOND);
68 | Toast.makeText(getActivity(), "缓存成功,缓存时间:"+cacheTime+"秒", Toast.LENGTH_SHORT).show();
69 | }else {
70 | SecondLevelCacheKit.getInstance(getActivity()).put("key_string", cacheValue);
71 | Toast.makeText(getActivity(), "缓存成功,永久缓存", Toast.LENGTH_SHORT).show();
72 | }
73 | }
74 |
75 | }
76 | });
77 | view.findViewById(R.id.btn_string_read).setOnClickListener(new OnClickListener() {
78 |
79 | @Override
80 | public void onClick(View v) {
81 | String result = SecondLevelCacheKit.getInstance(getActivity()).getAsString("key_string");
82 | if (TextUtils.isEmpty(result)) {
83 | mTipsTextView.setText("未查找到缓存数据");
84 | }else {
85 | mTipsTextView.setText("Key:key_string Value:"+result);
86 | }
87 | }
88 | });
89 | }
90 |
91 |
92 | /**
93 | * JSONObject 缓存
94 | * @param view
95 | */
96 | private void initJsonObjectCache(View view) {
97 | view.findViewById(R.id.btn_jso_save).setOnClickListener(new OnClickListener() {
98 |
99 | @Override
100 | public void onClick(View v) {
101 | String cacheValue = mCacheValueEditText.getText().toString().trim();
102 | int cacheTime = 0;
103 | if (!TextUtils.isEmpty(cacheValue)) {
104 |
105 | JSONObject jsonObject = null;
106 | try {
107 |
108 | if (cacheValue.startsWith("{")&& cacheValue.endsWith("}")) {
109 | jsonObject = new JSONObject(cacheValue);
110 | }else {
111 | Toast.makeText(getActivity(), "JSONObject格式需要存储值以\"{\"开头\"}\"结尾", Toast.LENGTH_SHORT).show();
112 | return;
113 | }
114 | } catch (JSONException e) {
115 | e.printStackTrace();
116 | }
117 |
118 | //如果缓存时间为空,那么不设置缓存时间(永久缓存)
119 | if (!TextUtils.isEmpty(mCacheTimeEditText.getText().toString().trim())) {
120 | cacheTime = Integer.parseInt(mCacheTimeEditText.getText().toString().trim());
121 | //存储key为”key_jso“,值为EdiText输入内容
122 | SecondLevelCacheKit.getInstance(getActivity()).put("key_jso", jsonObject, cacheTime, TimeUnit.SECOND);
123 | Toast.makeText(getActivity(), "缓存成功,缓存时间:"+cacheTime+"秒", Toast.LENGTH_SHORT).show();
124 | }else {
125 | SecondLevelCacheKit.getInstance(getActivity()).put("key_jso", jsonObject);
126 | Toast.makeText(getActivity(), "缓存成功,永久缓存", Toast.LENGTH_SHORT).show();
127 | }
128 | }
129 | }
130 | });
131 | view.findViewById(R.id.btn_jso_read).setOnClickListener(new OnClickListener() {
132 |
133 | @Override
134 | public void onClick(View v) {
135 | JSONObject result = SecondLevelCacheKit.getInstance(getActivity()).getAsJSONObject("key_jso");
136 | if (result==null||TextUtils.isEmpty(result.toString())) {
137 | mTipsTextView.setText("未查找到缓存数据");
138 | }else {
139 | mTipsTextView.setText("Key:key_jso Value:"+result.toString());
140 | }
141 | }
142 | });
143 |
144 | }
145 |
146 |
147 | /**
148 | * JSONArray 缓存
149 | * @param view
150 | */
151 | private void initJsonArrayCache(View view) {
152 | view.findViewById(R.id.btn_jsa_save).setOnClickListener(new OnClickListener() {
153 |
154 | @Override
155 | public void onClick(View v) {
156 | String cacheValue = mCacheValueEditText.getText().toString().trim();
157 | int cacheTime = 0;
158 | if (!TextUtils.isEmpty(cacheValue)) {
159 |
160 | JSONArray jsonArray = null;
161 | try {
162 | if (cacheValue.startsWith("[")&& cacheValue.endsWith("]")) {
163 | jsonArray = new JSONArray(cacheValue);
164 | }else {
165 | Toast.makeText(getActivity(), "JSONArray格式需要存储值以\"[\"开头\"]\"结尾", Toast.LENGTH_SHORT).show();
166 | return;
167 | }
168 |
169 | } catch (JSONException e) {
170 | e.printStackTrace();
171 | }
172 |
173 | //如果缓存时间为空,那么不设置缓存时间(永久缓存)
174 | if (!TextUtils.isEmpty(mCacheTimeEditText.getText().toString().trim())) {
175 | cacheTime = Integer.parseInt(mCacheTimeEditText.getText().toString().trim());
176 | //存储key为”key_jsa“,值为EdiText输入内容
177 | SecondLevelCacheKit.getInstance(getActivity()).put("key_jsa", jsonArray, cacheTime, TimeUnit.SECOND);
178 | Toast.makeText(getActivity(), "缓存成功,缓存时间:"+cacheTime+"秒", Toast.LENGTH_SHORT).show();
179 | }else {
180 | SecondLevelCacheKit.getInstance(getActivity()).put("key_jsa", jsonArray);
181 | Toast.makeText(getActivity(), "缓存成功,永久缓存", Toast.LENGTH_SHORT).show();
182 | }
183 | }
184 | }
185 | });
186 | view.findViewById(R.id.btn_jsa_read).setOnClickListener(new OnClickListener() {
187 |
188 | @Override
189 | public void onClick(View v) {
190 | JSONArray result = SecondLevelCacheKit.getInstance(getActivity()).getAsJSONArray("key_jsa");
191 | if (result==null||TextUtils.isEmpty(result.toString())) {
192 | mTipsTextView.setText("未查找到缓存数据");
193 | }else {
194 | mTipsTextView.setText("Key:key_jsa Value:"+result.toString());
195 | }
196 | }
197 | });
198 |
199 | }
200 |
201 |
202 | /**
203 | * ByteArray 缓存
204 | * @param view
205 | */
206 | private void initByteArrayCache(View view) {
207 | view.findViewById(R.id.btn_byte_save).setOnClickListener(new OnClickListener() {
208 |
209 | @Override
210 | public void onClick(View v) {
211 | String cacheValue = mCacheValueEditText.getText().toString().trim();
212 | int cacheTime = 0;
213 | if (!TextUtils.isEmpty(cacheValue)) {
214 | //如果缓存时间为空,那么不设置缓存时间(永久缓存)
215 | if (!TextUtils.isEmpty(mCacheTimeEditText.getText().toString().trim())) {
216 | cacheTime = Integer.parseInt(mCacheTimeEditText.getText().toString().trim());
217 | //存储key为”key_byte“,值为EdiText输入内容
218 | SecondLevelCacheKit.getInstance(getActivity()).put("key_byte", cacheValue.getBytes(), cacheTime, TimeUnit.SECOND);
219 | Toast.makeText(getActivity(), "缓存成功,缓存时间:"+cacheTime+"秒", Toast.LENGTH_SHORT).show();
220 | }else {
221 | SecondLevelCacheKit.getInstance(getActivity()).put("key_byte", cacheValue.getBytes());
222 | Toast.makeText(getActivity(), "缓存成功,永久缓存", Toast.LENGTH_SHORT).show();
223 | }
224 |
225 | }
226 |
227 | }
228 | });
229 | view.findViewById(R.id.btn_byte_read).setOnClickListener(new OnClickListener() {
230 |
231 | @Override
232 | public void onClick(View v) {
233 | byte[] result = SecondLevelCacheKit.getInstance(getActivity()).getAsBytes("key_byte");
234 | if (result==null||TextUtils.isEmpty(new String(result))) {
235 | mTipsTextView.setText("未查找到缓存数据");
236 | }else {
237 | mTipsTextView.setText("Key:key_byte Value:"+new String(result));
238 | }
239 | }
240 | });
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/XDroidCache/src/com/xdroid/cache/SecondLevelCacheKit.java:
--------------------------------------------------------------------------------
1 | package com.xdroid.cache;
2 |
3 | import java.io.Serializable;
4 |
5 | import org.json.JSONArray;
6 | import org.json.JSONObject;
7 |
8 | import com.xdroid.cache.interfaces.ICache;
9 | import com.xdroid.cache.manage.DiskCacheManager;
10 | import com.xdroid.cache.manage.MemoryCacheManager;
11 |
12 | import android.content.Context;
13 | import android.graphics.Bitmap;
14 | import android.graphics.drawable.Drawable;
15 | import android.util.Log;
16 |
17 | /**
18 | * The second level cache tool
19 | *
20 | * @author Robin
21 | * @since 2015-10-14 19:06:11
22 | *
23 | */
24 | public class SecondLevelCacheKit implements ICache {
25 |
26 | private static final String TAG = "system.out";
27 |
28 | private static Context mContext;
29 |
30 | private static DiskCacheManager mDdiskCacheManager;
31 |
32 | private static volatile SecondLevelCacheKit INSTANCE = null;
33 |
34 | public static SecondLevelCacheKit getInstance(Context context) {
35 | if (INSTANCE == null) {
36 | synchronized (SecondLevelCacheKit.class) {
37 | if (INSTANCE == null) {
38 | INSTANCE = new SecondLevelCacheKit();
39 | }
40 | }
41 | mContext = context.getApplicationContext();
42 | mDdiskCacheManager = DiskCacheManager.getInstance(mContext);
43 | }
44 | return INSTANCE;
45 | }
46 |
47 | /*----------------------------------------------------String-----------------------------------------------------*/
48 |
49 | @Override
50 | public void put(String key, String value) {
51 | // Memory
52 | @SuppressWarnings("unchecked")
53 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
54 | .getInstance();
55 | memoryCacheManager.put(key, value);
56 | Log.i(TAG, "Memory cache insert success");
57 |
58 | // Disk
59 | mDdiskCacheManager.put(key, value);
60 | Log.i(TAG, "Disk cache insert success");
61 | }
62 |
63 | @Override
64 | public void put(String key, String value, int cacheTime, int timeUnit) {
65 | // Memory
66 | @SuppressWarnings("unchecked")
67 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
68 | .getInstance();
69 | memoryCacheManager.put(key, value, cacheTime, timeUnit);
70 | Log.i(TAG, "Memory cache insert success");
71 |
72 | // Disk
73 | mDdiskCacheManager.put(key, value, cacheTime, timeUnit);
74 | Log.i(TAG, "Disk cache insert success");
75 | }
76 |
77 | @Override
78 | public String getAsString(String key) {
79 | // Memory
80 | @SuppressWarnings("unchecked")
81 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
82 | .getInstance();
83 | String result = memoryCacheManager.getAsString(key);
84 | if (result != null) {
85 | return result;
86 | }
87 |
88 | // Disk
89 | result = mDdiskCacheManager.getAsString(key);
90 |
91 | return result;
92 | }
93 |
94 | /*----------------------------------------------------JSONObject-----------------------------------------------------*/
95 |
96 | @Override
97 | public void put(String key, JSONObject jsonObject) {
98 | // Memory
99 | @SuppressWarnings("unchecked")
100 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
101 | .getInstance();
102 | memoryCacheManager.put(key, jsonObject);
103 | Log.i(TAG, "Memory cache insert success");
104 |
105 | // Disk
106 | mDdiskCacheManager.put(key, jsonObject);
107 | Log.i(TAG, "Disk cache insert success");
108 | }
109 |
110 | @Override
111 | public void put(String key, JSONObject jsonObject, int cacheTime, int timeUnit) {
112 | // Memory
113 | @SuppressWarnings("unchecked")
114 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
115 | .getInstance();
116 | memoryCacheManager.put(key, jsonObject, cacheTime, timeUnit);
117 | Log.i(TAG, "Memory cache insert success");
118 |
119 | // Disk
120 | mDdiskCacheManager.put(key, jsonObject, cacheTime, timeUnit);
121 | Log.i(TAG, "Disk cache insert success");
122 |
123 | }
124 |
125 | @Override
126 | public JSONObject getAsJSONObject(String key) {
127 | // Memory
128 | @SuppressWarnings("unchecked")
129 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
130 | .getInstance();
131 | JSONObject result = memoryCacheManager.getAsJSONObject(key);
132 | if (result != null) {
133 | return result;
134 | }
135 |
136 | // Disk
137 | result = mDdiskCacheManager.getAsJSONObject(key);
138 | return result;
139 | }
140 |
141 | /*----------------------------------------------------JSONArray-----------------------------------------------------*/
142 |
143 | @Override
144 | public void put(String key, JSONArray jsonArray) {
145 | // Memory
146 | @SuppressWarnings("unchecked")
147 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
148 | .getInstance();
149 | memoryCacheManager.put(key, jsonArray);
150 | Log.i(TAG, "Memory cache insert success");
151 |
152 | // Disk
153 | mDdiskCacheManager.put(key, jsonArray);
154 | Log.i(TAG, "Disk cache insert success");
155 | }
156 |
157 | @Override
158 | public void put(String key, JSONArray jsonArray, int cacheTime, int timeUnit) {
159 | // Memory
160 | @SuppressWarnings("unchecked")
161 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
162 | .getInstance();
163 | memoryCacheManager.put(key, jsonArray, cacheTime, timeUnit);
164 | Log.i(TAG, "Memory cache insert success");
165 |
166 | // Disk
167 | mDdiskCacheManager.put(key, jsonArray, cacheTime, timeUnit);
168 | Log.i(TAG, "Disk cache insert success");
169 | }
170 |
171 | @Override
172 | public JSONArray getAsJSONArray(String key) {
173 | // Memory
174 | @SuppressWarnings("unchecked")
175 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
176 | .getInstance();
177 | JSONArray result = memoryCacheManager.getAsJSONArray(key);
178 | if (result != null) {
179 | return result;
180 | }
181 |
182 | // Disk
183 | result = mDdiskCacheManager.getAsJSONArray(key);
184 | return result;
185 | }
186 |
187 | /*----------------------------------------------------byte[ ]-----------------------------------------------------*/
188 |
189 | @Override
190 | public void put(String key, byte[] value) {
191 | // Memory
192 | @SuppressWarnings("unchecked")
193 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
194 | .getInstance();
195 | memoryCacheManager.put(key, value);
196 | Log.i(TAG, "Memory cache insert success");
197 |
198 | // Disk
199 | mDdiskCacheManager.put(key, value);
200 | Log.i(TAG, "Disk cache insert success");
201 | }
202 |
203 | @Override
204 | public void put(String key, byte[] value, int cacheTime, int timeUnit) {
205 | // Memory
206 | @SuppressWarnings("unchecked")
207 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
208 | .getInstance();
209 | memoryCacheManager.put(key, value, cacheTime, timeUnit);
210 | Log.i(TAG, "Memory cache insert success");
211 |
212 | // Disk
213 | mDdiskCacheManager.put(key, value, cacheTime, timeUnit);
214 | Log.i(TAG, "Disk cache insert success");
215 | }
216 |
217 | @Override
218 | public byte[] getAsBytes(String key) {
219 | // Memory
220 | @SuppressWarnings("unchecked")
221 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
222 | .getInstance();
223 | byte[] result = memoryCacheManager.getAsBytes(key);
224 | if (result != null) {
225 | return result;
226 | }
227 |
228 | // Disk
229 | result = mDdiskCacheManager.getAsBytes(key);
230 | return result;
231 | }
232 |
233 | /*----------------------------------------------------Serializable-----------------------------------------------------*/
234 |
235 | @Override
236 | public void put(String key, Serializable value) {
237 | // Memory
238 | @SuppressWarnings("unchecked")
239 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
240 | .getInstance();
241 | memoryCacheManager.put(key, value);
242 | Log.i(TAG, "Memory cache insert success");
243 |
244 | // Disk
245 | mDdiskCacheManager.put(key, value);
246 | Log.i(TAG, "Disk cache insert success");
247 | }
248 |
249 | @Override
250 | public void put(String key, Serializable value, int cacheTime, int timeUnit) {
251 | // Memory
252 | @SuppressWarnings("unchecked")
253 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
254 | .getInstance();
255 | memoryCacheManager.put(key, value, cacheTime, timeUnit);
256 | Log.i(TAG, "Memory cache insert success");
257 |
258 | // Disk
259 | mDdiskCacheManager.put(key, value, cacheTime, timeUnit);
260 | Log.i(TAG, "Disk cache insert success");
261 | }
262 |
263 | @SuppressWarnings("unchecked")
264 | @Override
265 | public T getAsSerializable(String key) {
266 | // Memory
267 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
268 | .getInstance();
269 | Serializable result = memoryCacheManager.getAsSerializable(key);
270 | if (result != null) {
271 | return (T) result;
272 | }
273 |
274 | // Disk
275 | result = mDdiskCacheManager.getAsSerializable(key);
276 | return (T) result;
277 | }
278 |
279 | /*----------------------------------------------------Bitmap-----------------------------------------------------*/
280 |
281 | @Override
282 | public void put(String key, Bitmap bitmap) {
283 | // Memory
284 | @SuppressWarnings("unchecked")
285 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
286 | .getInstance();
287 | memoryCacheManager.put(key, bitmap);
288 | Log.i(TAG, "Memory cache insert success");
289 |
290 | // Disk
291 | mDdiskCacheManager.put(key, bitmap);
292 | Log.i(TAG, "Disk cache insert success");
293 | }
294 |
295 | @Override
296 | public void put(String key, Bitmap bitmap, int cacheTime, int timeUnit) {
297 | // Memory
298 | @SuppressWarnings("unchecked")
299 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
300 | .getInstance();
301 | memoryCacheManager.put(key, bitmap, cacheTime, timeUnit);
302 | Log.i(TAG, "Memory cache insert success");
303 |
304 | // Disk
305 | mDdiskCacheManager.put(key, bitmap, cacheTime, timeUnit);
306 | Log.i(TAG, "Disk cache insert success");
307 |
308 | }
309 |
310 | @Override
311 | public Bitmap getAsBitmap(String key) {
312 | // Memory
313 | @SuppressWarnings("unchecked")
314 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
315 | .getInstance();
316 | Bitmap result = memoryCacheManager.getAsBitmap(key);
317 | if (result != null) {
318 | return result;
319 | }
320 |
321 | // Disk
322 | result = mDdiskCacheManager.getAsBitmap(key);
323 | return result;
324 | }
325 |
326 | /*----------------------------------------------------Drawable-----------------------------------------------------*/
327 |
328 | @Override
329 | public void put(String key, Drawable value) {
330 | // Memory
331 | @SuppressWarnings("unchecked")
332 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
333 | .getInstance();
334 | memoryCacheManager.put(key, value);
335 | Log.i(TAG, "Memory cache insert success");
336 |
337 | // Disk
338 | mDdiskCacheManager.put(key, value);
339 | Log.i(TAG, "Disk cache insert success");
340 | }
341 |
342 | @Override
343 | public void put(String key, Drawable value, int cacheTime, int timeUnit) {
344 | // Memory
345 | @SuppressWarnings("unchecked")
346 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
347 | .getInstance();
348 | memoryCacheManager.put(key, value, cacheTime, timeUnit);
349 | Log.i(TAG, "Memory cache insert success");
350 |
351 | // Disk
352 | mDdiskCacheManager.put(key, value, cacheTime, timeUnit);
353 | Log.i(TAG, "Disk cache insert success");
354 | }
355 |
356 | @Override
357 | public Drawable getAsDrawable(String key) {
358 | // Memory
359 | @SuppressWarnings("unchecked")
360 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
361 | .getInstance();
362 | Drawable result = memoryCacheManager.getAsDrawable(key);
363 | if (result != null) {
364 | return result;
365 | }
366 |
367 | // Disk
368 | result = mDdiskCacheManager.getAsDrawable(key);
369 | return result;
370 | }
371 |
372 | @Override
373 | public boolean remove(String key) {
374 | // Memory
375 | @SuppressWarnings("unchecked")
376 | MemoryCacheManager memoryCacheManager = (MemoryCacheManager) MemoryCacheManager
377 | .getInstance();
378 | memoryCacheManager.remove(key);
379 |
380 | // Disk
381 | return mDdiskCacheManager.remove(key);
382 | }
383 |
384 | }
385 |
--------------------------------------------------------------------------------
/XDroidCache/src/com/xdroid/cache/manage/MemoryCacheManager.java:
--------------------------------------------------------------------------------
1 | package com.xdroid.cache.manage;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.ByteArrayOutputStream;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.io.ObjectInputStream;
8 | import java.io.ObjectOutputStream;
9 | import java.io.Serializable;
10 |
11 | import org.json.JSONArray;
12 | import org.json.JSONException;
13 | import org.json.JSONObject;
14 |
15 | import com.xdroid.cache.interfaces.ICache;
16 | import com.xdroid.cache.memory.LruCache;
17 | import com.xdroid.cache.utils.CacheHelper;
18 | import com.xdroid.cache.utils.ImageHelper;
19 |
20 | import android.graphics.Bitmap;
21 | import android.graphics.drawable.Drawable;
22 | import android.util.Log;
23 |
24 | /**
25 | * Manage memory cache
26 | *
27 | * @author Robin
28 | * @since 2015-05-07 23:18:22
29 | * @param
30 | * @param
31 | */
32 | public class MemoryCacheManager implements ICache {
33 |
34 | private static final String TAG = "system.out";
35 |
36 | public static MemoryCacheManager, ?> mMemoryCacheManager;
37 |
38 | private LruCache mMemoryCache;
39 |
40 | private int cacheSize = (int) Runtime.getRuntime().maxMemory() / 8;
41 |
42 | /*
43 | * ====================================================================
44 | * Constructor
45 | * ====================================================================
46 | */
47 |
48 | public MemoryCacheManager() {
49 | if (mMemoryCache == null) {
50 | mMemoryCache = new LruCache(cacheSize) {
51 | @Override
52 | protected int sizeOf(KeyType key, ValueType value) {
53 | return super.sizeOf(key, value);
54 | }
55 | };
56 | }
57 | }
58 |
59 | public static MemoryCacheManager, ?> getInstance() {
60 | if (mMemoryCacheManager == null) {
61 | mMemoryCacheManager = new MemoryCacheManager();
62 | }
63 | return mMemoryCacheManager;
64 | }
65 |
66 | /*
67 | * ========================================================================
68 | * Public Method
69 | * ========================================================================
70 | */
71 |
72 | /**
73 | * Set the data to cache
74 | *
75 | * @param key
76 | * @param value
77 | */
78 | public void putDataToMemory(KeyType key, ValueType value) {
79 | if (value instanceof String) {
80 |
81 | } else if (value instanceof JSONObject) {
82 |
83 | }
84 | mMemoryCache.put(key, value);
85 | }
86 |
87 | /**
88 | * Read the data from cache
89 | *
90 | * @param key
91 | * @return
92 | */
93 | public ValueType getDataFromMemory(KeyType key) {
94 | ValueType valueType = mMemoryCache.get(key);
95 | if (valueType == null) {
96 | Log.e(TAG, "Not find entry from memory");
97 | }
98 | return valueType;
99 | }
100 |
101 | /**
102 | * Remove the data from cache
103 | *
104 | * @param key
105 | */
106 | public void removeDataFromMemory(KeyType key) {
107 | if (getDataFromMemory(key) != null) {
108 | mMemoryCache.remove(key);
109 | }
110 | }
111 |
112 | /*
113 | * ========================================================================
114 | * Override ICache
115 | * ========================================================================
116 | */
117 |
118 | /*----------------------------------------------------String-----------------------------------------------------*/
119 |
120 | @SuppressWarnings("unchecked")
121 | @Override
122 | public void put(String key, String value) {
123 | ((MemoryCacheManager) getInstance()).putDataToMemory(key, value);
124 | }
125 |
126 | @Override
127 | public void put(String key, String value, int cacheTime, int timeUnit) {
128 | int expirationTime = CacheHelper.calculateCacheTime(cacheTime, timeUnit);
129 | put(key, CacheHelper.convertStringWithDate(expirationTime, value));
130 | }
131 |
132 | @SuppressWarnings("unchecked")
133 | @Override
134 | public String getAsString(String key) {
135 | String result = ((MemoryCacheManager) getInstance()).getDataFromMemory(key);
136 | // If the memory cache exists already
137 | return checkCacheExpiredWithString(key, result);
138 | }
139 |
140 | /*----------------------------------------------------JSONObject-----------------------------------------------------*/
141 |
142 | @SuppressWarnings("unchecked")
143 | @Override
144 | public void put(String key, JSONObject jsonObject) {
145 | ((MemoryCacheManager) getInstance()).putDataToMemory(key, jsonObject.toString());
146 | }
147 |
148 | @Override
149 | public void put(String key, JSONObject jsonObject, int cacheTime, int timeUnit) {
150 | int expirationTime = CacheHelper.calculateCacheTime(cacheTime, timeUnit);
151 | put(key, CacheHelper.convertStringWithDate(expirationTime, jsonObject.toString()));
152 | }
153 |
154 | @SuppressWarnings("unchecked")
155 | @Override
156 | public JSONObject getAsJSONObject(String key) {
157 | String result = ((MemoryCacheManager) getInstance()).getDataFromMemory(key);
158 | String jsonStr = checkCacheExpiredWithString(key, result);
159 | if (jsonStr != null) {
160 | try {
161 | JSONObject jsonObject = new JSONObject(jsonStr);
162 | return jsonObject;
163 | } catch (JSONException e) {
164 | e.printStackTrace();
165 | return null;
166 | }
167 | }
168 | return null;
169 | }
170 |
171 | /*----------------------------------------------------JSONArray-----------------------------------------------------*/
172 |
173 | @SuppressWarnings("unchecked")
174 | @Override
175 | public void put(String key, JSONArray jsonArray) {
176 | ((MemoryCacheManager) getInstance()).putDataToMemory(key, jsonArray.toString());
177 | }
178 |
179 | @Override
180 | public void put(String key, JSONArray jsonArray, int cacheTime, int timeUnit) {
181 | int expirationTime = CacheHelper.calculateCacheTime(cacheTime, timeUnit);
182 | put(key, CacheHelper.convertStringWithDate(expirationTime, jsonArray.toString()));
183 | }
184 |
185 | @Override
186 | public JSONArray getAsJSONArray(String key) {
187 | @SuppressWarnings("unchecked")
188 | String result = ((MemoryCacheManager) getInstance()).getDataFromMemory(key);
189 | String jsonStr = checkCacheExpiredWithString(key, result);
190 | if (jsonStr != null) {
191 | try {
192 | JSONArray jsonArray = new JSONArray(jsonStr);
193 | return jsonArray;
194 | } catch (JSONException e) {
195 | e.printStackTrace();
196 | return null;
197 | }
198 | }
199 | return null;
200 | }
201 |
202 | /*----------------------------------------------------byte[ ]-----------------------------------------------------*/
203 | @SuppressWarnings("unchecked")
204 | @Override
205 | public void put(String key, byte[] value) {
206 | ((MemoryCacheManager) getInstance()).putDataToMemory(key, value);
207 | }
208 |
209 | @Override
210 | public void put(String key, byte[] value, int cacheTime, int timeUnit) {
211 | int expirationTime = CacheHelper.calculateCacheTime(cacheTime, timeUnit);
212 | put(key, CacheHelper.convertByteArrayWithDate(expirationTime, value));
213 | }
214 |
215 | @Override
216 | public byte[] getAsBytes(String key) {
217 | @SuppressWarnings("unchecked")
218 | byte[] result = ((MemoryCacheManager) getInstance()).getDataFromMemory(key);
219 | return checkCacheExpiredWithByte(key, result);
220 | }
221 |
222 | /*----------------------------------------------------Serializable-----------------------------------------------------*/
223 |
224 | @SuppressWarnings("unchecked")
225 | @Override
226 | public void put(String key, Serializable value) {
227 | ByteArrayOutputStream baos = null;
228 | ObjectOutputStream oos = null;
229 | try {
230 | baos = new ByteArrayOutputStream();
231 | oos = new ObjectOutputStream(baos);
232 | oos.writeObject(value);
233 | byte[] data = baos.toByteArray();
234 |
235 | ((MemoryCacheManager) getInstance()).putDataToMemory(key, data);
236 | } catch (Exception e) {
237 | e.printStackTrace();
238 | } finally {
239 | try {
240 | oos.close();
241 | } catch (IOException e) {
242 | }
243 | }
244 |
245 | }
246 |
247 | @SuppressWarnings("unchecked")
248 | @Override
249 | public void put(String key, Serializable value, int cacheTime, int timeUnit) {
250 | ByteArrayOutputStream baos = null;
251 | ObjectOutputStream oos = null;
252 | try {
253 | baos = new ByteArrayOutputStream();
254 | oos = new ObjectOutputStream(baos);
255 | oos.writeObject(value);
256 | byte[] data = baos.toByteArray();
257 |
258 | int expirationTime = CacheHelper.calculateCacheTime(cacheTime, timeUnit);
259 | ((MemoryCacheManager) getInstance()).putDataToMemory(key,
260 | CacheHelper.convertByteArrayWithDate(expirationTime, data));
261 | } catch (Exception e) {
262 | e.printStackTrace();
263 | } finally {
264 | try {
265 | oos.close();
266 | } catch (IOException e) {
267 | }
268 | }
269 |
270 | }
271 |
272 | @SuppressWarnings("unchecked")
273 | @Override
274 | public T getAsSerializable(String key) {
275 | T t = null;
276 |
277 | byte[] bs = ((MemoryCacheManager) getInstance()).getDataFromMemory(key);
278 | byte[] data = checkCacheExpiredWithByte(key, bs);
279 | if (data == null) {
280 | return null;
281 | }
282 | InputStream is = new ByteArrayInputStream(data);
283 |
284 | ObjectInputStream ois = null;
285 | try {
286 | ois = new ObjectInputStream(is);
287 | t = (T) ois.readObject();
288 | } catch (ClassNotFoundException e) {
289 | e.printStackTrace();
290 | } catch (IOException e) {
291 | e.printStackTrace();
292 | } finally {
293 | try {
294 | if (ois != null)
295 | ois.close();
296 | } catch (IOException e) {
297 | e.printStackTrace();
298 | }
299 | }
300 | return t;
301 | }
302 |
303 | /*----------------------------------------------------Bitmap-----------------------------------------------------*/
304 |
305 | @SuppressWarnings("unchecked")
306 | @Override
307 | public void put(String key, Bitmap bitmap) {
308 | ((MemoryCacheManager) getInstance()).putDataToMemory(key, ImageHelper.bitmap2Bytes(bitmap));
309 | }
310 |
311 | @SuppressWarnings("unchecked")
312 | @Override
313 | public void put(String key, Bitmap bitmap, int cacheTime, int timeUnit) {
314 | int expirationTime = CacheHelper.calculateCacheTime(cacheTime, timeUnit);
315 | ((MemoryCacheManager) getInstance()).putDataToMemory(key, CacheHelper.convertByteArrayWithDate(expirationTime, ImageHelper.bitmap2Bytes(bitmap)));
316 | }
317 |
318 | @Override
319 | public Bitmap getAsBitmap(String key) {
320 | @SuppressWarnings("unchecked")
321 | byte[] bs = ((MemoryCacheManager) getInstance()).getDataFromMemory(key);
322 | byte[] bytes = checkCacheExpiredWithByte(key, bs);
323 | if (bytes == null) {
324 | return null;
325 | }else {
326 | return ImageHelper.bytes2Bitmap(bytes);
327 | }
328 |
329 | }
330 |
331 | /*----------------------------------------------------Drawable-----------------------------------------------------*/
332 |
333 | @Override
334 | public void put(String key, Drawable value) {
335 | put(key, ImageHelper.drawable2Bitmap(value));
336 | }
337 |
338 | @Override
339 | public void put(String key, Drawable value, int cacheTime, int timeUnit) {
340 | put(key, ImageHelper.drawable2Bitmap(value), cacheTime, timeUnit);
341 | }
342 |
343 | @Override
344 | public Drawable getAsDrawable(String key) {
345 | @SuppressWarnings("unchecked")
346 | byte[] bs = ((MemoryCacheManager) getInstance()).getDataFromMemory(key);
347 | byte[] bytes = checkCacheExpiredWithByte(key, bs);
348 | if (bytes == null) {
349 | return null;
350 | }else {
351 | Bitmap bitmap = ImageHelper.bytes2Bitmap(bytes);
352 | return bitmap == null ? null : ImageHelper.bitmap2Drawable(bitmap);
353 | }
354 |
355 | }
356 |
357 |
358 | @SuppressWarnings("unchecked")
359 | @Override
360 | public boolean remove(String key) {
361 | ((MemoryCacheManager) getInstance()).removeDataFromMemory(key);
362 | Log.i(TAG, "Memory cache delete success");
363 | return true;
364 | }
365 |
366 | private String checkCacheExpiredWithString(String key, String result) {
367 | if (result != null) {
368 | if (!CacheHelper.isExpired(result)) { // unexpired
369 | String originalResult = CacheHelper.clearDateInfo(result);
370 | Log.i(TAG, "Memory cache hint :" + originalResult + "---->time interval:"
371 | + CacheHelper.getCacheTimeInterval(result) / 1000 + " s");
372 | return originalResult;
373 | } else {
374 | Log.i(TAG, "Memory cache expired:" + "---->time interval:"
375 | + CacheHelper.getCacheTimeInterval(result) / 1000 + " s");
376 | remove(key);
377 | return null;
378 | }
379 | }else {
380 | return result;
381 | }
382 |
383 | }
384 |
385 | private byte[] checkCacheExpiredWithByte(String key, byte[] result) {
386 | if (result != null) {
387 | if (!CacheHelper.isExpired(result)) { // unexpired
388 | byte[] originalResult = CacheHelper.clearDateInfo(result);
389 | Log.i(TAG, "Memory cache hint :" + originalResult + "---->time interval:"
390 | + CacheHelper.getCacheTimeInterval(result) / 1000 + " s");
391 | return originalResult;
392 | } else {
393 | Log.i(TAG, "Memory cache expired:" + "---->time interval:"
394 | + CacheHelper.getCacheTimeInterval(result) / 1000 + " s");
395 | remove(key);
396 | return null;
397 | }
398 | }else {
399 | return result;
400 | }
401 |
402 | }
403 |
404 | }
405 |
--------------------------------------------------------------------------------
/XDroidCache/src/com/xdroid/cache/memory/LruCache.java:
--------------------------------------------------------------------------------
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,
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 | package com.xdroid.cache.memory;
18 |
19 | import java.util.LinkedHashMap;
20 | import java.util.Map;
21 |
22 | /**
23 | * BEGIN LAYOUTLIB CHANGE
24 | * This is a custom version that doesn't use the non standard LinkedHashMap#eldest.
25 | * END LAYOUTLIB CHANGE
26 | *
27 | * A cache that holds strong references to a limited number of values. Each time
28 | * a value is accessed, it is moved to the head of a queue. When a value is
29 | * added to a full cache, the value at the end of that queue is evicted and may
30 | * become eligible for garbage collection.
31 | *
32 | *
If your cached values hold resources that need to be explicitly released,
33 | * override {@link #entryRemoved}.
34 | *
35 | *
If a cache miss should be computed on demand for the corresponding keys,
36 | * override {@link #create}. This simplifies the calling code, allowing it to
37 | * assume a value will always be returned, even when there's a cache miss.
38 | *
39 | *
By default, the cache size is measured in the number of entries. Override
40 | * {@link #sizeOf} to size the cache in different units. For example, this cache
41 | * is limited to 4MiB of bitmaps:
42 | *
This class does not allow null to be used as a key or value. A return
59 | * value of null from {@link #get}, {@link #put} or {@link #remove} is
60 | * unambiguous: the key was not in the cache.
61 | *
62 | *
This class appeared in Android 3.1 (Honeycomb MR1); it's available as part
63 | * of Android's
64 | * Support Package for earlier releases.
65 | */
66 | public class LruCache {
67 | private final LinkedHashMap map;
68 |
69 | /** Size of this cache in units. Not necessarily the number of elements. */
70 | private int size;
71 | private int maxSize;
72 |
73 | private int putCount;
74 | private int createCount;
75 | private int evictionCount;
76 | private int hitCount;
77 | private int missCount;
78 |
79 | /**
80 | * @param maxSize for caches that do not override {@link #sizeOf}, this is
81 | * the maximum number of entries in the cache. For all other caches,
82 | * this is the maximum sum of the sizes of the entries in this cache.
83 | */
84 | public LruCache(int maxSize) {
85 | if (maxSize <= 0) {
86 | throw new IllegalArgumentException("maxSize <= 0");
87 | }
88 | this.maxSize = maxSize;
89 | this.map = new LinkedHashMap(0, 0.75f, true);
90 | }
91 |
92 | /**
93 | * Sets the size of the cache.
94 | * @param maxSize The new maximum size.
95 | *
96 | * @hide
97 | */
98 | public void resize(int maxSize) {
99 | if (maxSize <= 0) {
100 | throw new IllegalArgumentException("maxSize <= 0");
101 | }
102 |
103 | synchronized (this) {
104 | this.maxSize = maxSize;
105 | }
106 | trimToSize(maxSize);
107 | }
108 |
109 | /**
110 | * Returns the value for {@code key} if it exists in the cache or can be
111 | * created by {@code #create}. If a value was returned, it is moved to the
112 | * head of the queue. This returns null if a value is not cached and cannot
113 | * be created.
114 | */
115 | public final V get(K key) {
116 | if (key == null) {
117 | throw new NullPointerException("key == null");
118 | }
119 |
120 | V mapValue;
121 | synchronized (this) {
122 | mapValue = map.get(key);
123 | if (mapValue != null) {
124 | hitCount++;
125 | return mapValue;
126 | }
127 | missCount++;
128 | }
129 |
130 | /*
131 | * Attempt to create a value. This may take a long time, and the map
132 | * may be different when create() returns. If a conflicting value was
133 | * added to the map while create() was working, we leave that value in
134 | * the map and release the created value.
135 | */
136 |
137 | V createdValue = create(key);
138 | if (createdValue == null) {
139 | return null;
140 | }
141 |
142 | synchronized (this) {
143 | createCount++;
144 | mapValue = map.put(key, createdValue);
145 |
146 | if (mapValue != null) {
147 | // There was a conflict so undo that last put
148 | map.put(key, mapValue);
149 | } else {
150 | size += safeSizeOf(key, createdValue);
151 | }
152 | }
153 |
154 | if (mapValue != null) {
155 | entryRemoved(false, key, createdValue, mapValue);
156 | return mapValue;
157 | } else {
158 | trimToSize(maxSize);
159 | return createdValue;
160 | }
161 | }
162 |
163 | /**
164 | * Caches {@code value} for {@code key}. The value is moved to the head of
165 | * the queue.
166 | *
167 | * @return the previous value mapped by {@code key}.
168 | */
169 | public final V put(K key, V value) {
170 | if (key == null || value == null) {
171 | throw new NullPointerException("key == null || value == null");
172 | }
173 |
174 | V previous;
175 | synchronized (this) {
176 | putCount++;
177 | size += safeSizeOf(key, value);
178 | previous = map.put(key, value);
179 | if (previous != null) {
180 | size -= safeSizeOf(key, previous);
181 | }
182 | }
183 |
184 | if (previous != null) {
185 | entryRemoved(false, key, previous, value);
186 | }
187 |
188 | trimToSize(maxSize);
189 | return previous;
190 | }
191 |
192 | /**
193 | * @param maxSize the maximum size of the cache before returning. May be -1
194 | * to evict even 0-sized elements.
195 | */
196 | private void trimToSize(int maxSize) {
197 | while (true) {
198 | K key;
199 | V value;
200 | synchronized (this) {
201 | if (size < 0 || (map.isEmpty() && size != 0)) {
202 | throw new IllegalStateException(getClass().getName()
203 | + ".sizeOf() is reporting inconsistent results!");
204 | }
205 |
206 | if (size <= maxSize) {
207 | break;
208 | }
209 |
210 | // BEGIN LAYOUTLIB CHANGE
211 | // get the last item in the linked list.
212 | // This is not efficient, the goal here is to minimize the changes
213 | // compared to the platform version.
214 | Map.Entry toEvict = null;
215 | for (Map.Entry entry : map.entrySet()) {
216 | toEvict = entry;
217 | }
218 | // END LAYOUTLIB CHANGE
219 |
220 | if (toEvict == null) {
221 | break;
222 | }
223 |
224 | key = toEvict.getKey();
225 | value = toEvict.getValue();
226 | map.remove(key);
227 | size -= safeSizeOf(key, value);
228 | evictionCount++;
229 | }
230 |
231 | entryRemoved(true, key, value, null);
232 | }
233 | }
234 |
235 | /**
236 | * Removes the entry for {@code key} if it exists.
237 | *
238 | * @return the previous value mapped by {@code key}.
239 | */
240 | public final V remove(K key) {
241 | if (key == null) {
242 | throw new NullPointerException("key == null");
243 | }
244 |
245 | V previous;
246 | synchronized (this) {
247 | previous = map.remove(key);
248 | if (previous != null) {
249 | size -= safeSizeOf(key, previous);
250 | }
251 | }
252 |
253 | if (previous != null) {
254 | entryRemoved(false, key, previous, null);
255 | }
256 |
257 | return previous;
258 | }
259 |
260 | /**
261 | * Called for entries that have been evicted or removed. This method is
262 | * invoked when a value is evicted to make space, removed by a call to
263 | * {@link #remove}, or replaced by a call to {@link #put}. The default
264 | * implementation does nothing.
265 | *
266 | *
The method is called without synchronization: other threads may
267 | * access the cache while this method is executing.
268 | *
269 | * @param evicted true if the entry is being removed to make space, false
270 | * if the removal was caused by a {@link #put} or {@link #remove}.
271 | * @param newValue the new value for {@code key}, if it exists. If non-null,
272 | * this removal was caused by a {@link #put}. Otherwise it was caused by
273 | * an eviction or a {@link #remove}.
274 | */
275 | protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
276 |
277 | /**
278 | * Called after a cache miss to compute a value for the corresponding key.
279 | * Returns the computed value or null if no value can be computed. The
280 | * default implementation returns null.
281 | *
282 | *
The method is called without synchronization: other threads may
283 | * access the cache while this method is executing.
284 | *
285 | *
If a value for {@code key} exists in the cache when this method
286 | * returns, the created value will be released with {@link #entryRemoved}
287 | * and discarded. This can occur when multiple threads request the same key
288 | * at the same time (causing multiple values to be created), or when one
289 | * thread calls {@link #put} while another is creating a value for the same
290 | * key.
291 | */
292 | protected V create(K key) {
293 | return null;
294 | }
295 |
296 | private int safeSizeOf(K key, V value) {
297 | int result = sizeOf(key, value);
298 | if (result < 0) {
299 | throw new IllegalStateException("Negative size: " + key + "=" + value);
300 | }
301 | return result;
302 | }
303 |
304 | /**
305 | * Returns the size of the entry for {@code key} and {@code value} in
306 | * user-defined units. The default implementation returns 1 so that size
307 | * is the number of entries and max size is the maximum number of entries.
308 | *
309 | *
An entry's size must not change while it is in the cache.
310 | */
311 | protected int sizeOf(K key, V value) {
312 | return 1;
313 | }
314 |
315 | /**
316 | * Clear the cache, calling {@link #entryRemoved} on each removed entry.
317 | */
318 | public final void evictAll() {
319 | trimToSize(-1); // -1 will evict 0-sized elements
320 | }
321 |
322 | /**
323 | * For caches that do not override {@link #sizeOf}, this returns the number
324 | * of entries in the cache. For all other caches, this returns the sum of
325 | * the sizes of the entries in this cache.
326 | */
327 | public synchronized final int size() {
328 | return size;
329 | }
330 |
331 | /**
332 | * For caches that do not override {@link #sizeOf}, this returns the maximum
333 | * number of entries in the cache. For all other caches, this returns the
334 | * maximum sum of the sizes of the entries in this cache.
335 | */
336 | public synchronized final int maxSize() {
337 | return maxSize;
338 | }
339 |
340 | /**
341 | * Returns the number of times {@link #get} returned a value that was
342 | * already present in the cache.
343 | */
344 | public synchronized final int hitCount() {
345 | return hitCount;
346 | }
347 |
348 | /**
349 | * Returns the number of times {@link #get} returned null or required a new
350 | * value to be created.
351 | */
352 | public synchronized final int missCount() {
353 | return missCount;
354 | }
355 |
356 | /**
357 | * Returns the number of times {@link #create(Object)} returned a value.
358 | */
359 | public synchronized final int createCount() {
360 | return createCount;
361 | }
362 |
363 | /**
364 | * Returns the number of times {@link #put} was called.
365 | */
366 | public synchronized final int putCount() {
367 | return putCount;
368 | }
369 |
370 | /**
371 | * Returns the number of values that have been evicted.
372 | */
373 | public synchronized final int evictionCount() {
374 | return evictionCount;
375 | }
376 |
377 | /**
378 | * Returns a copy of the current contents of the cache, ordered from least
379 | * recently accessed to most recently accessed.
380 | */
381 | public synchronized final Map snapshot() {
382 | return new LinkedHashMap(map);
383 | }
384 |
385 | @Override public synchronized final String toString() {
386 | int accesses = hitCount + missCount;
387 | int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
388 | return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
389 | maxSize, hitCount, missCount, hitPercent);
390 | }
391 | }
392 |
--------------------------------------------------------------------------------
/XDroidCache/src/com/xdroid/cache/manage/DiskCacheManager.java:
--------------------------------------------------------------------------------
1 | package com.xdroid.cache.manage;
2 |
3 | import java.io.BufferedWriter;
4 | import java.io.ByteArrayInputStream;
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.File;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.io.ObjectInputStream;
10 | import java.io.ObjectOutputStream;
11 | import java.io.OutputStream;
12 | import java.io.OutputStreamWriter;
13 | import java.io.Serializable;
14 | import java.security.MessageDigest;
15 | import java.security.NoSuchAlgorithmException;
16 |
17 | import org.json.JSONArray;
18 | import org.json.JSONException;
19 | import org.json.JSONObject;
20 |
21 | import com.xdroid.cache.disk.DiskLruCache;
22 | import com.xdroid.cache.interfaces.ICache;
23 | import com.xdroid.cache.utils.CacheHelper;
24 | import com.xdroid.cache.utils.ImageHelper;
25 |
26 | import android.content.Context;
27 | import android.content.pm.PackageInfo;
28 | import android.content.pm.PackageManager.NameNotFoundException;
29 | import android.graphics.Bitmap;
30 | import android.graphics.drawable.Drawable;
31 | import android.os.Environment;
32 | import android.util.Log;
33 |
34 | /**
35 | * Manage the disk cache
36 | *
37 | * @author Robin
38 | * @since 2015-05-07 23:31:23
39 | */
40 | public class DiskCacheManager implements ICache {
41 |
42 | private static DiskCacheManager mCacheManager;
43 |
44 | private DiskLruCache mDiskLruCache;
45 |
46 | private static final int DEFAULT_VALUE_COUNT = 1;
47 |
48 | private static final int DEFAULT_MAX_SIZE = 10 * 1024 * 1024;
49 |
50 | private static final String TAG = "system.out";
51 |
52 | /*
53 | * ====================================================================
54 | * Constructor
55 | * ====================================================================
56 | */
57 |
58 | public DiskCacheManager(Context context) {
59 | init(context);
60 | }
61 |
62 | public static DiskCacheManager getInstance(Context context) {
63 | if (mCacheManager == null) {
64 | mCacheManager = new DiskCacheManager(context);
65 | }
66 | return mCacheManager;
67 | }
68 |
69 | private void init(Context context) {
70 | if (mDiskLruCache == null) {
71 | File cacheDir = getDiskCacheDir(context, "diskcache");
72 | if (!cacheDir.exists()) {
73 | cacheDir.mkdirs();
74 | }
75 | try {
76 | mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), DEFAULT_VALUE_COUNT,
77 | DEFAULT_MAX_SIZE);
78 | } catch (IOException e) {
79 | e.printStackTrace();
80 | }
81 | }
82 |
83 | }
84 |
85 | /*
86 | * ====================================================================
87 | * Public Method
88 | * ====================================================================
89 | */
90 |
91 | public void close() throws IOException {
92 | mDiskLruCache.close();
93 | }
94 |
95 | public void delete() throws IOException {
96 | mDiskLruCache.delete();
97 | }
98 |
99 | public void flush() throws IOException {
100 | mDiskLruCache.flush();
101 | }
102 |
103 | public boolean isClosed() {
104 | return mDiskLruCache.isClosed();
105 | }
106 |
107 | public long size() {
108 | return mDiskLruCache.size();
109 | }
110 |
111 | public void setMaxSize(long maxSize) {
112 | mDiskLruCache.setMaxSize(maxSize);
113 | }
114 |
115 | public File getDirectory() {
116 | return mDiskLruCache.getDirectory();
117 | }
118 |
119 | public long getMaxSize() {
120 | return mDiskLruCache.getMaxSize();
121 | }
122 |
123 | /*
124 | * =========================================================================
125 | * Utilities
126 | * =========================================================================
127 | */
128 |
129 | public InputStream get(String key) {
130 | try {
131 | DiskLruCache.Snapshot snapshot = mDiskLruCache.get(hashKeyForDisk(key));
132 | if (snapshot == null) {
133 | Log.e(TAG, "Not find entry from disk , or entry.readable = false");
134 | return null;
135 | }
136 | return snapshot.getInputStream(0);
137 |
138 | } catch (IOException e) {
139 | e.printStackTrace();
140 | return null;
141 | }
142 |
143 | }
144 |
145 | public DiskLruCache.Editor editor(String key) {
146 | try {
147 | key = hashKeyForDisk(key);
148 | DiskLruCache.Editor edit = mDiskLruCache.edit(key);
149 | if (edit == null) {
150 | Log.w(TAG, "the entry spcified key:" + key + " is editing by other . ");
151 | }
152 | return edit;
153 | } catch (IOException e) {
154 | e.printStackTrace();
155 | }
156 |
157 | return null;
158 | }
159 |
160 | /**
161 | * Record cache synchronization to the journal file.
162 | */
163 | /*
164 | * public void fluchCache() { if (mDiskLruCache != null) { try {
165 | * mDiskLruCache.flush(); } catch (IOException e) { e.printStackTrace(); } }
166 | * }
167 | */
168 |
169 | public static String InputStreamToString(InputStream is) throws Exception {
170 | int BUFFER_SIZE = 4096;
171 | ByteArrayOutputStream outStream = new ByteArrayOutputStream();
172 | byte[] data = new byte[BUFFER_SIZE];
173 | int count = -1;
174 | while ((count = is.read(data, 0, BUFFER_SIZE)) != -1)
175 | outStream.write(data, 0, count);
176 |
177 | data = null;
178 | return new String(outStream.toByteArray(), "ISO-8859-1");
179 | }
180 |
181 | /**
182 | * Using the MD5 algorithm to encrypt the key of the incoming and return.
183 | */
184 | public String hashKeyForDisk(String key) {
185 | String cacheKey;
186 | try {
187 | final MessageDigest mDigest = MessageDigest.getInstance("MD5");
188 | mDigest.update(key.getBytes());
189 | cacheKey = bytesToHexString(mDigest.digest());
190 | } catch (NoSuchAlgorithmException e) {
191 | cacheKey = String.valueOf(key.hashCode());
192 | }
193 | return cacheKey;
194 | }
195 |
196 | private String bytesToHexString(byte[] bytes) {
197 | StringBuilder sb = new StringBuilder();
198 | for (int i = 0; i < bytes.length; i++) {
199 | String hex = Integer.toHexString(0xFF & bytes[i]);
200 | if (hex.length() == 1) {
201 | sb.append('0');
202 | }
203 | sb.append(hex);
204 | }
205 | return sb.toString();
206 | }
207 |
208 | private static int getAppVersion(Context context) {
209 | try {
210 | PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
211 | return info.versionCode;
212 | } catch (NameNotFoundException e) {
213 | e.printStackTrace();
214 | }
215 | return 1;
216 | }
217 |
218 | /**
219 | * According to the incoming a unique name for the path of the hard disk
220 | * cache address.
221 | */
222 | public static File getDiskCacheDir(Context context, String uniqueName) {
223 | String cachePath;
224 | if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
225 | || !Environment.isExternalStorageRemovable()) {
226 | File cacheDir = context.getExternalCacheDir();
227 | if (cacheDir != null) {
228 | cachePath = cacheDir.getPath(); /// sdcard/Android/data//cache
230 | } else {
231 | cachePath = context.getCacheDir().getPath(); // /data/data//cache
233 | }
234 |
235 | } else {
236 | cachePath = context.getCacheDir().getPath(); // /data/data//cache
238 | }
239 | return new File(cachePath + File.separator + uniqueName);
240 | }
241 |
242 | /*
243 | * =======================================================================
244 | * Private Method
245 | * =======================================================================
246 | */
247 |
248 | private void putStringToDisk(String key, String value) {
249 | DiskLruCache.Editor edit = null;
250 | BufferedWriter bw = null;
251 | try {
252 | edit = editor(key);
253 | if (edit == null)
254 | return;
255 | OutputStream os = edit.newOutputStream(0);
256 | bw = new BufferedWriter(new OutputStreamWriter(os));
257 | bw.write(value);
258 | edit.commit();
259 | } catch (IOException e) {
260 | e.printStackTrace();
261 | try {
262 | edit.abort();
263 | } catch (IOException e1) {
264 | e1.printStackTrace();
265 | }
266 | } finally {
267 | try {
268 | if (bw != null)
269 | bw.close();
270 | } catch (IOException e) {
271 | e.printStackTrace();
272 | }
273 | }
274 | }
275 |
276 | private String getStringFromDisk(String key) {
277 | InputStream inputStream = null;
278 | try {
279 | inputStream = get(key);
280 | if (inputStream == null)
281 | return null;
282 | StringBuilder sb = new StringBuilder();
283 | int len = 0;
284 | byte[] buf = new byte[128];
285 | while ((len = inputStream.read(buf)) != -1) {
286 | sb.append(new String(buf, 0, len));
287 | }
288 | String result = sb.toString();
289 |
290 | return result;
291 |
292 | } catch (IOException e) {
293 | e.printStackTrace();
294 | if (inputStream != null)
295 | try {
296 | inputStream.close();
297 | } catch (IOException e1) {
298 | e1.printStackTrace();
299 | }
300 | }
301 | return null;
302 | }
303 |
304 | private void putBytesToDisk(String key, byte[] value) {
305 | OutputStream out = null;
306 | DiskLruCache.Editor editor = null;
307 | try {
308 | editor = editor(key);
309 | if (editor == null) {
310 | return;
311 | }
312 | out = editor.newOutputStream(0);
313 | out.write(value);
314 | out.flush();
315 | editor.commit();
316 | } catch (Exception e) {
317 | e.printStackTrace();
318 | try {
319 | editor.abort();
320 | } catch (IOException e1) {
321 | e1.printStackTrace();
322 | }
323 |
324 | } finally {
325 | if (out != null) {
326 | try {
327 | out.close();
328 | } catch (IOException e) {
329 | e.printStackTrace();
330 | }
331 | }
332 | }
333 | }
334 |
335 | public byte[] getBytesFromDisk(String key) {
336 | byte[] res = null;
337 | InputStream is = get(key);
338 | if (is == null)
339 | return null;
340 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
341 | try {
342 | byte[] buf = new byte[256];
343 | int len = 0;
344 | while ((len = is.read(buf)) != -1) {
345 | baos.write(buf, 0, len);
346 | }
347 | res = baos.toByteArray();
348 | } catch (IOException e) {
349 | e.printStackTrace();
350 | }
351 |
352 | return res;
353 | }
354 |
355 | private String checkCacheExpiredWithString(String key, String result) {
356 | if (result != null) {
357 | if (!CacheHelper.isExpired(result)) { // unexpired
358 | String originalResult = CacheHelper.clearDateInfo(result);
359 | Log.i(TAG, "Disk cache hint :" + originalResult + "---->time interval:"
360 | + CacheHelper.getCacheTimeInterval(result) / 1000 + " s");
361 | return originalResult;
362 | } else {
363 | Log.i(TAG, "Disk cache expired:" + "---->time interval:"
364 | + CacheHelper.getCacheTimeInterval(result) / 1000 + " s");
365 | remove(key);
366 | return null;
367 | }
368 | }else {
369 | return result;
370 | }
371 |
372 | }
373 |
374 | private byte[] checkCacheExpiredWithByte(String key, byte[] result) {
375 | if (result != null) {
376 | if (!CacheHelper.isExpired(result)) { // unexpired
377 | byte[] originalResult = CacheHelper.clearDateInfo(result);
378 | Log.i(TAG, "Disk cache hint :" + originalResult + "---->time interval:"
379 | + CacheHelper.getCacheTimeInterval(result) / 1000 + " s");
380 | return originalResult;
381 | } else {
382 | Log.i(TAG, "Disk cache expired:" + "---->time interval:"
383 | + CacheHelper.getCacheTimeInterval(result) / 1000 + " s");
384 | remove(key);
385 | return null;
386 | }
387 | }else {
388 | return result;
389 | }
390 |
391 | }
392 |
393 |
394 | /*
395 | * ====================================================================
396 | * Override ICache
397 | * ====================================================================
398 | */
399 |
400 | /*----------------------------------------------------String-----------------------------------------------------*/
401 |
402 |
403 | @Override
404 | public void put(String key, String value) {
405 | putStringToDisk(key, value);
406 |
407 | }
408 |
409 | @Override
410 | public void put(String key, String value, int cacheTime, int timeUnit) {
411 | int expirationTime = CacheHelper.calculateCacheTime(cacheTime, timeUnit);
412 | putStringToDisk(key, CacheHelper.convertStringWithDate(expirationTime, value));
413 | }
414 |
415 | @Override
416 | public String getAsString(String key) {
417 | String result = getStringFromDisk(key);
418 | return checkCacheExpiredWithString(key, result);
419 | }
420 |
421 | /*----------------------------------------------------JSONObject-----------------------------------------------------*/
422 |
423 | @Override
424 | public void put(String key, JSONObject jsonObject) {
425 | putStringToDisk(key, jsonObject.toString());
426 | }
427 |
428 | @Override
429 | public void put(String key, JSONObject jsonObject, int cacheTime, int timeUnit) {
430 | int expirationTime = CacheHelper.calculateCacheTime(cacheTime, timeUnit);
431 | putStringToDisk(key, CacheHelper.convertStringWithDate(expirationTime, jsonObject.toString()));
432 | }
433 |
434 | @Override
435 | public JSONObject getAsJSONObject(String key) {
436 | String result = getStringFromDisk(key);
437 | String JSONString = checkCacheExpiredWithString(key, result);
438 | try {
439 | if (JSONString != null)
440 | return new JSONObject(JSONString);
441 | } catch (JSONException e) {
442 | e.printStackTrace();
443 | }
444 | return null;
445 | }
446 |
447 | /*----------------------------------------------------JSONArray-----------------------------------------------------*/
448 |
449 | @Override
450 | public void put(String key, JSONArray jsonArray) {
451 | putStringToDisk(key, jsonArray.toString());
452 | }
453 |
454 | @Override
455 | public void put(String key, JSONArray jsonArray, int cacheTime, int timeUnit) {
456 | int expirationTime = CacheHelper.calculateCacheTime(cacheTime, timeUnit);
457 | putStringToDisk(key, CacheHelper.convertStringWithDate(expirationTime, jsonArray.toString()));
458 | }
459 |
460 | @Override
461 | public JSONArray getAsJSONArray(String key) {
462 | String result = getStringFromDisk(key);
463 | String JSONString = checkCacheExpiredWithString(key, result);
464 | try {
465 | JSONArray obj = new JSONArray(JSONString);
466 | return obj;
467 | } catch (Exception e) {
468 | e.printStackTrace();
469 | return null;
470 | }
471 | }
472 |
473 | /*----------------------------------------------------byte[ ]-----------------------------------------------------*/
474 |
475 | @Override
476 | public void put(String key, byte[] value) {
477 | putBytesToDisk(key, value);
478 | }
479 |
480 |
481 | @Override
482 | public void put(String key, byte[] value, int cacheTime, int timeUnit) {
483 | int expirationTime = CacheHelper.calculateCacheTime(cacheTime, timeUnit);
484 | putBytesToDisk(key, CacheHelper.convertByteArrayWithDate(expirationTime, value));
485 | }
486 |
487 | @Override
488 | public byte[] getAsBytes(String key) {
489 | byte[] bs = getBytesFromDisk(key);
490 | return checkCacheExpiredWithByte(key,bs );
491 | }
492 |
493 | /*----------------------------------------------------Serializable-----------------------------------------------------*/
494 |
495 | @Override
496 | public void put(String key, Serializable value) {
497 | /*DiskLruCache.Editor editor = editor(key);
498 | ObjectOutputStream oos = null;
499 | if (editor == null)
500 | return;
501 | try {
502 | OutputStream os = editor.newOutputStream(0);
503 | oos = new ObjectOutputStream(os);
504 | oos.writeObject(value);
505 | oos.flush();
506 | editor.commit();
507 | } catch (IOException e) {
508 | e.printStackTrace();
509 | try {
510 | editor.abort();
511 | } catch (IOException e1) {
512 | e1.printStackTrace();
513 | }
514 | } finally {
515 | try {
516 | if (oos != null)
517 | oos.close();
518 | } catch (IOException e) {
519 | e.printStackTrace();
520 | }
521 | }*/
522 | ByteArrayOutputStream baos = null;
523 | ObjectOutputStream oos = null;
524 | try {
525 | baos = new ByteArrayOutputStream();
526 | oos = new ObjectOutputStream(baos);
527 | oos.writeObject(value);
528 | byte[] data = baos.toByteArray();
529 | putBytesToDisk(key, data);
530 | } catch (Exception e) {
531 | e.printStackTrace();
532 | } finally {
533 | try {
534 | oos.close();
535 | } catch (IOException e) {
536 | }
537 | }
538 | }
539 |
540 |
541 | @Override
542 | public void put(String key, Serializable value, int cacheTime, int timeUnit) {
543 | ByteArrayOutputStream baos = null;
544 | ObjectOutputStream oos = null;
545 | try {
546 | baos = new ByteArrayOutputStream();
547 | oos = new ObjectOutputStream(baos);
548 | oos.writeObject(value);
549 | byte[] data = baos.toByteArray();
550 |
551 | int expirationTime = CacheHelper.calculateCacheTime(cacheTime, timeUnit);
552 | putBytesToDisk(key, CacheHelper.convertByteArrayWithDate(expirationTime, data));
553 | } catch (Exception e) {
554 | e.printStackTrace();
555 | } finally {
556 | try {
557 | oos.close();
558 | } catch (IOException e) {
559 | }
560 | }
561 |
562 | }
563 |
564 | @SuppressWarnings("unchecked")
565 | @Override
566 | public T getAsSerializable(String key) {
567 | T t = null;
568 |
569 | // InputStream is = get(key);
570 | byte[] bs = getBytesFromDisk(key);
571 | byte[] data = checkCacheExpiredWithByte(key, bs);
572 | if (data == null) {
573 | return null;
574 | }
575 | InputStream is = new ByteArrayInputStream(data);
576 |
577 | ObjectInputStream ois = null;
578 | try {
579 | ois = new ObjectInputStream(is);
580 | t = (T) ois.readObject();
581 | } catch (ClassNotFoundException e) {
582 | e.printStackTrace();
583 | } catch (IOException e) {
584 | e.printStackTrace();
585 | } finally {
586 | try {
587 | if (ois != null)
588 | ois.close();
589 | } catch (IOException e) {
590 | e.printStackTrace();
591 | }
592 | }
593 | return t;
594 | }
595 |
596 | /*----------------------------------------------------Bitmap-----------------------------------------------------*/
597 |
598 | @Override
599 | public void put(String key, Bitmap bitmap) {
600 | putBytesToDisk(key, ImageHelper.bitmap2Bytes(bitmap));
601 | }
602 |
603 | @Override
604 | public void put(String key, Bitmap bitmap, int cacheTime, int timeUnit) {
605 | int expirationTime = CacheHelper.calculateCacheTime(cacheTime, timeUnit);
606 | putBytesToDisk(key, CacheHelper.convertByteArrayWithDate(expirationTime, ImageHelper.bitmap2Bytes(bitmap)));
607 | }
608 |
609 | @Override
610 | public Bitmap getAsBitmap(String key) {
611 | byte[] bs = getBytesFromDisk(key);
612 | byte[] bytes = checkCacheExpiredWithByte(key, bs);
613 | if (bytes == null) {
614 | return null;
615 | }else {
616 | return ImageHelper.bytes2Bitmap(bytes);
617 | }
618 | }
619 |
620 | /*----------------------------------------------------Drawable-----------------------------------------------------*/
621 |
622 | @Override
623 | public void put(String key, Drawable value) {
624 | put(key, ImageHelper.drawable2Bitmap(value));
625 | }
626 |
627 | @Override
628 | public void put(String key, Drawable value, int cacheTime, int timeUnit) {
629 | put(key, ImageHelper.drawable2Bitmap(value), cacheTime, timeUnit);
630 | }
631 |
632 | @Override
633 | public Drawable getAsDrawable(String key) {
634 | byte[] bs = getBytesFromDisk(key);
635 | byte[] bytes = checkCacheExpiredWithByte(key, bs);
636 | if (bytes == null) {
637 | return null;
638 | }else {
639 | Bitmap bitmap = ImageHelper.bytes2Bitmap(bytes);
640 | return bitmap == null ? null : ImageHelper.bitmap2Drawable(bitmap);
641 | }
642 |
643 | }
644 |
645 | @Override
646 | public boolean remove(String key) {
647 | try {
648 | key = hashKeyForDisk(key);
649 | boolean isSuccess = mDiskLruCache.remove(key);
650 | if (isSuccess) {
651 | Log.i(TAG, "Disk cache delete success");
652 | } else {
653 | Log.i(TAG, "Disk cache delete failed");
654 | }
655 | return isSuccess;
656 | } catch (IOException e) {
657 | e.printStackTrace();
658 | }
659 | return false;
660 | }
661 |
662 | }
663 |
--------------------------------------------------------------------------------
/XDroidCache/src/com/xdroid/cache/disk/DiskLruCache.java:
--------------------------------------------------------------------------------
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,
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 | package com.xdroid.cache.disk;
18 |
19 | import java.io.BufferedWriter;
20 | import java.io.Closeable;
21 | import java.io.EOFException;
22 | import java.io.File;
23 | import java.io.FileInputStream;
24 | import java.io.FileNotFoundException;
25 | import java.io.FileOutputStream;
26 | import java.io.FilterOutputStream;
27 | import java.io.IOException;
28 | import java.io.InputStream;
29 | import java.io.InputStreamReader;
30 | import java.io.OutputStream;
31 | import java.io.OutputStreamWriter;
32 | import java.io.Writer;
33 | import java.util.ArrayList;
34 | import java.util.Iterator;
35 | import java.util.LinkedHashMap;
36 | import java.util.Map;
37 | import java.util.concurrent.Callable;
38 | import java.util.concurrent.LinkedBlockingQueue;
39 | import java.util.concurrent.ThreadPoolExecutor;
40 | import java.util.concurrent.TimeUnit;
41 | import java.util.regex.Matcher;
42 | import java.util.regex.Pattern;
43 |
44 | /**
45 | * A cache that uses a bounded amount of space on a filesystem. Each cache
46 | * entry has a string key and a fixed number of values. Each key must match
47 | * the regex [a-z0-9_-]{1,120}. Values are byte sequences,
48 | * accessible as streams or files. Each value must be between {@code 0} and
49 | * {@code Integer.MAX_VALUE} bytes in length.
50 | *
51 | *
The cache stores its data in a directory on the filesystem. This
52 | * directory must be exclusive to the cache; the cache may delete or overwrite
53 | * files from its directory. It is an error for multiple processes to use the
54 | * same cache directory at the same time.
55 | *
56 | *
This cache limits the number of bytes that it will store on the
57 | * filesystem. When the number of stored bytes exceeds the limit, the cache will
58 | * remove entries in the background until the limit is satisfied. The limit is
59 | * not strict: the cache may temporarily exceed it while waiting for files to be
60 | * deleted. The limit does not include filesystem overhead or the cache
61 | * journal so space-sensitive applications should set a conservative limit.
62 | *
63 | *
Clients call {@link #edit} to create or update the values of an entry. An
64 | * entry may have only one editor at one time; if a value is not available to be
65 | * edited then {@link #edit} will return null.
66 | *
67 | *
When an entry is being created it is necessary to
68 | * supply a full set of values; the empty value should be used as a
69 | * placeholder if necessary.
70 | *
When an entry is being edited, it is not necessary
71 | * to supply data for every value; values default to their previous
72 | * value.
73 | *
74 | * Every {@link #edit} call must be matched by a call to {@link com.xdroid.request.cache.diskcache.DiskLruCache.Editor#commit}
75 | * or {@link com.xdroid.request.cache.diskcache.DiskLruCache.Editor#abort}. Committing is atomic: a read observes the full set
76 | * of values as they were before or after the commit, but never a mix of values.
77 | *
78 | *
Clients call {@link #get} to read a snapshot of an entry. The read will
79 | * observe the value at the time that {@link #get} was called. Updates and
80 | * removals after the call do not impact ongoing reads.
81 | *
82 | *
This class is tolerant of some I/O errors. If files are missing from the
83 | * filesystem, the corresponding entries will be dropped from the cache. If
84 | * an error occurs while writing a cache value, the edit will fail silently.
85 | * Callers should handle other problems by catching {@code IOException} and
86 | * responding appropriately.
87 | */
88 | public final class DiskLruCache implements Closeable {
89 | static final String JOURNAL_FILE = "journal";
90 | static final String JOURNAL_FILE_TEMP = "journal.tmp";
91 | static final String JOURNAL_FILE_BACKUP = "journal.bkp";
92 | static final String MAGIC = "libcore.io.DiskLruCache";
93 | static final String VERSION_1 = "1";
94 | static final long ANY_SEQUENCE_NUMBER = -1;
95 | static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}";
96 | static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN);
97 | private static final String CLEAN = "CLEAN";
98 | private static final String DIRTY = "DIRTY";
99 | private static final String REMOVE = "REMOVE";
100 | private static final String READ = "READ";
101 |
102 | /*
103 | * This cache uses a journal file named "journal". A typical journal file
104 | * looks like this:
105 | * libcore.io.DiskLruCache
106 | * 1
107 | * 100
108 | * 2
109 | *
110 | * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
111 | * DIRTY 335c4c6028171cfddfbaae1a9c313c52
112 | * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
113 | * REMOVE 335c4c6028171cfddfbaae1a9c313c52
114 | * DIRTY 1ab96a171faeeee38496d8b330771a7a
115 | * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
116 | * READ 335c4c6028171cfddfbaae1a9c313c52
117 | * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
118 | *
119 | * The first five lines of the journal form its header. They are the
120 | * constant string "libcore.io.DiskLruCache", the disk cache's version,
121 | * the application's version, the value count, and a blank line.
122 | *
123 | * Each of the subsequent lines in the file is a record of the state of a
124 | * cache entry. Each line contains space-separated values: a state, a key,
125 | * and optional state-specific values.
126 | * o DIRTY lines track that an entry is actively being created or updated.
127 | * Every successful DIRTY action should be followed by a CLEAN or REMOVE
128 | * action. DIRTY lines without a matching CLEAN or REMOVE indicate that
129 | * temporary files may need to be deleted.
130 | * o CLEAN lines track a cache entry that has been successfully published
131 | * and may be read. A publish line is followed by the lengths of each of
132 | * its values.
133 | * o READ lines track accesses for LRU.
134 | * o REMOVE lines track entries that have been deleted.
135 | *
136 | * The journal file is appended to as cache operations occur. The journal may
137 | * occasionally be compacted by dropping redundant lines. A temporary file named
138 | * "journal.tmp" will be used during compaction; that file should be deleted if
139 | * it exists when the cache is opened.
140 | */
141 |
142 | private final File directory;
143 | private final File journalFile;
144 | private final File journalFileTmp;
145 | private final File journalFileBackup;
146 | private final int appVersion;
147 | private long maxSize;
148 | private final int valueCount;
149 | private long size = 0;
150 | private Writer journalWriter;
151 | private final LinkedHashMap lruEntries =
152 | new LinkedHashMap(0, 0.75f, true);
153 | private int redundantOpCount;
154 |
155 | /**
156 | * To differentiate between old and current snapshots, each entry is given
157 | * a sequence number each time an edit is committed. A snapshot is stale if
158 | * its sequence number is not equal to its entry's sequence number.
159 | */
160 | private long nextSequenceNumber = 0;
161 |
162 | /** This cache uses a single background thread to evict entries. */
163 | final ThreadPoolExecutor executorService =
164 | new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue());
165 | private final Callable cleanupCallable = new Callable() {
166 | public Void call() throws Exception {
167 | synchronized (DiskLruCache.this) {
168 | if (journalWriter == null) {
169 | return null; // Closed.
170 | }
171 | trimToSize();
172 | if (journalRebuildRequired()) {
173 | rebuildJournal();
174 | redundantOpCount = 0;
175 | }
176 | }
177 | return null;
178 | }
179 | };
180 |
181 | private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
182 | this.directory = directory;
183 | this.appVersion = appVersion;
184 | this.journalFile = new File(directory, JOURNAL_FILE);
185 | this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
186 | this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
187 | this.valueCount = valueCount;
188 | this.maxSize = maxSize;
189 | }
190 |
191 | /**
192 | * Opens the cache in {@code directory}, creating a cache if none exists
193 | * there.
194 | *
195 | * @param directory a writable directory
196 | * @param valueCount the number of values per cache entry. Must be positive.
197 | * @param maxSize the maximum number of bytes this cache should use to store
198 | * @throws java.io.IOException if reading or writing the cache directory fails
199 | */
200 | public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
201 | throws IOException {
202 | if (maxSize <= 0) {
203 | throw new IllegalArgumentException("maxSize <= 0");
204 | }
205 | if (valueCount <= 0) {
206 | throw new IllegalArgumentException("valueCount <= 0");
207 | }
208 |
209 | // If a bkp file exists, use it instead.
210 | File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
211 | if (backupFile.exists()) {
212 | File journalFile = new File(directory, JOURNAL_FILE);
213 | // If journal file also exists just delete backup file.
214 | if (journalFile.exists()) {
215 | backupFile.delete();
216 | } else {
217 | renameTo(backupFile, journalFile, false);
218 | }
219 | }
220 |
221 | // Prefer to pick up where we left off.
222 | DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
223 | if (cache.journalFile.exists()) {
224 | try {
225 | cache.readJournal();
226 | cache.processJournal();
227 | return cache;
228 | } catch (IOException journalIsCorrupt) {
229 | System.out
230 | .println("DiskLruCache "
231 | + directory
232 | + " is corrupt: "
233 | + journalIsCorrupt.getMessage()
234 | + ", removing");
235 | cache.delete();
236 | }
237 | }
238 |
239 | // Create a new empty cache.
240 | directory.mkdirs();
241 | cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
242 | cache.rebuildJournal();
243 | return cache;
244 | }
245 |
246 | private void readJournal() throws IOException {
247 | StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
248 | try {
249 | String magic = reader.readLine();
250 | String version = reader.readLine();
251 | String appVersionString = reader.readLine();
252 | String valueCountString = reader.readLine();
253 | String blank = reader.readLine();
254 | if (!MAGIC.equals(magic)
255 | || !VERSION_1.equals(version)
256 | || !Integer.toString(appVersion).equals(appVersionString)
257 | || !Integer.toString(valueCount).equals(valueCountString)
258 | || !"".equals(blank)) {
259 | throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
260 | + valueCountString + ", " + blank + "]");
261 | }
262 |
263 | int lineCount = 0;
264 | while (true) {
265 | try {
266 | readJournalLine(reader.readLine());
267 | lineCount++;
268 | } catch (EOFException endOfJournal) {
269 | break;
270 | }
271 | }
272 | redundantOpCount = lineCount - lruEntries.size();
273 |
274 | // If we ended on a truncated line, rebuild the journal before appending to it.
275 | if (reader.hasUnterminatedLine()) {
276 | rebuildJournal();
277 | } else {
278 | journalWriter = new BufferedWriter(new OutputStreamWriter(
279 | new FileOutputStream(journalFile, true), Util.US_ASCII));
280 | }
281 | } finally {
282 | Util.closeQuietly(reader);
283 | }
284 | }
285 |
286 | private void readJournalLine(String line) throws IOException {
287 | int firstSpace = line.indexOf(' ');
288 | if (firstSpace == -1) {
289 | throw new IOException("unexpected journal line: " + line);
290 | }
291 |
292 | int keyBegin = firstSpace + 1;
293 | int secondSpace = line.indexOf(' ', keyBegin);
294 | final String key;
295 | if (secondSpace == -1) {
296 | key = line.substring(keyBegin);
297 | if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
298 | lruEntries.remove(key);
299 | return;
300 | }
301 | } else {
302 | key = line.substring(keyBegin, secondSpace);
303 | }
304 |
305 | Entry entry = lruEntries.get(key);
306 | if (entry == null) {
307 | entry = new Entry(key);
308 | lruEntries.put(key, entry);
309 | }
310 |
311 | if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
312 | String[] parts = line.substring(secondSpace + 1).split(" ");
313 | entry.readable = true;
314 | entry.currentEditor = null;
315 | entry.setLengths(parts);
316 | } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
317 | entry.currentEditor = new Editor(entry);
318 | } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
319 | // This work was already done by calling lruEntries.get().
320 | } else {
321 | throw new IOException("unexpected journal line: " + line);
322 | }
323 | }
324 |
325 | /**
326 | * Computes the initial size and collects garbage as a part of opening the
327 | * cache. Dirty entries are assumed to be inconsistent and will be deleted.
328 | */
329 | private void processJournal() throws IOException {
330 | deleteIfExists(journalFileTmp);
331 | for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) {
332 | Entry entry = i.next();
333 | if (entry.currentEditor == null) {
334 | for (int t = 0; t < valueCount; t++) {
335 | size += entry.lengths[t];
336 | }
337 | } else {
338 | entry.currentEditor = null;
339 | for (int t = 0; t < valueCount; t++) {
340 | deleteIfExists(entry.getCleanFile(t));
341 | deleteIfExists(entry.getDirtyFile(t));
342 | }
343 | i.remove();
344 | }
345 | }
346 | }
347 |
348 | /**
349 | * Creates a new journal that omits redundant information. This replaces the
350 | * current journal if it exists.
351 | */
352 | private synchronized void rebuildJournal() throws IOException {
353 | if (journalWriter != null) {
354 | journalWriter.close();
355 | }
356 |
357 | Writer writer = new BufferedWriter(
358 | new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));
359 | try {
360 | writer.write(MAGIC);
361 | writer.write("\n");
362 | writer.write(VERSION_1);
363 | writer.write("\n");
364 | writer.write(Integer.toString(appVersion));
365 | writer.write("\n");
366 | writer.write(Integer.toString(valueCount));
367 | writer.write("\n");
368 | writer.write("\n");
369 |
370 | for (Entry entry : lruEntries.values()) {
371 | if (entry.currentEditor != null) {
372 | writer.write(DIRTY + ' ' + entry.key + '\n');
373 | } else {
374 | writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
375 | }
376 | }
377 | } finally {
378 | writer.close();
379 | }
380 |
381 | if (journalFile.exists()) {
382 | renameTo(journalFile, journalFileBackup, true);
383 | }
384 | renameTo(journalFileTmp, journalFile, false);
385 | journalFileBackup.delete();
386 |
387 | journalWriter = new BufferedWriter(
388 | new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
389 | }
390 |
391 | private static void deleteIfExists(File file) throws IOException {
392 | if (file.exists() && !file.delete()) {
393 | throw new IOException();
394 | }
395 | }
396 |
397 | private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
398 | if (deleteDestination) {
399 | deleteIfExists(to);
400 | }
401 | if (!from.renameTo(to)) {
402 | throw new IOException();
403 | }
404 | }
405 |
406 | /**
407 | * Returns a snapshot of the entry named {@code key}, or null if it doesn't
408 | * exist is not currently readable. If a value is returned, it is moved to
409 | * the head of the LRU queue.
410 | */
411 | public synchronized Snapshot get(String key) throws IOException {
412 | checkNotClosed();
413 | validateKey(key);
414 | Entry entry = lruEntries.get(key);
415 | if (entry == null) {
416 | return null;
417 | }
418 |
419 | if (!entry.readable) {
420 | return null;
421 | }
422 |
423 | // Open all streams eagerly to guarantee that we see a single published
424 | // snapshot. If we opened streams lazily then the streams could come
425 | // from different edits.
426 | InputStream[] ins = new InputStream[valueCount];
427 | try {
428 | for (int i = 0; i < valueCount; i++) {
429 | ins[i] = new FileInputStream(entry.getCleanFile(i));
430 | }
431 | } catch (FileNotFoundException e) {
432 | // A file must have been deleted manually!
433 | for (int i = 0; i < valueCount; i++) {
434 | if (ins[i] != null) {
435 | Util.closeQuietly(ins[i]);
436 | } else {
437 | break;
438 | }
439 | }
440 | return null;
441 | }
442 |
443 | redundantOpCount++;
444 | journalWriter.append(READ + ' ' + key + '\n');
445 | if (journalRebuildRequired()) {
446 | executorService.submit(cleanupCallable);
447 | }
448 |
449 | return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);
450 | }
451 |
452 | /**
453 | * Returns an editor for the entry named {@code key}, or null if another
454 | * edit is in progress.
455 | */
456 | public Editor edit(String key) throws IOException {
457 | return edit(key, ANY_SEQUENCE_NUMBER);
458 | }
459 |
460 | private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
461 | checkNotClosed();
462 | validateKey(key);
463 | Entry entry = lruEntries.get(key);
464 | if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
465 | || entry.sequenceNumber != expectedSequenceNumber)) {
466 | return null; // Snapshot is stale.
467 | }
468 | if (entry == null) {
469 | entry = new Entry(key);
470 | lruEntries.put(key, entry);
471 | } else if (entry.currentEditor != null) {
472 | return null; // Another edit is in progress.
473 | }
474 |
475 | Editor editor = new Editor(entry);
476 | entry.currentEditor = editor;
477 |
478 | // Flush the journal before creating files to prevent file leaks.
479 | journalWriter.write(DIRTY + ' ' + key + '\n');
480 | journalWriter.flush();
481 | return editor;
482 | }
483 |
484 | /** Returns the directory where this cache stores its data. */
485 | public File getDirectory() {
486 | return directory;
487 | }
488 |
489 | /**
490 | * Returns the maximum number of bytes that this cache should use to store
491 | * its data.
492 | */
493 | public synchronized long getMaxSize() {
494 | return maxSize;
495 | }
496 |
497 | /**
498 | * Changes the maximum number of bytes the cache can store and queues a job
499 | * to trim the existing store, if necessary.
500 | */
501 | public synchronized void setMaxSize(long maxSize) {
502 | this.maxSize = maxSize;
503 | executorService.submit(cleanupCallable);
504 | }
505 |
506 | /**
507 | * Returns the number of bytes currently being used to store the values in
508 | * this cache. This may be greater than the max size if a background
509 | * deletion is pending.
510 | */
511 | public synchronized long size() {
512 | return size;
513 | }
514 |
515 | private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
516 | Entry entry = editor.entry;
517 | if (entry.currentEditor != editor) {
518 | throw new IllegalStateException();
519 | }
520 |
521 | // If this edit is creating the entry for the first time, every index must have a value.
522 | if (success && !entry.readable) {
523 | for (int i = 0; i < valueCount; i++) {
524 | if (!editor.written[i]) {
525 | editor.abort();
526 | throw new IllegalStateException("Newly created entry didn't create value for index " + i);
527 | }
528 | if (!entry.getDirtyFile(i).exists()) {
529 | editor.abort();
530 | return;
531 | }
532 | }
533 | }
534 |
535 | for (int i = 0; i < valueCount; i++) {
536 | File dirty = entry.getDirtyFile(i);
537 | if (success) {
538 | if (dirty.exists()) {
539 | File clean = entry.getCleanFile(i);
540 | dirty.renameTo(clean);
541 | long oldLength = entry.lengths[i];
542 | long newLength = clean.length();
543 | entry.lengths[i] = newLength;
544 | size = size - oldLength + newLength;
545 | }
546 | } else {
547 | deleteIfExists(dirty);
548 | }
549 | }
550 |
551 | redundantOpCount++;
552 | entry.currentEditor = null;
553 | if (entry.readable | success) {
554 | entry.readable = true;
555 | journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
556 | if (success) {
557 | entry.sequenceNumber = nextSequenceNumber++;
558 | }
559 | } else {
560 | lruEntries.remove(entry.key);
561 | journalWriter.write(REMOVE + ' ' + entry.key + '\n');
562 | }
563 | journalWriter.flush();
564 |
565 | if (size > maxSize || journalRebuildRequired()) {
566 | executorService.submit(cleanupCallable);
567 | }
568 | }
569 |
570 | /**
571 | * We only rebuild the journal when it will halve the size of the journal
572 | * and eliminate at least 2000 ops.
573 | */
574 | private boolean journalRebuildRequired() {
575 | final int redundantOpCompactThreshold = 2000;
576 | return redundantOpCount >= redundantOpCompactThreshold //
577 | && redundantOpCount >= lruEntries.size();
578 | }
579 |
580 | /**
581 | * Drops the entry for {@code key} if it exists and can be removed. Entries
582 | * actively being edited cannot be removed.
583 | *
584 | * @return true if an entry was removed.
585 | */
586 | public synchronized boolean remove(String key) throws IOException {
587 | checkNotClosed();
588 | validateKey(key);
589 | Entry entry = lruEntries.get(key);
590 | if (entry == null || entry.currentEditor != null) {
591 | return false;
592 | }
593 |
594 | for (int i = 0; i < valueCount; i++) {
595 | File file = entry.getCleanFile(i);
596 | if (file.exists() && !file.delete()) {
597 | throw new IOException("failed to delete " + file);
598 | }
599 | size -= entry.lengths[i];
600 | entry.lengths[i] = 0;
601 | }
602 |
603 | redundantOpCount++;
604 | journalWriter.append(REMOVE + ' ' + key + '\n');
605 | lruEntries.remove(key);
606 |
607 | if (journalRebuildRequired()) {
608 | executorService.submit(cleanupCallable);
609 | }
610 |
611 | return true;
612 | }
613 |
614 | /** Returns true if this cache has been closed. */
615 | public synchronized boolean isClosed() {
616 | return journalWriter == null;
617 | }
618 |
619 | private void checkNotClosed() {
620 | if (journalWriter == null) {
621 | throw new IllegalStateException("cache is closed");
622 | }
623 | }
624 |
625 | /** Force buffered operations to the filesystem. */
626 | public synchronized void flush() throws IOException {
627 | checkNotClosed();
628 | trimToSize();
629 | journalWriter.flush();
630 | }
631 |
632 | /** Closes this cache. Stored values will remain on the filesystem. */
633 | public synchronized void close() throws IOException {
634 | if (journalWriter == null) {
635 | return; // Already closed.
636 | }
637 | for (Entry entry : new ArrayList(lruEntries.values())) {
638 | if (entry.currentEditor != null) {
639 | entry.currentEditor.abort();
640 | }
641 | }
642 | trimToSize();
643 | journalWriter.close();
644 | journalWriter = null;
645 | }
646 |
647 | private void trimToSize() throws IOException {
648 | while (size > maxSize) {
649 | Map.Entry toEvict = lruEntries.entrySet().iterator().next();
650 | remove(toEvict.getKey());
651 | }
652 | }
653 |
654 | /**
655 | * Closes the cache and deletes all of its stored values. This will delete
656 | * all files in the cache directory including files that weren't created by
657 | * the cache.
658 | */
659 | public void delete() throws IOException {
660 | close();
661 | Util.deleteContents(directory);
662 | }
663 |
664 | private void validateKey(String key) {
665 | Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
666 | if (!matcher.matches()) {
667 | throw new IllegalArgumentException("keys must match regex "
668 | + STRING_KEY_PATTERN + ": \"" + key + "\"");
669 | }
670 | }
671 |
672 | private static String inputStreamToString(InputStream in) throws IOException {
673 | return Util.readFully(new InputStreamReader(in, Util.UTF_8));
674 | }
675 |
676 | /** A snapshot of the values for an entry. */
677 | public final class Snapshot implements Closeable {
678 | private final String key;
679 | private final long sequenceNumber;
680 | private final InputStream[] ins;
681 | private final long[] lengths;
682 |
683 | private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) {
684 | this.key = key;
685 | this.sequenceNumber = sequenceNumber;
686 | this.ins = ins;
687 | this.lengths = lengths;
688 | }
689 |
690 | /**
691 | * Returns an editor for this snapshot's entry, or null if either the
692 | * entry has changed since this snapshot was created or if another edit
693 | * is in progress.
694 | */
695 | public Editor edit() throws IOException {
696 | return DiskLruCache.this.edit(key, sequenceNumber);
697 | }
698 |
699 | /** Returns the unbuffered stream with the value for {@code index}. */
700 | public InputStream getInputStream(int index) {
701 | return ins[index];
702 | }
703 |
704 | /** Returns the string value for {@code index}. */
705 | public String getString(int index) throws IOException {
706 | return inputStreamToString(getInputStream(index));
707 | }
708 |
709 | /** Returns the byte length of the value for {@code index}. */
710 | public long getLength(int index) {
711 | return lengths[index];
712 | }
713 |
714 | public void close() {
715 | for (InputStream in : ins) {
716 | Util.closeQuietly(in);
717 | }
718 | }
719 | }
720 |
721 | private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
722 | @Override
723 | public void write(int b) throws IOException {
724 | // Eat all writes silently. Nom nom.
725 | }
726 | };
727 |
728 | /** Edits the values for an entry. */
729 | public final class Editor {
730 | private final Entry entry;
731 | private final boolean[] written;
732 | private boolean hasErrors;
733 | private boolean committed;
734 |
735 | private Editor(Entry entry) {
736 | this.entry = entry;
737 | this.written = (entry.readable) ? null : new boolean[valueCount];
738 | }
739 |
740 | /**
741 | * Returns an unbuffered input stream to read the last committed value,
742 | * or null if no value has been committed.
743 | */
744 | public InputStream newInputStream(int index) throws IOException {
745 | synchronized (DiskLruCache.this) {
746 | if (entry.currentEditor != this) {
747 | throw new IllegalStateException();
748 | }
749 | if (!entry.readable) {
750 | return null;
751 | }
752 | try {
753 | return new FileInputStream(entry.getCleanFile(index));
754 | } catch (FileNotFoundException e) {
755 | return null;
756 | }
757 | }
758 | }
759 |
760 | /**
761 | * Returns the last committed value as a string, or null if no value
762 | * has been committed.
763 | */
764 | public String getString(int index) throws IOException {
765 | InputStream in = newInputStream(index);
766 | return in != null ? inputStreamToString(in) : null;
767 | }
768 |
769 | /**
770 | * Returns a new unbuffered output stream to write the value at
771 | * {@code index}. If the underlying output stream encounters errors
772 | * when writing to the filesystem, this edit will be aborted when
773 | * {@link #commit} is called. The returned output stream does not throw
774 | * IOExceptions.
775 | */
776 | public OutputStream newOutputStream(int index) throws IOException {
777 | if (index < 0 || index >= valueCount) {
778 | throw new IllegalArgumentException("Expected index " + index + " to "
779 | + "be greater than 0 and less than the maximum value count "
780 | + "of " + valueCount);
781 | }
782 | synchronized (DiskLruCache.this) {
783 | if (entry.currentEditor != this) {
784 | throw new IllegalStateException();
785 | }
786 | if (!entry.readable) {
787 | written[index] = true;
788 | }
789 | File dirtyFile = entry.getDirtyFile(index);
790 | FileOutputStream outputStream;
791 | try {
792 | outputStream = new FileOutputStream(dirtyFile);
793 | } catch (FileNotFoundException e) {
794 | // Attempt to recreate the cache directory.
795 | directory.mkdirs();
796 | try {
797 | outputStream = new FileOutputStream(dirtyFile);
798 | } catch (FileNotFoundException e2) {
799 | // We are unable to recover. Silently eat the writes.
800 | return NULL_OUTPUT_STREAM;
801 | }
802 | }
803 | return new FaultHidingOutputStream(outputStream);
804 | }
805 | }
806 |
807 | /** Sets the value at {@code index} to {@code value}. */
808 | public void set(int index, String value) throws IOException {
809 | Writer writer = null;
810 | try {
811 | writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8);
812 | writer.write(value);
813 | } finally {
814 | Util.closeQuietly(writer);
815 | }
816 | }
817 |
818 | /**
819 | * Commits this edit so it is visible to readers. This releases the
820 | * edit lock so another edit may be started on the same key.
821 | */
822 | public void commit() throws IOException {
823 | if (hasErrors) {
824 | completeEdit(this, false);
825 | remove(entry.key); // The previous entry is stale.
826 | } else {
827 | completeEdit(this, true);
828 | }
829 | committed = true;
830 | }
831 |
832 | /**
833 | * Aborts this edit. This releases the edit lock so another edit may be
834 | * started on the same key.
835 | */
836 | public void abort() throws IOException {
837 | completeEdit(this, false);
838 | }
839 |
840 | public void abortUnlessCommitted() {
841 | if (!committed) {
842 | try {
843 | abort();
844 | } catch (IOException ignored) {
845 | }
846 | }
847 | }
848 |
849 | private class FaultHidingOutputStream extends FilterOutputStream {
850 | private FaultHidingOutputStream(OutputStream out) {
851 | super(out);
852 | }
853 |
854 | @Override public void write(int oneByte) {
855 | try {
856 | out.write(oneByte);
857 | } catch (IOException e) {
858 | hasErrors = true;
859 | }
860 | }
861 |
862 | @Override public void write(byte[] buffer, int offset, int length) {
863 | try {
864 | out.write(buffer, offset, length);
865 | } catch (IOException e) {
866 | hasErrors = true;
867 | }
868 | }
869 |
870 | @Override public void close() {
871 | try {
872 | out.close();
873 | } catch (IOException e) {
874 | hasErrors = true;
875 | }
876 | }
877 |
878 | @Override public void flush() {
879 | try {
880 | out.flush();
881 | } catch (IOException e) {
882 | hasErrors = true;
883 | }
884 | }
885 | }
886 | }
887 |
888 | private final class Entry {
889 | private final String key;
890 |
891 | /** Lengths of this entry's files. */
892 | private final long[] lengths;
893 |
894 | /** True if this entry has ever been published. */
895 | private boolean readable;
896 |
897 | /** The ongoing edit or null if this entry is not being edited. */
898 | private Editor currentEditor;
899 |
900 | /** The sequence number of the most recently committed edit to this entry. */
901 | private long sequenceNumber;
902 |
903 | private Entry(String key) {
904 | this.key = key;
905 | this.lengths = new long[valueCount];
906 | }
907 |
908 | public String getLengths() throws IOException {
909 | StringBuilder result = new StringBuilder();
910 | for (long size : lengths) {
911 | result.append(' ').append(size);
912 | }
913 | return result.toString();
914 | }
915 |
916 | /** Set lengths using decimal numbers like "10123". */
917 | private void setLengths(String[] strings) throws IOException {
918 | if (strings.length != valueCount) {
919 | throw invalidLengths(strings);
920 | }
921 |
922 | try {
923 | for (int i = 0; i < strings.length; i++) {
924 | lengths[i] = Long.parseLong(strings[i]);
925 | }
926 | } catch (NumberFormatException e) {
927 | throw invalidLengths(strings);
928 | }
929 | }
930 |
931 | private IOException invalidLengths(String[] strings) throws IOException {
932 | throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
933 | }
934 |
935 | public File getCleanFile(int i) {
936 | return new File(directory, key + "." + i);
937 | }
938 |
939 | public File getDirtyFile(int i) {
940 | return new File(directory, key + "." + i + ".tmp");
941 | }
942 | }
943 | }
944 |
--------------------------------------------------------------------------------