1
/*
2
* Copyright (C) 2012 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.example.android.displayingbitmaps.util;
18
19
import android.content.Context;
20
import android.graphics.Bitmap;
21
import android.net.ConnectivityManager;
22
import android.net.NetworkInfo;
23
import android.os.Build;
24
import android.widget.Toast;
25
26
import com.example.android.common.logger.Log;
27
import com.example.android.displayingbitmaps.BuildConfig;
28
import com.example.android.displayingbitmaps.R;
29
30
import java.io.BufferedInputStream;
31
import java.io.BufferedOutputStream;
32
import java.io.File;
33
import java.io.FileDescriptor;
34
import java.io.FileInputStream;
35
import java.io.IOException;
36
import java.io.OutputStream;
37
import java.net.HttpURLConnection;
38
import java.net.URL;
39
40
/**
41
* A simple subclass of {@link ImageResizer} that fetches and resizes images fetched from a URL.
42
*/
43
public class ImageFetcher extends ImageResizer {
44
private static final String TAG = "ImageFetcher";
45
private static final int HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB
46
private static final String HTTP_CACHE_DIR = "http";
47
private static final int IO_BUFFER_SIZE = 8 * 1024;
48
49
private DiskLruCache mHttpDiskCache;
50
private File mHttpCacheDir;
51
private boolean mHttpDiskCacheStarting = true;
52
private final Object mHttpDiskCacheLock = new Object();
53
private static final int DISK_CACHE_INDEX = 0;
54
55
/**
56
* Initialize providing a target image width and height for the processing images.
57
*
58
* @param context
59
* @param imageWidth
60
* @param imageHeight
61
*/
62
public ImageFetcher(Context context, int imageWidth, int imageHeight) {
63
super(context, imageWidth, imageHeight);
64
init(context);
65
}
66
67
/**
68
* Initialize providing a single target image size (used for both width and height);
69
*
70
* @param context
71
* @param imageSize
72
*/
73
public ImageFetcher(Context context, int imageSize) {
74
super(context, imageSize);
75
init(context);
76
}
77
78
private void init(Context context) {
79
checkConnection(context);
80
mHttpCacheDir = ImageCache.getDiskCacheDir(context, HTTP_CACHE_DIR);
81
}
82
83
@Override
84
protected void initDiskCacheInternal() {
85
super.initDiskCacheInternal();
86
initHttpDiskCache();
87
}
88
89
private void initHttpDiskCache() {
90
if (!mHttpCacheDir.exists()) {
91
mHttpCacheDir.mkdirs();
92
}
93
synchronized (mHttpDiskCacheLock) {
94
if (ImageCache.getUsableSpace(mHttpCacheDir) > HTTP_CACHE_SIZE) {
95
try {
96
mHttpDiskCache = DiskLruCache.open(mHttpCacheDir, 1, 1, HTTP_CACHE_SIZE);
97
if (BuildConfig.DEBUG) {
98
Log.d(TAG, "HTTP cache initialized");
99
}
100
} catch (IOException e) {
101
mHttpDiskCache = null;
102
}
103
}
104
mHttpDiskCacheStarting = false;
105
mHttpDiskCacheLock.notifyAll();
106
}
107
}
108
109
@Override
110
protected void clearCacheInternal() {
111
super.clearCacheInternal();
112
synchronized (mHttpDiskCacheLock) {
113
if (mHttpDiskCache != null && !mHttpDiskCache.isClosed()) {
114
try {
115
mHttpDiskCache.delete();
116
if (BuildConfig.DEBUG) {
117
Log.d(TAG, "HTTP cache cleared");
118
}
119
} catch (IOException e) {
120
Log.e(TAG, "clearCacheInternal - " + e);
121
}
122
mHttpDiskCache = null;
123
mHttpDiskCacheStarting = true;
124
initHttpDiskCache();
125
}
126
}
127
}
128
129
@Override
130
protected void flushCacheInternal() {
131
super.flushCacheInternal();
132
synchronized (mHttpDiskCacheLock) {
133
if (mHttpDiskCache != null) {
134
try {
135
mHttpDiskCache.flush();
136
if (BuildConfig.DEBUG) {
137
Log.d(TAG, "HTTP cache flushed");
138
}
139
} catch (IOException e) {
140
Log.e(TAG, "flush - " + e);
141
}
142
}
143
}
144
}
145
146
@Override
147
protected void closeCacheInternal() {
148
super.closeCacheInternal();
149
synchronized (mHttpDiskCacheLock) {
150
if (mHttpDiskCache != null) {
151
try {
152
if (!mHttpDiskCache.isClosed()) {
153
mHttpDiskCache.close();
154
mHttpDiskCache = null;
155
if (BuildConfig.DEBUG) {
156
Log.d(TAG, "HTTP cache closed");
157
}
158
}
159
} catch (IOException e) {
160
Log.e(TAG, "closeCacheInternal - " + e);
161
}
162
}
163
}
164
}
165
166
/**
167
* Simple network connection check.
168
*
169
* @param context
170
*/
171
private void checkConnection(Context context) {
172
final ConnectivityManager cm =
173
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
174
final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
175
if (networkInfo == null || !networkInfo.isConnectedOrConnecting()) {
176
Toast.makeText(context, R.string.no_network_connection_toast, Toast.LENGTH_LONG).show();
177
Log.e(TAG, "checkConnection - no connection found");
178
}
179
}
180
181
/**
182
* The main process method, which will be called by the ImageWorker in the AsyncTask background
183
* thread.
184
*
185
* @param data The data to load the bitmap, in this case, a regular http URL
186
* @return The downloaded and resized bitmap
187
*/
188
private Bitmap processBitmap(String data) {
189
if (BuildConfig.DEBUG) {
190
Log.d(TAG, "processBitmap - " + data);
191
}
192
193
final String key = ImageCache.hashKeyForDisk(data);
194
FileDescriptor fileDescriptor = null;
195
FileInputStream fileInputStream = null;
196
DiskLruCache.Snapshot snapshot;
197
synchronized (mHttpDiskCacheLock) {
198
// Wait for disk cache to initialize
199
while (mHttpDiskCacheStarting) {
200
try {
201
mHttpDiskCacheLock.wait();
202
} catch (InterruptedException e) {}
203
}
204
205
if (mHttpDiskCache != null) {
206
try {
207
snapshot = mHttpDiskCache.get(key);
208
if (snapshot == null) {
209
if (BuildConfig.DEBUG) {
210
Log.d(TAG, "processBitmap, not found in http cache, downloading...");
211
}
212
DiskLruCache.Editor editor = mHttpDiskCache.edit(key);
213
if (editor != null) {
214
if (downloadUrlToStream(data,
215
editor.newOutputStream(DISK_CACHE_INDEX))) {
216
editor.commit();
217
} else {
218
editor.abort();
219
}
220
}
221
snapshot = mHttpDiskCache.get(key);
222
}
223
if (snapshot != null) {
224
fileInputStream =
225
(FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
226
fileDescriptor = fileInputStream.getFD();
227
}
228
} catch (IOException e) {
229
Log.e(TAG, "processBitmap - " + e);
230
} catch (IllegalStateException e) {
231
Log.e(TAG, "processBitmap - " + e);
232
} finally {
233
if (fileDescriptor == null && fileInputStream != null) {
234
try {
235
fileInputStream.close();
236
} catch (IOException e) {}
237
}
238
}
239
}
240
}
241
242
Bitmap bitmap = null;
243
if (fileDescriptor != null) {
244
bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth,
245
mImageHeight, getImageCache());
246
}
247
if (fileInputStream != null) {
248
try {
249
fileInputStream.close();
250
} catch (IOException e) {}
251
}
252
return bitmap;
253
}
254
255
@Override
256
protected Bitmap processBitmap(Object data) {
257
return processBitmap(String.valueOf(data));
258
}
259
260
/**
261
* Download a bitmap from a URL and write the content to an output stream.
262
*
263
* @param urlString The URL to fetch
264
* @return true if successful, false otherwise
265
*/
266
public boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
267
disableConnectionReuseIfNecessary();
268
HttpURLConnection urlConnection = null;
269
BufferedOutputStream out = null;
270
BufferedInputStream in = null;
271
272
try {
273
final URL url = new URL(urlString);
274
urlConnection = (HttpURLConnection) url.openConnection();
275
in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
276
out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
277
278
int b;
279
while ((b = in.read()) != -1) {
280
out.write(b);
281
}
282
return true;
283
} catch (final IOException e) {
284
Log.e(TAG, "Error in downloadBitmap - " + e);
285
} finally {
286
if (urlConnection != null) {
287
urlConnection.disconnect();
288
}
289
try {
290
if (out != null) {
291
out.close();
292
}
293
if (in != null) {
294
in.close();
295
}
296
} catch (final IOException e) {}
297
}
298
return false;
299
}
300
301
/**
302
* Workaround for bug pre-Froyo, see here for more info:
303
* http://android-developers.blogspot.com/2011/09/androids-http-clients.html
304
*/
305
public static void disableConnectionReuseIfNecessary() {
306
// HTTP connection reuse which was buggy pre-froyo
307
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
308
System.setProperty("http.keepAlive", "false");
309
}
310
}
311
}