Skip to content

Most visited

Recently visited

navigation
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
}
This site uses cookies to store your preferences for site-specific language and display options.

Hooray!

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a one-minute survey?
Help us improve Android tools and documentation.