blob: b9ccb68cf8cab0334e638aef9a95da612c348437 [file] [log] [blame]
Alexander Lucas97842ff2014-03-07 14:56:55 -08001/*
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
17package com.example.android.displayingbitmaps.util;
18
19import android.content.Context;
20import android.graphics.Bitmap;
21import android.net.ConnectivityManager;
22import android.net.NetworkInfo;
23import android.os.Build;
24import android.widget.Toast;
25
26import com.example.android.common.logger.Log;
27import com.example.android.displayingbitmaps.BuildConfig;
28import com.example.android.displayingbitmaps.R;
29
30import java.io.BufferedInputStream;
31import java.io.BufferedOutputStream;
32import java.io.File;
33import java.io.FileDescriptor;
34import java.io.FileInputStream;
35import java.io.IOException;
36import java.io.OutputStream;
37import java.net.HttpURLConnection;
38import java.net.URL;
39
40/**
41 * A simple subclass of {@link ImageResizer} that fetches and resizes images fetched from a URL.
42 */
43public 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}