DisplayingBitmaps / src / com.example.android.displayingbitmaps / util /

ImageFetcher.java

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
}