├── .gitignore ├── README.md ├── library ├── AndroidManifest.xml ├── libs │ ├── android-support-v4.jar │ └── disklrucache-2.0.1.jar ├── pom.xml ├── project.properties ├── res │ └── values │ │ └── strings.xml └── src │ └── uk │ └── co │ └── senab │ └── bitmapcache │ ├── BitmapLruCache.java │ ├── BitmapMemoryLruCache.java │ ├── CacheableBitmapDrawable.java │ ├── CacheableImageView.java │ ├── Constants.java │ ├── IoUtils.java │ ├── Md5.java │ ├── SDK11.java │ └── WeakReferenceRunnable.java ├── pom.xml └── sample ├── AndroidManifest.xml ├── pom.xml ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── layout │ ├── activity_gridview.xml │ └── gridview_item_layout.xml └── values │ ├── colors.xml │ ├── dimens.xml │ └── strings.xml └── src └── uk └── co └── senab └── bitmapcache └── samples ├── GridViewActivity.java ├── LauncherActivity.java ├── NetworkedCacheableImageView.java ├── PugListAdapter.java ├── PugPagerAdapter.java ├── SDK11.java ├── SampleApplication.java └── ViewPagerActivity.java /.gitignore: -------------------------------------------------------------------------------- 1 | #Android generated 2 | bin 3 | gen 4 | 5 | #Eclipse 6 | .project 7 | .classpath 8 | .settings 9 | 10 | #IntelliJ IDEA 11 | .idea 12 | *.iml 13 | *.ipr 14 | *.iws 15 | out 16 | 17 | #Maven 18 | target 19 | release.properties 20 | pom.xml.* 21 | 22 | #Ant 23 | build.xml 24 | local.properties 25 | proguard.cfg 26 | 27 | #OSX 28 | .DS_Store 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android-BitmapCache 2 | ========================= 3 | 4 | This project came about as part of my blog post: [http://www.senab.co.uk/2012/07/01/android-bitmap-caching-revisited/](http://www.senab.co.uk/2012/07/01/android-bitmap-caching-revisited/) 5 | 6 | Android-BitmapCache is a specialised cache, for use with Android Bitmap objects. 7 | 8 | I have added a sample app to the source since, which can also be downloaded from the Downloads tab above. The sample app shows you how to use the library by creating a ViewPager of images downloaded from the web. These are cached in the LruCache and/or Disk Cache. 9 | 10 | ## Summary 11 | 12 | A cache which can be set to use multiple layers of caching for Bitmap objects 13 | in an Android app. Instances are created via a `BitmapLruCache.Builder` instance, 14 | which can be used to alter the settings of the resulting cache. 15 | 16 | Instances of this class should ideally be kept globally within your application, 17 | for example in the `Application` object. 18 | 19 | If you wish for the library and recycling feature to work, you **MUST** use the bundled [CacheableImageView](https://github.com/chrisbanes/Android-BitmapCache/blob/master/library/src/uk/co/senab/bitmapcache/CacheableImageView.java) wherever possible. 20 | 21 | ## Usage 22 | 23 | Clients can call `get(String)` to retrieve a cached value from the 24 | given Url. This will check all available caches for the value. There are also 25 | the `getFromDiskCache(String)` and `getFromMemoryCache(String)` 26 | which allow more granular access. 27 | 28 | There are a number of update methods. `put(String, InputStream)` and 29 | `put(String, InputStream, boolean)` are the preferred versions of the 30 | method, as they allow 1:1 caching to disk of the original content.
31 | `put(String, Bitmap)` and `put(String, Bitmap, boolean)` should 32 | only be used if you can't get access to the original InputStream. 33 | 34 | ## Obtaining 35 | The easy way to use the library is by downloading the JAR file, and importing it into your Eclipse project. You can find the latest JAR file from here: [http://bit.ly/android-bitmapcache-jar](http://bit.ly/android-bitmapcache-jar). Just remember that you must include all of the required libraries below too. 36 | 37 | If you are a Maven user you can also add this library as a dependency since it 38 | it distributed to the central repositories. Simply add the following to your 39 | `pom.xml`: 40 | 41 | ```xml 42 | 43 | com.github.chrisbanes.bitmapcache 44 | library 45 | (check pom.xml for latest version) 46 | 47 | ``` 48 | 49 | ### Requirements 50 | 51 | * [DiskLruCache](https://github.com/JakeWharton/DiskLruCache). Latest available. 52 | * [Android v4 Support Library](http://developer.android.com/tools/extras/support-library.html). Latest available. 53 | 54 | ## License 55 | 56 | Copyright 2011, 2012, 2013 Chris Banes 57 | 58 | Licensed under the Apache License, Version 2.0 (the "License"); 59 | you may not use this file except in compliance with the License. 60 | You may obtain a copy of the License at 61 | 62 | http://www.apache.org/licenses/LICENSE-2.0 63 | 64 | Unless required by applicable law or agreed to in writing, software 65 | distributed under the License is distributed on an "AS IS" BASIS, 66 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 67 | See the License for the specific language governing permissions and 68 | limitations under the License. -------------------------------------------------------------------------------- /library/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /library/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbanes/Android-BitmapCache/147e5c078324c8f61765750da756c4a7d1f2aae3/library/libs/android-support-v4.jar -------------------------------------------------------------------------------- /library/libs/disklrucache-2.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbanes/Android-BitmapCache/147e5c078324c8f61765750da756c4a7d1f2aae3/library/libs/disklrucache-2.0.1.jar -------------------------------------------------------------------------------- /library/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.github.chrisbanes.bitmapcache 6 | library 7 | jar 8 | Android-BitmapCache Library 9 | 10 | 11 | com.github.chrisbanes.bitmapcache 12 | parent 13 | 2.3 14 | 15 | 16 | 17 | 18 | com.google.android 19 | android 20 | 21 | 22 | com.jakewharton 23 | disklrucache 24 | jar 25 | 2.0.1 26 | 27 | 28 | com.google.android 29 | support-v4 30 | r7 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /library/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-16 15 | android.library=true 16 | -------------------------------------------------------------------------------- /library/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Android-BitmapMemoryCache 4 | 5 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/bitmapcache/BitmapLruCache.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 uk.co.senab.bitmapcache; 18 | 19 | import com.jakewharton.disklrucache.DiskLruCache; 20 | 21 | import android.content.Context; 22 | import android.content.res.Resources; 23 | import android.graphics.Bitmap; 24 | import android.graphics.BitmapFactory; 25 | import android.os.AsyncTask; 26 | import android.os.Build; 27 | import android.os.Looper; 28 | import android.os.Process; 29 | import android.util.Log; 30 | 31 | import java.io.File; 32 | import java.io.FileInputStream; 33 | import java.io.FileNotFoundException; 34 | import java.io.IOException; 35 | import java.io.InputStream; 36 | import java.io.OutputStream; 37 | import java.util.HashMap; 38 | import java.util.concurrent.ScheduledFuture; 39 | import java.util.concurrent.ScheduledThreadPoolExecutor; 40 | import java.util.concurrent.TimeUnit; 41 | import java.util.concurrent.locks.ReentrantLock; 42 | 43 | /** 44 | * A cache which can be set to use multiple layers of caching for Bitmap objects in an Android app. 45 | * Instances are created via a {@link Builder} instance, which can be used to alter the settings of 46 | * the resulting cache. 47 | * 48 | *

Instances of this class should ideally be kept globally with the application, for example in 49 | * the {@link android.app.Application Application} object. You should also use the bundled {@link 50 | * CacheableImageView} wherever possible, as the memory cache has a closeStream relationship with it. 51 | *

52 | * 53 | *

Clients can call {@link #get(String)} to retrieve a cached value from the given Url. This 54 | * will check all available caches for the value. There are also the {@link 55 | * #getFromDiskCache(String, android.graphics.BitmapFactory.Options)} and {@link 56 | * #getFromMemoryCache(String)} which allow more granular access.

57 | * 58 | *

There are a number of update methods. {@link #put(String, InputStream)} and {@link 59 | * #put(String, InputStream)} are the preferred versions of the method, as they allow 1:1 caching to 60 | * disk of the original content.
{@link #put(String, Bitmap)}} should only be used if you 61 | * can't get access to the original InputStream.

62 | * 63 | * @author Chris Banes 64 | */ 65 | public class BitmapLruCache { 66 | 67 | /** 68 | * The recycle policy controls if the {@link android.graphics.Bitmap#recycle()} is automatically 69 | * called, when it is no longer being used. To set this, use the {@link 70 | * Builder#setRecyclePolicy(uk.co.senab.bitmapcache.BitmapLruCache.RecyclePolicy) 71 | * Builder.setRecyclePolicy()} method. 72 | */ 73 | public static enum RecyclePolicy { 74 | /** 75 | * The Bitmap is never recycled automatically. 76 | */ 77 | DISABLED, 78 | 79 | /** 80 | * The Bitmap is only automatically recycled if running on a device API v10 or earlier. 81 | */ 82 | PRE_HONEYCOMB_ONLY, 83 | 84 | /** 85 | * The Bitmap is always recycled when no longer being used. This is the default. 86 | */ 87 | ALWAYS; 88 | 89 | boolean canInBitmap() { 90 | switch (this) { 91 | case PRE_HONEYCOMB_ONLY: 92 | case DISABLED: 93 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; 94 | } 95 | return false; 96 | } 97 | 98 | boolean canRecycle() { 99 | switch (this) { 100 | case DISABLED: 101 | return false; 102 | case PRE_HONEYCOMB_ONLY: 103 | return Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB; 104 | case ALWAYS: 105 | return true; 106 | } 107 | 108 | return false; 109 | } 110 | } 111 | 112 | // The number of seconds after the last edit that the Disk Cache should be 113 | // flushed 114 | static final int DISK_CACHE_FLUSH_DELAY_SECS = 5; 115 | 116 | /** 117 | * @throws IllegalStateException if the calling thread is the main/UI thread. 118 | */ 119 | private static void checkNotOnMainThread() { 120 | if (Looper.myLooper() == Looper.getMainLooper()) { 121 | throw new IllegalStateException( 122 | "This method should not be called from the main/UI thread."); 123 | } 124 | } 125 | 126 | /** 127 | * The disk cache only accepts a reduced range of characters for the key values. This method 128 | * transforms the {@code url} into something accepted from {@link DiskLruCache}. Currently we 129 | * simply return a MD5 hash of the url. 130 | * 131 | * @param url - Key to be transformed 132 | * @return key which can be used for the disk cache 133 | */ 134 | private static String transformUrlForDiskCacheKey(String url) { 135 | return Md5.encode(url); 136 | } 137 | 138 | private File mTempDir; 139 | 140 | private Resources mResources; 141 | 142 | /** 143 | * Memory Cache Variables 144 | */ 145 | private BitmapMemoryLruCache mMemoryCache; 146 | 147 | private RecyclePolicy mRecyclePolicy; 148 | 149 | /** 150 | * Disk Cache Variables 151 | */ 152 | private DiskLruCache mDiskCache; 153 | 154 | // Variables which are only used when the Disk Cache is enabled 155 | private HashMap mDiskCacheEditLocks; 156 | 157 | private ScheduledThreadPoolExecutor mDiskCacheFlusherExecutor; 158 | 159 | private DiskCacheFlushRunnable mDiskCacheFlusherRunnable; 160 | 161 | // Transient 162 | private ScheduledFuture mDiskCacheFuture; 163 | 164 | BitmapLruCache(Context context) { 165 | if (null != context) { 166 | // Make sure we have the application context 167 | context = context.getApplicationContext(); 168 | 169 | mTempDir = context.getCacheDir(); 170 | mResources = context.getResources(); 171 | } 172 | } 173 | 174 | /** 175 | * Returns whether any of the enabled caches contain the specified URL.

If you have the 176 | * disk cache enabled, you should not call this method from main/UI thread. 177 | * 178 | * @param url the URL to search for. 179 | * @return {@code true} if any of the caches contain the specified URL, {@code false} 180 | * otherwise. 181 | */ 182 | public boolean contains(String url) { 183 | return containsInMemoryCache(url) || containsInDiskCache(url); 184 | } 185 | 186 | /** 187 | * Returns whether the Disk Cache contains the specified URL. You should not call this method 188 | * from main/UI thread. 189 | * 190 | * @param url the URL to search for. 191 | * @return {@code true} if the Disk Cache is enabled and contains the specified URL, {@code 192 | * false} otherwise. 193 | */ 194 | public boolean containsInDiskCache(String url) { 195 | if (null != mDiskCache) { 196 | checkNotOnMainThread(); 197 | 198 | try { 199 | return null != mDiskCache.get(transformUrlForDiskCacheKey(url)); 200 | } catch (IOException e) { 201 | e.printStackTrace(); 202 | } 203 | } 204 | 205 | return false; 206 | } 207 | 208 | /** 209 | * Returns whether the Memory Cache contains the specified URL. This method is safe to be called 210 | * from the main thread. 211 | * 212 | * @param url the URL to search for. 213 | * @return {@code true} if the Memory Cache is enabled and contains the specified URL, {@code 214 | * false} otherwise. 215 | */ 216 | public boolean containsInMemoryCache(String url) { 217 | return null != mMemoryCache && null != mMemoryCache.get(url); 218 | } 219 | 220 | /** 221 | * Returns the value for {@code url}. This will check all caches currently enabled.

If you 222 | * have the disk cache enabled, you should not call this method from main/UI thread. 223 | * 224 | * @param url - String representing the URL of the image 225 | */ 226 | public CacheableBitmapDrawable get(String url) { 227 | return get(url, null); 228 | } 229 | 230 | /** 231 | * Returns the value for {@code url}. This will check all caches currently enabled.

If you 232 | * have the disk cache enabled, you should not call this method from main/UI thread. 233 | * 234 | * @param url - String representing the URL of the image 235 | * @param decodeOpts - Options used for decoding the contents from the disk cache only. 236 | */ 237 | public CacheableBitmapDrawable get(String url, BitmapFactory.Options decodeOpts) { 238 | CacheableBitmapDrawable result; 239 | 240 | // First try Memory Cache 241 | result = getFromMemoryCache(url); 242 | 243 | if (null == result) { 244 | // Memory Cache failed, so try Disk Cache 245 | result = getFromDiskCache(url, decodeOpts); 246 | } 247 | 248 | return result; 249 | } 250 | 251 | /** 252 | * Returns the value for {@code url} in the disk cache only. You should not call this method 253 | * from main/UI thread.

If enabled, the result of this method will be cached in the memory 254 | * cache.

Unless you have a specific requirement to only query the disk cache, you should 255 | * call {@link #get(String)} instead. 256 | * 257 | * @param url - String representing the URL of the image 258 | * @param decodeOpts - Options used for decoding the contents from the disk cache. 259 | * @return Value for {@code url} from disk cache, or {@code null} if the disk cache is not 260 | * enabled. 261 | */ 262 | public CacheableBitmapDrawable getFromDiskCache(final String url, 263 | final BitmapFactory.Options decodeOpts) { 264 | CacheableBitmapDrawable result = null; 265 | 266 | if (null != mDiskCache) { 267 | checkNotOnMainThread(); 268 | 269 | try { 270 | final String key = transformUrlForDiskCacheKey(url); 271 | // Try and decode bitmap 272 | result = decodeBitmap(new SnapshotInputStreamProvider(key), url, decodeOpts); 273 | 274 | if (null != result) { 275 | if (null != mMemoryCache) { 276 | mMemoryCache.put(result); 277 | } 278 | } else { 279 | // If we get here, the file in the cache can't be 280 | // decoded. Remove it and schedule a flush. 281 | mDiskCache.remove(key); 282 | scheduleDiskCacheFlush(); 283 | } 284 | } catch (IOException e) { 285 | e.printStackTrace(); 286 | } 287 | } 288 | 289 | return result; 290 | } 291 | 292 | /** 293 | * Returns the value for {@code url} in the memory cache only. This method is safe to be called 294 | * from the main thread.

You should check the result of this method before starting a 295 | * threaded call. 296 | * 297 | * @param url - String representing the URL of the image 298 | * @return Value for {@code url} from memory cache, or {@code null} if the disk cache is not 299 | * enabled. 300 | */ 301 | public CacheableBitmapDrawable getFromMemoryCache(final String url) { 302 | CacheableBitmapDrawable result = null; 303 | 304 | if (null != mMemoryCache) { 305 | synchronized (mMemoryCache) { 306 | result = mMemoryCache.get(url); 307 | 308 | // If we get a value, but it has a invalid bitmap, remove it 309 | if (null != result && !result.isBitmapValid()) { 310 | mMemoryCache.remove(url); 311 | result = null; 312 | } 313 | } 314 | } 315 | 316 | return result; 317 | } 318 | 319 | /** 320 | * @return true if the Disk Cache is enabled. 321 | */ 322 | public boolean isDiskCacheEnabled() { 323 | return null != mDiskCache; 324 | } 325 | 326 | /** 327 | * @return true if the Memory Cache is enabled. 328 | */ 329 | public boolean isMemoryCacheEnabled() { 330 | return null != mMemoryCache; 331 | } 332 | 333 | /** 334 | * Caches {@code bitmap} for {@code url} into all enabled caches. If the disk cache is enabled, 335 | * the bitmap will be compressed losslessly.

If you have the disk cache enabled, you should 336 | * not call this method from main/UI thread. 337 | * 338 | * @param url - String representing the URL of the image. 339 | * @param bitmap - Bitmap which has been decoded from {@code url}. 340 | * @return CacheableBitmapDrawable which can be used to display the bitmap. 341 | */ 342 | public CacheableBitmapDrawable put(final String url, final Bitmap bitmap) { 343 | return put(url, bitmap, Bitmap.CompressFormat.PNG, 100); 344 | } 345 | 346 | /** 347 | * Caches {@code bitmap} for {@code url} into all enabled caches. If the disk cache is enabled, 348 | * the bitmap will be compressed with the settings you provide. 349 | *

If you have the disk cache enabled, you should not call this method from main/UI thread. 350 | * 351 | * @param url - String representing the URL of the image. 352 | * @param bitmap - Bitmap which has been decoded from {@code url}. 353 | * @param compressFormat - Compression Format to use 354 | * @param compressQuality - Level of compression to use 355 | * @return CacheableBitmapDrawable which can be used to display the bitmap. 356 | * 357 | * @see Bitmap#compress(Bitmap.CompressFormat, int, OutputStream) 358 | */ 359 | public CacheableBitmapDrawable put(final String url, final Bitmap bitmap, 360 | Bitmap.CompressFormat compressFormat, int compressQuality) { 361 | 362 | CacheableBitmapDrawable d = new CacheableBitmapDrawable(url, mResources, bitmap, 363 | mRecyclePolicy, CacheableBitmapDrawable.SOURCE_UNKNOWN); 364 | 365 | if (null != mMemoryCache) { 366 | mMemoryCache.put(d); 367 | } 368 | 369 | if (null != mDiskCache) { 370 | checkNotOnMainThread(); 371 | 372 | final String key = transformUrlForDiskCacheKey(url); 373 | final ReentrantLock lock = getLockForDiskCacheEdit(key); 374 | lock.lock(); 375 | 376 | OutputStream os = null; 377 | 378 | try { 379 | DiskLruCache.Editor editor = mDiskCache.edit(key); 380 | os = editor.newOutputStream(0); 381 | bitmap.compress(compressFormat, compressQuality, os); 382 | os.flush(); 383 | editor.commit(); 384 | } catch (IOException e) { 385 | Log.e(Constants.LOG_TAG, "Error while writing to disk cache", e); 386 | } finally { 387 | IoUtils.closeStream(os); 388 | lock.unlock(); 389 | scheduleDiskCacheFlush(); 390 | } 391 | } 392 | 393 | return d; 394 | } 395 | 396 | /** 397 | * Caches resulting bitmap from {@code inputStream} for {@code url} into all enabled caches. 398 | * This version of the method should be preferred as it allows the original image contents to be 399 | * cached, rather than a re-compressed version.

The contents of the InputStream will be 400 | * copied to a temporary file, then the file will be decoded into a Bitmap. Providing the decode 401 | * worked:

You should not call this method from the main/UI thread. 404 | * 405 | * @param url - String representing the URL of the image 406 | * @param inputStream - InputStream opened from {@code url} 407 | * @return CacheableBitmapDrawable which can be used to display the bitmap. 408 | */ 409 | public CacheableBitmapDrawable put(final String url, final InputStream inputStream) { 410 | return put(url, inputStream, null); 411 | } 412 | 413 | /** 414 | * Caches resulting bitmap from {@code inputStream} for {@code url} into all enabled caches. 415 | * This version of the method should be preferred as it allows the original image contents to be 416 | * cached, rather than a re-compressed version.

The contents of the InputStream will be 417 | * copied to a temporary file, then the file will be decoded into a Bitmap, using the optional 418 | * decodeOpts. Providing the decode worked:

You should not 421 | * call this method from the main/UI thread. 422 | * 423 | * @param url - String representing the URL of the image 424 | * @param inputStream - InputStream opened from {@code url} 425 | * @param decodeOpts - Options used for decoding. This does not affect what is cached in the 426 | * disk cache (if enabled). 427 | * @return CacheableBitmapDrawable which can be used to display the bitmap. 428 | */ 429 | public CacheableBitmapDrawable put(final String url, final InputStream inputStream, 430 | final BitmapFactory.Options decodeOpts) { 431 | checkNotOnMainThread(); 432 | 433 | // First we need to save the stream contents to a temporary file, so it 434 | // can be read multiple times 435 | File tmpFile = null; 436 | try { 437 | tmpFile = File.createTempFile("bitmapcache_", null, mTempDir); 438 | 439 | // Pipe InputStream to file 440 | IoUtils.copy(inputStream, tmpFile); 441 | } catch (IOException e) { 442 | Log.e(Constants.LOG_TAG, "Error writing to saving stream to temp file: " + url, e); 443 | } 444 | 445 | CacheableBitmapDrawable d = null; 446 | 447 | if (null != tmpFile) { 448 | // Try and decode File 449 | d = decodeBitmap(new FileInputStreamProvider(tmpFile), url, decodeOpts); 450 | 451 | if (d != null) { 452 | if (null != mMemoryCache) { 453 | d.setCached(true); 454 | mMemoryCache.put(d.getUrl(), d); 455 | } 456 | 457 | if (null != mDiskCache) { 458 | final String key = transformUrlForDiskCacheKey(url); 459 | final ReentrantLock lock = getLockForDiskCacheEdit(url); 460 | lock.lock(); 461 | 462 | try { 463 | DiskLruCache.Editor editor = mDiskCache.edit(key); 464 | IoUtils.copy(tmpFile, editor.newOutputStream(0)); 465 | editor.commit(); 466 | } catch (IOException e) { 467 | Log.e(Constants.LOG_TAG, "Error writing to disk cache. URL: " + url, e); 468 | } finally { 469 | lock.unlock(); 470 | scheduleDiskCacheFlush(); 471 | } 472 | } 473 | } 474 | 475 | // Finally, delete the temporary file 476 | tmpFile.delete(); 477 | } 478 | 479 | return d; 480 | } 481 | 482 | /** 483 | * Removes the entry for {@code url} from all enabled caches, if it exists.

If you have the 484 | * disk cache enabled, you should not call this method from main/UI thread. 485 | */ 486 | public void remove(String url) { 487 | if (null != mMemoryCache) { 488 | mMemoryCache.remove(url); 489 | } 490 | 491 | if (null != mDiskCache) { 492 | checkNotOnMainThread(); 493 | 494 | try { 495 | mDiskCache.remove(transformUrlForDiskCacheKey(url)); 496 | scheduleDiskCacheFlush(); 497 | } catch (IOException e) { 498 | e.printStackTrace(); 499 | } 500 | } 501 | } 502 | 503 | /** 504 | * This method iterates through the memory cache (if enabled) and removes any entries which are 505 | * not currently being displayed. A good place to call this would be from {@link 506 | * android.app.Application#onLowMemory() Application.onLowMemory()}. 507 | */ 508 | public void trimMemory() { 509 | if (null != mMemoryCache) { 510 | mMemoryCache.trimMemory(); 511 | } 512 | } 513 | 514 | synchronized void setDiskCache(DiskLruCache diskCache) { 515 | mDiskCache = diskCache; 516 | 517 | if (null != diskCache) { 518 | mDiskCacheEditLocks = new HashMap(); 519 | mDiskCacheFlusherExecutor = new ScheduledThreadPoolExecutor(1); 520 | mDiskCacheFlusherRunnable = new DiskCacheFlushRunnable(diskCache); 521 | } 522 | } 523 | 524 | void setMemoryCache(BitmapMemoryLruCache memoryCache) { 525 | mMemoryCache = memoryCache; 526 | mRecyclePolicy = memoryCache.getRecyclePolicy(); 527 | } 528 | 529 | private ReentrantLock getLockForDiskCacheEdit(String url) { 530 | synchronized (mDiskCacheEditLocks) { 531 | ReentrantLock lock = mDiskCacheEditLocks.get(url); 532 | if (null == lock) { 533 | lock = new ReentrantLock(); 534 | mDiskCacheEditLocks.put(url, lock); 535 | } 536 | return lock; 537 | } 538 | } 539 | 540 | private void scheduleDiskCacheFlush() { 541 | // If we already have a flush scheduled, cancel it 542 | if (null != mDiskCacheFuture) { 543 | mDiskCacheFuture.cancel(false); 544 | } 545 | 546 | // Schedule a flush 547 | mDiskCacheFuture = mDiskCacheFlusherExecutor 548 | .schedule(mDiskCacheFlusherRunnable, DISK_CACHE_FLUSH_DELAY_SECS, 549 | TimeUnit.SECONDS); 550 | } 551 | 552 | private CacheableBitmapDrawable decodeBitmap(InputStreamProvider ip, String url, 553 | BitmapFactory.Options opts) { 554 | 555 | Bitmap bm = null; 556 | InputStream is = null; 557 | int source = CacheableBitmapDrawable.SOURCE_NEW; 558 | 559 | try { 560 | if (mRecyclePolicy.canInBitmap()) { 561 | // Create an options instance if we haven't been provided with one 562 | if (opts == null) { 563 | opts = new BitmapFactory.Options(); 564 | } 565 | 566 | if (opts.inSampleSize <= 1) { 567 | opts.inSampleSize = 1; 568 | 569 | if (addInBitmapOptions(ip, opts)) { 570 | source = CacheableBitmapDrawable.SOURCE_INBITMAP; 571 | } 572 | } 573 | } 574 | 575 | // Get InputStream for actual decode 576 | is = ip.getInputStream(); 577 | // Decode stream 578 | bm = BitmapFactory.decodeStream(is, null, opts); 579 | } catch (Exception e) { 580 | Log.e(Constants.LOG_TAG, "Unable to decode stream", e); 581 | } finally { 582 | IoUtils.closeStream(is); 583 | } 584 | 585 | if (bm != null) { 586 | return new CacheableBitmapDrawable(url, mResources, bm, mRecyclePolicy, source); 587 | } 588 | return null; 589 | } 590 | 591 | private boolean addInBitmapOptions(InputStreamProvider ip, BitmapFactory.Options opts) { 592 | // Create InputStream for decoding the bounds 593 | final InputStream is = ip.getInputStream(); 594 | // Decode the bounds so we know what size Bitmap to look for 595 | opts.inJustDecodeBounds = true; 596 | BitmapFactory.decodeStream(is, null, opts); 597 | IoUtils.closeStream(is); 598 | 599 | // Turn off just decoding bounds 600 | opts.inJustDecodeBounds = false; 601 | // Make sure the decoded file is mutable 602 | opts.inMutable = true; 603 | 604 | // Try and find Bitmap to use for inBitmap 605 | Bitmap reusableBm = mMemoryCache.getBitmapFromRemoved(opts.outWidth, opts.outHeight); 606 | if (reusableBm != null) { 607 | if (Constants.DEBUG) { 608 | Log.i(Constants.LOG_TAG, "Using inBitmap"); 609 | } 610 | SDK11.addInBitmapOption(opts, reusableBm); 611 | return true; 612 | } 613 | 614 | return false; 615 | } 616 | 617 | /** 618 | * Builder class for {link {@link BitmapLruCache}. An example call: 619 | * 620 | *

621 |      * BitmapLruCache.Builder builder = new BitmapLruCache.Builder();
622 |      * builder.setMemoryCacheEnabled(true).setMemoryCacheMaxSizeUsingHeapSize(this);
623 |      * builder.setDiskCacheEnabled(true).setDiskCacheLocation(...);
624 |      *
625 |      * BitmapLruCache cache = builder.build();
626 |      * 
627 | * 628 | * @author Chris Banes 629 | */ 630 | public final static class Builder { 631 | 632 | static final int MEGABYTE = 1024 * 1024; 633 | 634 | static final float DEFAULT_MEMORY_CACHE_HEAP_RATIO = 1f / 8f; 635 | 636 | static final float MAX_MEMORY_CACHE_HEAP_RATIO = 0.75f; 637 | 638 | static final int DEFAULT_DISK_CACHE_MAX_SIZE_MB = 10; 639 | 640 | static final int DEFAULT_MEM_CACHE_MAX_SIZE_MB = 3; 641 | 642 | static final RecyclePolicy DEFAULT_RECYCLE_POLICY = RecyclePolicy.PRE_HONEYCOMB_ONLY; 643 | 644 | // Only used for Javadoc 645 | static final float DEFAULT_MEMORY_CACHE_HEAP_PERCENTAGE = DEFAULT_MEMORY_CACHE_HEAP_RATIO 646 | * 100; 647 | 648 | static final float MAX_MEMORY_CACHE_HEAP_PERCENTAGE = MAX_MEMORY_CACHE_HEAP_RATIO * 100; 649 | 650 | private static long getHeapSize() { 651 | return Runtime.getRuntime().maxMemory(); 652 | } 653 | 654 | private Context mContext; 655 | 656 | private boolean mDiskCacheEnabled; 657 | 658 | private File mDiskCacheLocation; 659 | 660 | private long mDiskCacheMaxSize; 661 | 662 | private boolean mMemoryCacheEnabled; 663 | 664 | private int mMemoryCacheMaxSize; 665 | 666 | private RecyclePolicy mRecyclePolicy; 667 | 668 | /** 669 | * @deprecated You should now use {@link Builder(Context)}. This is so that we can reliably 670 | * set up correctly. 671 | */ 672 | public Builder() { 673 | this(null); 674 | } 675 | 676 | public Builder(Context context) { 677 | mContext = context; 678 | 679 | // Disk Cache is disabled by default, but it's default size is set 680 | mDiskCacheMaxSize = DEFAULT_DISK_CACHE_MAX_SIZE_MB * MEGABYTE; 681 | 682 | // Memory Cache is enabled by default, with a small maximum size 683 | mMemoryCacheEnabled = true; 684 | mMemoryCacheMaxSize = DEFAULT_MEM_CACHE_MAX_SIZE_MB * MEGABYTE; 685 | mRecyclePolicy = DEFAULT_RECYCLE_POLICY; 686 | } 687 | 688 | /** 689 | * @return A new {@link BitmapLruCache} created with the arguments supplied to this 690 | * builder. 691 | */ 692 | public BitmapLruCache build() { 693 | final BitmapLruCache cache = new BitmapLruCache(mContext); 694 | 695 | if (isValidOptionsForMemoryCache()) { 696 | if (Constants.DEBUG) { 697 | Log.d("BitmapLruCache.Builder", "Creating Memory Cache"); 698 | } 699 | cache.setMemoryCache(new BitmapMemoryLruCache(mMemoryCacheMaxSize, mRecyclePolicy)); 700 | } 701 | 702 | if (isValidOptionsForDiskCache()) { 703 | new AsyncTask() { 704 | 705 | @Override 706 | protected DiskLruCache doInBackground(Void... params) { 707 | try { 708 | return DiskLruCache.open(mDiskCacheLocation, 0, 1, mDiskCacheMaxSize); 709 | } catch (IOException e) { 710 | e.printStackTrace(); 711 | return null; 712 | } 713 | } 714 | 715 | @Override 716 | protected void onPostExecute(DiskLruCache result) { 717 | cache.setDiskCache(result); 718 | } 719 | 720 | }.execute(); 721 | } 722 | 723 | return cache; 724 | } 725 | 726 | /** 727 | * Set whether the Disk Cache should be enabled. Defaults to {@code false}. 728 | * 729 | * @return This Builder object to allow for chaining of calls to set methods. 730 | */ 731 | public Builder setDiskCacheEnabled(boolean enabled) { 732 | mDiskCacheEnabled = enabled; 733 | return this; 734 | } 735 | 736 | /** 737 | * Set the Disk Cache location. This location should be read-writeable. 738 | * 739 | * @return This Builder object to allow for chaining of calls to set methods. 740 | */ 741 | public Builder setDiskCacheLocation(File location) { 742 | mDiskCacheLocation = location; 743 | return this; 744 | } 745 | 746 | /** 747 | * Set the maximum number of bytes the Disk Cache should use to store values. Defaults to 748 | * {@value #DEFAULT_DISK_CACHE_MAX_SIZE_MB}MB. 749 | * 750 | * @return This Builder object to allow for chaining of calls to set methods. 751 | */ 752 | public Builder setDiskCacheMaxSize(long maxSize) { 753 | mDiskCacheMaxSize = maxSize; 754 | return this; 755 | } 756 | 757 | /** 758 | * Set whether the Memory Cache should be enabled. Defaults to {@code true}. 759 | * 760 | * @return This Builder object to allow for chaining of calls to set methods. 761 | */ 762 | public Builder setMemoryCacheEnabled(boolean enabled) { 763 | mMemoryCacheEnabled = enabled; 764 | return this; 765 | } 766 | 767 | /** 768 | * Set the maximum number of bytes the Memory Cache should use to store values. Defaults to 769 | * {@value #DEFAULT_MEM_CACHE_MAX_SIZE_MB}MB. 770 | * 771 | * @return This Builder object to allow for chaining of calls to set methods. 772 | */ 773 | public Builder setMemoryCacheMaxSize(int size) { 774 | mMemoryCacheMaxSize = size; 775 | return this; 776 | } 777 | 778 | /** 779 | * Sets the Memory Cache maximum size to be the default value of {@value 780 | * #DEFAULT_MEMORY_CACHE_HEAP_PERCENTAGE}% of heap size. 781 | * 782 | * @return This Builder object to allow for chaining of calls to set methods. 783 | */ 784 | public Builder setMemoryCacheMaxSizeUsingHeapSize() { 785 | return setMemoryCacheMaxSizeUsingHeapSize(DEFAULT_MEMORY_CACHE_HEAP_RATIO); 786 | } 787 | 788 | /** 789 | * Sets the Memory Cache maximum size to be the given percentage of heap size. This is 790 | * capped at {@value #MAX_MEMORY_CACHE_HEAP_PERCENTAGE}% of the app heap size. 791 | * 792 | * @param percentageOfHeap - percentage of heap size. Valid values are 0.0 <= x <= {@value 793 | * #MAX_MEMORY_CACHE_HEAP_RATIO}. 794 | * @return This Builder object to allow for chaining of calls to set methods. 795 | */ 796 | public Builder setMemoryCacheMaxSizeUsingHeapSize(float percentageOfHeap) { 797 | int size = Math 798 | .round(getHeapSize() * Math.min(percentageOfHeap, MAX_MEMORY_CACHE_HEAP_RATIO)); 799 | return setMemoryCacheMaxSize(size); 800 | } 801 | 802 | /** 803 | * Sets the recycle policy. This controls if {@link android.graphics.Bitmap#recycle()} is 804 | * called. 805 | * 806 | * @param recyclePolicy - New recycle policy, can not be null. 807 | * @return This Builder object to allow for chaining of calls to set methods. 808 | */ 809 | public Builder setRecyclePolicy(RecyclePolicy recyclePolicy) { 810 | if (null == recyclePolicy) { 811 | throw new IllegalArgumentException("The recycle policy can not be null"); 812 | } 813 | 814 | mRecyclePolicy = recyclePolicy; 815 | return this; 816 | } 817 | 818 | private boolean isValidOptionsForDiskCache() { 819 | boolean valid = mDiskCacheEnabled; 820 | 821 | if (valid) { 822 | if (null == mDiskCacheLocation) { 823 | Log.i(Constants.LOG_TAG, 824 | "Disk Cache has been enabled, but no location given. Please call setDiskCacheLocation(...)"); 825 | valid = false; 826 | } else if (!mDiskCacheLocation.canWrite()) { 827 | Log.i(Constants.LOG_TAG, 828 | "Disk Cache Location is not write-able, disabling disk caching."); 829 | valid = false; 830 | } 831 | } 832 | 833 | return valid; 834 | } 835 | 836 | private boolean isValidOptionsForMemoryCache() { 837 | return mMemoryCacheEnabled && mMemoryCacheMaxSize > 0; 838 | } 839 | } 840 | 841 | static final class DiskCacheFlushRunnable implements Runnable { 842 | 843 | private final DiskLruCache mDiskCache; 844 | 845 | public DiskCacheFlushRunnable(DiskLruCache cache) { 846 | mDiskCache = cache; 847 | } 848 | 849 | public void run() { 850 | // Make sure we're running with a background priority 851 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 852 | 853 | if (Constants.DEBUG) { 854 | Log.d(Constants.LOG_TAG, "Flushing Disk Cache"); 855 | } 856 | try { 857 | mDiskCache.flush(); 858 | } catch (IOException e) { 859 | e.printStackTrace(); 860 | } 861 | } 862 | } 863 | 864 | interface InputStreamProvider { 865 | InputStream getInputStream(); 866 | } 867 | 868 | static class FileInputStreamProvider implements InputStreamProvider { 869 | final File mFile; 870 | 871 | FileInputStreamProvider(File file) { 872 | mFile = file; 873 | } 874 | 875 | @Override 876 | public InputStream getInputStream() { 877 | try { 878 | return new FileInputStream(mFile); 879 | } catch (FileNotFoundException e) { 880 | Log.e(Constants.LOG_TAG, "Could not decode file: " + mFile.getAbsolutePath(), e); 881 | } 882 | return null; 883 | } 884 | } 885 | 886 | final class SnapshotInputStreamProvider implements InputStreamProvider { 887 | final String mKey; 888 | 889 | SnapshotInputStreamProvider(String key) { 890 | mKey = key; 891 | } 892 | 893 | @Override 894 | public InputStream getInputStream() { 895 | try { 896 | DiskLruCache.Snapshot snapshot = mDiskCache.get(mKey); 897 | if (snapshot != null) { 898 | return snapshot.getInputStream(0); 899 | } 900 | } catch (IOException e) { 901 | Log.e(Constants.LOG_TAG, "Could open disk cache for url: " + mKey, e); 902 | } 903 | return null; 904 | } 905 | } 906 | } 907 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/bitmapcache/BitmapMemoryLruCache.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 | package uk.co.senab.bitmapcache; 17 | 18 | import android.graphics.Bitmap; 19 | import android.support.v4.util.LruCache; 20 | 21 | import java.lang.ref.SoftReference; 22 | import java.util.Collections; 23 | import java.util.HashSet; 24 | import java.util.Iterator; 25 | import java.util.Map.Entry; 26 | import java.util.Set; 27 | 28 | final class BitmapMemoryLruCache extends LruCache { 29 | 30 | private final Set> mRemovedEntries; 31 | private final BitmapLruCache.RecyclePolicy mRecyclePolicy; 32 | 33 | BitmapMemoryLruCache(int maxSize, BitmapLruCache.RecyclePolicy policy) { 34 | super(maxSize); 35 | 36 | mRecyclePolicy = policy; 37 | mRemovedEntries = policy.canInBitmap() 38 | ? Collections.synchronizedSet(new HashSet>()) 39 | : null; 40 | } 41 | 42 | CacheableBitmapDrawable put(CacheableBitmapDrawable value) { 43 | if (null != value) { 44 | value.setCached(true); 45 | return put(value.getUrl(), value); 46 | } 47 | 48 | return null; 49 | } 50 | 51 | BitmapLruCache.RecyclePolicy getRecyclePolicy() { 52 | return mRecyclePolicy; 53 | } 54 | 55 | @Override 56 | protected int sizeOf(String key, CacheableBitmapDrawable value) { 57 | return value.getMemorySize(); 58 | } 59 | 60 | @Override 61 | protected void entryRemoved(boolean evicted, String key, CacheableBitmapDrawable oldValue, 62 | CacheableBitmapDrawable newValue) { 63 | // Notify the wrapper that it's no longer being cached 64 | oldValue.setCached(false); 65 | 66 | if (mRemovedEntries != null && oldValue.isBitmapValid() && oldValue.isBitmapMutable()) { 67 | synchronized (mRemovedEntries) { 68 | mRemovedEntries.add(new SoftReference(oldValue)); 69 | } 70 | } 71 | } 72 | 73 | Bitmap getBitmapFromRemoved(final int width, final int height) { 74 | if (mRemovedEntries == null) { 75 | return null; 76 | } 77 | 78 | Bitmap result = null; 79 | 80 | synchronized (mRemovedEntries) { 81 | final Iterator> it = mRemovedEntries.iterator(); 82 | 83 | while (it.hasNext()) { 84 | CacheableBitmapDrawable value = it.next().get(); 85 | 86 | if (value != null && value.isBitmapValid() && value.isBitmapMutable()) { 87 | if (value.getIntrinsicWidth() == width 88 | && value.getIntrinsicHeight() == height) { 89 | it.remove(); 90 | result = value.getBitmap(); 91 | break; 92 | } 93 | } else { 94 | it.remove(); 95 | } 96 | } 97 | } 98 | 99 | return result; 100 | } 101 | 102 | void trimMemory() { 103 | final Set> values = snapshot().entrySet(); 104 | 105 | for (Entry entry : values) { 106 | CacheableBitmapDrawable value = entry.getValue(); 107 | if (null == value || !value.isBeingDisplayed()) { 108 | remove(entry.getKey()); 109 | } 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/bitmapcache/CacheableBitmapDrawable.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 uk.co.senab.bitmapcache; 18 | 19 | import android.content.res.Resources; 20 | import android.graphics.Bitmap; 21 | import android.graphics.Canvas; 22 | import android.graphics.drawable.BitmapDrawable; 23 | import android.os.Handler; 24 | import android.os.Looper; 25 | import android.util.Log; 26 | 27 | public class CacheableBitmapDrawable extends BitmapDrawable { 28 | 29 | public static final int SOURCE_UNKNOWN = -1; 30 | public static final int SOURCE_NEW = 0; 31 | public static final int SOURCE_INBITMAP = 1; 32 | 33 | static final String LOG_TAG = "CacheableBitmapDrawable"; 34 | 35 | // URL Associated with this Bitmap 36 | private final String mUrl; 37 | 38 | private BitmapLruCache.RecyclePolicy mRecyclePolicy; 39 | 40 | // Number of Views currently displaying bitmap 41 | private int mDisplayingCount; 42 | 43 | // Has it been displayed yet 44 | private boolean mHasBeenDisplayed; 45 | 46 | // Number of caches currently referencing the wrapper 47 | private int mCacheCount; 48 | 49 | // The CheckStateRunnable currently being delayed 50 | private Runnable mCheckStateRunnable; 51 | 52 | // Throwable which records the stack trace when we recycle 53 | private Throwable mStackTraceWhenRecycled; 54 | 55 | // Handler which may be used later 56 | private static final Handler sHandler = new Handler(Looper.getMainLooper()); 57 | 58 | private final int mMemorySize; 59 | 60 | private final int mSource; 61 | 62 | CacheableBitmapDrawable(String url, Resources resources, Bitmap bitmap, 63 | BitmapLruCache.RecyclePolicy recyclePolicy, int source) { 64 | super(resources, bitmap); 65 | 66 | mMemorySize = null != bitmap ? (bitmap.getRowBytes() * bitmap.getHeight()) : 0; 67 | mUrl = url; 68 | mRecyclePolicy = recyclePolicy; 69 | mDisplayingCount = 0; 70 | mCacheCount = 0; 71 | mSource = source; 72 | } 73 | 74 | @Override 75 | public void draw(Canvas canvas) { 76 | try { 77 | super.draw(canvas); 78 | } catch (RuntimeException re) { 79 | // A RuntimeException has been thrown, probably due to a recycled Bitmap. If we have 80 | // one, print the method stack when the recycle() call happened 81 | if (null != mStackTraceWhenRecycled) { 82 | mStackTraceWhenRecycled.printStackTrace(); 83 | } 84 | 85 | // Finally throw the original exception 86 | throw re; 87 | } 88 | } 89 | 90 | /** 91 | * @return Amount of memory currently being used by {@code Bitmap} 92 | */ 93 | int getMemorySize() { 94 | return mMemorySize; 95 | } 96 | 97 | /** 98 | * @return the URL associated with the BitmapDrawable 99 | */ 100 | public String getUrl() { 101 | return mUrl; 102 | } 103 | 104 | /** 105 | * @return One of {@link #SOURCE_NEW}, {@link #SOURCE_INBITMAP} or {@link #SOURCE_UNKNOWN} 106 | * depending on how this Bitmap was created. 107 | */ 108 | public int getSource() { 109 | return mSource; 110 | } 111 | 112 | /** 113 | * Returns true when this wrapper has a bitmap and the bitmap has not been recycled. 114 | * 115 | * @return true - if the bitmap has not been recycled. 116 | */ 117 | public synchronized boolean isBitmapValid() { 118 | Bitmap bitmap = getBitmap(); 119 | return null != bitmap && !bitmap.isRecycled(); 120 | } 121 | 122 | public synchronized boolean isBitmapMutable() { 123 | Bitmap bitmap = getBitmap(); 124 | return null != bitmap && bitmap.isMutable(); 125 | } 126 | 127 | /** 128 | * @return true - if the bitmap is currently being displayed by a {@link CacheableImageView}. 129 | */ 130 | public synchronized boolean isBeingDisplayed() { 131 | return mDisplayingCount > 0; 132 | } 133 | 134 | /** 135 | * @return true - if the wrapper is currently referenced by a cache. 136 | */ 137 | public synchronized boolean isReferencedByCache() { 138 | return mCacheCount > 0; 139 | } 140 | 141 | /** 142 | * Used to signal to the Drawable whether it is being used or not. 143 | * 144 | * @param beingUsed - true if being used, false if not. 145 | */ 146 | public synchronized void setBeingUsed(boolean beingUsed) { 147 | if (beingUsed) { 148 | mDisplayingCount++; 149 | mHasBeenDisplayed = true; 150 | } else { 151 | mDisplayingCount--; 152 | } 153 | checkState(); 154 | } 155 | 156 | /** 157 | * Used to signal to the wrapper whether it is being referenced by a cache or not. 158 | * 159 | * @param added - true if the wrapper has been added to a cache, false if removed. 160 | */ 161 | synchronized void setCached(boolean added) { 162 | if (added) { 163 | mCacheCount++; 164 | } else { 165 | mCacheCount--; 166 | } 167 | checkState(); 168 | } 169 | 170 | private void cancelCheckStateCallback() { 171 | if (null != mCheckStateRunnable) { 172 | if (Constants.DEBUG) { 173 | Log.d(LOG_TAG, "Cancelling checkState() callback for: " + mUrl); 174 | } 175 | sHandler.removeCallbacks(mCheckStateRunnable); 176 | mCheckStateRunnable = null; 177 | } 178 | } 179 | 180 | /** 181 | * Calls {@link #checkState(boolean)} with default parameter of false. 182 | */ 183 | private void checkState() { 184 | checkState(false); 185 | } 186 | 187 | /** 188 | * Checks whether the wrapper is currently referenced by a cache, and is being displayed. If 189 | * neither of those conditions are met then the bitmap is ready to be recycled. Whether this 190 | * happens now, or is delayed depends on whether the Drawable has been displayed or not.
    191 | *
  • If it has been displayed, it is recycled straight away.
  • If it has not been 192 | * displayed, and ignoreBeenDisplayed is false, a call to 193 | * checkState(true) is queued to be called after a delay.
  • If it has not 194 | * been displayed, and ignoreBeenDisplayed is true, it is recycled 195 | * straight away.
196 | * 197 | * @param ignoreBeenDisplayed - Whether to ignore the 'has been displayed' flag when deciding 198 | * whether to recycle() now. 199 | * @see Constants#UNUSED_DRAWABLE_RECYCLE_DELAY_MS 200 | */ 201 | private synchronized void checkState(final boolean ignoreBeenDisplayed) { 202 | if (Constants.DEBUG) { 203 | Log.d(LOG_TAG, String.format( 204 | "checkState(). Been Displayed: %b, Displaying: %d, Caching: %d, URL: %s", 205 | mHasBeenDisplayed, mDisplayingCount, mCacheCount, mUrl)); 206 | } 207 | 208 | // If the policy doesn't let us recycle, return now 209 | if (!mRecyclePolicy.canRecycle()) { 210 | return; 211 | } 212 | 213 | // Cancel the callback, if one is queued. 214 | cancelCheckStateCallback(); 215 | 216 | // We're not being referenced or used anywhere 217 | if (mCacheCount <= 0 && mDisplayingCount <= 0 && isBitmapValid()) { 218 | 219 | /** 220 | * If we have been displayed or we don't care whether we have 221 | * been or not, then recycle() now. Otherwise, we retry after a delay. 222 | */ 223 | if (mHasBeenDisplayed || ignoreBeenDisplayed) { 224 | if (Constants.DEBUG) { 225 | Log.d(LOG_TAG, "Recycling bitmap with url: " + mUrl); 226 | } 227 | // Record the current method stack just in case 228 | mStackTraceWhenRecycled = new Throwable("Recycled Bitmap Method Stack"); 229 | 230 | getBitmap().recycle(); 231 | } else { 232 | if (Constants.DEBUG) { 233 | Log.d(LOG_TAG, 234 | "Unused Bitmap which hasn't been displayed, delaying recycle(): " 235 | + mUrl); 236 | } 237 | mCheckStateRunnable = new CheckStateRunnable(this); 238 | sHandler.postDelayed(mCheckStateRunnable, 239 | Constants.UNUSED_DRAWABLE_RECYCLE_DELAY_MS); 240 | } 241 | } 242 | } 243 | 244 | /** 245 | * Runnable which run a {@link CacheableBitmapDrawable#checkState(boolean) checkState(false)} 246 | * call. 247 | * 248 | * @author chrisbanes 249 | */ 250 | private static final class CheckStateRunnable 251 | extends WeakReferenceRunnable { 252 | 253 | public CheckStateRunnable(CacheableBitmapDrawable object) { 254 | super(object); 255 | } 256 | 257 | @Override 258 | public void run(CacheableBitmapDrawable object) { 259 | object.checkState(true); 260 | } 261 | } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/bitmapcache/CacheableImageView.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 | package uk.co.senab.bitmapcache; 17 | 18 | import android.content.Context; 19 | import android.graphics.drawable.Drawable; 20 | import android.net.Uri; 21 | import android.util.AttributeSet; 22 | import android.widget.ImageView; 23 | 24 | public class CacheableImageView extends ImageView { 25 | 26 | private static void onDrawableSet(Drawable drawable) { 27 | if (drawable instanceof CacheableBitmapDrawable) { 28 | ((CacheableBitmapDrawable) drawable).setBeingUsed(true); 29 | } 30 | } 31 | 32 | private static void onDrawableUnset(final Drawable drawable) { 33 | if (drawable instanceof CacheableBitmapDrawable) { 34 | ((CacheableBitmapDrawable) drawable).setBeingUsed(false); 35 | } 36 | } 37 | 38 | public CacheableImageView(Context context) { 39 | super(context); 40 | } 41 | 42 | public CacheableImageView(Context context, AttributeSet attrs) { 43 | super(context, attrs); 44 | } 45 | 46 | public CacheableImageView(Context context, AttributeSet attrs, int defStyle) { 47 | super(context, attrs, defStyle); 48 | } 49 | 50 | @Override 51 | public void setImageDrawable(Drawable drawable) { 52 | final Drawable previousDrawable = getDrawable(); 53 | 54 | // Set new Drawable 55 | super.setImageDrawable(drawable); 56 | 57 | if (drawable != previousDrawable) { 58 | onDrawableSet(drawable); 59 | onDrawableUnset(previousDrawable); 60 | } 61 | } 62 | 63 | @Override 64 | public void setImageResource(int resId) { 65 | final Drawable previousDrawable = getDrawable(); 66 | super.setImageResource(resId); 67 | onDrawableUnset(previousDrawable); 68 | } 69 | 70 | @Override 71 | public void setImageURI(Uri uri) { 72 | final Drawable previousDrawable = getDrawable(); 73 | super.setImageURI(uri); 74 | onDrawableUnset(previousDrawable); 75 | } 76 | 77 | @Override 78 | protected void onDetachedFromWindow() { 79 | super.onDetachedFromWindow(); 80 | 81 | // Will cause displayed bitmap wrapper to be 'free-able' 82 | setImageDrawable(null); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/bitmapcache/Constants.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 | package uk.co.senab.bitmapcache; 17 | 18 | class Constants { 19 | 20 | static boolean DEBUG = false; 21 | 22 | static String LOG_TAG = "BitmapCache"; 23 | 24 | static final int UNUSED_DRAWABLE_RECYCLE_DELAY_MS = 2000; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/bitmapcache/IoUtils.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 | package uk.co.senab.bitmapcache; 17 | 18 | import android.util.Log; 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.io.OutputStream; 26 | 27 | class IoUtils { 28 | 29 | static void closeStream(InputStream is) { 30 | if (is != null) { 31 | try { 32 | is.close(); 33 | } catch (IOException e) { 34 | Log.i(Constants.LOG_TAG, "Failed to close InputStream", e); 35 | } 36 | } 37 | } 38 | 39 | static void closeStream(OutputStream os) { 40 | if (os != null) { 41 | try { 42 | os.close(); 43 | } catch (IOException e) { 44 | Log.i(Constants.LOG_TAG, "Failed to close OutputStream", e); 45 | } 46 | } 47 | } 48 | 49 | static long copy(File in, OutputStream out) throws IOException { 50 | return copy(new FileInputStream(in), out); 51 | } 52 | 53 | static long copy(InputStream in, File out) throws IOException { 54 | return copy(in, new FileOutputStream(out)); 55 | } 56 | 57 | /** 58 | * Pipe an InputStream to the given OutputStream

Taken from Apache Commons IOUtils. 59 | */ 60 | private static long copy(InputStream input, OutputStream output) throws IOException { 61 | try { 62 | byte[] buffer = new byte[1024 * 4]; 63 | long count = 0; 64 | int n; 65 | while (-1 != (n = input.read(buffer))) { 66 | output.write(buffer, 0, n); 67 | count += n; 68 | } 69 | output.flush(); 70 | return count; 71 | } finally { 72 | IoUtils.closeStream(input); 73 | IoUtils.closeStream(output); 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/bitmapcache/Md5.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 | package uk.co.senab.bitmapcache; 17 | 18 | import java.security.MessageDigest; 19 | import java.security.NoSuchAlgorithmException; 20 | 21 | class Md5 { 22 | 23 | private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 24 | 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 25 | 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; 26 | 27 | public static String encode(String string) { 28 | try { 29 | MessageDigest digest = MessageDigest.getInstance("MD5"); 30 | return bytesToHexString(digest.digest(string.getBytes())); 31 | } catch (NoSuchAlgorithmException e) { 32 | e.printStackTrace(); 33 | } 34 | return null; 35 | } 36 | 37 | private static String bytesToHexString(byte[] bytes) { 38 | final char[] buf = new char[bytes.length * 2]; 39 | 40 | byte b; 41 | int c = 0; 42 | for (int i = 0, z = bytes.length; i < z; i++) { 43 | b = bytes[i]; 44 | buf[c++] = DIGITS[(b >> 4) & 0xf]; 45 | buf[c++] = DIGITS[b & 0xf]; 46 | } 47 | 48 | return new String(buf); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/bitmapcache/SDK11.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 uk.co.senab.bitmapcache; 18 | 19 | import android.annotation.TargetApi; 20 | import android.graphics.Bitmap; 21 | import android.graphics.BitmapFactory; 22 | import android.os.Build; 23 | 24 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 25 | class SDK11 { 26 | 27 | static void addInBitmapOption(BitmapFactory.Options opts, Bitmap inBitmap) { 28 | opts.inBitmap = inBitmap; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /library/src/uk/co/senab/bitmapcache/WeakReferenceRunnable.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 | package uk.co.senab.bitmapcache; 17 | 18 | import java.lang.ref.WeakReference; 19 | 20 | abstract class WeakReferenceRunnable implements Runnable { 21 | 22 | private final WeakReference mObjectRef; 23 | 24 | public WeakReferenceRunnable(T object) { 25 | mObjectRef = new WeakReference(object); 26 | } 27 | 28 | @Override 29 | public final void run() { 30 | T object = mObjectRef.get(); 31 | 32 | if (null != object) { 33 | run(object); 34 | } 35 | } 36 | 37 | public abstract void run(T object); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.github.chrisbanes.bitmapcache 6 | parent 7 | pom 8 | 2.3 9 | Android-BitmapCache Project 10 | A custom Bitmap cache implementation for Android 11 | https://github.com/chrisbanes/Android-BitmapCache 12 | 13 | 14 | org.sonatype.oss 15 | oss-parent 16 | 7 17 | 18 | 19 | 20 | 21 | Apache License Version 2.0 22 | http://www.apache.org/licenses/LICENSE-2.0.txt 23 | repo 24 | 25 | 26 | 27 | 28 | https://github.com/chrisbanes/Android-BitmapCache 29 | scm:git:git://github.com/chrisbanes/Android-BitmapCache.git 30 | scm:git:git@github.com:chrisbanes/Android-BitmapCache.git 31 | v2.3 32 | 33 | 34 | 35 | 36 | Chris Banes 37 | http://about.me/chrisbanes 38 | chrisbanes 39 | 40 | 41 | 42 | 43 | library 44 | sample 45 | 46 | 47 | 48 | 49 | UTF-8 50 | UTF-8 51 | 1.6 52 | 4.1.1.4 53 | 16 54 | 3.6.0 55 | 56 | 57 | 58 | 59 | 60 | com.google.android 61 | android 62 | ${android.version} 63 | provided 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-compiler-plugin 75 | 3.1 76 | 77 | ${java.version} 78 | ${java.version} 79 | 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-release-plugin 84 | 2.4.1 85 | 86 | v@{project.version} 87 | 88 | 89 | 90 | com.jayway.maven.plugins.android.generation2 91 | android-maven-plugin 92 | ${android-maven.version} 93 | 94 | 95 | ${android.platform} 96 | 97 | true 98 | ${sourceCompatibility} 99 | ${sourceCompatibility} 100 | 101 | true 102 | 103 | 104 | 105 | src 106 | 107 | 108 | -------------------------------------------------------------------------------- /sample/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /sample/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.github.chrisbanes.bitmapcache 6 | sample 7 | apk 8 | Android-BitmapCache Sample 9 | 10 | 11 | com.github.chrisbanes.bitmapcache 12 | parent 13 | 2.3 14 | 15 | 16 | 17 | 18 | com.google.android 19 | android 20 | 21 | 22 | ${project.groupId} 23 | library 24 | jar 25 | ${project.version} 26 | 27 | 28 | 29 | 30 | 31 | 32 | com.jayway.maven.plugins.android.generation2 33 | android-maven-plugin 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /sample/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-16 15 | android.library.reference.1=../library 16 | -------------------------------------------------------------------------------- /sample/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbanes/Android-BitmapCache/147e5c078324c8f61765750da756c4a7d1f2aae3/sample/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbanes/Android-BitmapCache/147e5c078324c8f61765750da756c4a7d1f2aae3/sample/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbanes/Android-BitmapCache/147e5c078324c8f61765750da756c4a7d1f2aae3/sample/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/res/layout/activity_gridview.xml: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /sample/res/layout/gridview_item_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 19 | 20 | -------------------------------------------------------------------------------- /sample/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #CCff4444 5 | #CC99cc00 6 | #CC33B5E5 7 | 8 | -------------------------------------------------------------------------------- /sample/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 155dp 5 | 6 | -------------------------------------------------------------------------------- /sample/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | BitmapCacheSamples 4 | 5 | -------------------------------------------------------------------------------- /sample/src/uk/co/senab/bitmapcache/samples/GridViewActivity.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 | package uk.co.senab.bitmapcache.samples; 17 | 18 | import org.json.JSONArray; 19 | import org.json.JSONException; 20 | import org.json.JSONObject; 21 | 22 | import android.app.Activity; 23 | import android.os.AsyncTask; 24 | import android.os.Bundle; 25 | import android.widget.GridView; 26 | 27 | import java.io.BufferedReader; 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.io.InputStreamReader; 31 | import java.net.HttpURLConnection; 32 | import java.net.MalformedURLException; 33 | import java.net.URL; 34 | import java.util.ArrayList; 35 | import java.util.HashSet; 36 | 37 | public class GridViewActivity extends Activity { 38 | 39 | static final int PUG_COUNT = 60; 40 | 41 | /** 42 | * This task simply gets a list of URLs of Photos from PugMe 43 | */ 44 | private class PugListAsyncTask extends AsyncTask> { 45 | 46 | static final String PUG_ME_URL = "http://pugme.herokuapp.com/bomb?count=" + PUG_COUNT; 47 | 48 | @Override 49 | protected ArrayList doInBackground(Void... params) { 50 | try { 51 | HttpURLConnection conn = (HttpURLConnection) new URL(PUG_ME_URL).openConnection(); 52 | conn.setRequestProperty("Accept", "application/json"); 53 | InputStream is = conn.getInputStream(); 54 | 55 | StringBuilder sb = new StringBuilder(); 56 | BufferedReader r = new BufferedReader(new InputStreamReader(is), 1024); 57 | for (String line = r.readLine(); line != null; line = r.readLine()) { 58 | sb.append(line); 59 | } 60 | try { 61 | is.close(); 62 | } catch (IOException e) { 63 | } 64 | 65 | String response = sb.toString(); 66 | JSONObject document = new JSONObject(response); 67 | 68 | JSONArray pugsJsonArray = document.getJSONArray("pugs"); 69 | HashSet pugUrls = new HashSet(pugsJsonArray.length()); 70 | 71 | for (int i = 0, z = pugsJsonArray.length(); i < z; i++) { 72 | pugUrls.add(pugsJsonArray.getString(i)); 73 | } 74 | 75 | return new ArrayList(pugUrls); 76 | 77 | } catch (MalformedURLException e) { 78 | e.printStackTrace(); 79 | } catch (IOException e) { 80 | e.printStackTrace(); 81 | } catch (JSONException e) { 82 | e.printStackTrace(); 83 | } 84 | 85 | return null; 86 | } 87 | 88 | @Override 89 | protected void onPostExecute(ArrayList result) { 90 | super.onPostExecute(result); 91 | 92 | PugListAdapter adapter = new PugListAdapter(GridViewActivity.this, result); 93 | mGridView.setAdapter(adapter); 94 | } 95 | 96 | } 97 | 98 | private GridView mGridView; 99 | 100 | @Override 101 | public void onCreate(Bundle savedInstanceState) { 102 | super.onCreate(savedInstanceState); 103 | 104 | setContentView(R.layout.activity_gridview); 105 | 106 | mGridView = (GridView) findViewById(R.id.gridView1); 107 | 108 | // Start Pug List Download 109 | new PugListAsyncTask().execute(); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /sample/src/uk/co/senab/bitmapcache/samples/LauncherActivity.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 | package uk.co.senab.bitmapcache.samples; 17 | 18 | import android.app.ListActivity; 19 | import android.content.Intent; 20 | import android.os.Bundle; 21 | import android.view.View; 22 | import android.widget.ArrayAdapter; 23 | import android.widget.ListView; 24 | 25 | public class LauncherActivity extends ListActivity { 26 | 27 | public static final String[] options = {"GridView", "ViewPager",}; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setListAdapter( 33 | new ArrayAdapter(this, android.R.layout.simple_list_item_1, options)); 34 | } 35 | 36 | @Override 37 | protected void onListItemClick(ListView l, View v, int position, long id) { 38 | Intent intent; 39 | 40 | switch (position) { 41 | default: 42 | case 0: 43 | intent = new Intent(this, GridViewActivity.class); 44 | break; 45 | case 1: 46 | intent = new Intent(this, ViewPagerActivity.class); 47 | break; 48 | } 49 | 50 | startActivity(intent); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /sample/src/uk/co/senab/bitmapcache/samples/NetworkedCacheableImageView.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 uk.co.senab.bitmapcache.samples; 18 | 19 | import android.content.Context; 20 | import android.graphics.BitmapFactory; 21 | import android.graphics.drawable.BitmapDrawable; 22 | import android.os.AsyncTask; 23 | import android.os.Build; 24 | import android.util.AttributeSet; 25 | import android.util.Log; 26 | import android.widget.ImageView; 27 | 28 | import java.io.BufferedInputStream; 29 | import java.io.IOException; 30 | import java.io.InputStream; 31 | import java.lang.ref.WeakReference; 32 | import java.net.HttpURLConnection; 33 | import java.net.URL; 34 | import java.util.concurrent.RejectedExecutionException; 35 | 36 | import uk.co.senab.bitmapcache.BitmapLruCache; 37 | import uk.co.senab.bitmapcache.CacheableBitmapDrawable; 38 | import uk.co.senab.bitmapcache.CacheableImageView; 39 | 40 | /** 41 | * Simple extension of CacheableImageView which allows downloading of Images of the Internet. 42 | * 43 | * This code isn't production quality, but works well enough for this sample.s 44 | * 45 | * @author Chris Banes 46 | */ 47 | public class NetworkedCacheableImageView extends CacheableImageView { 48 | 49 | public interface OnImageLoadedListener { 50 | void onImageLoaded(CacheableBitmapDrawable result); 51 | } 52 | 53 | /** 54 | * This task simply fetches an Bitmap from the specified URL and wraps it in a wrapper. This 55 | * implementation is NOT 'best practice' or production ready code. 56 | */ 57 | private static class ImageUrlAsyncTask 58 | extends AsyncTask { 59 | 60 | private final BitmapLruCache mCache; 61 | 62 | private final WeakReference mImageViewRef; 63 | private final OnImageLoadedListener mListener; 64 | 65 | private final BitmapFactory.Options mDecodeOpts; 66 | 67 | ImageUrlAsyncTask(ImageView imageView, BitmapLruCache cache, 68 | BitmapFactory.Options decodeOpts, OnImageLoadedListener listener) { 69 | mCache = cache; 70 | mImageViewRef = new WeakReference(imageView); 71 | mListener = listener; 72 | mDecodeOpts = decodeOpts; 73 | } 74 | 75 | @Override 76 | protected CacheableBitmapDrawable doInBackground(String... params) { 77 | try { 78 | // Return early if the ImageView has disappeared. 79 | if (null == mImageViewRef.get()) { 80 | return null; 81 | } 82 | 83 | final String url = params[0]; 84 | 85 | // Now we're not on the main thread we can check all caches 86 | CacheableBitmapDrawable result = mCache.get(url, mDecodeOpts); 87 | 88 | if (null == result) { 89 | Log.d("ImageUrlAsyncTask", "Downloading: " + url); 90 | 91 | // The bitmap isn't cached so download from the web 92 | HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); 93 | InputStream is = new BufferedInputStream(conn.getInputStream()); 94 | 95 | // Add to cache 96 | result = mCache.put(url, is, mDecodeOpts); 97 | } else { 98 | Log.d("ImageUrlAsyncTask", "Got from Cache: " + url); 99 | } 100 | 101 | return result; 102 | 103 | } catch (IOException e) { 104 | Log.e("ImageUrlAsyncTask", e.toString()); 105 | } 106 | 107 | return null; 108 | } 109 | 110 | @Override 111 | protected void onPostExecute(CacheableBitmapDrawable result) { 112 | super.onPostExecute(result); 113 | 114 | ImageView iv = mImageViewRef.get(); 115 | if (null != iv) { 116 | iv.setImageDrawable(result); 117 | } 118 | 119 | if (null != mListener) { 120 | mListener.onImageLoaded(result); 121 | } 122 | } 123 | } 124 | 125 | private final BitmapLruCache mCache; 126 | 127 | private ImageUrlAsyncTask mCurrentTask; 128 | 129 | public NetworkedCacheableImageView(Context context, AttributeSet attrs) { 130 | super(context, attrs); 131 | mCache = SampleApplication.getApplication(context).getBitmapCache(); 132 | } 133 | 134 | /** 135 | * Loads the Bitmap. 136 | * 137 | * @param url - URL of image 138 | * @param fullSize - Whether the image should be kept at the original size 139 | * @return true if the bitmap was found in the cache 140 | */ 141 | public boolean loadImage(String url, final boolean fullSize, OnImageLoadedListener listener) { 142 | // First check whether there's already a task running, if so cancel it 143 | if (null != mCurrentTask) { 144 | mCurrentTask.cancel(true); 145 | } 146 | 147 | // Check to see if the memory cache already has the bitmap. We can 148 | // safely do 149 | // this on the main thread. 150 | BitmapDrawable wrapper = mCache.getFromMemoryCache(url); 151 | 152 | if (null != wrapper) { 153 | // The cache has it, so just display it 154 | setImageDrawable(wrapper); 155 | return true; 156 | } else { 157 | // Memory Cache doesn't have the URL, do threaded request... 158 | setImageDrawable(null); 159 | 160 | BitmapFactory.Options decodeOpts = null; 161 | 162 | if (!fullSize) { 163 | //decodeOpts = new BitmapFactory.Options(); 164 | //decodeOpts.inSampleSize = 2; 165 | } 166 | 167 | mCurrentTask = new ImageUrlAsyncTask(this, mCache, decodeOpts, listener); 168 | 169 | try { 170 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 171 | SDK11.executeOnThreadPool(mCurrentTask, url); 172 | } else { 173 | mCurrentTask.execute(url); 174 | } 175 | } catch (RejectedExecutionException e) { 176 | // This shouldn't happen, but might. 177 | } 178 | 179 | return false; 180 | } 181 | } 182 | 183 | } 184 | -------------------------------------------------------------------------------- /sample/src/uk/co/senab/bitmapcache/samples/PugListAdapter.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 | package uk.co.senab.bitmapcache.samples; 17 | 18 | import android.content.Context; 19 | import android.view.LayoutInflater; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | import android.widget.BaseAdapter; 23 | import android.widget.TextView; 24 | 25 | import java.lang.ref.WeakReference; 26 | import java.util.ArrayList; 27 | 28 | import uk.co.senab.bitmapcache.CacheableBitmapDrawable; 29 | 30 | public class PugListAdapter extends BaseAdapter { 31 | 32 | private final ArrayList mPugUrls; 33 | 34 | private final Context mContext; 35 | 36 | public PugListAdapter(Context context, ArrayList pugUrls) { 37 | mPugUrls = pugUrls; 38 | mContext = context; 39 | } 40 | 41 | @Override 42 | public int getCount() { 43 | return null != mPugUrls ? mPugUrls.size() : 0; 44 | } 45 | 46 | @Override 47 | public String getItem(int position) { 48 | return mPugUrls.get(position); 49 | } 50 | 51 | @Override 52 | public long getItemId(int position) { 53 | return position; 54 | } 55 | 56 | @Override 57 | public View getView(int position, View convertView, ViewGroup parent) { 58 | if (null == convertView) { 59 | convertView = LayoutInflater.from(mContext) 60 | .inflate(R.layout.gridview_item_layout, parent, false); 61 | } 62 | 63 | NetworkedCacheableImageView imageView = (NetworkedCacheableImageView) convertView 64 | .findViewById(R.id.nciv_pug); 65 | TextView status = (TextView) convertView.findViewById(R.id.tv_status); 66 | 67 | final boolean fromCache = imageView 68 | .loadImage(mPugUrls.get(position), false, new UpdateTextViewListener(status)); 69 | 70 | if (fromCache) { 71 | status.setText("From Memory Cache"); 72 | status.setBackgroundColor(mContext.getResources().getColor(R.color.translucent_green)); 73 | } else { 74 | status.setText("Loading..."); 75 | status.setBackgroundDrawable(null); 76 | } 77 | 78 | return convertView; 79 | } 80 | 81 | static class UpdateTextViewListener 82 | implements NetworkedCacheableImageView.OnImageLoadedListener { 83 | private final WeakReference mTextViewRef; 84 | 85 | public UpdateTextViewListener(TextView tv) { 86 | mTextViewRef = new WeakReference(tv); 87 | } 88 | 89 | @Override 90 | public void onImageLoaded(CacheableBitmapDrawable result) { 91 | final TextView tv = mTextViewRef.get(); 92 | if (tv == null) { 93 | return; 94 | } 95 | 96 | if (result == null) { 97 | tv.setText("Failed"); 98 | tv.setBackgroundDrawable(null); 99 | return; 100 | } 101 | 102 | switch (result.getSource()) { 103 | case CacheableBitmapDrawable.SOURCE_UNKNOWN: 104 | case CacheableBitmapDrawable.SOURCE_NEW: 105 | tv.setText("From Disk/Network"); 106 | tv.setBackgroundColor(tv.getResources().getColor(R.color.translucent_red)); 107 | break; 108 | case CacheableBitmapDrawable.SOURCE_INBITMAP: 109 | tv.setText("Reused Bitmap"); 110 | tv.setBackgroundColor(tv.getResources().getColor(R.color.translucent_blue)); 111 | break; 112 | } 113 | } 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /sample/src/uk/co/senab/bitmapcache/samples/PugPagerAdapter.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 | package uk.co.senab.bitmapcache.samples; 17 | 18 | import android.content.Context; 19 | import android.support.v4.view.PagerAdapter; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | import android.view.ViewGroup.LayoutParams; 23 | import android.widget.ImageView.ScaleType; 24 | 25 | import java.util.ArrayList; 26 | 27 | public class PugPagerAdapter extends PagerAdapter { 28 | 29 | private final ArrayList mPugUrls; 30 | 31 | private final Context mContext; 32 | 33 | public PugPagerAdapter(Context context, ArrayList pugUrls) { 34 | mPugUrls = pugUrls; 35 | mContext = context; 36 | } 37 | 38 | @Override 39 | public int getCount() { 40 | return null != mPugUrls ? mPugUrls.size() : 0; 41 | } 42 | 43 | @Override 44 | public View instantiateItem(ViewGroup container, int position) { 45 | NetworkedCacheableImageView imageView = new NetworkedCacheableImageView(mContext, null); 46 | 47 | String pugUrl = mPugUrls.get(position); 48 | imageView.loadImage(pugUrl, true, null); 49 | 50 | imageView.setScaleType(ScaleType.FIT_CENTER); 51 | container.addView(imageView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 52 | 53 | return imageView; 54 | } 55 | 56 | @Override 57 | public void destroyItem(ViewGroup container, int position, Object object) { 58 | container.removeView((View) object); 59 | } 60 | 61 | @Override 62 | public boolean isViewFromObject(View view, Object object) { 63 | return view == object; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /sample/src/uk/co/senab/bitmapcache/samples/SDK11.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 | package uk.co.senab.bitmapcache.samples; 17 | 18 | import android.annotation.TargetApi; 19 | import android.os.AsyncTask; 20 | import android.os.Build; 21 | 22 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 23 | public class SDK11 { 24 | 25 | public static

void executeOnThreadPool(AsyncTask task, P... params) { 26 | task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /sample/src/uk/co/senab/bitmapcache/samples/SampleApplication.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 uk.co.senab.bitmapcache.samples; 18 | 19 | import android.app.Application; 20 | import android.content.Context; 21 | import android.os.Environment; 22 | 23 | import java.io.File; 24 | 25 | import uk.co.senab.bitmapcache.BitmapLruCache; 26 | 27 | public class SampleApplication extends Application { 28 | 29 | private BitmapLruCache mCache; 30 | 31 | @Override 32 | public void onCreate() { 33 | super.onCreate(); 34 | 35 | File cacheLocation; 36 | 37 | // If we have external storage use it for the disk cache. Otherwise we use 38 | // the cache dir 39 | if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 40 | cacheLocation = new File( 41 | Environment.getExternalStorageDirectory() + "/Android-BitmapCache"); 42 | } else { 43 | cacheLocation = new File(getFilesDir() + "/Android-BitmapCache"); 44 | } 45 | cacheLocation.mkdirs(); 46 | 47 | BitmapLruCache.Builder builder = new BitmapLruCache.Builder(this); 48 | builder.setMemoryCacheEnabled(true).setMemoryCacheMaxSizeUsingHeapSize(); 49 | builder.setDiskCacheEnabled(true).setDiskCacheLocation(cacheLocation); 50 | 51 | mCache = builder.build(); 52 | } 53 | 54 | public BitmapLruCache getBitmapCache() { 55 | return mCache; 56 | } 57 | 58 | public static SampleApplication getApplication(Context context) { 59 | return (SampleApplication) context.getApplicationContext(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /sample/src/uk/co/senab/bitmapcache/samples/ViewPagerActivity.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Chris Banes. 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 | package uk.co.senab.bitmapcache.samples; 17 | 18 | import org.json.JSONArray; 19 | import org.json.JSONException; 20 | import org.json.JSONObject; 21 | 22 | import android.app.Activity; 23 | import android.os.AsyncTask; 24 | import android.os.Bundle; 25 | import android.support.v4.view.ViewPager; 26 | 27 | import java.io.BufferedReader; 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.io.InputStreamReader; 31 | import java.net.HttpURLConnection; 32 | import java.net.MalformedURLException; 33 | import java.net.URL; 34 | import java.util.ArrayList; 35 | import java.util.HashSet; 36 | 37 | public class ViewPagerActivity extends Activity { 38 | 39 | static final int PUG_COUNT = 20; 40 | 41 | /** 42 | * This task simply gets a list of URLs of Photos from PugMe 43 | */ 44 | private class PugListAsyncTask extends AsyncTask> { 45 | 46 | static final String PUG_ME_URL = "http://pugme.herokuapp.com/bomb?count=" + PUG_COUNT; 47 | 48 | @Override 49 | protected ArrayList doInBackground(Void... params) { 50 | try { 51 | HttpURLConnection conn = (HttpURLConnection) new URL(PUG_ME_URL).openConnection(); 52 | conn.setRequestProperty("Accept", "application/json"); 53 | InputStream is = conn.getInputStream(); 54 | 55 | StringBuilder sb = new StringBuilder(); 56 | BufferedReader r = new BufferedReader(new InputStreamReader(is), 1024); 57 | for (String line = r.readLine(); line != null; line = r.readLine()) { 58 | sb.append(line); 59 | } 60 | try { 61 | is.close(); 62 | } catch (IOException e) { 63 | } 64 | 65 | String response = sb.toString(); 66 | JSONObject document = new JSONObject(response); 67 | 68 | JSONArray pugsJsonArray = document.getJSONArray("pugs"); 69 | HashSet pugUrls = new HashSet(pugsJsonArray.length()); 70 | 71 | for (int i = 0, z = pugsJsonArray.length(); i < z; i++) { 72 | pugUrls.add(pugsJsonArray.getString(i)); 73 | } 74 | 75 | return new ArrayList(pugUrls); 76 | 77 | } catch (MalformedURLException e) { 78 | e.printStackTrace(); 79 | } catch (IOException e) { 80 | e.printStackTrace(); 81 | } catch (JSONException e) { 82 | e.printStackTrace(); 83 | } 84 | 85 | return null; 86 | } 87 | 88 | @Override 89 | protected void onPostExecute(ArrayList result) { 90 | super.onPostExecute(result); 91 | 92 | PugPagerAdapter adapter = new PugPagerAdapter(ViewPagerActivity.this, result); 93 | mViewPager.setAdapter(adapter); 94 | } 95 | 96 | } 97 | 98 | private ViewPager mViewPager; 99 | 100 | @Override 101 | public void onCreate(Bundle savedInstanceState) { 102 | super.onCreate(savedInstanceState); 103 | 104 | mViewPager = new ViewPager(this); 105 | setContentView(mViewPager); 106 | 107 | // Start Pug List Download 108 | new PugListAsyncTask().execute(); 109 | } 110 | 111 | } 112 | --------------------------------------------------------------------------------