keep history after reset to ub-camera-haleakala
diff --git a/.gitignore b/.gitignore
index 0542fc3..9221793 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,10 @@
 .gradle
 build
+*.iws
+doc/**
+bin/**
 library/bin/*
 library/gen/*
-library/local.properties
 library/.gradle
 library/.settings
 library/target
@@ -11,13 +13,15 @@
 .idea/*
 *.DS_Store
 *.swp
+*.swo
 proguard-project.txt
 samples/flickr/.idea/*
 samples/flickr/gen
 samples/flickr/out
 samples/flickr/bin
-samples/flickr/local.properties
 samples/flickr/target
+integration/volley/target/**
+**local.properties
 *.keystore
 **/.idea/*
 .settings
diff --git a/MODULE_LICENSE_BSD b/.gitmodules
similarity index 100%
rename from MODULE_LICENSE_BSD
rename to .gitmodules
diff --git a/.travis.yml b/.travis.yml
index e502fbc..c1a2a27 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,12 @@
 language: android
 android:
   components:
-    - build-tools-19.1.0
+  - build-tools-19.1.0
   licenses: 
-    - 'android-sdk-license.*'
+  - 'android-sdk-license.*'
 
-script: 'travis_retry ./gradlew clean build'
+script: 'travis_retry ./gradlew build --parallel'
+
+after_success:
+- scripts/travis-sonatype-publish.sh
+- ./gradlew jacocoTestReport coveralls
diff --git a/Android.mk b/Android.mk
index 16b4c55..1a75271 100644
--- a/Android.mk
+++ b/Android.mk
@@ -2,11 +2,12 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := $(call all-java-files-under, library/src)
+LOCAL_SRC_FILES += $(call all-java-files-under, third_party/disklrucache/src)
 LOCAL_SRC_FILES += $(call all-java-files-under, third_party/gif_decoder/src)
-LOCAL_MANIFEST_FILE := library/AndroidManifest.xml
+LOCAL_SRC_FILES += $(call all-java-files-under, third_party/gif_encoder/src)
+LOCAL_MANIFEST_FILE := library/src/main/AndroidManifest.xml
 
 LOCAL_STATIC_JAVA_LIBRARIES := volley
-LOCAL_STATIC_JAVA_LIBRARIES += libDiskLruCacheGlide
 LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4
 
 LOCAL_MODULE := glide
@@ -16,6 +17,3 @@
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 include $(CLEAR_VARS)
-LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
-  libDiskLruCacheGlide:third_party/disklrucache/disklrucache-2.0.2.jar
-include $(BUILD_MULTI_PREBUILT)
diff --git a/LICENSE b/LICENSE
index 6544141..f5111ee 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,3 +1,5 @@
+License for everything not in third_party and not otherwise marked:
+
 Copyright 2014 Google, Inc. All rights reserved.
 
 Redistribution and use in source and binary forms, with or without modification, are
@@ -23,3 +25,70 @@
 The views and conclusions contained in the software and documentation are those of the
 authors and should not be interpreted as representing official policies, either expressed
 or implied, of Google, Inc.
+---------------------------------------------------------------------------------------------
+License for third_party/disklrucache:
+
+Copyright 2012 Jake Wharton
+Copyright 2011 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+---------------------------------------------------------------------------------------------
+License for third_party/gif_decoder:
+
+Copyright (c) 2013 Xcellent Creations, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+---------------------------------------------------------------------------------------------
+License for third_party/gif_encoder/AnimatedGifEncoder.java and
+third_party/gif_encoder/LZWEncoder.java:
+
+No copyright asserted on the source code of this class. May be used for any
+purpose, however, refer to the Unisys LZW patent for restrictions on use of
+the associated LZWEncoder class. Please forward any corrections to
+kweiner@fmsware.com.
+
+-----------------------------------------------------------------------------
+License for third_party/gif_encoder/NeuQuant.java
+
+Copyright (c) 1994 Anthony Dekker
+
+NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
+"Kohonen neural networks for optimal colour quantization" in "Network:
+Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
+the algorithm.
+
+Any party obtaining a copy of these files from the author, directly or
+indirectly, is granted, free of charge, a full and unrestricted irrevocable,
+world-wide, paid up, royalty-free, nonexclusive right and license to deal in
+this software and documentation files (the "Software"), including without
+limitation the rights to use, copy, modify, merge, publish, distribute,
+sublicense, and/or sell copies of the Software, and to permit persons who
+receive copies from any such party to do so, with the only requirement being
+that this copyright notice remain intact.
diff --git a/NOTICE b/NOTICE
deleted file mode 100644
index 6544141..0000000
--- a/NOTICE
+++ /dev/null
@@ -1,25 +0,0 @@
-Copyright 2014 Google, Inc. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
-   1. Redistributions of source code must retain the above copyright notice, this list of
-         conditions and the following disclaimer.
-
-   2. Redistributions in binary form must reproduce the above copyright notice, this list
-         of conditions and the following disclaimer in the documentation and/or other materials
-         provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY GOOGLE, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those of the
-authors and should not be interpreted as representing official policies, either expressed
-or implied, of Google, Inc.
diff --git a/README.md b/README.md
index 054b5cc..36297b7 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,72 @@
 Glide
 =====
-Glide is fast and efficient image loading library for Android that wraps image downloading, resizing, memory and disk caching, and bitmap recycling into one simple and easy to use interface. By default, Glide includes an implementation for fetching images over http based on Google's Volley project for fast, parallelized network operations on Android.
 
-Glide's primary focus is on making scrolling any kind of a list of images as smooth and fast as possible, but Glide is also effective for almost any case where you need to fetch, resize, and display a remote image.
+[![Build Status](https://travis-ci.org/bumptech/glide.svg?branch=master)](https://travis-ci.org/bumptech/glide)
+
+Glide is a fast and efficient open source media management framework for Android that wraps media decoding, memory and
+disk caching, and resource pooling into a simple and easy to use interface.
+
+![](static/glide_logo.png)
+
+Glide supports fetching, decoding, and displaying video stills, images, and animated GIFs. Glide includes a flexible api
+that allows developers to plug in to almost any network stack. By default Glide uses a custom HttpUrlConnection based
+stack, but also includes utility libraries plug in to Google's Volley project or Square's OkHttp library instead.
+
+Glide's primary focus is on making scrolling any kind of a list of images as smooth and fast as possible, but Glide is
+also effective for almost any case where you need to fetch, resize, and display a remote image.
+
+Download
+--------
+You can download a jar from GitHub's [release page](https://github.com/bumptech/glide/releases).
+
+Or use Gradle:
+
+```groovy
+repositories {
+  mavenCentral()
+}
+
+dependencies {
+    compile 'com.github.bumptech.glide:glide:3.3.+'
+    compile 'com.android.support:support-v4:19.1.0'
+}
+```
+
+Or Maven:
+
+```xml
+<dependency>
+  <groupId>com.github.bumptech.glide</groupId>
+  <artifactId>glide</artifactId>
+  <version>3.3.1</version>
+  <type>aar</type>
+</dependency>
+<dependency>
+  <groupId>com.google.android</groupId>
+  <artifactId>support-v4</artifactId>
+  <version>r7</version>
+</dependency>
+```
 
 How do I use Glide?
 -------------------
-You can download a .jar from GitHub's release page for the Glide project. The wiki also has pages on a variety of topics and the javadocs for version 2.0+ will also be available via a link there as well.
+Checkout the [GitHub wiki](https://github.com/bumptech/glide/wiki) for pages on a variety of topics, and see the [javadocs](http://bumptech.github.io/glide/javadocs/latest/index.html).
 
 Simple use cases will look something like this:
 
 ```Java
 
-//For a simple view:
+// For a simple view:
 @Override
 public void onCreate(Bundle savedInstanceState) {
     ...
 
     ImageView imageView = (ImageView) findViewById(R.id.my_image_view);
 
-    Glide.load("http://goo.gl/h8qOq7").into(imageView);
+    Glide.with(this).load("http://goo.gl/h8qOq7").into(imageView);
 }
 
-//For a list:
+// For a list:
 @Override
 public View getView(int position, View recycled, ViewGroup container) {
     final ImageView myImageView;
@@ -35,10 +79,11 @@
 
     String url = myUrls.get(position);
 
-    Glide.load(url)
+    Glide.with(myFragment)
+        .load(url)
         .centerCrop()
         .placeholder(R.drawable.loading_spinner)
-        .animate(R.anim.fade_in)
+        .crossFade()
         .into(myImageView);
 
     return myImageView;
@@ -46,25 +91,152 @@
 
 ```
 
+Volley
+-------
+Volley is now an optional dependency that can be included via a utility library. To use Volley to fetch media over http/https:
+
+With Gradle:
+
+```groovy
+dependencies {
+    compile 'com.github.bumptech.glide:volley-integration:1.0.+'
+    compile 'com.mcxiaoke.volley:library:1.0.+'
+}
+```
+
+Or with Maven:
+
+```xml
+<dependency>
+    <groupId>com.github.bumptech.glide</groupId>
+    <artifactId>volley-integration</artifactId>
+    <version>1.0.1</version>
+    <type>jar</type>
+</dependency>
+<dependency>
+    <groupId>com.mcxiaoke.volley</groupId>
+    <artifactId>library</artifactId>
+    <version>1.0.5</version>
+    <type>aar</type>
+</dependency>
+```
+
+Then in your Activity or Application, register the Volley based model loader:
+```java
+public void onCreate() {
+  Glide.get(this).register(GlideUrl.class, InputStream.class,
+        new VolleyUrlLoader.Factory(yourRequestQueue));
+  ...
+}
+```
+
+After the call to register any requests using http or https will go through Volley.
+
+OkHttp
+------
+In addition to Volley, Glide also includes support for fetching media using OkHttp. To use OkHttp to fetch media over http/https:
+
+With Gradle:
+
+```groovy
+dependencies {
+    compile 'com.github.bumptech.glide:okhttp-integration:1.0.+'
+    compile 'com.squareup.okhttp:okhttp:2.0.+'
+}
+```
+
+Or with Maven:
+
+```xml
+<dependency>
+    <groupId>com.github.bumptech.glide</groupId>
+    <artifactId>okhttp-integration</artifactId>
+    <version>1.0.1</version>
+    <type>jar</type>
+</dependency>
+<dependency>
+    <groupId>com.squareup.okhttp</groupId>
+    <artifactId>okhttp</artifactId>
+    <version>2.0.0</version>
+    <type>jar</type>
+</dependency>
+```
+
+Then in your Activity or Application, register the OkHttp based model loader:
+```java
+public void onCreate() {
+  Glide.get(this).register(GlideUrl.class, InputStream.class,
+        new OkHttpUrlLoader.Factory(yourOkHttpClient));
+  ...
+}
+```
+
+Android SDK Version
+-------------------
+Glide requires a minimum sdk version of 10.
+
+License
+-------
+BSD, part MIT and Apache 2.0. See LICENSE file for details.
+
 Status
 ------
-Glide has been in use at Bump for about six months in two of our Android apps at version 1.0. Version 2.0 is the first public release with a stable api. Comments/bugs/questions/pull requests welcome!
+Version 3.x is a stable public release used in multiple open source projects at Google including in the Android Camera app and in the 2014 Google IO app. Comments/bugs/questions/pull requests welcome!
 
 Build
 ------
 Building Glide with gradle is fairly straight forward:
 
 ```
-cd glide/library
-./gradlew build
+git clone git@github.com:bumptech/glide.git
+cd glide
+git submodule init && git submodule update
+./gradlew jar
 ```
 
-Note: Make sure your Android SDK has the Android Support Repository installed, and that your `$ANDROID_HOME` environment variable is pointing at the SDK.
+Note: Make sure your Android SDK has the Android Support Repository installed, and that your `$ANDROID_HOME` environment variable is pointing at the SDK or add a `local.properties` file in the root project with a `sdk.dir=...` line.
+
+Samples
+-------
+Follow the steps in the 'Build' section to setup the project and then:
+
+```
+./gradlew :samples:flickr:run
+./gradlew :samples:giphy:run
+./gradlew :samples:svg:run
+```
+
+Development
+-----------
+Follow the steps in the 'Build' section to setup the project and then edit the files however you wish. Intellij's [IDEA 14 early access build](http://confluence.jetbrains.com/display/IDEADEV/IDEA+14+EAP) cleanly imports both Glide's source and tests and is the recommended way to work with Glide. Earlier versions of intellij do not import the gradle project cleanly. Although Android Studio imports the source cleanly, it is not possible to run or debug the tests without manually modifying the tests' classpath.
+
+To open the project in Intellij 14:
+
+1. Go to File.
+2. Click on 'Open...'
+3. Navigate to Glide's root directory.
+4. Select settings.gradle.
+
+Getting Help
+------------
+To report a specific problem or feature request, [open a new issue on Github](https://github.com/bumptech/glide/issues/new). For questions, suggestions, or anything else, join or email [Glide's discussion group](https://groups.google.com/forum/#!forum/glidelibrary)
+
+Contributing
+------------
+Before submitting pull requests, contributors must sign Google's [individual contribution license agreement](https://developers.google.com/open-source/cla/individual).
 
 Thanks
 ------
-Thanks to the Android project and Jake Wharton for the [disk cache implementation](https://github.com/JakeWharton/DiskLruCache) included with Glide. Thanks also to the Android team for [Volley](https://android.googlesource.com/platform/frameworks/volley/).
+* The Android team and Jake Wharton for the [disk cache implementation](https://github.com/JakeWharton/DiskLruCache) Glide's disk cache is based on.
+* Dave Smith for the [gif decoder gist](https://gist.github.com/devunwired/4479231) Glide's gif decoder is based on.
+* Chris Banes for his [gradle-mvn-push](https://github.com/chrisbanes/gradle-mvn-push) script.
+* Corey Hall for Glide's [amazing logo](static/glide_logo.png).
+* Everyone who has contributed code and reported issues!
 
 Author
 ------
 Sam Judd - @samajudd
+
+Disclaimer
+---------
+This is not an official Google product.
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index eabf7c2..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,13 +0,0 @@
-buildscript {
-    repositories {
-        mavenCentral()
-    }
-    dependencies {
-        classpath 'com.android.tools.build:gradle:0.11.+'
-    }
-}
-
-task wrapper(type: Wrapper) {
-    gradleVersion = '1.10'
-}
-
diff --git a/gradle.properties b/gradle.properties
deleted file mode 100644
index 1a644c7..0000000
--- a/gradle.properties
+++ /dev/null
@@ -1 +0,0 @@
-org.gradle.daemon=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 0087cd3..0000000
--- a/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 03bea6d..0000000
--- a/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Mon Jun 23 11:25:59 PDT 2014
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-1.10-bin.zip
diff --git a/gradlew b/gradlew
deleted file mode 100755
index 91a7e26..0000000
--- a/gradlew
+++ /dev/null
@@ -1,164 +0,0 @@
-#!/usr/bin/env bash
-
-##############################################################################
-##
-##  Gradle start up script for UN*X
-##
-##############################################################################
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
-    echo "$*"
-}
-
-die ( ) {
-    echo
-    echo "$*"
-    echo
-    exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-esac
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
-    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
-    ls=`ls -ld "$PRG"`
-    link=`expr "$ls" : '.*-> \(.*\)$'`
-    if expr "$link" : '/.*' > /dev/null; then
-        PRG="$link"
-    else
-        PRG=`dirname "$PRG"`"/$link"
-    fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >&-
-APP_HOME="`pwd -P`"
-cd "$SAVED" >&-
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
-    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
-        # IBM's JDK on AIX uses strange locations for the executables
-        JAVACMD="$JAVA_HOME/jre/sh/java"
-    else
-        JAVACMD="$JAVA_HOME/bin/java"
-    fi
-    if [ ! -x "$JAVACMD" ] ; then
-        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-    fi
-else
-    JAVACMD="java"
-    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
-    MAX_FD_LIMIT=`ulimit -H -n`
-    if [ $? -eq 0 ] ; then
-        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
-            MAX_FD="$MAX_FD_LIMIT"
-        fi
-        ulimit -n $MAX_FD
-        if [ $? -ne 0 ] ; then
-            warn "Could not set maximum file descriptor limit: $MAX_FD"
-        fi
-    else
-        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
-    fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
-    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
-    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
-    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
-    # We build the pattern for arguments to be converted via cygpath
-    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
-    SEP=""
-    for dir in $ROOTDIRSRAW ; do
-        ROOTDIRS="$ROOTDIRS$SEP$dir"
-        SEP="|"
-    done
-    OURCYGPATTERN="(^($ROOTDIRS))"
-    # Add a user-defined pattern to the cygpath arguments
-    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
-        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
-    fi
-    # Now convert the arguments - kludge to limit ourselves to /bin/sh
-    i=0
-    for arg in "$@" ; do
-        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
-        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
-
-        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
-            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
-        else
-            eval `echo args$i`="\"$arg\""
-        fi
-        i=$((i+1))
-    done
-    case $i in
-        (0) set -- ;;
-        (1) set -- "$args0" ;;
-        (2) set -- "$args0" "$args1" ;;
-        (3) set -- "$args0" "$args1" "$args2" ;;
-        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
-    esac
-fi
-
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
-    JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
-
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
deleted file mode 100644
index aec9973..0000000
--- a/gradlew.bat
+++ /dev/null
@@ -1,90 +0,0 @@
-@if "%DEBUG%" == "" @echo off

-@rem ##########################################################################

-@rem

-@rem  Gradle startup script for Windows

-@rem

-@rem ##########################################################################

-

-@rem Set local scope for the variables with windows NT shell

-if "%OS%"=="Windows_NT" setlocal

-

-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

-set DEFAULT_JVM_OPTS=

-

-set DIRNAME=%~dp0

-if "%DIRNAME%" == "" set DIRNAME=.

-set APP_BASE_NAME=%~n0

-set APP_HOME=%DIRNAME%

-

-@rem Find java.exe

-if defined JAVA_HOME goto findJavaFromJavaHome

-

-set JAVA_EXE=java.exe

-%JAVA_EXE% -version >NUL 2>&1

-if "%ERRORLEVEL%" == "0" goto init

-

-echo.

-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

-echo.

-echo Please set the JAVA_HOME variable in your environment to match the

-echo location of your Java installation.

-

-goto fail

-

-:findJavaFromJavaHome

-set JAVA_HOME=%JAVA_HOME:"=%

-set JAVA_EXE=%JAVA_HOME%/bin/java.exe

-

-if exist "%JAVA_EXE%" goto init

-

-echo.

-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

-echo.

-echo Please set the JAVA_HOME variable in your environment to match the

-echo location of your Java installation.

-

-goto fail

-

-:init

-@rem Get command-line arguments, handling Windowz variants

-

-if not "%OS%" == "Windows_NT" goto win9xME_args

-if "%@eval[2+2]" == "4" goto 4NT_args

-

-:win9xME_args

-@rem Slurp the command line arguments.

-set CMD_LINE_ARGS=

-set _SKIP=2

-

-:win9xME_args_slurp

-if "x%~1" == "x" goto execute

-

-set CMD_LINE_ARGS=%*

-goto execute

-

-:4NT_args

-@rem Get arguments from the 4NT Shell from JP Software

-set CMD_LINE_ARGS=%$

-

-:execute

-@rem Setup the command line

-

-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

-

-@rem Execute Gradle

-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

-

-:end

-@rem End local scope for the variables with windows NT shell

-if "%ERRORLEVEL%"=="0" goto mainEnd

-

-:fail

-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

-rem the _cmd.exe /c_ return code!

-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

-exit /b 1

-

-:mainEnd

-if "%OS%"=="Windows_NT" endlocal

-

-:omega

diff --git a/install_maven_dependencies/install-android-deps.sh b/install_maven_dependencies/install-android-deps.sh
deleted file mode 100755
index 52e9a17..0000000
--- a/install_maven_dependencies/install-android-deps.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/sh
-# This script installs the necessary Android dependencies to compile Glide and run
-# the test suite.
-# 
-# Pre-requisites: 
-# Using the android sdk tool, under Android 4.4.2 (API 19), install:
-#   SDK Platform
-#   Glass Development Kit Preview
-# Also using the android sdk tool, under Android 4.4.0 (API 14), install:
-#   SDK Platform
-
-git clone https://github.com/mosabua/maven-android-sdk-deployer.git
-cd maven-android-sdk-deployer 
-mvn clean install -N && cd platforms && mvn clean install -N && cd android-19 && mvn clean install || {
-  echo 'Failed to install 4.4 SDK, install relevant packages in android SDK first';
-  exit 1; 
-}
-cd ../..
-mvn clean install -N && cd platforms && mvn clean install -N && cd android-14 && mvn clean install || {
-  echo 'Failed to install 4.0 SDK, install relevant packages in android SDK first';
-  exit 1;
-}
-cd ../..
-mvn clean install -N && cd extras && mvn clean install -N && cd compatibility-v4 && mvn clean install || {
-  echo 'Failed to install android-support-v4, install support library in android SDK first';
-  exit 1;
-}
-cd ../../..
-rm -rf maven-android-sdk-deployer
diff --git a/library/.gitignore b/library/.gitignore
deleted file mode 100644
index 367a921..0000000
--- a/library/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-tests/ant.properties
-tests/local.properties
-tests/gen/**/*
-tests/bin
-libs/volley.jar
-libs/gifdecoder*.jar
-libs/disklrucache*.jar
-
diff --git a/library/ant.properties b/library/ant.properties
deleted file mode 100644
index b0971e8..0000000
--- a/library/ant.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-# This file is used to override default values used by the Ant build system.
-#
-# This file must be checked into Version Control Systems, as it is
-# integral to the build system of your project.
-
-# This file is only used by the Ant script.
-
-# You can use this to override default values such as
-#  'source.dir' for the location of your java source folder and
-#  'out.dir' for the location of your output folder.
-
-# You can also use it define how the release builds are signed by declaring
-# the following properties:
-#  'key.store' for the location of your keystore and
-#  'key.alias' for the name of the key to use.
-# The password will be asked during the build when you use the 'release' target.
-
diff --git a/library/build.gradle b/library/build.gradle
deleted file mode 100644
index 55e637f..0000000
--- a/library/build.gradle
+++ /dev/null
@@ -1,92 +0,0 @@
-import org.apache.tools.ant.taskdefs.Delete
-
-buildscript {
-    repositories {
-        mavenCentral()
-        // TODO: remove this when robolectric 2.4 is released.
-        maven {
-            url "https://oss.sonatype.org/content/repositories/snapshots"
-        }
-    }
-    dependencies {
-        classpath 'org.robolectric:robolectric-gradle-plugin:0.11.+'
-        classpath 'com.android.tools.build:gradle:0.11.2'
-    }
-}
-
-apply plugin: 'android-library'
-apply plugin: 'robolectric'
-
-repositories {
-    mavenCentral()
-    // TODO: remove this when robolectric 2.4 is released.
-    maven {
-        url "https://oss.sonatype.org/content/repositories/snapshots"
-    }
-}
-
-dependencies {
-    compile project(':third_party:gif_decoder')
-
-    compile 'com.android.support:support-v4:19.1.+'
-    compile 'com.mcxiaoke.volley:library:1.0.+'
-    compile 'com.jakewharton:disklrucache:2.0.+'
-
-    androidTestCompile 'org.hamcrest:hamcrest-core:1.3'
-    androidTestCompile 'junit:junit:4.11'
-    androidTestCompile 'org.mockito:mockito-all:1.9.5'
-    androidTestCompile 'org.robolectric:robolectric:2.3'
-    androidTestCompile group: 'org.robolectric', name: 'robolectric', version: '2.4-SNAPSHOT', changing: true
-}
-
-ext {
-    versionMajor = 3
-    versionMinor = 2
-    versionPatch = 0
-    versionBuild = 5
-}
-
-def getVersionName() {
-    return "${versionMajor}.${versionMinor}.${versionPatch}a"
-}
-
-android {
-    compileSdkVersion 19
-    buildToolsVersion = '19.1.0'
-
-    defaultConfig {
-        minSdkVersion 10
-        targetSdkVersion 19
-        versionCode versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
-        versionName getVersionName()
-    }
-
-    sourceSets {
-        main {
-            java.srcDirs         = ['src/main/java']
-            manifest.srcFile 'AndroidManifest.xml'
-        }
-
-        androidTest {
-            java.srcDirs = ['src/test/java']
-        }
-    }
-}
-
-def getJarName() {
-    return "glide-${getVersionName()}.jar"
-}
-
-//Build a jar, from http://stackoverflow.com/a/19037807/1002054
-task clearJar(type: org.gradle.api.tasks.Delete) {
-    delete "build/libs/${getJarName()}"
-}
-
-task makeJar(type: Copy) {
-    from('build/intermediates/bundles/release/')
-    into('build/libs/')
-    include('classes.jar')
-    rename ('classes.jar', getJarName())
-}
-
-makeJar.dependsOn(clearJar, build)
diff --git a/library/build.xml b/library/build.xml
deleted file mode 100644
index a6e9e00..0000000
--- a/library/build.xml
+++ /dev/null
@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project name="glide" default="help">
-
-  <!-- The local.properties file is created and updated by the 'android' tool.
-       It contains the path to the SDK. It should *NOT* be checked into
-       Version Control Systems. -->
-  <property file="local.properties"/>
-
-  <!-- The ant.properties file can be created by you. It is only edited by the
-       'android' tool to add properties to it.
-       This is the place to change some Ant specific build properties.
-       Here are some properties you may want to change/update:
-
-       source.dir
-           The name of the source directory. Default is 'src'.
-       out.dir
-           The name of the output directory. Default is 'bin'.
-
-       For other overridable properties, look at the beginning of the rules
-       files in the SDK, at tools/ant/build.xml
-
-       Properties related to the SDK location or the project target should
-       be updated using the 'android' tool with the 'update' action.
-
-       This file is an integral part of the build system for your
-       application and should be checked into Version Control Systems.
-
-       -->
-  <property file="ant.properties"/>
-
-  <!-- if sdk.dir was not set from one of the property file, then
-       get it from the ANDROID_HOME env var.
-       This must be done before we load project.properties since
-       the proguard config can use sdk.dir -->
-  <property environment="env"/>
-  <condition property="sdk.dir" value="${env.ANDROID_HOME}">
-    <isset property="env.ANDROID_HOME"/>
-  </condition>
-
-  <!-- The project.properties file is created and updated by the 'android'
-       tool, as well as ADT.
-
-       This contains project specific properties such as project target, and library
-       dependencies. Lower level build properties are stored in ant.properties
-       (or in .classpath for Eclipse projects).
-
-       This file is an integral part of the build system for your
-       application and should be checked into Version Control Systems. -->
-  <loadproperties srcFile="project.properties"/>
-
-  <!-- quick check on sdk.dir -->
-  <fail
-    message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
-    unless="sdk.dir"
-    />
-
-  <!--
-      Import per project custom build rules if present at the root of the project.
-      This is the place to put custom intermediary targets such as:
-          -pre-build
-          -pre-compile
-          -post-compile (This is typically used for code obfuscation.
-                         Compiled code location: ${out.classes.absolute.dir}
-                         If this is not done in place, override ${out.dex.input.absolute.dir})
-          -post-package
-          -post-build
-          -pre-clean
-  -->
-  <import file="custom_rules.xml" optional="true"/>
-
-  <!-- Import the actual build file.
-
-       To customize existing targets, there are two options:
-       - Customize only one target:
-           - copy/paste the target into this file, *before* the
-             <import> task.
-           - customize it to your needs.
-       - Customize the whole content of build.xml
-           - copy/paste the content of the rules files (minus the top node)
-             into this file, replacing the <import> task.
-           - customize to your needs.
-
-       ***********************
-       ****** IMPORTANT ******
-       ***********************
-       In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
-       in order to avoid having your file be overridden by tools such as "android update project"
-  -->
-  <!-- version-tag: 1 -->
-  <import file="${sdk.dir}/tools/ant/build.xml"/>
-
-</project>
diff --git a/library/custom_rules.xml b/library/custom_rules.xml
deleted file mode 100644
index a5da3a3..0000000
--- a/library/custom_rules.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project name="glide-rules" default="help">
-    <xmlproperty file="AndroidManifest.xml" prefix="mymanifest" collapseAttributes="true"/>
-
-    <target name="jar" depends="-compile">
-        <jar destfile="bin/glide-${mymanifest.manifest.android:versionName}.jar"
-            basedir="bin/classes" >
-            <zipgroupfileset dir="libs" includes="disklrucache*.jar,volley.jar" />
-        </jar>
-    </target>
-</project>
diff --git a/library/gradlew b/library/gradlew
deleted file mode 100755
index 91a7e26..0000000
--- a/library/gradlew
+++ /dev/null
@@ -1,164 +0,0 @@
-#!/usr/bin/env bash
-
-##############################################################################
-##
-##  Gradle start up script for UN*X
-##
-##############################################################################
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
-    echo "$*"
-}
-
-die ( ) {
-    echo
-    echo "$*"
-    echo
-    exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-esac
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
-    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
-    ls=`ls -ld "$PRG"`
-    link=`expr "$ls" : '.*-> \(.*\)$'`
-    if expr "$link" : '/.*' > /dev/null; then
-        PRG="$link"
-    else
-        PRG=`dirname "$PRG"`"/$link"
-    fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >&-
-APP_HOME="`pwd -P`"
-cd "$SAVED" >&-
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
-    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
-        # IBM's JDK on AIX uses strange locations for the executables
-        JAVACMD="$JAVA_HOME/jre/sh/java"
-    else
-        JAVACMD="$JAVA_HOME/bin/java"
-    fi
-    if [ ! -x "$JAVACMD" ] ; then
-        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-    fi
-else
-    JAVACMD="java"
-    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
-    MAX_FD_LIMIT=`ulimit -H -n`
-    if [ $? -eq 0 ] ; then
-        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
-            MAX_FD="$MAX_FD_LIMIT"
-        fi
-        ulimit -n $MAX_FD
-        if [ $? -ne 0 ] ; then
-            warn "Could not set maximum file descriptor limit: $MAX_FD"
-        fi
-    else
-        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
-    fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
-    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
-    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
-    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
-    # We build the pattern for arguments to be converted via cygpath
-    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
-    SEP=""
-    for dir in $ROOTDIRSRAW ; do
-        ROOTDIRS="$ROOTDIRS$SEP$dir"
-        SEP="|"
-    done
-    OURCYGPATTERN="(^($ROOTDIRS))"
-    # Add a user-defined pattern to the cygpath arguments
-    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
-        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
-    fi
-    # Now convert the arguments - kludge to limit ourselves to /bin/sh
-    i=0
-    for arg in "$@" ; do
-        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
-        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
-
-        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
-            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
-        else
-            eval `echo args$i`="\"$arg\""
-        fi
-        i=$((i+1))
-    done
-    case $i in
-        (0) set -- ;;
-        (1) set -- "$args0" ;;
-        (2) set -- "$args0" "$args1" ;;
-        (3) set -- "$args0" "$args1" "$args2" ;;
-        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
-    esac
-fi
-
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
-    JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
-
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/library/gradlew.bat b/library/gradlew.bat
deleted file mode 100644
index aec9973..0000000
--- a/library/gradlew.bat
+++ /dev/null
@@ -1,90 +0,0 @@
-@if "%DEBUG%" == "" @echo off

-@rem ##########################################################################

-@rem

-@rem  Gradle startup script for Windows

-@rem

-@rem ##########################################################################

-

-@rem Set local scope for the variables with windows NT shell

-if "%OS%"=="Windows_NT" setlocal

-

-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

-set DEFAULT_JVM_OPTS=

-

-set DIRNAME=%~dp0

-if "%DIRNAME%" == "" set DIRNAME=.

-set APP_BASE_NAME=%~n0

-set APP_HOME=%DIRNAME%

-

-@rem Find java.exe

-if defined JAVA_HOME goto findJavaFromJavaHome

-

-set JAVA_EXE=java.exe

-%JAVA_EXE% -version >NUL 2>&1

-if "%ERRORLEVEL%" == "0" goto init

-

-echo.

-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

-echo.

-echo Please set the JAVA_HOME variable in your environment to match the

-echo location of your Java installation.

-

-goto fail

-

-:findJavaFromJavaHome

-set JAVA_HOME=%JAVA_HOME:"=%

-set JAVA_EXE=%JAVA_HOME%/bin/java.exe

-

-if exist "%JAVA_EXE%" goto init

-

-echo.

-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

-echo.

-echo Please set the JAVA_HOME variable in your environment to match the

-echo location of your Java installation.

-

-goto fail

-

-:init

-@rem Get command-line arguments, handling Windowz variants

-

-if not "%OS%" == "Windows_NT" goto win9xME_args

-if "%@eval[2+2]" == "4" goto 4NT_args

-

-:win9xME_args

-@rem Slurp the command line arguments.

-set CMD_LINE_ARGS=

-set _SKIP=2

-

-:win9xME_args_slurp

-if "x%~1" == "x" goto execute

-

-set CMD_LINE_ARGS=%*

-goto execute

-

-:4NT_args

-@rem Get arguments from the 4NT Shell from JP Software

-set CMD_LINE_ARGS=%$

-

-:execute

-@rem Setup the command line

-

-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

-

-@rem Execute Gradle

-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

-

-:end

-@rem End local scope for the variables with windows NT shell

-if "%ERRORLEVEL%"=="0" goto mainEnd

-

-:fail

-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

-rem the _cmd.exe /c_ return code!

-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

-exit /b 1

-

-:mainEnd

-if "%OS%"=="Windows_NT" endlocal

-

-:omega

diff --git a/library/lint.xml b/library/lint.xml
index afdace9..d9d6c9f 100644
--- a/library/lint.xml
+++ b/library/lint.xml
@@ -1,8 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <lint>
     <issue id="AllowBackup" severity="ignore" />
-    <issue id="InlinedApi">
-        <ignore path="src/main/java/com/bumptech/glide/load/engine/cache/LruResourceCache.java" />
-        <ignore path="src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPool.java" />
-    </issue>
 </lint>
diff --git a/library/pom.xml b/library/pom.xml
deleted file mode 100644
index 94bb357..0000000
--- a/library/pom.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.bumptech.glide</groupId>
-    <artifactId>glide-parent</artifactId>
-    <version>3.3.0-SNAPSHOT</version>
-    <relativePath>../pom.xml</relativePath>
-  </parent>
-
-  <artifactId>glide</artifactId>
-  <packaging>aar</packaging>
-  <name>Glide</name>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.roboguice</groupId>
-      <artifactId>roboguice</artifactId>
-      <version>2.0</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-      <version>4.0.1</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <version>4.11</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.jakewharton</groupId>
-      <artifactId>disklrucache</artifactId>
-      <version>2.0.2</version>
-    </dependency>
-    <dependency>
-      <groupId>org.robolectric</groupId>
-      <artifactId>robolectric</artifactId>
-      <version>2.4-SNAPSHOT</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.hamcrest</groupId>
-      <artifactId>hamcrest-core</artifactId>
-      <version>1.2</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.mockito</groupId>
-      <artifactId>mockito-all</artifactId>
-      <version>1.9.5</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.bumptech.glide</groupId>
-      <artifactId>glide-gif-decoder</artifactId>
-      <version>3.3.0-SNAPSHOT</version>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/library/project.properties b/library/project.properties
deleted file mode 100644
index 402ea59..0000000
--- a/library/project.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Project target.
-target=android-19
-
-# https://code.google.com/p/android/issues/detail?id=40487
-renderscript.opt.level=O0
diff --git a/library/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
similarity index 100%
rename from library/AndroidManifest.xml
rename to library/src/main/AndroidManifest.xml
diff --git a/library/src/main/java/com/bumptech/glide/BitmapOptions.java b/library/src/main/java/com/bumptech/glide/BitmapOptions.java
new file mode 100644
index 0000000..2849d1c
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/BitmapOptions.java
@@ -0,0 +1,9 @@
+package com.bumptech.glide;
+
+interface BitmapOptions {
+
+    GenericRequestBuilder<?, ?, ?, ?> fitCenter();
+
+    GenericRequestBuilder<?, ?, ?, ?> centerCrop();
+
+}
diff --git a/library/src/main/java/com/bumptech/glide/BitmapRequestBuilder.java b/library/src/main/java/com/bumptech/glide/BitmapRequestBuilder.java
index 52fa9f8..07e7c4a 100644
--- a/library/src/main/java/com/bumptech/glide/BitmapRequestBuilder.java
+++ b/library/src/main/java/com/bumptech/glide/BitmapRequestBuilder.java
@@ -1,59 +1,65 @@
 package com.bumptech.glide;
 
-import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.os.ParcelFileDescriptor;
 import android.view.animation.Animation;
+import android.widget.ImageView;
 
 import com.bumptech.glide.load.DecodeFormat;
 import com.bumptech.glide.load.Encoder;
+import com.bumptech.glide.load.Key;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.ResourceEncoder;
 import com.bumptech.glide.load.Transformation;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 import com.bumptech.glide.load.model.ImageVideoWrapper;
-import com.bumptech.glide.load.resource.bitmap.CenterCrop;
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
 import com.bumptech.glide.load.resource.bitmap.Downsampler;
 import com.bumptech.glide.load.resource.bitmap.FileDescriptorBitmapDecoder;
-import com.bumptech.glide.load.resource.bitmap.FitCenter;
 import com.bumptech.glide.load.resource.bitmap.ImageVideoBitmapDecoder;
 import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder;
 import com.bumptech.glide.load.resource.bitmap.VideoBitmapDecoder;
+import com.bumptech.glide.load.resource.file.FileToStreamDecoder;
 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
-import com.bumptech.glide.manager.RequestTracker;
 import com.bumptech.glide.provider.LoadProvider;
 import com.bumptech.glide.request.RequestListener;
-import com.bumptech.glide.request.ViewPropertyAnimation;
+import com.bumptech.glide.request.animation.ViewPropertyAnimation;
+import com.bumptech.glide.request.target.Target;
 
+import java.io.File;
 import java.io.InputStream;
 
 /**
  * A class for creating a request to load a bitmap for an image or from a video. Sets a variety of type independent
  * options including resizing, animations, and placeholders.
  *
+ * <p>
+ *     Warning - It is <em>not</em> safe to use this builder after calling <code>into()</code>, it may be pooled and
+ *     reused.
+ * </p>
+ *
  * @param <ModelType> The type of model that will be loaded into the target.
  * @param <TranscodeType> The type of the transcoded resource that the target will receive
  */
-@SuppressWarnings("unused") //public api
-public class BitmapRequestBuilder<ModelType, TranscodeType> extends GenericRequestBuilder<ModelType, ImageVideoWrapper,
-        Bitmap, TranscodeType> {
+public class BitmapRequestBuilder<ModelType, TranscodeType>
+        extends GenericRequestBuilder<ModelType, ImageVideoWrapper, Bitmap, TranscodeType> implements BitmapOptions {
     private final BitmapPool bitmapPool;
+
     private Downsampler downsampler = Downsampler.AT_LEAST;
-    private DecodeFormat decodeFormat = DecodeFormat.ALWAYS_ARGB_8888;
+    private DecodeFormat decodeFormat;
     private ResourceDecoder<InputStream, Bitmap> imageDecoder;
     private ResourceDecoder<ParcelFileDescriptor, Bitmap> videoDecoder;
-    private Glide glide;
 
-    BitmapRequestBuilder(Context context, ModelType model,
-            LoadProvider<ModelType, ImageVideoWrapper, Bitmap, TranscodeType> streamLoadProvider,
-            Class<TranscodeType> transcodeClass, Glide glide, RequestTracker requestTracker) {
-        super(context, model, streamLoadProvider, transcodeClass, glide, requestTracker);
-        this.glide = glide;
-        this.bitmapPool = glide.getBitmapPool();
+    BitmapRequestBuilder(LoadProvider<ModelType, ImageVideoWrapper, Bitmap, TranscodeType> loadProvider,
+            Class<TranscodeType> transcodeClass, GenericRequestBuilder<ModelType, ?, ?, ?> other) {
+        super(loadProvider, transcodeClass, other);
+        this.bitmapPool = other.glide.getBitmapPool();
+        this.decodeFormat =  other.glide.getDecodeFormat();
 
-        imageDecoder = new StreamBitmapDecoder(bitmapPool);
-        videoDecoder = new FileDescriptorBitmapDecoder(bitmapPool);
+        imageDecoder = new StreamBitmapDecoder(bitmapPool, decodeFormat);
+        videoDecoder = new FileDescriptorBitmapDecoder(bitmapPool, decodeFormat);
     }
 
     /**
@@ -61,7 +67,7 @@
      *
      * @see #downsample(Downsampler)
      *
-     * @return This RequestBuilder
+     * @return This request builder.
      */
     public BitmapRequestBuilder<ModelType, TranscodeType> approximate() {
         return downsample(Downsampler.AT_LEAST);
@@ -72,13 +78,25 @@
      *
      * @see #downsample(Downsampler)
      *
-     * @return This RequestBuilder
+     * @return This request builder.
      */
     public BitmapRequestBuilder<ModelType, TranscodeType> asIs() {
         return downsample(Downsampler.NONE);
     }
 
     /**
+     * Load images at a size that is at most exactly as big as the target using
+     * {@link com.bumptech.glide.load.resource.bitmap.Downsampler#AT_MOST}.
+     *
+     * @see #downsample(com.bumptech.glide.load.resource.bitmap.Downsampler)
+     *
+     * @return This request builder.
+     */
+    public BitmapRequestBuilder<ModelType, TranscodeType> atMost() {
+        return downsample(Downsampler.AT_MOST);
+    }
+
+    /**
      * Load images using the given {@link Downsampler}. Replaces any existing image decoder. Defaults to
      * {@link Downsampler#AT_LEAST}. Will be ignored if the data represented by the model is a video. This replaces any
      * previous calls to {@link #imageDecoder(ResourceDecoder)}  and {@link #decoder(ResourceDecoder)} with default
@@ -86,8 +104,8 @@
      *
      * @see #imageDecoder
      *
-     * @param downsampler The downsampler
-     * @return This RequestBuilder
+     * @param downsampler The downsampler.
+     * @return This request builder.
      */
     private BitmapRequestBuilder<ModelType, TranscodeType> downsample(Downsampler downsampler) {
         this.downsampler = downsampler;
@@ -96,49 +114,102 @@
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> thumbnail(float sizeMultiplier) {
         super.thumbnail(sizeMultiplier);
         return this;
     }
 
-    public BitmapRequestBuilder<ModelType, TranscodeType> thumbnail(BitmapRequestBuilder<ModelType, TranscodeType>
+    /**
+     * Loads and displays the {@link android.graphics.Bitmap} retrieved by the given thumbnail request if it finishes
+     * before this request. Best used for loading thumbnail {@link Bitmap}s that are smaller and will be loaded more
+     * quickly than the fullsize {@link Bitmap}. There are no guarantees about the order in which the requests will
+     * actually finish. However, if the thumb request completes after the full request, the thumb
+     * {@link android.graphics.Bitmap} will never replace the full image.
+     *
+     * @see #thumbnail(float)
+     *
+     * <p>
+     *     Note - Any options on the main request will not be passed on to the thumbnail request. For example, if
+     *     you want an animation to occur when either the full {@link android.graphics.Bitmap} loads or the thumbnail
+     *     loads, you need to call {@link #animate(int)} on both the thumb and the full request. For a simpler thumbnail
+     *     option where these options are applied to the humbnail as well, see {@link #thumbnail(float)}.
+     * </p>
+     *
+     * <p>
+     *     Only the thumbnail call on the main request will be obeyed, recursive calls to this method are ignored.
+     * </p>
+     *
+     * @param thumbnailRequest The request to use to load the thumbnail.
+     * @return This request builder.
+     */
+    public BitmapRequestBuilder<ModelType, TranscodeType> thumbnail(BitmapRequestBuilder<?, TranscodeType>
             thumbnailRequest) {
         super.thumbnail(thumbnailRequest);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> sizeMultiplier(float sizeMultiplier) {
         super.sizeMultiplier(sizeMultiplier);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> decoder(ResourceDecoder<ImageVideoWrapper, Bitmap> decoder) {
         super.decoder(decoder);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public BitmapRequestBuilder<ModelType, TranscodeType> cacheDecoder(
-            ResourceDecoder<InputStream, Bitmap> cacheDecoder) {
+    public BitmapRequestBuilder<ModelType, TranscodeType> cacheDecoder(ResourceDecoder<File, Bitmap> cacheDecoder) {
         super.cacheDecoder(cacheDecoder);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> encoder(ResourceEncoder<Bitmap> encoder) {
         super.encoder(encoder);
         return this;
     }
 
+    /**
+     * Sets the {@link com.bumptech.glide.load.ResourceDecoder} that will be used to decode {@link Bitmap}s obtained
+     * from an {@link java.io.InputStream}.
+     *
+     * @see #videoDecoder
+     *
+     * @param decoder The decoder to use to decode {@link Bitmap}s.
+     * @return This request builder.
+     */
     public BitmapRequestBuilder<ModelType, TranscodeType> imageDecoder(ResourceDecoder<InputStream, Bitmap> decoder) {
         imageDecoder = decoder;
         super.decoder(new ImageVideoBitmapDecoder(decoder, videoDecoder));
         return this;
     }
 
+    /**
+     * Sets the {@link com.bumptech.glide.load.ResourceDecoder} that will be used to decode {@link Bitmap}s obtained
+     * from an {@link android.os.ParcelFileDescriptor}.
+     *
+     * @param decoder The decoder to use to decode {@link Bitmap}s.
+     * @return This request builder.
+     */
     public BitmapRequestBuilder<ModelType, TranscodeType> videoDecoder(
             ResourceDecoder<ParcelFileDescriptor, Bitmap> decoder) {
         videoDecoder = decoder;
@@ -148,9 +219,10 @@
 
     /**
      * Sets the preferred format for {@link Bitmap}s decoded in this request. Defaults to
-     * {@link DecodeFormat#ALWAYS_ARGB_8888}. This replaces any previous calls to {@link #imageDecoder(ResourceDecoder)},
-     * {@link #videoDecoder(ResourceDecoder)} and {@link #decoder(ResourceDecoder)} with default decoders with the
-     * appropriate options set.
+     * {@link DecodeFormat#PREFER_RGB_565}. This replaces any previous calls to {@link #imageDecoder(ResourceDecoder)},
+     * {@link #videoDecoder(ResourceDecoder)}, {@link #decoder(ResourceDecoder)} and
+     * {@link #cacheDecoder(com.bumptech.glide.load.ResourceDecoder)}} with default decoders with the appropriate
+     * options set.
      *
      * <p>
      *     Note - If using a {@link Transformation} that expect bitmaps to support transparency, this should always be
@@ -167,6 +239,7 @@
         this.decodeFormat = format;
         imageDecoder = new StreamBitmapDecoder(downsampler, bitmapPool, format);
         videoDecoder = new FileDescriptorBitmapDecoder(new VideoBitmapDecoder(), bitmapPool, format);
+        super.cacheDecoder(new FileToStreamDecoder<Bitmap>(new StreamBitmapDecoder(downsampler, bitmapPool, format)));
         super.decoder(new ImageVideoBitmapDecoder(imageDecoder, videoDecoder));
         return this;
     }
@@ -178,29 +251,61 @@
     }
 
     /**
-     * Transform images using {@link CenterCrop}.
+     * Transform images using the given {@link com.bumptech.glide.load.resource.bitmap.BitmapTransformation}s.
      *
-     * @return This RequestBuilder
+     * @see #centerCrop()
+     * @see #fitCenter()
+     * @see #transform(com.bumptech.glide.load.Transformation[])
+     *
+     * @param transformations The transformations to apply in order.
+     * @return This request builder.
+     */
+    public BitmapRequestBuilder<ModelType, TranscodeType> transform(BitmapTransformation... transformations) {
+        super.transform(transformations);
+        return this;
+    }
+
+    /**
+     * Transform images using {@link com.bumptech.glide.load.resource.bitmap.CenterCrop}.
+     *
+     * @see #fitCenter()
+     * @see #transform(com.bumptech.glide.load.resource.bitmap.BitmapTransformation...)
+     * @see #transform(com.bumptech.glide.load.Transformation[])
+     *
+     * @return This request builder.
      */
     public BitmapRequestBuilder<ModelType, TranscodeType> centerCrop() {
         return transform(glide.getBitmapCenterCrop());
     }
 
     /**
-     * Transform images using {@link FitCenter}.
+     * Transform images using {@link com.bumptech.glide.load.resource.bitmap.FitCenter}.
      *
-     * @return This RequestBuilder
+     * @see #centerCrop()
+     * @see #transform(com.bumptech.glide.load.resource.bitmap.BitmapTransformation...)
+     * @see #transform(com.bumptech.glide.load.Transformation[])
+     *
+     * @return This request builder.
      */
     public BitmapRequestBuilder<ModelType, TranscodeType> fitCenter() {
         return transform(glide.getBitmapFitCenter());
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * @see #fitCenter()
+     * @see #centerCrop()
+     */
     @Override
-    public BitmapRequestBuilder<ModelType, TranscodeType> transform(Transformation<Bitmap> transformation) {
-        super.transform(transformation);
+    public BitmapRequestBuilder<ModelType, TranscodeType> transform(Transformation<Bitmap>... transformations) {
+        super.transform(transformations);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> transcoder(
             ResourceTranscoder<Bitmap, TranscodeType> transcoder) {
@@ -208,95 +313,186 @@
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public BitmapRequestBuilder<ModelType, TranscodeType> dontAnimate() {
+        super.dontAnimate();
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> animate(int animationId) {
         super.animate(animationId);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Deprecated
+    @SuppressWarnings("deprecation")
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> animate(Animation animation) {
         super.animate(animation);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> animate(ViewPropertyAnimation.Animator animator) {
         super.animate(animator);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> placeholder(int resourceId) {
         super.placeholder(resourceId);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> placeholder(Drawable drawable) {
         super.placeholder(drawable);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> error(int resourceId) {
         super.error(resourceId);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> error(Drawable drawable) {
         super.error(drawable);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> listener(
-            RequestListener<ModelType, TranscodeType> requestListener) {
+            RequestListener<? super ModelType, TranscodeType> requestListener) {
         super.listener(requestListener);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> skipMemoryCache(boolean skip) {
         super.skipMemoryCache(skip);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public BitmapRequestBuilder<ModelType, TranscodeType> skipDiskCache(boolean skip) {
-        super.skipDiskCache(skip);
+    public BitmapRequestBuilder<ModelType, TranscodeType> diskCacheStrategy(DiskCacheStrategy  strategy) {
+        super.diskCacheStrategy(strategy);
         return this;
     }
 
-    @Override
-    public BitmapRequestBuilder<ModelType, TranscodeType> skipCache(boolean skip) {
-        super.skipCache(skip);
-        return this;
-    }
-
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> override(int width, int height) {
         super.override(width, height);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> thumbnail(
-            GenericRequestBuilder<ModelType, ImageVideoWrapper, Bitmap, TranscodeType> thumbnailRequest) {
+            GenericRequestBuilder<?, ?, ?, TranscodeType> thumbnailRequest) {
         super.thumbnail(thumbnailRequest);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public BitmapRequestBuilder<ModelType, TranscodeType> sourceEncoder(Encoder<ImageVideoWrapper> sourceEncoder) {
         super.sourceEncoder(sourceEncoder);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public BitmapRequestBuilder<ModelType, TranscodeType> cacheSource(boolean cacheSource) {
-        super.cacheSource(cacheSource);
+    public BitmapRequestBuilder<ModelType, TranscodeType> dontTransform() {
+        super.dontTransform();
         return this;
     }
+
+    @Override
+    public BitmapRequestBuilder<ModelType, TranscodeType> signature(Key signature) {
+        super.signature(signature);
+        return this;
+    }
+
+    @Override
+    public BitmapRequestBuilder<ModelType, TranscodeType> load(ModelType model) {
+        super.load(model);
+        return this;
+    }
+
+    @Override
+    public BitmapRequestBuilder<ModelType, TranscodeType> clone() {
+        return (BitmapRequestBuilder<ModelType, TranscodeType>) super.clone();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>
+     *     Note - If no transformation is set for this load, a default transformation will be applied based on the
+     *     value returned from {@link android.widget.ImageView#getScaleType()}. To avoid this default transformation,
+     *     use {@link #dontTransform()}.
+     * </p>
+     *
+     * @param view {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public Target<TranscodeType> into(ImageView view) {
+        return super.into(view);
+    }
+
+    @Override
+    void applyFitCenter() {
+        fitCenter();
+    }
+
+    @Override
+    void applyCenterCrop() {
+        centerCrop();
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/BitmapTypeRequest.java b/library/src/main/java/com/bumptech/glide/BitmapTypeRequest.java
index 3480a00..8e2b227 100644
--- a/library/src/main/java/com/bumptech/glide/BitmapTypeRequest.java
+++ b/library/src/main/java/com/bumptech/glide/BitmapTypeRequest.java
@@ -1,67 +1,101 @@
 package com.bumptech.glide;
 
-import android.content.Context;
 import android.graphics.Bitmap;
 import android.os.ParcelFileDescriptor;
+
 import com.bumptech.glide.load.model.ImageVideoModelLoader;
 import com.bumptech.glide.load.model.ImageVideoWrapper;
 import com.bumptech.glide.load.model.ModelLoader;
 import com.bumptech.glide.load.resource.transcode.BitmapBytesTranscoder;
 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
-import com.bumptech.glide.manager.RequestTracker;
+import com.bumptech.glide.provider.DataLoadProvider;
 import com.bumptech.glide.provider.FixedLoadProvider;
 
 import java.io.InputStream;
 
-public class BitmapTypeRequest<A> extends BitmapRequestBuilder<A, Bitmap> {
-    private final Context context;
-    private final A model;
-    private final ModelLoader<A, InputStream> streamModelLoader;
-    private ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader;
+/**
+ * A class for creating a load request that either loads an {@link Bitmap} directly or that adds an
+ * {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} to transcode the {@link Bitmap} into another
+ * resource type.
+ *
+ * @param <ModelType> The type of model to load the {@link Bitmap} or transcoded class from.
+ */
+public class BitmapTypeRequest<ModelType> extends BitmapRequestBuilder<ModelType, Bitmap> {
+    private final ModelLoader<ModelType, InputStream> streamModelLoader;
+    private final ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader;
     private final Glide glide;
-    private RequestTracker requestTracker;
-    private RequestManager.OptionsApplier optionsApplier;
+    private final RequestManager.OptionsApplier optionsApplier;
 
     private static <A, R> FixedLoadProvider<A, ImageVideoWrapper, Bitmap, R> buildProvider(Glide glide,
             ModelLoader<A, InputStream> streamModelLoader,
             ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader,
-            Class<R> transcodedClass,
-            ResourceTranscoder<Bitmap, R> transcoder) {
-            return streamModelLoader == null && fileDescriptorModelLoader == null ? null :
-                    new FixedLoadProvider<A, ImageVideoWrapper, Bitmap, R>(
-                            new ImageVideoModelLoader<A>(streamModelLoader, fileDescriptorModelLoader),
-                            transcoder != null ? transcoder : glide.buildTranscoder(Bitmap.class, transcodedClass),
-                            glide.buildDataProvider(ImageVideoWrapper.class, Bitmap.class));
+            Class<R> transcodedClass, ResourceTranscoder<Bitmap, R> transcoder) {
+        if (streamModelLoader == null && fileDescriptorModelLoader == null) {
+            return null;
+        }
+
+        if (transcoder == null) {
+            transcoder = glide.buildTranscoder(Bitmap.class, transcodedClass);
+        }
+        DataLoadProvider<ImageVideoWrapper, Bitmap> loadProvider = glide.buildDataProvider(ImageVideoWrapper.class,
+                Bitmap.class);
+        ImageVideoModelLoader<A> modelLoader = new ImageVideoModelLoader<A>(streamModelLoader,
+                fileDescriptorModelLoader);
+
+        return new FixedLoadProvider<A, ImageVideoWrapper, Bitmap, R>(modelLoader, transcoder, loadProvider);
     }
 
-    BitmapTypeRequest(Context context, A model,
-            ModelLoader<A, InputStream> streamModelLoader,
-            ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader,
-            Glide glide, RequestTracker requestTracker, RequestManager.OptionsApplier optionsApplier) {
-        super(context, model,
-                buildProvider(glide, streamModelLoader, fileDescriptorModelLoader, Bitmap.class, null),
-                Bitmap.class,
-                glide, requestTracker);
-        this.context = context;
-        this.model = model;
+    BitmapTypeRequest(GenericRequestBuilder<ModelType, ?, ?, ?> other,
+            ModelLoader<ModelType, InputStream> streamModelLoader,
+            ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader,
+            RequestManager.OptionsApplier optionsApplier) {
+        super(buildProvider(other.glide, streamModelLoader, fileDescriptorModelLoader, Bitmap.class, null),
+                Bitmap.class, other);
         this.streamModelLoader = streamModelLoader;
         this.fileDescriptorModelLoader = fileDescriptorModelLoader;
-        this.glide = glide;
-        this.requestTracker= requestTracker;
+        this.glide = other.glide;
         this.optionsApplier = optionsApplier;
     }
 
-    public <R> BitmapRequestBuilder<A, R> transcode(ResourceTranscoder<Bitmap, R> transcoder, Class<R> transcodeClass) {
-        return optionsApplier.apply(model, new BitmapRequestBuilder<A, R>(context, model,
+    /**
+     * Sets a transcoder to transcode the decoded and transformed {@link Bitmap} into another resource type.
+     *
+     * @param transcoder The transoder to use.
+     * @param transcodeClass The {@link Class} of the resource the {@link Bitmap} will be transcoded to.
+     * @param <R> The type of the resource the {@link Bitmap} will be transcoded to.
+     * @return This request builder.
+     */
+    public <R> BitmapRequestBuilder<ModelType, R> transcode(ResourceTranscoder<Bitmap, R> transcoder,
+            Class<R> transcodeClass) {
+        return optionsApplier.apply(new BitmapRequestBuilder<ModelType, R>(
                 buildProvider(glide, streamModelLoader, fileDescriptorModelLoader, transcodeClass, transcoder),
-                transcodeClass, glide, requestTracker));
+                transcodeClass, this));
     }
 
-    public BitmapRequestBuilder<A, byte[]> toBytes() {
+    /**
+     * Transcodes the decoded and transformed {@link Bitmap} to bytes by compressing it as a JPEG to a byte array.
+     * array.
+     *
+     * @see #toBytes(android.graphics.Bitmap.CompressFormat, int)
+     *
+     * @return This request builder.
+     */
+    public BitmapRequestBuilder<ModelType, byte[]> toBytes() {
         return transcode(new BitmapBytesTranscoder(), byte[].class);
     }
 
-    public BitmapRequestBuilder<A, byte[]> toBytes(Bitmap.CompressFormat compressFormat, int quality) {
+    /**
+     * Transcodes the decoded and transformed {@link android.graphics.Bitmap} to bytes by compressing it using the
+     * given format and quality to a byte array.
+     *
+     * @see android.graphics.Bitmap#compress(android.graphics.Bitmap.CompressFormat, int, java.io.OutputStream)
+     * @see #toBytes()
+     *
+     * @param compressFormat The {@link android.graphics.Bitmap.CompressFormat} to use to compress the {@link Bitmap}.
+     * @param quality The quality level from 0-100 to use to compress the {@link Bitmap}.
+     * @return This request builder.
+     */
+    public BitmapRequestBuilder<ModelType, byte[]> toBytes(Bitmap.CompressFormat compressFormat, int quality) {
         return transcode(new BitmapBytesTranscoder(compressFormat, quality), byte[].class);
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/DataLoadProvider.java b/library/src/main/java/com/bumptech/glide/DataLoadProvider.java
deleted file mode 100644
index 9fa77a6..0000000
--- a/library/src/main/java/com/bumptech/glide/DataLoadProvider.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.bumptech.glide;
-
-import com.bumptech.glide.load.Encoder;
-import com.bumptech.glide.load.ResourceDecoder;
-import com.bumptech.glide.load.ResourceEncoder;
-
-import java.io.InputStream;
-
-/**
- * @param <T> The type of data the resource will be decoded from.
- * @param <Z> The type of resource that will be decoded.
- */
-public interface DataLoadProvider<T, Z> {
-
-    public ResourceDecoder<InputStream, Z> getCacheDecoder();
-
-    public ResourceDecoder<T, Z> getSourceDecoder();
-
-    public Encoder<T> getSourceEncoder();
-
-    public ResourceEncoder<Z> getEncoder();
-}
diff --git a/library/src/main/java/com/bumptech/glide/DownloadOptions.java b/library/src/main/java/com/bumptech/glide/DownloadOptions.java
new file mode 100644
index 0000000..ae472c8
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/DownloadOptions.java
@@ -0,0 +1,29 @@
+package com.bumptech.glide;
+
+import com.bumptech.glide.request.FutureTarget;
+import com.bumptech.glide.request.target.Target;
+
+import java.io.File;
+
+interface DownloadOptions {
+
+    /**
+     * Loads the original unmodified data into the cache and calls the given Target with the cache File.
+     *
+     * @param target The Target that will receive the cache File when the load completes
+     * @param <Y> The type of Target.
+     * @return The given Target.
+     */
+    <Y extends Target<File>> Y downloadOnly(Y target);
+
+
+    /**
+     * Loads the original unmodified data into the cache and returns a {@link java.util.concurrent.Future} that can be
+     * used to retrieve the cache File containing the data.
+     *
+     * @param width The width in pixels to use to fetch the data.
+     * @param height The height in pixels to use to fetch the data.
+     * @return A {@link java.util.concurrent.Future} that can be used to retrieve the cache File containing the data.
+     */
+    FutureTarget<File> downloadOnly(int width, int height);
+}
diff --git a/library/src/main/java/com/bumptech/glide/DrawableOptions.java b/library/src/main/java/com/bumptech/glide/DrawableOptions.java
new file mode 100644
index 0000000..ebc1aa5
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/DrawableOptions.java
@@ -0,0 +1,65 @@
+package com.bumptech.glide;
+
+import android.view.animation.Animation;
+
+interface DrawableOptions {
+
+    /**
+     * Applies a cross fade transformation that fades from the placeholder to the loaded
+     * {@link android.graphics.drawable.Drawable}. If no placeholder is set, the Drawable will instead simply fade in.
+     *
+     * @see #crossFade(int)
+     * @see #crossFade(int, int)
+     *
+     * @return This request builder.
+     */
+     GenericRequestBuilder<?, ?, ?, ?> crossFade();
+
+    /**
+     * Applies a cross fade transformation that fades from the placeholder to the loaded
+     * {@link android.graphics.drawable.Drawable}. If no placeholder is set the Drawable will instead simply fade in.
+     *
+     * @see #crossFade()
+     * @see #crossFade(int, int)
+     *
+     * @param duration The duration of the cross fade and initial fade in.
+     * @return This request builder.
+     */
+    GenericRequestBuilder<?, ?, ?, ?> crossFade(int duration);
+
+
+    /**
+     * Applies a cross fade transformation that des from the placeholder to the loaded
+     * {@link android.graphics.drawable.Drawable}. If no placeholder is set, the Drawable will instead be animated in
+     * using the given {@link android.view.animation.Animation}.
+     *
+     * @see #crossFade()
+     * @see #crossFade(int)
+     * @see #crossFade(int, int)
+     *
+     * @deprecated If this builder is used for multiple loads, using this method will result in multiple view's being
+     * asked to start an animation using a single {@link android.view.animation.Animation} object which results in
+     * views animating repeatedly. Use {@link #crossFade(int, int)}} instead, or be sure to call this method once
+     * per call to {@link com.bumptech.glide.GenericRequestBuilder#load(Object)} to avoid re-using animation objects.
+     * Scheduled to be removed in Glide 4.0.
+     * @param animation The Animation to use if no placeholder is set.
+     * @param duration The duration of the cross fade animation.
+     * @return This request builder.
+     */
+    @Deprecated
+    GenericRequestBuilder<?, ?, ?, ?> crossFade(Animation animation, int duration);
+
+    /**
+     * Applies a cross fade transformation that des from the placeholder to the loaded
+     * {@link android.graphics.drawable.Drawable}. If no placeholder is set, the Drawable will instead be animated in
+     * using the {@link android.view.animation.Animation} loaded from the given animation id.
+     *
+     * @see #crossFade()
+     * @see #crossFade(int)
+     *
+     * @param animationId The id of the Animation to use if no placeholder is set.
+     * @param duration The duration of the cross fade animation.
+     * @return This request builder.
+     */
+    GenericRequestBuilder<?, ?, ?, ?> crossFade(int animationId, int duration);
+}
diff --git a/library/src/main/java/com/bumptech/glide/DrawableRequestBuilder.java b/library/src/main/java/com/bumptech/glide/DrawableRequestBuilder.java
index c715959..845a01e 100644
--- a/library/src/main/java/com/bumptech/glide/DrawableRequestBuilder.java
+++ b/library/src/main/java/com/bumptech/glide/DrawableRequestBuilder.java
@@ -4,215 +4,445 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.view.animation.Animation;
+import android.widget.ImageView;
+
 import com.bumptech.glide.load.Encoder;
+import com.bumptech.glide.load.Key;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.ResourceEncoder;
 import com.bumptech.glide.load.Transformation;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
 import com.bumptech.glide.load.model.ImageVideoWrapper;
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
 import com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapper;
 import com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperTransformation;
 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
+import com.bumptech.glide.manager.Lifecycle;
 import com.bumptech.glide.manager.RequestTracker;
 import com.bumptech.glide.provider.LoadProvider;
-import com.bumptech.glide.request.DrawableCrossFadeViewAnimation;
 import com.bumptech.glide.request.RequestListener;
-import com.bumptech.glide.request.ViewPropertyAnimation;
+import com.bumptech.glide.request.animation.DrawableCrossFadeFactory;
+import com.bumptech.glide.request.animation.ViewPropertyAnimation;
+import com.bumptech.glide.request.target.Target;
 
-import java.io.InputStream;
+import java.io.File;
 
-public class DrawableRequestBuilder<ModelType> extends
-        GenericRequestBuilder<ModelType, ImageVideoWrapper, GifBitmapWrapper, Drawable> {
-    private final Glide glide;
-    private final Context context;
+/**
+ * A class for creating a request to load a {@link GlideDrawable}.
+ *
+ * <p>
+ *     Warning - It is <em>not</em> safe to use this builder after calling <code>into()</code>, it may be pooled and
+ *     reused.
+ * </p>
+ *
+ * @param <ModelType> The type of model that will be loaded into the target.
+ */
+public class DrawableRequestBuilder<ModelType>
+        extends GenericRequestBuilder<ModelType, ImageVideoWrapper, GifBitmapWrapper, GlideDrawable>
+        implements BitmapOptions, DrawableOptions {
 
-    DrawableRequestBuilder(Context context, ModelType model,
-            LoadProvider<ModelType, ImageVideoWrapper, GifBitmapWrapper, Drawable> loadProvider, Glide glide,
-            RequestTracker requestTracker) {
-        super(context, model, loadProvider, Drawable.class, glide, requestTracker);
-        this.context = context;
-        this.glide = glide;
+    DrawableRequestBuilder(Context context, Class<ModelType> modelClass,
+            LoadProvider<ModelType, ImageVideoWrapper, GifBitmapWrapper, GlideDrawable> loadProvider, Glide glide,
+            RequestTracker requestTracker, Lifecycle lifecycle) {
+        super(context, modelClass, loadProvider, GlideDrawable.class, glide, requestTracker, lifecycle);
+        // Default to animating.
+        crossFade();
     }
 
+    /**
+     * Loads and displays the {@link GlideDrawable} retrieved by the given thumbnail request if it finishes before this
+     * request. Best used for loading thumbnail {@link GlideDrawable}s that are smaller and will be loaded more quickly
+     * than the fullsize {@link GlideDrawable}. There are no guarantees about the order in which the requests will
+     * actually finish. However, if the thumb request completes after the full request, the thumb {@link GlideDrawable}
+     * will never replace the full image.
+     *
+     * @see #thumbnail(float)
+     *
+     * <p>
+     *     Note - Any options on the main request will not be passed on to the thumbnail request. For example, if
+     *     you want an animation to occur when either the full {@link GlideDrawable} loads or the thumbnail loads,
+     *     you need to call {@link #animate(int)} on both the thumb and the full request. For a simpler thumbnail
+     *     option where these options are applied to the humbnail as well, see {@link #thumbnail(float)}.
+     * </p>
+     *
+     * <p>
+     *     Only the thumbnail call on the main request will be obeyed, recursive calls to this method are ignored.
+     * </p>
+     *
+     * @param thumbnailRequest The request to use to load the thumbnail.
+     * @return This builder object.
+     */
     public DrawableRequestBuilder<ModelType> thumbnail(
-            DrawableRequestBuilder<ModelType> thumbnailRequest) {
+            DrawableRequestBuilder<?> thumbnailRequest) {
         super.thumbnail(thumbnailRequest);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> thumbnail(
-            GenericRequestBuilder<ModelType, ImageVideoWrapper, GifBitmapWrapper, Drawable> thumbnailRequest) {
+            GenericRequestBuilder<?, ?, ?, GlideDrawable> thumbnailRequest) {
         super.thumbnail(thumbnailRequest);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> thumbnail(float sizeMultiplier) {
         super.thumbnail(sizeMultiplier);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public DrawableRequestBuilder<ModelType> sizeMultiplier(
-            float sizeMultiplier) {
+    public DrawableRequestBuilder<ModelType> sizeMultiplier(float sizeMultiplier) {
         super.sizeMultiplier(sizeMultiplier);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public DrawableRequestBuilder<ModelType> decoder(
-            ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> decoder) {
+    public DrawableRequestBuilder<ModelType> decoder(ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> decoder) {
         super.decoder(decoder);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public DrawableRequestBuilder<ModelType> cacheDecoder(
-            ResourceDecoder<InputStream, GifBitmapWrapper> cacheDecoder) {
+    public DrawableRequestBuilder<ModelType> cacheDecoder(ResourceDecoder<File, GifBitmapWrapper> cacheDecoder) {
         super.cacheDecoder(cacheDecoder);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public DrawableRequestBuilder<ModelType> encoder(
-            ResourceEncoder<GifBitmapWrapper> encoder) {
+    public DrawableRequestBuilder<ModelType> encoder(ResourceEncoder<GifBitmapWrapper> encoder) {
         super.encoder(encoder);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> priority(Priority priority) {
         super.priority(priority);
         return this;
     }
 
+    /**
+     * Transform {@link GlideDrawable}s using the given
+     * {@link com.bumptech.glide.load.resource.bitmap.BitmapTransformation}s.
+     *
+     * <p>
+     *     Note - Bitmap transformations will apply individually to each frame of animated GIF images and also to
+     *     individual {@link Bitmap}s.
+     * </p>
+     *
+     * @see #centerCrop()
+     * @see #fitCenter()
+     * @see #bitmapTransform(com.bumptech.glide.load.Transformation[])
+     * @see #transform(com.bumptech.glide.load.Transformation[])
+     *
+     * @param transformations The transformations to apply in order.
+     * @return This request builder.
+     */
+    public DrawableRequestBuilder<ModelType> transform(BitmapTransformation... transformations) {
+        return bitmapTransform(transformations);
+    }
+
+    /**
+     * Transform {@link GlideDrawable}s using {@link com.bumptech.glide.load.resource.bitmap.CenterCrop}.
+     *
+     * @see #fitCenter()
+     * @see #transform(com.bumptech.glide.load.resource.bitmap.BitmapTransformation...)
+     * @see #bitmapTransform(com.bumptech.glide.load.Transformation[])
+     * @see #transform(com.bumptech.glide.load.Transformation[])
+     *
+     * @return This request builder.
+     */
+    @SuppressWarnings("unchecked")
     public DrawableRequestBuilder<ModelType> centerCrop() {
         return transform(glide.getDrawableCenterCrop());
     }
 
+    /**
+     * Transform {@link GlideDrawable}s using {@link com.bumptech.glide.load.resource.bitmap.FitCenter}.
+     *
+     * @see #centerCrop()
+     * @see #transform(com.bumptech.glide.load.resource.bitmap.BitmapTransformation...)
+     * @see #bitmapTransform(com.bumptech.glide.load.Transformation[])
+     * @see #transform(com.bumptech.glide.load.Transformation[])
+     *
+     * @return This request builder.
+     */
+    @SuppressWarnings("unchecked")
     public DrawableRequestBuilder<ModelType> fitCenter() {
         return transform(glide.getDrawableFitCenter());
     }
 
-    public DrawableRequestBuilder<ModelType> bitmapTransform(Transformation<Bitmap> bitmapTransformation) {
-        return transform(new GifBitmapWrapperTransformation(bitmapTransformation));
+    /**
+     * Transform {@link GlideDrawable}s using the given {@link android.graphics.Bitmap} transformations. Replaces any
+     * previous transformations.
+     *
+     * @see #fitCenter()
+     * @see #centerCrop()
+     * @see #transform(com.bumptech.glide.load.resource.bitmap.BitmapTransformation...)
+     * @see #transform(com.bumptech.glide.load.Transformation[])
+     *
+     * @return This request builder.
+     */
+    public DrawableRequestBuilder<ModelType> bitmapTransform(Transformation<Bitmap>... bitmapTransformations) {
+        GifBitmapWrapperTransformation[] transformations =
+                new GifBitmapWrapperTransformation[bitmapTransformations.length];
+        for (int i = 0; i < bitmapTransformations.length; i++) {
+            transformations[i] = new GifBitmapWrapperTransformation(glide.getBitmapPool(), bitmapTransformations[i]);
+        }
+        return transform(transformations);
     }
 
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see #bitmapTransform(com.bumptech.glide.load.Transformation[])
+     * @see #centerCrop()
+     * @see #fitCenter()
+     */
     @Override
-    public DrawableRequestBuilder<ModelType> transform(Transformation<GifBitmapWrapper> transformation) {
+    public DrawableRequestBuilder<ModelType> transform(Transformation<GifBitmapWrapper>... transformation) {
         super.transform(transformation);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> transcoder(
-            ResourceTranscoder<GifBitmapWrapper, Drawable> transcoder) {
+            ResourceTranscoder<GifBitmapWrapper, GlideDrawable> transcoder) {
         super.transcoder(transcoder);
         return this;
     }
 
-    public DrawableRequestBuilder<ModelType> crossFade() {
-        super.animate(new DrawableCrossFadeViewAnimation.DrawableCrossFadeFactory());
+    /**
+     * {@inheritDoc}
+     */
+    public final DrawableRequestBuilder<ModelType> crossFade() {
+        super.animate(new DrawableCrossFadeFactory<GlideDrawable>());
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public DrawableRequestBuilder<ModelType> crossFade(int duration) {
-        super.animate(new DrawableCrossFadeViewAnimation.DrawableCrossFadeFactory(duration));
+        super.animate(new DrawableCrossFadeFactory<GlideDrawable>(duration));
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Deprecated
     public DrawableRequestBuilder<ModelType> crossFade(Animation animation, int duration) {
-        super.animate(new DrawableCrossFadeViewAnimation.DrawableCrossFadeFactory(animation, duration));
+        super.animate(new DrawableCrossFadeFactory<GlideDrawable>(animation, duration));
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public DrawableRequestBuilder<ModelType> crossFade(int animationId, int duration) {
-        super.animate(new DrawableCrossFadeViewAnimation.DrawableCrossFadeFactory(context, animationId, duration));
+        super.animate(new DrawableCrossFadeFactory<GlideDrawable>(context, animationId,
+                duration));
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public DrawableRequestBuilder<ModelType> dontAnimate() {
+        super.dontAnimate();
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> animate(ViewPropertyAnimation.Animator animator) {
         super.animate(animator);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> animate(int animationId) {
         super.animate(animationId);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Deprecated
+    @SuppressWarnings("deprecation")
     @Override
     public DrawableRequestBuilder<ModelType> animate(Animation animation) {
         super.animate(animation);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> placeholder(int resourceId) {
         super.placeholder(resourceId);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> placeholder(Drawable drawable) {
         super.placeholder(drawable);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> error(int resourceId) {
         super.error(resourceId);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> error(Drawable drawable) {
         super.error(drawable);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> listener(
-            RequestListener<ModelType, Drawable> requestListener) {
+            RequestListener<? super ModelType, GlideDrawable> requestListener) {
         super.listener(requestListener);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public DrawableRequestBuilder<ModelType> diskCacheStrategy(DiskCacheStrategy strategy) {
+        super.diskCacheStrategy(strategy);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> skipMemoryCache(boolean skip) {
         super.skipMemoryCache(skip);
         return this;
     }
 
-    @Override
-    public DrawableRequestBuilder<ModelType> skipDiskCache(boolean skip) {
-        super.skipDiskCache(skip);
-        return this;
-    }
-
-    @Override
-    public DrawableRequestBuilder<ModelType> skipCache(boolean skip) {
-        super.skipCache(skip);
-        return this;
-    }
-
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> override(int width, int height) {
         super.override(width, height);
         return this;
     }
 
-    @Override
-    public DrawableRequestBuilder<ModelType> cacheSource(boolean cacheSource) {
-        super.cacheSource(cacheSource);
-        return this;
-    }
-
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public DrawableRequestBuilder<ModelType> sourceEncoder(Encoder<ImageVideoWrapper> sourceEncoder) {
         super.sourceEncoder(sourceEncoder);
         return this;
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public DrawableRequestBuilder<ModelType> dontTransform() {
+        super.dontTransform();
+        return this;
+    }
+
+    @Override
+    public DrawableRequestBuilder<ModelType> signature(Key signature) {
+        super.signature(signature);
+        return this;
+    }
+
+    @Override
+    public DrawableRequestBuilder<ModelType> load(ModelType model) {
+        super.load(model);
+        return this;
+    }
+
+    @Override
+    public DrawableRequestBuilder<ModelType> clone() {
+        return (DrawableRequestBuilder<ModelType>) super.clone();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>
+     *     Note - If no transformation is set for this load, a default transformation will be applied based on the
+     *     value returned from {@link android.widget.ImageView#getScaleType()}. To avoid this default transformation,
+     *     use {@link #dontTransform()}.
+     * </p>
+     *
+     * @param view {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public Target<GlideDrawable> into(ImageView view) {
+        return super.into(view);
+    }
+
+    @Override
+    void applyFitCenter() {
+        fitCenter();
+    }
+
+    @Override
+    void applyCenterCrop() {
+        centerCrop();
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/DrawableTypeRequest.java b/library/src/main/java/com/bumptech/glide/DrawableTypeRequest.java
index 9b3e01d..ee67f1a 100644
--- a/library/src/main/java/com/bumptech/glide/DrawableTypeRequest.java
+++ b/library/src/main/java/com/bumptech/glide/DrawableTypeRequest.java
@@ -1,63 +1,110 @@
 package com.bumptech.glide;
 
 import android.content.Context;
-import android.graphics.drawable.Drawable;
 import android.os.ParcelFileDescriptor;
+
 import com.bumptech.glide.load.model.ImageVideoModelLoader;
 import com.bumptech.glide.load.model.ImageVideoWrapper;
 import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
 import com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapper;
 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
+import com.bumptech.glide.manager.Lifecycle;
 import com.bumptech.glide.manager.RequestTracker;
+import com.bumptech.glide.provider.DataLoadProvider;
 import com.bumptech.glide.provider.FixedLoadProvider;
+import com.bumptech.glide.request.FutureTarget;
+import com.bumptech.glide.request.target.Target;
 
+import java.io.File;
 import java.io.InputStream;
 
-public class DrawableTypeRequest<A> extends DrawableRequestBuilder<A> {
-    private final ModelLoader<A, InputStream> streamModelLoader;
-    private final ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader;
-    private final Context context;
-    private final Glide glide;
-    private RequestTracker requestTracker;
-    private RequestManager.OptionsApplier optionsApplier;
-    private final A model;
+/**
+ * A class for creating a load request that loads either an animated GIF drawable or a Bitmap drawable directly, or
+ * adds an {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} to transcode the data into a
+ * resource type other than a {@link android.graphics.drawable.Drawable}.
+ *
+ * @param <ModelType> The type of model to use to load the {@link android.graphics.drawable.BitmapDrawable} or
+ * {@link com.bumptech.glide.load.resource.gif.GifDrawable}.
+ */
+public class DrawableTypeRequest<ModelType> extends DrawableRequestBuilder<ModelType> implements DownloadOptions {
+    private final ModelLoader<ModelType, InputStream> streamModelLoader;
+    private final ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader;
+    private final RequestManager.OptionsApplier optionsApplier;
 
     private static <A, Z, R> FixedLoadProvider<A, ImageVideoWrapper, Z, R> buildProvider(Glide glide,
             ModelLoader<A, InputStream> streamModelLoader,
             ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader, Class<Z> resourceClass,
             Class<R> transcodedClass,
             ResourceTranscoder<Z, R> transcoder) {
-            return streamModelLoader == null && fileDescriptorModelLoader == null ? null :
-                    new FixedLoadProvider<A, ImageVideoWrapper, Z, R>(
-                            new ImageVideoModelLoader<A>(streamModelLoader, fileDescriptorModelLoader),
-                            transcoder != null ? transcoder : glide.buildTranscoder(resourceClass, transcodedClass),
-                            glide.buildDataProvider(ImageVideoWrapper.class, resourceClass));
+        if (streamModelLoader == null && fileDescriptorModelLoader == null) {
+            return null;
+        }
+
+        if (transcoder == null) {
+            transcoder = glide.buildTranscoder(resourceClass, transcodedClass);
+        }
+        DataLoadProvider<ImageVideoWrapper, Z> dataLoadProvider = glide.buildDataProvider(ImageVideoWrapper.class,
+                resourceClass);
+        ImageVideoModelLoader<A> modelLoader = new ImageVideoModelLoader<A>(streamModelLoader,
+                fileDescriptorModelLoader);
+        return new FixedLoadProvider<A, ImageVideoWrapper, Z, R>(modelLoader, transcoder, dataLoadProvider);
     }
 
-
-    DrawableTypeRequest(A model, ModelLoader<A, InputStream> streamModelLoader,
-            ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader, Context context, Glide glide,
-            RequestTracker requestTracker, RequestManager.OptionsApplier optionsApplier) {
-        super(context, model,
+    DrawableTypeRequest(Class<ModelType> modelClass, ModelLoader<ModelType, InputStream> streamModelLoader,
+            ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader, Context context, Glide glide,
+            RequestTracker requestTracker, Lifecycle lifecycle, RequestManager.OptionsApplier optionsApplier) {
+        super(context, modelClass,
                 buildProvider(glide, streamModelLoader, fileDescriptorModelLoader, GifBitmapWrapper.class,
-                        Drawable.class, null),
-                glide, requestTracker);
-        this.model = model;
+                        GlideDrawable.class, null),
+                glide, requestTracker, lifecycle);
         this.streamModelLoader = streamModelLoader;
         this.fileDescriptorModelLoader = fileDescriptorModelLoader;
-        this.context = context;
-        this.glide = glide;
-        this.requestTracker = requestTracker;
         this.optionsApplier = optionsApplier;
     }
 
-    public BitmapTypeRequest<A> asBitmap() {
-        return optionsApplier.apply(model, new BitmapTypeRequest<A>(context, model, streamModelLoader,
-                fileDescriptorModelLoader, glide, requestTracker, optionsApplier));
+    /**
+     * Attempts to always load the resource as a {@link android.graphics.Bitmap}, even if it could actually be animated.
+     *
+     * @return A new request builder for loading a {@link android.graphics.Bitmap}
+     */
+    public BitmapTypeRequest<ModelType> asBitmap() {
+        return optionsApplier.apply(new BitmapTypeRequest<ModelType>(this, streamModelLoader,
+                fileDescriptorModelLoader, optionsApplier));
     }
 
-    public GifTypeRequest<A> asGif() {
-        return optionsApplier.apply(model, new GifTypeRequest<A>(context, model, streamModelLoader, glide,
-                requestTracker, optionsApplier));
+    /**
+     * Attempts to always load the resource as a {@link com.bumptech.glide.load.resource.gif.GifDrawable}.
+     * <p>
+     *     If the underlying data is not a GIF, this will fail. As a result, this should only be used if the model
+     *     represents an animated GIF and the caller wants to interact with the GIfDrawable directly. Normally using
+     *     just an {@link com.bumptech.glide.DrawableTypeRequest} is sufficient because it will determine whether or
+     *     not the given data represents an animated GIF and return the appropriate animated or not animated
+     *     {@link android.graphics.drawable.Drawable} automatically.
+     * </p>
+     *
+     * @return A new request builder for loading a {@link com.bumptech.glide.load.resource.gif.GifDrawable}.
+     */
+    public GifTypeRequest<ModelType> asGif() {
+        return optionsApplier.apply(new GifTypeRequest<ModelType>(this, streamModelLoader, optionsApplier));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public <Y extends Target<File>> Y downloadOnly(Y target) {
+        return getDownloadOnlyRequest().downloadOnly(target);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public FutureTarget<File> downloadOnly(int width, int height) {
+        return getDownloadOnlyRequest().downloadOnly(width, height);
+    }
+
+    private GenericTranscodeRequest<ModelType, InputStream, File> getDownloadOnlyRequest() {
+        return optionsApplier.apply(new GenericTranscodeRequest<ModelType, InputStream, File>(File.class, this,
+                streamModelLoader, InputStream.class, File.class, optionsApplier));
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/GenericRequestBuilder.java b/library/src/main/java/com/bumptech/glide/GenericRequestBuilder.java
index a16469e..13319d7 100644
--- a/library/src/main/java/com/bumptech/glide/GenericRequestBuilder.java
+++ b/library/src/main/java/com/bumptech/glide/GenericRequestBuilder.java
@@ -2,150 +2,158 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
 import android.view.animation.Animation;
 import android.widget.ImageView;
+
 import com.bumptech.glide.load.Encoder;
+import com.bumptech.glide.load.Key;
 import com.bumptech.glide.load.MultiTransformation;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.ResourceEncoder;
-import com.bumptech.glide.load.SkipCache;
 import com.bumptech.glide.load.Transformation;
-import com.bumptech.glide.load.UnitTransformation;
-import com.bumptech.glide.load.model.ModelLoader;
-import com.bumptech.glide.load.model.NullEncoder;
-import com.bumptech.glide.load.resource.bitmap.BitmapDecoder;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.bumptech.glide.load.resource.UnitTransformation;
 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
+import com.bumptech.glide.manager.Lifecycle;
 import com.bumptech.glide.manager.RequestTracker;
 import com.bumptech.glide.provider.ChildLoadProvider;
 import com.bumptech.glide.provider.LoadProvider;
+import com.bumptech.glide.request.FutureTarget;
 import com.bumptech.glide.request.GenericRequest;
-import com.bumptech.glide.request.GlideAnimationFactory;
-import com.bumptech.glide.request.NoAnimation;
 import com.bumptech.glide.request.Request;
 import com.bumptech.glide.request.RequestCoordinator;
+import com.bumptech.glide.request.RequestFutureTarget;
 import com.bumptech.glide.request.RequestListener;
 import com.bumptech.glide.request.ThumbnailRequestCoordinator;
-import com.bumptech.glide.request.ViewAnimation;
-import com.bumptech.glide.request.ViewPropertyAnimation;
-import com.bumptech.glide.request.target.BitmapImageViewTarget;
+import com.bumptech.glide.request.animation.GlideAnimationFactory;
+import com.bumptech.glide.request.animation.NoAnimation;
+import com.bumptech.glide.request.animation.ViewAnimationFactory;
+import com.bumptech.glide.request.animation.ViewPropertyAnimation;
+import com.bumptech.glide.request.animation.ViewPropertyAnimationFactory;
+import com.bumptech.glide.request.target.PreloadTarget;
 import com.bumptech.glide.request.target.Target;
+import com.bumptech.glide.signature.EmptySignature;
+import com.bumptech.glide.util.Util;
 
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
+import java.io.File;
 
 /**
- * A generic class that can handle loading a bitmap either from an image or as a thumbnail from a video given
- * models loaders to translate a model into generic resources for either an image or a video and decoders that can
- * decode those resources into bitmaps.
+ * A generic class that can handle setting options and staring loads for generic resource types.
  *
- * @param <ModelType> The type of model representing the image or video.
- * @param <DataType> The data type that the image {@link ModelLoader} will provide that can be decoded by the image
- *      {@link BitmapDecoder}.
+ * @param <ModelType> The type of model representing the resource.
+ * @param <DataType> The data type that the resource {@link com.bumptech.glide.load.model.ModelLoader} will provide that
+ *                  can be decoded by the {@link com.bumptech.glide.load.ResourceDecoder}.
  * @param <ResourceType> The type of the resource that will be loaded.
+ * @param <TranscodeType> The type of resource the decoded resource will be transcoded to.
  */
-public class GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> {
-    private final Context context;
-    private final ModelType model;
-    private final ChildLoadProvider<ModelType, DataType, ResourceType, TranscodeType> loadProvider;
-    private final Class<TranscodeType> transcodeClass;
-    private final Glide glide;
-    private final RequestTracker requestTracker;
-    private List<Transformation<ResourceType>> transformations = null;
-    private Transformation<ResourceType> singleTransformation = UnitTransformation.get();
+public class GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> implements Cloneable {
+    protected final Class<ModelType> modelClass;
+    protected final Context context;
+    protected final Glide glide;
+    protected final Class<TranscodeType> transcodeClass;
+    protected final RequestTracker requestTracker;
+    protected final Lifecycle lifecycle;
+    private ChildLoadProvider<ModelType, DataType, ResourceType, TranscodeType> loadProvider;
+
+    private ModelType model;
+    private Key signature = EmptySignature.obtain();
+    // model may occasionally be null, so to enforce that load() was called, set a boolean rather than relying on model
+    // not to be null.
+    private boolean isModelSet;
     private int placeholderId;
     private int errorId;
-    private RequestListener<ModelType, TranscodeType> requestListener;
+    private RequestListener<? super ModelType, TranscodeType> requestListener;
     private Float thumbSizeMultiplier;
-    private GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType>
-            thumbnailRequestBuilder;
+    private GenericRequestBuilder<?, ?, ?, TranscodeType> thumbnailRequestBuilder;
     private Float sizeMultiplier = 1f;
     private Drawable placeholderDrawable;
     private Drawable errorPlaceholder;
     private Priority priority = null;
     private boolean isCacheable = true;
-    private ResourceEncoder<ResourceType> preSkipEncoder;
     private GlideAnimationFactory<TranscodeType> animationFactory = NoAnimation.getFactory();
     private int overrideHeight = -1;
     private int overrideWidth = -1;
-    private boolean cacheSource = false;
-    private Encoder<DataType> preSkipSourceEncoder;
+    private DiskCacheStrategy diskCacheStrategy = DiskCacheStrategy.RESULT;
+    private Transformation<ResourceType> transformation = UnitTransformation.get();
+    private boolean isTransformationSet;
 
-    GenericRequestBuilder(Context context, ModelType model,
+    GenericRequestBuilder(LoadProvider<ModelType, DataType, ResourceType, TranscodeType> loadProvider,
+            Class<TranscodeType> transcodeClass, GenericRequestBuilder<ModelType, ?, ?, ?> other) {
+        this(other.context, other.modelClass, loadProvider, transcodeClass, other.glide, other.requestTracker,
+                other.lifecycle);
+        this.model = other.model;
+        this.isModelSet = other.isModelSet;
+        this.signature = other.signature;
+        this.diskCacheStrategy = other.diskCacheStrategy;
+        this.isCacheable = other.isCacheable;
+    }
+
+    GenericRequestBuilder(Context context, Class<ModelType> modelClass,
             LoadProvider<ModelType, DataType, ResourceType, TranscodeType> loadProvider,
-            Class<TranscodeType> transcodeClass, Glide glide, RequestTracker requestTracker) {
+            Class<TranscodeType> transcodeClass, Glide glide, RequestTracker requestTracker, Lifecycle lifecycle) {
+        this.context = context;
+        this.modelClass = modelClass;
         this.transcodeClass = transcodeClass;
         this.glide = glide;
         this.requestTracker = requestTracker;
-        this.loadProvider = loadProvider != null ?
-                new ChildLoadProvider<ModelType, DataType, ResourceType, TranscodeType>(loadProvider) : null;
-        preSkipEncoder = loadProvider != null ? loadProvider.getEncoder() : null;
+        this.lifecycle = lifecycle;
+        this.loadProvider = loadProvider != null
+                ? new ChildLoadProvider<ModelType, DataType, ResourceType, TranscodeType>(loadProvider) : null;
 
         if (context == null) {
             throw new NullPointerException("Context can't be null");
         }
-        if (model != null && loadProvider == null) {
+        if (modelClass != null && loadProvider == null) {
             throw new NullPointerException("LoadProvider must not be null");
         }
-        this.context = context;
-        this.model = model;
     }
 
     /**
-     * Loads and displays the image retrieved by the given thumbnail request if it finishes before this request.
-     * Best used for loading thumbnail images that are smaller and will be loaded more quickly than the fullsize
-     * image. There are no guarantees about the order in which the requests will actually finish. However, if the
-     * thumb request completes after the full request, the thumb image will never replace the full image.
+     * Loads and displays the resource retrieved by the given thumbnail request if it finishes before this request.
+     * Best used for loading thumbnail resources that are smaller and will be loaded more quickly than the full size
+     * resource. There are no guarantees about the order in which the requests will actually finish. However, if the
+     * thumb request completes after the full request, the thumb resource will never replace the full resource.
      *
      * @see #thumbnail(float)
      *
      * <p>
-     *     Note - Any options on the main request will not be passed on to the thumbnail request. For example, if
-     *     you want an animation to occur when either the full image loads or the thumbnail loads, you need to call
-     *     {@link #animate(int)} on both the thumb and the full request. For a simpler thumbnail option, see
-     *     {@link #thumbnail(float)}.
-     * </p>
-     *
-     * <p>
-     *     Only the thumbnail call on the main request will be obeyed.
+     *     Recursive calls to thumbnail are supported.
      * </p>
      *
      * @param thumbnailRequest The request to use to load the thumbnail.
-     * @return This builder object.
+     * @return This request builder.
      */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> thumbnail(
-            GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType>
-                    thumbnailRequest) {
+            GenericRequestBuilder<?, ?, ?, TranscodeType> thumbnailRequest) {
         this.thumbnailRequestBuilder = thumbnailRequest;
 
         return this;
     }
 
     /**
-     * Loads an image in an identical manner to this request except with the dimensions of the target multiplied
+     * Loads a resource in an identical manner to this request except with the dimensions of the target multiplied
      * by the given size multiplier. If the thumbnail load completes before the fullsize load, the thumbnail will
      * be shown. If the thumbnail load completes afer the fullsize load, the thumbnail will not be shown.
      *
      * <p>
-     *     Note - The thumbnail image will be smaller than the size requested so the target (or {@link ImageView})
-     *     must be able to scale the thumbnail appropriately. See {@link ImageView.ScaleType}.
+     *     Note - The thumbnail resource will be smaller than the size requested so the target (or {@link ImageView})
+     *     must be able to scale the thumbnail appropriately. See {@link android.widget.ImageView.ScaleType}.
      * </p>
      *
      * <p>
-     *     Almost all options will be copied from the original load, including the {@link ModelLoader},
-     *     {@link BitmapDecoder}, and {@link Transformation}s. However, {@link #placeholder(int)} and
-     *     {@link #error(int)}, and {@link #listener(RequestListener)} will only be used on the fullsize load and
-     *     will not be copied for the thumbnail load.
+     *     Almost all options will be copied from the original load, including the
+     *     {@link com.bumptech.glide.load.model.ModelLoader}, {@link com.bumptech.glide.load.ResourceDecoder}, and
+     *     {@link Transformation}s. However, {@link #placeholder(int)} and {@link #error(int)},
+     *     and {@link #listener(RequestListener)} will only be used on the fullsize load and will not be copied for
+     *     the thumbnail load.
      * </p>
      *
      * <p>
-     *     Only the thumbnail call on the main request will be obeyed.
+     *     Recursive calls to thumbnail are supported.
      * </p>
      *
      * @param sizeMultiplier The multiplier to apply to the {@link Target}'s dimensions when loading the thumbnail.
-     * @return This builder object.
+     * @return This request builder.
      */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> thumbnail(
             float sizeMultiplier) {
@@ -158,11 +166,12 @@
     }
 
     /**
-     * Applies a multiplier to the {@link Target}'s size before loading the image. Useful for loading thumbnails
-     * or trying to avoid loading huge bitmaps on devices with overly dense screens.
+     * Applies a multiplier to the {@link Target}'s size before loading the resource. Useful for loading thumbnails
+     * or trying to avoid loading huge resources (particularly {@link android.graphics.Bitmap}s on devices with overly
+     * dense screens.
      *
-     * @param sizeMultiplier The multiplier to apply to the {@link Target}'s dimensions when loading the image.
-     * @return This builder object.
+     * @param sizeMultiplier The multiplier to apply to the {@link Target}'s dimensions when loading the resource.
+     * @return This request builder.
      */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> sizeMultiplier(
             float sizeMultiplier) {
@@ -175,13 +184,13 @@
     }
 
     /**
-     * Loads the resource from the given data type using the given {@link BitmapDecoder}.
+     * Sets the {@link com.bumptech.glide.load.ResourceDecoder} to use to load the resource from the original data.
+     * By default, this decoder will only be used if the final transformed resource is not in the disk cache.
      *
-     * <p>
-     *     Will be ignored if the data represented by the given model is not a video.
-     * </p>
+     * @see #cacheDecoder(com.bumptech.glide.load.ResourceDecoder)
+     * @see com.bumptech.glide.load.engine.DiskCacheStrategy
      *
-     * @param decoder The {@link BitmapDecoder} to use to decode the video resource.
+     * @param decoder The {@link com.bumptech.glide.load.ResourceDecoder} to use to decode the resource.
      * @return This request builder.
      */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> decoder(
@@ -195,8 +204,18 @@
         return this;
     }
 
+    /**
+     * Sets the {@link com.bumptech.glide.load.ResourceDecoder} to use to load the resource from the disk cache. By
+     * default, this decoder will only be used if the final transformed resource is already in the disk cache.
+     *
+     * @see #decoder(com.bumptech.glide.load.ResourceDecoder)
+     * @see com.bumptech.glide.load.engine.DiskCacheStrategy
+     *
+     * @param cacheDecoder The decoder to use.
+     * @return This request builder.
+     */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> cacheDecoder(
-            ResourceDecoder <InputStream, ResourceType> cacheDecoder) {
+            ResourceDecoder<File, ResourceType> cacheDecoder) {
         // loadProvider will be null if model is null, in which case we're not going to load anything so it's ok to
         // ignore the decoder.
         if (loadProvider != null) {
@@ -208,11 +227,9 @@
 
     /**
      * Sets the source encoder to use to encode the data retrieved by this request directly into cache. The returned
-     * resouce will then be decoded from the cached data.
+     * resource will then be decoded from the cached data.
      *
-     * <p>
-     *     Note - This encoder will not be used unless
-     * </p>
+     * @see com.bumptech.glide.load.engine.DiskCacheStrategy
      *
      * @param sourceEncoder The encoder to use.
      * @return This request builder.
@@ -221,54 +238,53 @@
             Encoder<DataType> sourceEncoder) {
         if (loadProvider != null) {
             loadProvider.setSourceEncoder(sourceEncoder);
-            preSkipSourceEncoder = sourceEncoder;
         }
 
         return this;
     }
 
     /**
-     * Attempts to write the data retrieved by this request to cache first and then decodes the resource from the cached
-     * source data. Only makes sense for remote or transient data as a means of either avoiding downloading the same
-     * data repeatedly or preserving some content you expect to be removed.
+     * Sets the {@link com.bumptech.glide.load.engine.DiskCacheStrategy} to use for this load. Defaults to
+     * {@link com.bumptech.glide.load.engine.DiskCacheStrategy#RESULT}.
      *
      * <p>
-     *     Note that if this is set to true the {@link ResourceDecoder} set as the decoder will not be used, instead the
-     *     cache decoder will be used.
+     *     For most applications {@link com.bumptech.glide.load.engine.DiskCacheStrategy#RESULT} is ideal.
+     *     Applications that use the same resource multiple times in multiple sizes and are willing to trade off some
+     *     speed and disk space in return for lower bandwidth usage may want to consider using
+     *     {@link com.bumptech.glide.load.engine.DiskCacheStrategy#SOURCE} or
+     *     {@link com.bumptech.glide.load.engine.DiskCacheStrategy#RESULT}. Any download only operations should
+     *     typically use {@link com.bumptech.glide.load.engine.DiskCacheStrategy#SOURCE}.
      * </p>
      *
-     * <p>
-     *     If no {@link Encoder} is set or available for the given data type, this may cause the load to fail.
-     * </p>
-     *
-     * @see #sourceEncoder(Encoder)
-     * @see #decoder(ResourceDecoder)
-     * @see #cacheDecoder(ResourceDecoder)
-     * @see #skipCache(boolean)
-     *
-     * @param cacheSource True to write the data directly to cache .
+     * @param strategy The strategy to use.
      * @return This request builder.
      */
-    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> cacheSource(boolean cacheSource) {
-        this.cacheSource = cacheSource;
-        if (!cacheSource) {
-            if (loadProvider != null) {
-                preSkipSourceEncoder = loadProvider.getSourceEncoder();
-            }
-            final Encoder<DataType> skipCache = NullEncoder.get();
-            return sourceEncoder(skipCache);
-        } else {
-            return sourceEncoder(preSkipSourceEncoder);
-        }
+    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType>  diskCacheStrategy(
+            DiskCacheStrategy strategy) {
+        this.diskCacheStrategy = strategy;
+
+        return this;
     }
 
+    /**
+     * Sets the {@link com.bumptech.glide.load.Encoder} to use to encode the original data directly to cache. Will only
+     * be used if the original data is not already in cache and if the
+     * {@link com.bumptech.glide.load.engine.DiskCacheStrategy} is set to
+     * {@link com.bumptech.glide.load.engine.DiskCacheStrategy#SOURCE} or
+     * {@link com.bumptech.glide.load.engine.DiskCacheStrategy#ALL}.
+     *
+     * @see #sourceEncoder(com.bumptech.glide.load.Encoder)
+     * @see com.bumptech.glide.load.engine.DiskCacheStrategy
+     *
+     * @param encoder The encoder to use.
+     * @return This request builder.
+     */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> encoder(
             ResourceEncoder<ResourceType> encoder) {
         // loadProvider will be null if model is null, in which case we're not going to load anything so it's ok to
         // ignore the encoder.
         if (loadProvider != null) {
             loadProvider.setEncoder(encoder);
-            preSkipEncoder = encoder;
         }
 
         return this;
@@ -288,25 +304,45 @@
     }
 
     /**
-     * Transform images with the given {@link Transformation}. Appends this transformation onto any existing
-     * transformations
+     * Transform resources with the given {@link Transformation}s. Replaces any existing transformation or
+     * transformations.
      *
-     * @param transformation the transformation to apply.
-     * @return This RequestBuilder
+     * @param transformations the transformations to apply in order.
+     * @return This request builder.
      */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> transform(
-            Transformation<ResourceType> transformation) {
-        if (singleTransformation == UnitTransformation.get()) {
-            singleTransformation = transformation;
+            Transformation<ResourceType>... transformations) {
+        isTransformationSet = true;
+        if (transformations.length == 1) {
+            transformation = transformations[0];
         } else {
-            transformations = new ArrayList<Transformation<ResourceType>>();
-            transformations.add(singleTransformation);
-            transformations.add(transformation);
+            transformation = new MultiTransformation<ResourceType>(transformations);
         }
 
         return this;
     }
 
+    /**
+     * Removes the current {@link com.bumptech.glide.load.Transformation}.
+     *
+     * @return This request builder.
+     */
+    @SuppressWarnings("unchecked")
+    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> dontTransform() {
+        Transformation<ResourceType> transformation = UnitTransformation.get();
+        return transform(transformation);
+    }
+
+    /**
+     * Sets the {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} to use for this load.
+     *
+     * @see com.bumptech.glide.load.resource.transcode.UnitTranscoder
+     * @see com.bumptech.glide.load.resource.transcode.GlideBitmapDrawableTranscoder
+     * @see com.bumptech.glide.load.resource.transcode.GifBitmapWrapperDrawableTranscoder
+     *
+     * @param transcoder The transcoder to use.
+     * @return This request builder.
+     */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> transcoder(
             ResourceTranscoder<ResourceType, TranscodeType> transcoder) {
         if (loadProvider != null) {
@@ -317,45 +353,56 @@
     }
 
     /**
-     * Sets an animation to run on the wrapped target when an image load finishes. Will only be run if the image
+     * Removes any existing animation set on the builder. Will be overridden by subsequent calls that set an animation.
+     * @return This request builder.
+     */
+    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> dontAnimate() {
+        GlideAnimationFactory<TranscodeType> animation = NoAnimation.getFactory();
+        return animate(animation);
+    }
+
+    /**
+     * Sets an animation to run on the wrapped target when an resource load finishes. Will only be run if the resource
      * was loaded asynchronously (ie was not in the memory cache)
      *
      * @param animationId The resource id of the animation to run
-     * @return This RequestBuilder
+     * @return This request builder.
      */
-    // This is safe because the view animation doesn't care about the resource type it receives.
-    @SuppressWarnings("unchecked")
-    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> animate(
-            int animationId) {
-        return animate(new ViewAnimation.ViewAnimationFactory(context, animationId));
+    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> animate(int animationId) {
+        return animate(new ViewAnimationFactory<TranscodeType>(context, animationId));
     }
 
     /**
-     * Sets an animation to run on the wrapped target when an image load finishes. Will only be run if the image
+     * Sets an animation to run on the wrapped target when a resource load finishes. Will only be run if the resource
      * was loaded asynchronously (ie was not in the memory cache)
      *
+     * @see #animate(int)
+     * @see #animate(com.bumptech.glide.request.animation.ViewPropertyAnimation.Animator)
+     *
+     * @deprecated If this builder is used for multiple loads, using this method will result in multiple view's being
+     * asked to start an animation using a single {@link android.view.animation.Animation} object which results in
+     * views animating repeatedly. Use {@link #animate(int)} or
+     * {@link #animate(com.bumptech.glide.request.animation.ViewPropertyAnimation.Animator)}. Scheduled to be removed in
+     * Glide 4.0.
      * @param animation The animation to run
-     * @return This RequestBuilder
+     * @return This request builder.
      */
-    // This is safe because the view animation doesn't care about the resource type it receives.
-    @SuppressWarnings("unchecked")
-    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> animate(
-            Animation animation) {
-        return animate(new ViewAnimation.ViewAnimationFactory(animation));
+    @Deprecated
+    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> animate(Animation animation) {
+        return animate(new ViewAnimationFactory<TranscodeType>(animation));
     }
 
     /**
-     * Sets an animator to run a {@link ViewPropertyAnimator} on a view that the target may be wrapping when a resource
-     * load finishes. Will only be run if the load was loaded asynchronously (ie was not in the memory cache).
+     * Sets an animator to run a {@link android.view.ViewPropertyAnimator} on a view that the target may be wrapping
+     * when a resource load finishes. Will only be run if the load was loaded asynchronously (ie was not in the
+     * memory cache).
      *
-     * @param animator The {@link ViewPropertyAnimation.Animator} to run.
-     * @return This RequestBuilder.
+     * @param animator The {@link com.bumptech.glide.request.animation.ViewPropertyAnimation.Animator} to run.
+     * @return This request builder.
      */
-    // This is safe because the view property animation doesn't care about the resource type it receives.
-    @SuppressWarnings("unchecked")
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> animate(
             ViewPropertyAnimation.Animator animator) {
-        return animate(new ViewPropertyAnimation.ViewPropertyAnimationFactory(animator));
+        return animate(new ViewPropertyAnimationFactory<TranscodeType>(animator));
     }
 
     GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> animate(
@@ -369,10 +416,11 @@
     }
 
     /**
-     * Sets a resource to display while an image is loading
+     * Sets an Android resource id for a {@link android.graphics.drawable.Drawable} resourceto display while a resource
+     * is loading.
      *
      * @param resourceId The id of the resource to use as a placeholder
-     * @return This RequestBuilder
+     * @return This request builder.
      */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> placeholder(
             int resourceId) {
@@ -382,10 +430,10 @@
     }
 
     /**
-     * Sets a drawable to display while an image is loading.
+     * Sets an {@link android.graphics.drawable.Drawable} to display while a resource is loading.
      *
      * @param drawable The drawable to display as a placeholder.
-     * @return This RequestBuilder.
+     * @return This request builder.
      */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> placeholder(
             Drawable drawable) {
@@ -395,10 +443,10 @@
     }
 
     /**
-     * Sets a resource to display if a load fails
+     * Sets a resource to display if a load fails.
      *
-     * @param resourceId The id of the resource to use as a placeholder
-     * @return This request
+     * @param resourceId The id of the resource to use as a placeholder.
+     * @return This request builder.
      */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> error(
             int resourceId) {
@@ -411,7 +459,7 @@
      * Sets a {@link Drawable} to display if a load fails.
      *
      * @param drawable The drawable to display.
-     * @return This RequestBuilder.
+     * @return This request builder.
      */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> error(
             Drawable drawable) {
@@ -421,15 +469,15 @@
     }
 
     /**
-     * Sets a RequestBuilder listener to monitor the image load. It's best to create a single instance of an
+     * Sets a RequestBuilder listener to monitor the resource load. It's best to create a single instance of an
      * exception handler per type of request (usually activity/fragment) rather than pass one in per request to
      * avoid some redundant object allocation.
      *
      * @param requestListener The request listener to use.
-     * @return This RequestBuilder.
+     * @return This request builder.
      */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> listener(
-            RequestListener<ModelType, TranscodeType> requestListener) {
+            RequestListener<? super ModelType, TranscodeType> requestListener) {
         this.requestListener = requestListener;
 
         return this;
@@ -444,7 +492,7 @@
      * </p>
      *
      * @param skip True to allow the resource to skip the memory cache.
-     * @return This RequestBuilder.
+     * @return This request builder.
      */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> skipMemoryCache(boolean skip) {
         this.isCacheable = !skip;
@@ -453,60 +501,20 @@
     }
 
     /**
-     * Allows the loaded resource to skip the disk cache.
-     *
-     * <p>
-     *     Note - this is not a guarantee. If a request is already pending for this resource and that request is not
-     *     also skipping the disk cache, the resource will be cached on disk.
-     * </p>
-     *
-     * @param skip True to allow the resource to skip the disk cache.
-     * @return This RequestBuilder.
-     */
-    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> skipDiskCache(boolean skip) {
-        if (skip) {
-            if (loadProvider != null) {
-                preSkipEncoder = loadProvider.getEncoder();
-            }
-            final SkipCache<ResourceType> skipCache = SkipCache.get();
-            return encoder(skipCache);
-        } else {
-            return encoder(preSkipEncoder);
-        }
-    }
-
-    /**
-     * Allows the resource to skip both the memory and the disk cache.
-     *
-     * @see #skipDiskCache(boolean)
-     * @see #skipMemoryCache(boolean)
-     *
-     * @param skip True to allow the resource to skip both the memory and the disk cache.
-     * @return This RequestBuilder.
-     */
-    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> skipCache(boolean skip) {
-        skipMemoryCache(skip);
-        skipDiskCache(skip);
-        cacheSource(false);
-
-        return this;
-    }
-
-    /**
      * Overrides the {@link Target}'s width and height with the given values. This is useful almost exclusively for
      * thumbnails, and should only be used when you both need a very specific sized image and when it is impossible or
-     * impractical to return that size from {@link Target#getSize(Target.SizeReadyCallback)}.
+     * impractical to return that size from {@link Target#getSize(com.bumptech.glide.request.target.SizeReadyCallback)}.
      *
-     * @param width The width to use to load the resource.
-     * @param height The height to use to load the resource.
-     * @return This RequestBuilder.
+     * @param width The width in pixels to use to load the resource.
+     * @param height The height in pixels to use to load the resource.
+     * @return This request builder.
      */
     public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> override(int width, int height) {
         if (width <= 0) {
-            throw new IllegalArgumentException("Width must be >= 0");
+            throw new IllegalArgumentException("Width must be > 0");
         }
         if (height <= 0) {
-            throw new IllegalArgumentException("Height must be >= 0");
+            throw new IllegalArgumentException("Height must be > 0");
         }
         this.overrideWidth = width;
         this.overrideHeight = height;
@@ -515,12 +523,83 @@
     }
 
     /**
-     * Set the target the image will be loaded into.
+     * Sets some additional data to be mixed in to the memory and disk cache keys allowing the caller more control over
+     * when cached data is invalidated.
      *
-     * @param target The target to load te image for
+     * <p>
+     *     Note - The signature does not replace the cache key, it is purely additive.
+     * </p>
+     *
+     * @see com.bumptech.glide.signature.StringSignature
+     *
+     * @param signature A unique non-null {@link com.bumptech.glide.load.Key} representing the current state of the
+     *                  model that will be mixed in to the cache key.
+     * @return This request builder.
+     */
+    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> signature(Key signature) {
+        if (signature == null) {
+            throw new NullPointerException("Signature must not be null");
+        }
+        this.signature = signature;
+        return this;
+    }
+
+    /**
+     * Sets the specific model to load data for.
+     *
+     * <p>
+     *      This method must be called at least once before {@link #into(com.bumptech.glide.request.target.Target)} is
+     *      called.
+     * </p>
+     *
+     * @param model The model to load data for, or null.
+     * @return This request builder.
+     */
+    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
+        this.model = model;
+        isModelSet = true;
+        return this;
+    }
+
+    /**
+     * Returns a copy of this request builder with all of the options set so far on this builder.
+     *
+     * <p>
+     *     This method returns a "deep" copy in that all non-immutable arguments are copied such that changes to one
+     *     builder will not affect the other builder. However, in addition to immutable arguments, the current model
+     *     is not copied copied so changes to the model will affect both builders.
+     * </p>
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> clone() {
+        try {
+            GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> clone =
+                    (GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType>) super.clone();
+            clone.loadProvider = loadProvider != null ? loadProvider.clone() : null;
+            return clone;
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Set the target the resource will be loaded into.
+     *
+     * @see Glide#clear(com.bumptech.glide.request.target.Target)
+     *
+     * @param target The target to load the resource into.
      * @return The given target.
      */
     public <Y extends Target<TranscodeType>> Y into(Y target) {
+        Util.assertMainThread();
+        if (target == null) {
+            throw new IllegalArgumentException("You must pass in a non null Target");
+        }
+        if (!isModelSet) {
+            throw new IllegalArgumentException("You must first set a model (try #load())");
+        }
+
         Request previous = target.getRequest();
 
         if (previous != null) {
@@ -531,25 +610,100 @@
 
         Request request = buildRequest(target);
         target.setRequest(request);
-        requestTracker.addRequest(request);
-        request.run();
+        lifecycle.addListener(target);
+        requestTracker.runRequest(request);
 
         return target;
     }
 
     /**
-     * Sets the {@link ImageView} the image will be loaded into, cancels any existing loads into the view, and frees
-     * any resources Glide has loaded into the view so they may be reused.
+     * Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into the view, and frees
+     * any resources Glide may have previously loaded into the view so they may be reused.
      *
-     * @see Glide#clear(View)
+     * @see Glide#clear(android.view.View)
      *
-     * @param view The view to cancel previous loads for and load the new image into.
-     * @return The {@link BitmapImageViewTarget} used to wrap the given {@link ImageView}.
+     * @param view The view to cancel previous loads for and load the new resource into.
+     * @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link ImageView}.
      */
     public Target<TranscodeType> into(ImageView view) {
+        Util.assertMainThread();
+        if (view == null) {
+            throw new IllegalArgumentException("You must pass in a non null View");
+        }
+
+        if (!isTransformationSet && view.getScaleType() != null) {
+            switch (view.getScaleType()) {
+                case CENTER_CROP:
+                    applyCenterCrop();
+                    break;
+                case FIT_CENTER:
+                case FIT_START:
+                case FIT_END:
+                    applyFitCenter();
+                    break;
+                //$CASES-OMITTED$
+                default:
+                    // silently ignore
+                    break;
+            }
+        }
+
         return into(glide.buildImageViewTarget(view, transcodeClass));
     }
 
+    /**
+     * Returns a future that can be used to do a blocking get on a background thread.
+     *
+     * @param width The desired width in pixels (note this will be overriden by {@link #override(int, int)} if
+     *              previously called).
+     * @param height The desired height in pixels (note this will be overriden by {@link #override(int, int)}}
+     *               if previously called).
+     *
+     * @see Glide#clear(com.bumptech.glide.request.FutureTarget)
+     *
+     * @return An {@link com.bumptech.glide.request.FutureTarget} that can be used to obtain the
+     *         resource in a blocking manner.
+     */
+    public FutureTarget<TranscodeType> into(int width, int height) {
+        final RequestFutureTarget<ModelType, TranscodeType> target =
+                new RequestFutureTarget<ModelType, TranscodeType>(glide.getMainHandler(), width, height);
+
+        // TODO: Currently all loads must be started on the main thread...
+        glide.getMainHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                if (!target.isCancelled()) {
+                    into(target);
+                }
+            }
+        });
+
+        return target;
+    }
+
+    /**
+     * Preloads the resource into the cache using the given width and height.
+     *
+     * <p>
+     *     Pre-loading is useful for making sure that resources you are going to to want in the near future are
+     *     available quickly.
+     * </p>
+     *
+     * @see com.bumptech.glide.ListPreloader
+     */
+    public Target<TranscodeType> preload(int width, int height) {
+        final PreloadTarget<TranscodeType> target = PreloadTarget.obtain(width, height);
+        return into(target);
+    }
+
+    void applyCenterCrop() {
+        // To be implemented by subclasses when possible.
+    }
+
+    void applyFitCenter() {
+        // To be implemented by subclasses when possible.
+    }
+
     private Priority getThumbnailPriority() {
         final Priority result;
         if (priority == Priority.LOW) {
@@ -576,10 +730,6 @@
                 thumbnailRequestBuilder.animationFactory = animationFactory;
             }
 
-            if (thumbnailRequestBuilder.requestListener == null && requestListener != null) {
-                thumbnailRequestBuilder.requestListener = requestListener;
-            }
-
             if (thumbnailRequestBuilder.priority == null) {
                 thumbnailRequestBuilder.priority = getThumbnailPriority();
             }
@@ -603,15 +753,12 @@
         }
     }
 
-    private <Z> Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
+    private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
             RequestCoordinator requestCoordinator) {
-        if (model == null) {
-            requestCoordinator = null;
-        }
-
         return GenericRequest.obtain(
                 loadProvider,
                 model,
+                signature,
                 context,
                 priority,
                 target,
@@ -623,21 +770,12 @@
                 requestListener,
                 requestCoordinator,
                 glide.getEngine(),
-                getFinalTransformation(),
+                transformation,
                 transcodeClass,
                 isCacheable,
                 animationFactory,
                 overrideWidth,
                 overrideHeight,
-                cacheSource);
-    }
-
-    @SuppressWarnings("unchecked")
-    private Transformation<ResourceType> getFinalTransformation() {
-        if (transformations == null) {
-            return singleTransformation;
-        } else {
-            return new MultiTransformation<ResourceType>(transformations);
-        }
+                diskCacheStrategy);
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/GenericTranscodeRequest.java b/library/src/main/java/com/bumptech/glide/GenericTranscodeRequest.java
index 082c5c9..ca62923 100644
--- a/library/src/main/java/com/bumptech/glide/GenericTranscodeRequest.java
+++ b/library/src/main/java/com/bumptech/glide/GenericTranscodeRequest.java
@@ -1,52 +1,110 @@
 package com.bumptech.glide;
 
 import android.content.Context;
+
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
 import com.bumptech.glide.load.model.ModelLoader;
 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
 import com.bumptech.glide.load.resource.transcode.UnitTranscoder;
+import com.bumptech.glide.manager.Lifecycle;
 import com.bumptech.glide.manager.RequestTracker;
+import com.bumptech.glide.provider.DataLoadProvider;
 import com.bumptech.glide.provider.FixedLoadProvider;
 import com.bumptech.glide.provider.LoadProvider;
+import com.bumptech.glide.request.FutureTarget;
+import com.bumptech.glide.request.target.Target;
 
-public class GenericTranscodeRequest<A, T, Z> extends GenericRequestBuilder<A, T, Z, Z>{
-    private final Context context;
-    private final A model;
-    private final Glide glide;
-    private final ModelLoader<A, T> modelLoader;
-    private final Class<T> dataClass;
-    private final Class<Z> resourceClass;
-    private final RequestTracker requestTracker;
+import java.io.File;
+
+/**
+ * A class for handling requests to load a generic resource type or transcode the generic resource type into another
+ * generic resource type.
+ *
+ * <p>
+ *     Warning - It is <em>not</em> safe to use this builder after calling <code>into()</code>, it may be pooled and
+ *     reused.
+ * </p>
+ *
+ * @param <ModelType> The type of the model used to retrieve data.
+ * @param <DataType> The type of data retrieved.
+ * @param <ResourceType> The type of resource to be decoded from the the data.
+ */
+public class GenericTranscodeRequest<ModelType, DataType, ResourceType>
+        extends GenericRequestBuilder<ModelType, DataType, ResourceType, ResourceType> implements DownloadOptions {
+    private final ModelLoader<ModelType, DataType> modelLoader;
+    private final Class<DataType> dataClass;
+    private final Class<ResourceType> resourceClass;
     private final RequestManager.OptionsApplier optionsApplier;
 
-    GenericTranscodeRequest(Context context, Glide glide, A model, ModelLoader<A, T> modelLoader,
-            Class<T> dataClass, Class<Z> resourceClass, RequestTracker requestTracker,
-            RequestManager.OptionsApplier optionsApplier) {
-        super(context, model,
-                build(glide, modelLoader, dataClass, resourceClass, (ResourceTranscoder<Z, Z>) null),
-                resourceClass, glide, requestTracker);
-        this.context = context;
-        this.model = model;
-        this.glide = glide;
-        this.modelLoader = modelLoader;
-        this.dataClass = dataClass;
-        this.resourceClass = resourceClass;
-        this.requestTracker = requestTracker;
-        this.optionsApplier = optionsApplier;
-    }
-
-    public <R> GenericRequestBuilder<A, T, Z, R> transcode(ResourceTranscoder<Z, R> transcoder,
-            Class<R> transcodeClass) {
-        return optionsApplier.apply(model, new GenericRequestBuilder<A, T, Z, R>(context, model,
-                build(glide, modelLoader, dataClass, resourceClass, transcoder), transcodeClass, glide,
-                requestTracker));
-    }
-
     private static <A, T, Z, R> LoadProvider<A, T, Z, R> build(Glide glide, ModelLoader<A, T> modelLoader,
             Class<T> dataClass, Class<Z> resourceClass, ResourceTranscoder<Z, R> transcoder) {
-        if (transcoder == null) {
-            transcoder = UnitTranscoder.get();
-        }
-        return new FixedLoadProvider<A, T, Z, R>(modelLoader, transcoder,
-                glide.buildDataProvider(dataClass, resourceClass));
+        DataLoadProvider<T, Z> dataLoadProvider = glide.buildDataProvider(dataClass, resourceClass);
+        return new FixedLoadProvider<A, T, Z, R>(modelLoader, transcoder, dataLoadProvider);
+    }
+
+    GenericTranscodeRequest(
+            Class<ResourceType> transcodeClass, GenericRequestBuilder<ModelType, ?, ?, ?> other,
+            ModelLoader<ModelType, DataType> modelLoader, Class<DataType> dataClass, Class<ResourceType> resourceClass,
+            RequestManager.OptionsApplier optionsApplier) {
+        super(build(other.glide, modelLoader, dataClass, resourceClass, UnitTranscoder.<ResourceType>get()),
+                transcodeClass, other);
+        this.modelLoader = modelLoader;
+        this.dataClass = dataClass;
+        this.resourceClass = resourceClass;
+        this.optionsApplier = optionsApplier;
+    }
+
+    GenericTranscodeRequest(Context context, Glide glide, Class<ModelType> modelClass,
+            ModelLoader<ModelType, DataType> modelLoader, Class<DataType> dataClass, Class<ResourceType> resourceClass,
+            RequestTracker requestTracker, Lifecycle lifecycle, RequestManager.OptionsApplier optionsApplier) {
+        super(context, modelClass, build(glide, modelLoader, dataClass, resourceClass,
+                        UnitTranscoder.<ResourceType>get()), resourceClass, glide, requestTracker, lifecycle);
+        this.modelLoader = modelLoader;
+        this.dataClass = dataClass;
+        this.resourceClass = resourceClass;
+        this.optionsApplier = optionsApplier;
+    }
+
+    /**
+     * Adds a transcoder to this request to transcode from the resource type to the given transcode type.
+     *
+     * @param transcoder The transcoder to use.
+     * @param transcodeClass The class of the resource type that will be transcoded to.
+     * @param <TranscodeType> The type of the resource that will be transcoded to.
+     * @return A new request builder to set options for the transcoded load.
+     */
+    public <TranscodeType> GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> transcode(
+            ResourceTranscoder<ResourceType, TranscodeType> transcoder, Class<TranscodeType> transcodeClass) {
+        LoadProvider<ModelType, DataType, ResourceType, TranscodeType> loadProvider = build(glide, modelLoader,
+                dataClass, resourceClass, transcoder);
+
+        return optionsApplier.apply(new GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType>(
+                loadProvider, transcodeClass, this));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public <Y extends Target<File>> Y downloadOnly(Y target) {
+        return getDownloadOnlyRequest().into(target);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public FutureTarget<File> downloadOnly(int width, int height) {
+        return getDownloadOnlyRequest().into(width, height);
+    }
+
+    private GenericRequestBuilder<ModelType, DataType, File, File> getDownloadOnlyRequest() {
+        ResourceTranscoder<File, File> transcoder = UnitTranscoder.get();
+        DataLoadProvider<DataType, File> dataLoadProvider = glide.buildDataProvider(dataClass, File.class);
+        FixedLoadProvider<ModelType, DataType, File, File> fixedLoadProvider =
+                new FixedLoadProvider<ModelType, DataType, File, File>(modelLoader, transcoder, dataLoadProvider);
+        return optionsApplier.apply(new GenericRequestBuilder<ModelType, DataType, File, File>(fixedLoadProvider,
+                File.class, this))
+                .priority(Priority.LOW)
+                .diskCacheStrategy(DiskCacheStrategy.SOURCE)
+                .skipMemoryCache(true);
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/GifRequestBuilder.java b/library/src/main/java/com/bumptech/glide/GifRequestBuilder.java
index 60cb682..474f2e7 100644
--- a/library/src/main/java/com/bumptech/glide/GifRequestBuilder.java
+++ b/library/src/main/java/com/bumptech/glide/GifRequestBuilder.java
@@ -1,195 +1,421 @@
 package com.bumptech.glide;
 
-import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.view.animation.Animation;
+
 import com.bumptech.glide.load.Encoder;
+import com.bumptech.glide.load.Key;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.ResourceEncoder;
 import com.bumptech.glide.load.Transformation;
-import com.bumptech.glide.load.resource.bitmap.CenterCrop;
-import com.bumptech.glide.load.resource.bitmap.FitCenter;
-import com.bumptech.glide.load.resource.gif.GifData;
-import com.bumptech.glide.load.resource.gif.GifDataTransformation;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
+import com.bumptech.glide.load.resource.gif.GifDrawable;
+import com.bumptech.glide.load.resource.gif.GifDrawableTransformation;
 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
-import com.bumptech.glide.manager.RequestTracker;
 import com.bumptech.glide.provider.LoadProvider;
 import com.bumptech.glide.request.RequestListener;
-import com.bumptech.glide.request.ViewPropertyAnimation;
+import com.bumptech.glide.request.animation.DrawableCrossFadeFactory;
+import com.bumptech.glide.request.animation.ViewPropertyAnimation;
 
+import java.io.File;
 import java.io.InputStream;
 
-public class GifRequestBuilder<ModelType, TranscodeType>
-        extends GenericRequestBuilder<ModelType, InputStream, GifData, TranscodeType> {
-    private Glide glide;
+/**
+ * A class for creating a request to load an animated gif.
+ *
+ * <p>
+ *     Warning - It is <em>not</em> safe to use this builder after calling <code>into()</code>, it may be pooled and
+ *     reused.
+ * </p>
+ *
+ * @param <ModelType> The type of model that will be loaded into the target.
+ */
+public class GifRequestBuilder<ModelType>
+        extends GenericRequestBuilder<ModelType, InputStream, GifDrawable, GifDrawable>
+        implements BitmapOptions, DrawableOptions {
 
-    GifRequestBuilder(Context context, ModelType model,
-            LoadProvider<ModelType, InputStream, GifData, TranscodeType> loadProvider,
-            Class<TranscodeType> transcodeClass, Glide glide, RequestTracker requestTracker) {
-        super(context, model, loadProvider, transcodeClass, glide, requestTracker);
-        this.glide = glide;
+    GifRequestBuilder(LoadProvider<ModelType, InputStream, GifDrawable, GifDrawable> loadProvider,
+            Class<GifDrawable> transcodeClass, GenericRequestBuilder<ModelType, ?, ?, ?> other) {
+        super(loadProvider, transcodeClass, other);
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> thumbnail(
-            GenericRequestBuilder<ModelType, InputStream, GifData, TranscodeType> thumbnailRequest) {
+    public GifRequestBuilder<ModelType> thumbnail(GenericRequestBuilder<?, ?, ?, GifDrawable> thumbnailRequest) {
         super.thumbnail(thumbnailRequest);
         return this;
     }
 
-    public GifRequestBuilder<ModelType, TranscodeType> thumbnail(
-            GifRequestBuilder<ModelType, TranscodeType> thumbnailRequest) {
+    /**
+     * Loads and displays the GIF retrieved by the given thumbnail request if it finishes before this
+     * request. Best used for loading thumbnail GIFs that are smaller and will be loaded more quickly
+     * than the fullsize GIF. There are no guarantees about the order in which the requests will actually
+     * finish. However, if the thumb request completes after the full request, the thumb GIF will never
+     * replace the full image.
+     *
+     * @see #thumbnail(float)
+     *
+     * <p>
+     *     Note - Any options on the main request will not be passed on to the thumbnail request. For example, if
+     *     you want an animation to occur when either the full GIF loads or the thumbnail loads,
+     *     you need to call {@link #animate(int)} on both the thumb and the full request. For a simpler thumbnail
+     *     option where these options are applied to the humbnail as well, see {@link #thumbnail(float)}.
+     * </p>
+     *
+     * <p>
+     *     Only the thumbnail call on the main request will be obeyed, recursive calls to this method are ignored.
+     * </p>
+     *
+     * @param thumbnailRequest The request to use to load the thumbnail.
+     * @return This builder object.
+     */
+    public GifRequestBuilder<ModelType> thumbnail(GifRequestBuilder<?> thumbnailRequest) {
         super.thumbnail(thumbnailRequest);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> thumbnail(float sizeMultiplier) {
+    public GifRequestBuilder<ModelType> thumbnail(float sizeMultiplier) {
         super.thumbnail(sizeMultiplier);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> sizeMultiplier(float sizeMultiplier) {
+    public GifRequestBuilder<ModelType> sizeMultiplier(float sizeMultiplier) {
         super.sizeMultiplier(sizeMultiplier);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> decoder(
-            ResourceDecoder<InputStream, GifData> decoder) {
+    public GifRequestBuilder<ModelType> decoder(
+            ResourceDecoder<InputStream, GifDrawable> decoder) {
         super.decoder(decoder);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> cacheDecoder(
-            ResourceDecoder<InputStream, GifData> cacheDecoder) {
+    public GifRequestBuilder<ModelType> cacheDecoder(
+            ResourceDecoder<File, GifDrawable> cacheDecoder) {
         super.cacheDecoder(cacheDecoder);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> encoder(
-            ResourceEncoder<GifData> encoder) {
+    public GifRequestBuilder<ModelType> encoder(
+            ResourceEncoder<GifDrawable> encoder) {
         super.encoder(encoder);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> priority(Priority priority) {
+    public GifRequestBuilder<ModelType> priority(Priority priority) {
         super.priority(priority);
         return this;
     }
 
-    public GifRequestBuilder<ModelType, TranscodeType> fitCenter() {
-        return transformBitmap(new FitCenter(glide.getBitmapPool()));
+    /**
+     * Transforms each frame of the GIF using {@link com.bumptech.glide.load.resource.bitmap.CenterCrop}.
+     *
+     * @see #fitCenter()
+     * @see #transformFrame(com.bumptech.glide.load.resource.bitmap.BitmapTransformation...)
+     * @see #transformFrame(com.bumptech.glide.load.Transformation[])
+     * @see #transform(com.bumptech.glide.load.Transformation[])
+     *
+     * @return This request builder.
+     */
+    public GifRequestBuilder<ModelType> centerCrop() {
+        return transformFrame(glide.getBitmapCenterCrop());
     }
 
-    public GifRequestBuilder<ModelType, TranscodeType> centerCrop() {
-        return transformBitmap(new CenterCrop(glide.getBitmapPool()));
+    /**
+     * Transforms each frame of the GIF using {@link com.bumptech.glide.load.resource.bitmap.FitCenter}.
+     *
+     * @see #centerCrop()
+     * @see #transformFrame(com.bumptech.glide.load.resource.bitmap.BitmapTransformation...)
+     * @see #transformFrame(com.bumptech.glide.load.Transformation[])
+     * @see #transform(com.bumptech.glide.load.Transformation[])
+     *
+     * @return This request builder..
+     */
+    public GifRequestBuilder<ModelType> fitCenter() {
+        return transformFrame(glide.getBitmapFitCenter());
     }
 
-    public GifRequestBuilder<ModelType, TranscodeType> transformBitmap(Transformation<Bitmap> bitmapTransformation) {
-        return transform(new GifDataTransformation(bitmapTransformation));
+    /**
+     * Transforms each frame of the GIF using the given transformations.
+     *
+     * @see #centerCrop()
+     * @see #fitCenter()
+     * @see #transformFrame(com.bumptech.glide.load.Transformation[])
+     * @see #transform(com.bumptech.glide.load.Transformation[])
+     *
+     * @param bitmapTransformations The transformations to apply in order to each frame.
+     * @return This request builder.
+     */
+    public GifRequestBuilder<ModelType> transformFrame(BitmapTransformation... bitmapTransformations) {
+        return transform(toGifTransformations(bitmapTransformations));
     }
 
+    /**
+     * Transforms each frame of the GIF using the given transformations.
+     *
+     * @see #fitCenter()
+     * @see #centerCrop()
+     * @see #transformFrame(com.bumptech.glide.load.resource.bitmap.BitmapTransformation...)
+     * @see #transform(com.bumptech.glide.load.Transformation[])
+     *
+     * @param bitmapTransformations The transformations to apply in order to each frame.
+     * @return This request builder.
+     */
+    public GifRequestBuilder<ModelType> transformFrame(Transformation<Bitmap>... bitmapTransformations) {
+        return transform(toGifTransformations(bitmapTransformations));
+    }
+
+    private GifDrawableTransformation[] toGifTransformations(Transformation<Bitmap>[] bitmapTransformations) {
+        GifDrawableTransformation[] transformations = new GifDrawableTransformation[bitmapTransformations.length];
+        for (int i = 0; i < bitmapTransformations.length; i++) {
+            transformations[i] = new GifDrawableTransformation(bitmapTransformations[i], glide.getBitmapPool());
+        }
+        return transformations;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see #fitCenter()
+     * @see #centerCrop()
+     * @see #transformFrame(com.bumptech.glide.load.resource.bitmap.BitmapTransformation...)
+     * @see #transformFrame(com.bumptech.glide.load.Transformation[])
+     *
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> transform(Transformation<GifData> transformation) {
-        super.transform(transformation);
+    public GifRequestBuilder<ModelType> transform(Transformation<GifDrawable>... transformations) {
+        super.transform(transformations);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> transcoder(
-            ResourceTranscoder<GifData, TranscodeType> transcoder) {
+    public GifRequestBuilder<ModelType> transcoder(ResourceTranscoder<GifDrawable, GifDrawable> transcoder) {
         super.transcoder(transcoder);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> animate(int animationId) {
+    public GifRequestBuilder<ModelType> crossFade() {
+        super.animate(new DrawableCrossFadeFactory<GifDrawable>());
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GifRequestBuilder<ModelType> crossFade(int duration) {
+        super.animate(new DrawableCrossFadeFactory<GifDrawable>(duration));
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Deprecated
+    @Override
+    public GifRequestBuilder<ModelType> crossFade(Animation animation, int duration) {
+        super.animate(new DrawableCrossFadeFactory<GifDrawable>(animation, duration));
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GifRequestBuilder<ModelType> crossFade(int animationId, int duration) {
+        super.animate(new DrawableCrossFadeFactory<GifDrawable>(context, animationId,
+                duration));
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GifRequestBuilder<ModelType> dontAnimate() {
+        super.dontAnimate();
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GifRequestBuilder<ModelType> animate(int animationId) {
         super.animate(animationId);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Deprecated
+    @SuppressWarnings("deprecation")
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> animate(Animation animation) {
+    public GifRequestBuilder<ModelType> animate(Animation animation) {
         super.animate(animation);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> animate(ViewPropertyAnimation.Animator animator) {
+    public GifRequestBuilder<ModelType> animate(ViewPropertyAnimation.Animator animator) {
         super.animate(animator);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> placeholder(int resourceId) {
+    public GifRequestBuilder<ModelType> placeholder(int resourceId) {
         super.placeholder(resourceId);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> placeholder(Drawable drawable) {
+    public GifRequestBuilder<ModelType> placeholder(Drawable drawable) {
         super.placeholder(drawable);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> error(int resourceId) {
+    public GifRequestBuilder<ModelType> error(int resourceId) {
         super.error(resourceId);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> error(Drawable drawable) {
+    public GifRequestBuilder<ModelType> error(Drawable drawable) {
         super.error(drawable);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> listener(
-            RequestListener<ModelType, TranscodeType> requestListener) {
+    public GifRequestBuilder<ModelType> listener(
+            RequestListener<? super ModelType, GifDrawable> requestListener) {
         super.listener(requestListener);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> skipMemoryCache(boolean skip) {
+    public GifRequestBuilder<ModelType> skipMemoryCache(boolean skip) {
         super.skipMemoryCache(skip);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> skipDiskCache(boolean skip) {
-        super.skipDiskCache( skip);
+    public GifRequestBuilder<ModelType> diskCacheStrategy(DiskCacheStrategy strategy) {
+        super.diskCacheStrategy(strategy);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> skipCache(boolean skip) {
-        super.skipCache(skip);
-        return this;
-    }
-
-    @Override
-    public GifRequestBuilder<ModelType, TranscodeType> override(int width, int height) {
+    public GifRequestBuilder<ModelType> override(int width, int height) {
         super.override(width, height);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> sourceEncoder(Encoder<InputStream> sourceEncoder) {
+    public GifRequestBuilder<ModelType> sourceEncoder(Encoder<InputStream> sourceEncoder) {
         super.sourceEncoder(sourceEncoder);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public GifRequestBuilder<ModelType, TranscodeType> cacheSource(boolean cacheSource) {
-        super.cacheSource(cacheSource);
+    public GifRequestBuilder<ModelType> dontTransform() {
+        super.dontTransform();
         return this;
     }
+
+    @Override
+    public GifRequestBuilder<ModelType> signature(Key signature) {
+        super.signature(signature);
+        return this;
+    }
+
+    @Override
+    public GifRequestBuilder<ModelType> load(ModelType model) {
+        super.load(model);
+        return this;
+    }
+
+    @Override
+    public GifRequestBuilder<ModelType> clone() {
+        return (GifRequestBuilder<ModelType>) super.clone();
+    }
+
+    @Override
+    void applyFitCenter() {
+        fitCenter();
+    }
+
+    @Override
+    void applyCenterCrop() {
+        centerCrop();
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/GifTypeRequest.java b/library/src/main/java/com/bumptech/glide/GifTypeRequest.java
index 33ea823..7fd2089 100644
--- a/library/src/main/java/com/bumptech/glide/GifTypeRequest.java
+++ b/library/src/main/java/com/bumptech/glide/GifTypeRequest.java
@@ -1,52 +1,69 @@
 package com.bumptech.glide;
 
-import android.content.Context;
 import com.bumptech.glide.load.model.ModelLoader;
-import com.bumptech.glide.load.resource.gif.GifData;
 import com.bumptech.glide.load.resource.gif.GifDrawable;
-import com.bumptech.glide.load.resource.transcode.GifDataBytesTranscoder;
+import com.bumptech.glide.load.resource.transcode.GifDrawableBytesTranscoder;
 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
-import com.bumptech.glide.manager.RequestTracker;
+import com.bumptech.glide.provider.DataLoadProvider;
 import com.bumptech.glide.provider.FixedLoadProvider;
 
 import java.io.InputStream;
 
-public class GifTypeRequest<A> extends GifRequestBuilder<A, GifDrawable> {
-    private final Context context;
-    private final A model;
-    private final ModelLoader<A, InputStream> streamModelLoader;
-    private final Glide glide;
-    private final RequestTracker requestTracker;
-    private RequestManager.OptionsApplier optionsApplier;
+/**
+ * A class for creating a load request that either loads an {@link com.bumptech.glide.load.resource.gif.GifDrawable}
+ * directly or that adds an {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} to transcode
+ * {@link com.bumptech.glide.load.resource.gif.GifDrawable} into another resource type.
+ *
+ * @param <ModelType> The type of model to load the {@link com.bumptech.glide.load.resource.gif.GifDrawable} or other
+ *           transcoded class from.
+ */
+public class GifTypeRequest<ModelType> extends GifRequestBuilder<ModelType> {
+    private final ModelLoader<ModelType, InputStream> streamModelLoader;
+    private final RequestManager.OptionsApplier optionsApplier;
 
-    private static <A, R> FixedLoadProvider<A, InputStream, GifData, R> buildProvider(Glide glide,
+    private static <A, R> FixedLoadProvider<A, InputStream, GifDrawable, R> buildProvider(Glide glide,
             ModelLoader<A, InputStream> streamModelLoader, Class<R> transcodeClass,
-            ResourceTranscoder<GifData, R> transcoder) {
-        if (transcoder == null) {
-            transcoder = glide.buildTranscoder(GifData.class, transcodeClass);
+            ResourceTranscoder<GifDrawable, R> transcoder) {
+        if (streamModelLoader == null) {
+            return null;
         }
-        return streamModelLoader == null ? null :
-                new FixedLoadProvider<A, InputStream, GifData, R>(streamModelLoader, transcoder,
-                        glide.buildDataProvider(InputStream.class, GifData.class));
 
+        if (transcoder == null) {
+            transcoder = glide.buildTranscoder(GifDrawable.class, transcodeClass);
+        }
+        DataLoadProvider<InputStream, GifDrawable> dataLoadProvider = glide.buildDataProvider(InputStream.class,
+                GifDrawable.class);
+        return new FixedLoadProvider<A, InputStream, GifDrawable, R>(streamModelLoader, transcoder, dataLoadProvider);
     }
 
-    GifTypeRequest(Context context, A model, ModelLoader<A, InputStream> streamModelLoader, Glide glide,
-            RequestTracker requestTracker, RequestManager.OptionsApplier optionsApplier){
-        super(context, model, buildProvider(glide, streamModelLoader, GifDrawable.class, null), GifDrawable.class,
-                glide, requestTracker);
-        this.context = context;
-        this.model = model;
+    GifTypeRequest(GenericRequestBuilder<ModelType, ?, ?, ?> other,
+            ModelLoader<ModelType, InputStream> streamModelLoader, RequestManager.OptionsApplier optionsApplier) {
+        super(buildProvider(other.glide, streamModelLoader, GifDrawable.class, null), GifDrawable.class, other);
         this.streamModelLoader = streamModelLoader;
-        this.glide = glide;
-        this.requestTracker = requestTracker;
         this.optionsApplier = optionsApplier;
+
+        // Default to animating.
+        crossFade();
     }
 
-    public <R> GifRequestBuilder<A, R> transcode(ResourceTranscoder<GifData, R> transcoder, Class<R> transcodeClass) {
-        return optionsApplier.apply(model, new GifRequestBuilder<A, R>(context, model,
-                buildProvider(glide, streamModelLoader, transcodeClass, transcoder), transcodeClass, glide,
-                requestTracker));
+    /**
+     * Sets a transcoder to transcode the decoded {@link com.bumptech.glide.load.resource.gif.GifDrawable} into another
+     * resource type.
+     *
+     * @param transcoder The transcoder to use.
+     * @param transcodeClass The {@link Class} of the resource the
+     * {@link com.bumptech.glide.load.resource.gif.GifDrawable} will be transcoded to.
+     *
+     * @param <R> The type of the resource the {@link com.bumptech.glide.load.resource.gif.GifDrawable} will be
+     *           trasncoded to.
+     * @return This request builder.
+     */
+    public <R> GenericRequestBuilder<ModelType, InputStream, GifDrawable, R> transcode(
+            ResourceTranscoder<GifDrawable, R> transcoder, Class<R> transcodeClass) {
+        FixedLoadProvider<ModelType, InputStream, GifDrawable, R> provider = buildProvider(glide, streamModelLoader,
+                transcodeClass, transcoder);
+        return optionsApplier.apply(new GenericRequestBuilder<ModelType, InputStream, GifDrawable, R>(provider,
+                transcodeClass, this));
     }
 
     /**
@@ -58,7 +75,7 @@
      *
      * @return A new Builder object to build a request to transform the given model into the bytes of an animated gif.
      */
-    public GifRequestBuilder<A, byte[]> toBytes() {
-        return transcode(new GifDataBytesTranscoder(), byte[].class);
+    public GenericRequestBuilder<ModelType, InputStream, GifDrawable, byte[]> toBytes() {
+        return transcode(new GifDrawableBytesTranscoder(), byte[].class);
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/Glide.java b/library/src/main/java/com/bumptech/glide/Glide.java
index cbe3200..5439a42 100644
--- a/library/src/main/java/com/bumptech/glide/Glide.java
+++ b/library/src/main/java/com/bumptech/glide/Glide.java
@@ -2,35 +2,38 @@
 
 import android.annotation.TargetApi;
 import android.app.Activity;
-import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
 import android.util.Log;
 import android.view.View;
 import android.widget.ImageView;
-import com.android.volley.RequestQueue;
+
+import com.bumptech.glide.load.DecodeFormat;
 import com.bumptech.glide.load.engine.Engine;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
-import com.bumptech.glide.load.engine.cache.DiskCache;
 import com.bumptech.glide.load.engine.cache.MemoryCache;
+import com.bumptech.glide.load.engine.prefill.BitmapPreFiller;
+import com.bumptech.glide.load.engine.prefill.PreFillType;
 import com.bumptech.glide.load.model.GenericLoaderFactory;
 import com.bumptech.glide.load.model.GlideUrl;
 import com.bumptech.glide.load.model.ImageVideoWrapper;
 import com.bumptech.glide.load.model.ModelLoader;
 import com.bumptech.glide.load.model.ModelLoaderFactory;
 import com.bumptech.glide.load.model.file_descriptor.FileDescriptorFileLoader;
-import com.bumptech.glide.load.model.file_descriptor.FileDescriptorModelLoader;
 import com.bumptech.glide.load.model.file_descriptor.FileDescriptorResourceLoader;
 import com.bumptech.glide.load.model.file_descriptor.FileDescriptorStringLoader;
 import com.bumptech.glide.load.model.file_descriptor.FileDescriptorUriLoader;
+import com.bumptech.glide.load.model.stream.HttpUrlGlideUrlLoader;
+import com.bumptech.glide.load.model.stream.StreamByteArrayLoader;
 import com.bumptech.glide.load.model.stream.StreamFileLoader;
-import com.bumptech.glide.load.model.stream.StreamModelLoader;
 import com.bumptech.glide.load.model.stream.StreamResourceLoader;
 import com.bumptech.glide.load.model.stream.StreamStringLoader;
 import com.bumptech.glide.load.model.stream.StreamUriLoader;
@@ -38,86 +41,92 @@
 import com.bumptech.glide.load.resource.bitmap.CenterCrop;
 import com.bumptech.glide.load.resource.bitmap.FileDescriptorBitmapDataLoadProvider;
 import com.bumptech.glide.load.resource.bitmap.FitCenter;
+import com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawable;
 import com.bumptech.glide.load.resource.bitmap.ImageVideoDataLoadProvider;
 import com.bumptech.glide.load.resource.bitmap.StreamBitmapDataLoadProvider;
-import com.bumptech.glide.load.resource.gif.GifData;
-import com.bumptech.glide.load.resource.gif.GifDataLoadProvider;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+import com.bumptech.glide.load.resource.file.StreamFileDataLoadProvider;
 import com.bumptech.glide.load.resource.gif.GifDrawable;
+import com.bumptech.glide.load.resource.gif.GifDrawableLoadProvider;
 import com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapper;
 import com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperTransformation;
-import com.bumptech.glide.load.resource.gifbitmap.ImageVideoGifDataLoadProvider;
-import com.bumptech.glide.load.resource.transcode.BitmapDrawableTranscoder;
+import com.bumptech.glide.load.resource.gifbitmap.ImageVideoGifDrawableLoadProvider;
 import com.bumptech.glide.load.resource.transcode.GifBitmapWrapperDrawableTranscoder;
-import com.bumptech.glide.load.resource.transcode.GifDataDrawableTranscoder;
+import com.bumptech.glide.load.resource.transcode.GlideBitmapDrawableTranscoder;
 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
-import com.bumptech.glide.load.resource.transcode.TranscoderFactory;
+import com.bumptech.glide.load.resource.transcode.TranscoderRegistry;
 import com.bumptech.glide.manager.RequestManagerRetriever;
-import com.bumptech.glide.provider.DataLoadProviderFactory;
-import com.bumptech.glide.request.GlideAnimation;
+import com.bumptech.glide.provider.DataLoadProvider;
+import com.bumptech.glide.provider.DataLoadProviderRegistry;
+import com.bumptech.glide.request.FutureTarget;
 import com.bumptech.glide.request.Request;
+import com.bumptech.glide.request.animation.GlideAnimation;
 import com.bumptech.glide.request.target.ImageViewTargetFactory;
 import com.bumptech.glide.request.target.Target;
 import com.bumptech.glide.request.target.ViewTarget;
-import com.bumptech.glide.volley.VolleyUrlLoader;
+import com.bumptech.glide.util.Util;
 
 import java.io.File;
 import java.io.InputStream;
 import java.net.URL;
 
 /**
- * A singleton to present a simple static interface for building requests with {@link BitmapRequestBuilder} and maintaining
- * an {@link Engine}, {@link BitmapPool}, {@link DiskCache} and {@link MemoryCache}.
- *
- * <p>
- * Note - This class is not thread safe.
- * </p>
+ * A singleton to present a simple static interface for building requests with {@link BitmapRequestBuilder} and
+ * maintaining an {@link Engine}, {@link BitmapPool}, {@link com.bumptech.glide.load.engine.cache.DiskCache} and
+ * {@link MemoryCache}.
  */
 public class Glide {
-    // 250 MB
+    /** 250 MB of cache. */
     static final int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
 
     private static final String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";
     private static final String TAG = "Glide";
-    private static Glide GLIDE;
+    private static volatile Glide glide;
 
-    private final GenericLoaderFactory loaderFactory = new GenericLoaderFactory();
-    private final RequestQueue requestQueue;
+    private final GenericLoaderFactory loaderFactory;
     private final Engine engine;
     private final BitmapPool bitmapPool;
     private final MemoryCache memoryCache;
+    private final DecodeFormat decodeFormat;
     private final ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();
-    private final TranscoderFactory transcoderFactory = new TranscoderFactory();
-    private final DataLoadProviderFactory dataLoadProviderFactory;
+    private final TranscoderRegistry transcoderRegistry = new TranscoderRegistry();
+    private final DataLoadProviderRegistry dataLoadProviderRegistry;
     private final CenterCrop bitmapCenterCrop;
     private final GifBitmapWrapperTransformation drawableCenterCrop;
     private final FitCenter bitmapFitCenter;
     private final GifBitmapWrapperTransformation drawableFitCenter;
+    private final Handler mainHandler;
+    private final BitmapPreFiller bitmapPreFiller;
 
     /**
-     * Try to get the external cache directory if available and default to the internal. Use a default name for the
-     * cache directory if no name is provided
+     * Returns a directory with a default name in the private cache directory of the application to use to store
+     * retrieved media and thumbnails.
      *
-     * @param context A context
-     * @return A File representing the default disk cache directory
+     * @see #getPhotoCacheDir(android.content.Context, String)
+     *
+     * @param context A context.
      */
     public static File getPhotoCacheDir(Context context) {
         return getPhotoCacheDir(context, DEFAULT_DISK_CACHE_DIR);
     }
 
     /**
-     * Try to get the external cache directory if available and default to the internal. Use a default name for the
-     * cache directory if no name is provided
+     * Returns a directory with the given name in the private cache directory of the application to use to store
+     * retrieved media and thumbnails.
      *
-     * @param context A context
-     * @param cacheName The name of the subdirectory in which to store the cache
-     * @return A File representing the default disk cache directory
+     * @see #getPhotoCacheDir(android.content.Context)
+     *
+     * @param context A context.
+     * @param cacheName The name of the subdirectory in which to store the cache.
      */
-    @SuppressWarnings("ResultOfMethodCallIgnored")
     public static File getPhotoCacheDir(Context context, String cacheName) {
         File cacheDir = context.getCacheDir();
         if (cacheDir != null) {
             File result = new File(cacheDir, cacheName);
-            result.mkdirs();
+            if (!result.mkdirs() && (!result.exists() || !result.isDirectory())) {
+                // File wasn't able to create a directory, or the result exists but not a directory
+                return null;
+            }
             return result;
         }
         if (Log.isLoggable(TAG, Log.ERROR)) {
@@ -132,11 +141,15 @@
      * @return the singleton
      */
     public static Glide get(Context context) {
-        if (GLIDE == null) {
-            GLIDE = new GlideBuilder(context).createGlide();
+        if (glide == null) {
+            synchronized (Glide.class) {
+                if (glide == null) {
+                    glide = new GlideBuilder(context).createGlide();
+                }
+            }
         }
 
-        return GLIDE;
+        return glide;
     }
 
     /**
@@ -146,7 +159,7 @@
      * @see #setup(GlideBuilder)
      */
     public static boolean isSetup() {
-        return GLIDE != null;
+        return glide != null;
     }
 
     /**
@@ -163,37 +176,50 @@
             throw new IllegalArgumentException("Glide is already setup, check with isSetup() first");
         }
 
-        GLIDE = builder.createGlide();
+        glide = builder.createGlide();
     }
 
+    // For testing.
     static void tearDown() {
-        GLIDE = null;
+        glide = null;
     }
 
-    Glide(Engine engine, RequestQueue requestQueue, MemoryCache memoryCache, BitmapPool bitmapPool,
-            Context context) {
+    Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, Context context, DecodeFormat decodeFormat) {
         this.engine = engine;
-        this.requestQueue = requestQueue;
         this.bitmapPool = bitmapPool;
         this.memoryCache = memoryCache;
+        this.decodeFormat = decodeFormat;
+        loaderFactory = new GenericLoaderFactory(context);
+        mainHandler = new Handler(Looper.getMainLooper());
+        bitmapPreFiller = new BitmapPreFiller(memoryCache, bitmapPool, decodeFormat);
 
-        dataLoadProviderFactory = new DataLoadProviderFactory();
+        dataLoadProviderRegistry = new DataLoadProviderRegistry();
 
-        dataLoadProviderFactory.register(InputStream.class, Bitmap.class, new StreamBitmapDataLoadProvider(bitmapPool));
-        dataLoadProviderFactory.register(ParcelFileDescriptor.class, Bitmap.class,
-                new FileDescriptorBitmapDataLoadProvider(bitmapPool));
+        StreamBitmapDataLoadProvider streamBitmapLoadProvider =
+                new StreamBitmapDataLoadProvider(bitmapPool, decodeFormat);
+        dataLoadProviderRegistry.register(InputStream.class, Bitmap.class, streamBitmapLoadProvider);
 
-        ImageVideoDataLoadProvider imageVideoDataLoadProvider = new ImageVideoDataLoadProvider(bitmapPool);
-        dataLoadProviderFactory.register(ImageVideoWrapper.class, Bitmap.class, imageVideoDataLoadProvider);
+        FileDescriptorBitmapDataLoadProvider fileDescriptorLoadProvider =
+                new FileDescriptorBitmapDataLoadProvider(bitmapPool, decodeFormat);
+        dataLoadProviderRegistry.register(ParcelFileDescriptor.class, Bitmap.class, fileDescriptorLoadProvider);
 
-        GifDataLoadProvider gifDataLoadProvider = new GifDataLoadProvider(context, bitmapPool);
-        dataLoadProviderFactory.register(InputStream.class, GifData.class, gifDataLoadProvider);
+        ImageVideoDataLoadProvider imageVideoDataLoadProvider =
+                new ImageVideoDataLoadProvider(streamBitmapLoadProvider, fileDescriptorLoadProvider);
+        dataLoadProviderRegistry.register(ImageVideoWrapper.class, Bitmap.class, imageVideoDataLoadProvider);
 
-        dataLoadProviderFactory.register(ImageVideoWrapper.class, GifBitmapWrapper.class,
-                new ImageVideoGifDataLoadProvider(imageVideoDataLoadProvider, gifDataLoadProvider));
+        GifDrawableLoadProvider gifDrawableLoadProvider =
+                new GifDrawableLoadProvider(context, bitmapPool);
+        dataLoadProviderRegistry.register(InputStream.class, GifDrawable.class, gifDrawableLoadProvider);
+
+        dataLoadProviderRegistry.register(ImageVideoWrapper.class, GifBitmapWrapper.class,
+                new ImageVideoGifDrawableLoadProvider(imageVideoDataLoadProvider, gifDrawableLoadProvider, bitmapPool));
+
+        dataLoadProviderRegistry.register(InputStream.class, File.class, new StreamFileDataLoadProvider());
 
         register(File.class, ParcelFileDescriptor.class, new FileDescriptorFileLoader.Factory());
         register(File.class, InputStream.class, new StreamFileLoader.Factory());
+        register(int.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
+        register(int.class, InputStream.class, new StreamResourceLoader.Factory());
         register(Integer.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
         register(Integer.class, InputStream.class, new StreamResourceLoader.Factory());
         register(String.class, ParcelFileDescriptor.class, new FileDescriptorStringLoader.Factory());
@@ -201,32 +227,54 @@
         register(Uri.class, ParcelFileDescriptor.class, new FileDescriptorUriLoader.Factory());
         register(Uri.class, InputStream.class, new StreamUriLoader.Factory());
         register(URL.class, InputStream.class, new StreamUrlLoader.Factory());
-        register(GlideUrl.class, InputStream.class, new VolleyUrlLoader.Factory(requestQueue));
+        register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
+        register(byte[].class, InputStream.class, new StreamByteArrayLoader.Factory());
 
-        transcoderFactory.register(Bitmap.class, BitmapDrawable.class,
-                new BitmapDrawableTranscoder(context.getResources(), bitmapPool));
-        transcoderFactory.register(GifBitmapWrapper.class, Drawable.class,
-                new GifBitmapWrapperDrawableTranscoder(new BitmapDrawableTranscoder(context.getResources(), bitmapPool),
-                        new GifDataDrawableTranscoder()));
-        transcoderFactory.register(GifData.class, GifDrawable.class, new GifDataDrawableTranscoder());
+        transcoderRegistry.register(Bitmap.class, GlideBitmapDrawable.class,
+                new GlideBitmapDrawableTranscoder(context.getResources(), bitmapPool));
+        transcoderRegistry.register(GifBitmapWrapper.class, GlideDrawable.class,
+                new GifBitmapWrapperDrawableTranscoder(
+                        new GlideBitmapDrawableTranscoder(context.getResources(), bitmapPool)));
 
         bitmapCenterCrop = new CenterCrop(bitmapPool);
-        drawableCenterCrop = new GifBitmapWrapperTransformation(bitmapCenterCrop);
+        drawableCenterCrop = new GifBitmapWrapperTransformation(bitmapPool, bitmapCenterCrop);
 
         bitmapFitCenter = new FitCenter(bitmapPool);
-        drawableFitCenter = new GifBitmapWrapperTransformation(bitmapFitCenter);
+        drawableFitCenter = new GifBitmapWrapperTransformation(bitmapPool, bitmapFitCenter);
     }
 
+    /**
+     * Returns the {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} used to temporarily store
+     * {@link android.graphics.Bitmap}s so they can be reused to avoid garbage collections.
+     *
+     * <p>
+     *     Note - Using this pool directly can lead to undefined behavior and strange drawing errors. Any
+     *     {@link android.graphics.Bitmap} added to the pool must not be currently in use in any other part of the
+     *     application. Any {@link android.graphics.Bitmap} added to the pool must be removed from the pool before it
+     *     is added a second time.
+     * </p>
+     *
+     * <p>
+     *     Note - To make effective use of the pool, any {@link android.graphics.Bitmap} removed from the pool must
+     *     eventually be re-added. Otherwise the pool will eventually empty and will not serve any useful purpose.
+     * </p>
+     *
+     * <p>
+     *     The primary reason this object is exposed is for use in custom
+     *     {@link com.bumptech.glide.load.ResourceDecoder}s and {@link com.bumptech.glide.load.Transformation}s. Use
+     *     outside of these classes is not generally recommended.
+     * </p>
+     */
     public BitmapPool getBitmapPool() {
         return bitmapPool;
     }
 
     <Z, R> ResourceTranscoder<Z, R> buildTranscoder(Class<Z> decodedClass, Class<R> transcodedClass) {
-        return transcoderFactory.get(decodedClass, transcodedClass);
+        return transcoderRegistry.get(decodedClass, transcodedClass);
     }
 
     <T, Z> DataLoadProvider<T, Z> buildDataProvider(Class<T> dataClass, Class<Z> decodedClass) {
-        return dataLoadProviderFactory.get(dataClass, decodedClass);
+        return dataLoadProviderRegistry.get(dataClass, decodedClass);
     }
 
     <R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
@@ -253,21 +301,52 @@
         return drawableFitCenter;
     }
 
+    Handler getMainHandler() {
+        return mainHandler;
+    }
+
+    DecodeFormat getDecodeFormat() {
+        return decodeFormat;
+    }
+
     private GenericLoaderFactory getLoaderFactory() {
         return loaderFactory;
     }
 
     /**
-     * Returns the {@link RequestQueue} Glide is using to fetch images over http/https.
+     * Pre-fills the {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} using the given sizes.
+     *
+     * <p>
+     *   Enough Bitmaps are added to completely fill the pool, so most or all of the Bitmaps currently in the pool will
+     *   be evicted. Bitmaps are allocated according to the weights of the given sizes, where each size gets
+     *   (weight / prefillWeightSum) percent of the pool to fill.
+     * </p>
+     *
+     * <p>
+     *     Note - Pre-filling is done asynchronously using and {@link android.os.MessageQueue.IdleHandler}. Any
+     *     currently running pre-fill will be cancelled and replaced by a call to this method.
+     * </p>
+     *
+     * <p>
+     *     This method should be used with caution, overly aggressive pre-filling is substantially worse than not
+     *     pre-filling at all. Pre-filling should only be started in onCreate to avoid constantly clearing and
+     *     re-filling the {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool}. Rotation should be carefully
+     *     considered as well. It may be worth calling this method only when no saved instance state exists so that
+     *     pre-filling only happens when the Activity is first created, rather than on every rotation.
+     * </p>
+     *
+     * @param bitmapAttributeBuilders The list of
+     *     {@link com.bumptech.glide.load.engine.prefill.PreFillType.Builder Builders} representing
+     *     individual sizes and configurations of {@link android.graphics.Bitmap}s to be pre-filled.
      */
-    public RequestQueue getRequestQueue() {
-        return requestQueue;
+    public void preFillBitmapPool(PreFillType.Builder... bitmapAttributeBuilders) {
+        bitmapPreFiller.preFill(bitmapAttributeBuilders);
     }
 
     /**
      * Clears as much memory as possible.
      *
-     * @see ComponentCallbacks2#onLowMemory()
+     * @see android.content.ComponentCallbacks2#onLowMemory()
      */
     public void clearMemory() {
         bitmapPool.clearMemory();
@@ -277,7 +356,7 @@
     /**
      * Clears some memory with the exact amount depending on the given level.
      *
-     * @see ComponentCallbacks2#onTrimMemory(int)
+     * @see android.content.ComponentCallbacks2#onTrimMemory(int)
      */
     public void trimMemory(int level) {
         bitmapPool.trimMemory(level);
@@ -306,14 +385,25 @@
      *
      * @param target The Target to cancel loads for.
      */
-    public static void clear(Target target) {
+    public static void clear(Target<?> target) {
+        Util.assertMainThread();
         Request request = target.getRequest();
-        if (request!= null) {
+        if (request != null) {
             request.clear();
         }
     }
 
     /**
+     * Cancel any pending loads Glide may have for the target and free any resources that may have been loaded into
+     * the target so they may be reused.
+     *
+     * @param target The target to cancel loads for.
+     */
+    public static void clear(FutureTarget<?> target) {
+        target.clear();
+    }
+
+    /**
      * Cancel any pending loads Glide may have for the view and free any resources that may have been loaded for the
      * view.
      *
@@ -327,17 +417,17 @@
      * @throws IllegalArgumentException if an object other than Glide's metadata is set as the view's tag.
      */
     public static void clear(View view) {
-        Target viewTarget = new ClearTarget(view);
+        Target<?> viewTarget = new ClearTarget(view);
         clear(viewTarget);
     }
 
     /**
      * Use the given factory to build a {@link ModelLoader} for models of the given class. Generally the best use of
      * this method is to replace one of the default factories or add an implementation for other similar low level
-     * models. Typically the {@link RequestManager#using(StreamModelLoader)} or
-     * {@link RequestManager#using(FileDescriptorModelLoader)} syntax is preferred because it directly links the model
-     * with the ModelLoader being used to load it. Any factory replaced by the given factory will have its
-     * {@link ModelLoaderFactory#teardown()}} method called.
+     * models. Typically the {@link RequestManager#using(com.bumptech.glide.load.model.stream.StreamModelLoader)} or
+     * {@link RequestManager#using(com.bumptech.glide.load.model.file_descriptor.FileDescriptorModelLoader)} syntax is
+     * preferred because it directly links the model with the ModelLoader being used to load it. Any factory replaced
+     * by the given factory will have its {@link ModelLoaderFactory#teardown()}} method called.
      *
      * <p>
      *     Note - If a factory already exists for the given class, it will be replaced. If that factory is not being
@@ -350,8 +440,8 @@
      *     retained statically.
      * </p>
      *
-     * @see RequestManager#using(FileDescriptorModelLoader)
-     * @see RequestManager#using(StreamModelLoader)
+     * @see RequestManager#using(com.bumptech.glide.load.model.file_descriptor.FileDescriptorModelLoader)
+     * @see RequestManager#using(com.bumptech.glide.load.model.stream.StreamModelLoader)
      *
      * @param modelClass The model class.
      * @param resourceClass The resource class the model loader will translate the model type into.
@@ -398,7 +488,13 @@
      */
     public static <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass,
             Context context) {
-        return Glide.get(context).getLoaderFactory().buildModelLoader(modelClass, resourceClass, context);
+         if (modelClass == null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Unable to load null model, setting placeholder only");
+            }
+            return null;
+        }
+        return Glide.get(context).getLoaderFactory().buildModelLoader(modelClass, resourceClass);
     }
 
     /**
@@ -416,13 +512,7 @@
      */
     @SuppressWarnings("unchecked")
     public static <T, Y> ModelLoader<T, Y> buildModelLoader(T model, Class<Y> resourceClass, Context context) {
-        if (model == null) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Unable to load null model, setting placeholder only");
-            }
-            return null;
-        }
-        return buildModelLoader((Class<T>) model.getClass(), resourceClass, context);
+        return buildModelLoader(model != null ? (Class<T>) model.getClass() : null, resourceClass, context);
     }
 
     /**
@@ -469,28 +559,81 @@
     /**
      * Begin a load with Glide by passing in a context.
      *
+     * <p>
+     *     Any requests started using a context will only have the application level options applied and will not be
+     *     started or stopped based on lifecycle events. In general, loads should be started at the level the result
+     *     will be used in. If the resource will be used in a view in a child fragment,
+     *     the load should be started with {@link #with(android.app.Fragment)}} using that child fragment. Similarly,
+     *     if the resource will be used in a view in the parent fragment, the load should be started with
+     *     {@link #with(android.app.Fragment)} using the parent fragment. In the same vein, if the resource will be used
+     *     in a view in an activity, the load should be started with {@link #with(android.app.Activity)}}.
+     * </p>
+     *
+     * <p>
+     *     This method is appropriate for resources that will be used outside of the normal fragment or activity
+     *     lifecycle (For example in services, or for notification thumbnails).
+     * </p>
+     *
+     * @see #with(android.app.Activity)
+     * @see #with(android.app.Fragment)
+     * @see #with(android.support.v4.app.Fragment)
+     * @see #with(android.support.v4.app.FragmentActivity)
+     *
      * @param context Any context, will not be retained.
-     * @return A model request to pass in the object representing the image to be loaded.
+     * @return A RequestManager for the top level application that can be used to start a load.
      */
     public static RequestManager with(Context context) {
-        return RequestManagerRetriever.get(context);
+        RequestManagerRetriever retriever = RequestManagerRetriever.get();
+        return retriever.get(context);
     }
 
+    /**
+     * Begin a load with Glide that will be tied to the given {@link android.app.Activity}'s lifecycle and that uses the
+     * given {@link Activity}'s default options.
+     *
+     * @param activity The activity to use.
+     * @return A RequestManager for the given activity that can be used to start a load.
+     */
     public static RequestManager with(Activity activity) {
-        return RequestManagerRetriever.get(activity);
+        RequestManagerRetriever retriever = RequestManagerRetriever.get();
+        return retriever.get(activity);
     }
 
+    /**
+     * Begin a load with Glide that will tied to the give {@link android.support.v4.app.FragmentActivity}'s lifecycle
+     * and that uses the given {@link android.support.v4.app.FragmentActivity}'s default options.
+     *
+     * @param activity The activity to use.
+     * @return A RequestManager for the given FragmentActivity that can be used to start a load.
+     */
     public static RequestManager with(FragmentActivity activity) {
-        return RequestManagerRetriever.get(activity);
+        RequestManagerRetriever retriever = RequestManagerRetriever.get();
+        return retriever.get(activity);
     }
 
-    @TargetApi(11)
+    /**
+     * Begin a load with Glide that will be tied to the given {@link android.app.Fragment}'s lifecycle and that uses
+     * the given {@link android.app.Fragment}'s default options.
+     *
+     * @param fragment The fragment to use.
+     * @return A RequestManager for the given Fragment that can be used to start a load.
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
     public static RequestManager with(android.app.Fragment fragment) {
-        return RequestManagerRetriever.get(fragment);
+        RequestManagerRetriever retriever = RequestManagerRetriever.get();
+        return retriever.get(fragment);
     }
 
+    /**
+     * Begin a load with Glide that will be tied to the given {@link android.support.v4.app.Fragment}'s lifecycle and
+     * that uses the given {@link android.support.v4.app.Fragment}'s default options.
+     *
+     * @param fragment The fragment to use.
+     * @return A RequestManager for the given Fragment that can be used to start a load.
+     */
     public static RequestManager with(Fragment fragment) {
-        return RequestManagerRetriever.get(fragment);
+        RequestManagerRetriever retriever = RequestManagerRetriever.get();
+        return retriever.get(fragment);
     }
 
     private static class ClearTarget extends ViewTarget<View, Object> {
@@ -499,9 +642,23 @@
         }
 
         @Override
-        public void onResourceReady(Object resource, GlideAnimation<Object> glideAnimation) { }
+        public void onLoadStarted(Drawable placeholder) {
+            // Do nothing.
+        }
 
         @Override
-        public void setPlaceholder(Drawable placeholder) { }
+        public void onLoadFailed(Exception e, Drawable errorDrawable) {
+            // Do nothing.
+        }
+
+        @Override
+        public void onResourceReady(Object resource, GlideAnimation<? super Object> glideAnimation) {
+            // Do nothing.
+        }
+
+        @Override
+        public void onLoadCleared(Drawable placeholder) {
+            // Do nothing.
+        }
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/GlideBuilder.java b/library/src/main/java/com/bumptech/glide/GlideBuilder.java
index 38c130e..49e4d2f 100644
--- a/library/src/main/java/com/bumptech/glide/GlideBuilder.java
+++ b/library/src/main/java/com/bumptech/glide/GlideBuilder.java
@@ -2,7 +2,8 @@
 
 import android.content.Context;
 import android.os.Build;
-import com.android.volley.RequestQueue;
+
+import com.bumptech.glide.load.DecodeFormat;
 import com.bumptech.glide.load.engine.Engine;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter;
@@ -14,76 +15,145 @@
 import com.bumptech.glide.load.engine.cache.MemoryCache;
 import com.bumptech.glide.load.engine.cache.MemorySizeCalculator;
 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
-import com.bumptech.glide.volley.RequestQueueWrapper;
 
 import java.io.File;
 import java.util.concurrent.ExecutorService;
 
+/**
+ * A builder class for setting default structural classes for Glide to use.
+ */
 public class GlideBuilder {
-    private RequestQueue requestQueue;
-    private Context context;
+    private final Context context;
+
     private Engine engine;
     private BitmapPool bitmapPool;
     private MemoryCache memoryCache;
     private DiskCache diskCache;
-    private ExecutorService resizeService;
+    private ExecutorService sourceService;
     private ExecutorService diskCacheService;
+    private DecodeFormat decodeFormat;
 
     public GlideBuilder(Context context) {
         this.context = context.getApplicationContext();
     }
 
-    public GlideBuilder setRequestQueue(RequestQueue requestQueue) {
-        this.requestQueue = requestQueue;
-        return this;
-    }
-
+    /**
+     * Sets the {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} implementation to use to store and
+     * retrieve reused {@link android.graphics.Bitmap}s.
+     *
+     * @param bitmapPool The pool to use.
+     * @return This builder.
+     */
     public GlideBuilder setBitmapPool(BitmapPool bitmapPool) {
         this.bitmapPool = bitmapPool;
         return this;
     }
 
+    /**
+     * Sets the {@link com.bumptech.glide.load.engine.cache.MemoryCache} implementation to store
+     * {@link com.bumptech.glide.load.engine.Resource}s that are not currently in use.
+     *
+     * @param memoryCache  The cache to use.
+     * @return This builder.
+     */
     public GlideBuilder setMemoryCache(MemoryCache memoryCache) {
         this.memoryCache = memoryCache;
         return this;
     }
 
+    /**
+     * Sets the {@link com.bumptech.glide.load.engine.cache.DiskCache} implementation to use to store
+     * {@link com.bumptech.glide.load.engine.Resource} data and thumbnails.
+     *
+     * @param diskCache The disk cache to use.
+     * @return This builder.
+     */
     public GlideBuilder setDiskCache(DiskCache diskCache) {
         this.diskCache = diskCache;
         return this;
     }
 
+    /**
+     * Sets the {@link java.util.concurrent.ExecutorService} implementation to use when retrieving
+     * {@link com.bumptech.glide.load.engine.Resource}s that are not already in the cache.
+     *
+     * <p>
+     *     Any implementation must order requests based on their {@link com.bumptech.glide.Priority} for thumbnail
+     *     requests to work properly.
+     * </p>
+     *
+     * @see #setDiskCacheService(java.util.concurrent.ExecutorService)
+     * @see com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor
+     *
+     * @param service The ExecutorService to use.
+     * @return This builder.
+     */
     public GlideBuilder setResizeService(ExecutorService service) {
-        this.resizeService = service;
+        this.sourceService = service;
         return this;
     }
 
+    /**
+     * Sets the {@link java.util.concurrent.ExecutorService} implementation to use when retrieving
+     * {@link com.bumptech.glide.load.engine.Resource}s that are currently in cache.
+     *
+     * <p>
+     *     Any implementation must order requests based on their {@link com.bumptech.glide.Priority} for thumbnail
+     *     requests to work properly.
+     * </p>
+     *
+     * @see #setResizeService(java.util.concurrent.ExecutorService)
+     * @see com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor
+     *
+     * @param service The ExecutorService to use.
+     * @return This builder.
+     */
     public GlideBuilder setDiskCacheService(ExecutorService service) {
         this.diskCacheService = service;
         return this;
     }
 
+    /**
+     * Sets the {@link com.bumptech.glide.load.DecodeFormat} that will be the default format for all the default
+     * decoders that can change the {@link android.graphics.Bitmap.Config} of the {@link android.graphics.Bitmap}s they
+     * decode.
+     *
+     * <p>
+     *     Decode format is always a suggestion, not a requirement. See {@link com.bumptech.glide.load.DecodeFormat} for
+     *     more details.
+     * </p>
+     *
+     * <p>
+     *     If you instantiate and use a custom decoder, it will use
+     *     {@link com.bumptech.glide.load.DecodeFormat#DEFAULT} as its default.
+     * </p>
+     *
+     * @param decodeFormat The format to use.
+     * @return This builder.
+     */
+    public GlideBuilder setDecodeFormat(DecodeFormat decodeFormat) {
+        this.decodeFormat = decodeFormat;
+        return this;
+    }
+
+    // For testing.
     GlideBuilder setEngine(Engine engine) {
         this.engine = engine;
         return this;
     }
 
     Glide createGlide() {
-        if (resizeService == null) {
+        if (sourceService == null) {
             final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
-            resizeService = new FifoPriorityThreadPoolExecutor(cores);
+            sourceService = new FifoPriorityThreadPoolExecutor(cores);
         }
         if (diskCacheService == null) {
             diskCacheService = new FifoPriorityThreadPoolExecutor(1);
         }
 
-        if (requestQueue == null) {
-            requestQueue = RequestQueueWrapper.getRequestQueue(context);
-        }
-
         MemorySizeCalculator calculator = new MemorySizeCalculator(context);
         if (bitmapPool == null) {
-            if (Build.VERSION.SDK_INT >= 11) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                 bitmapPool = new LruBitmapPool(calculator.getBitmapPoolSize());
             } else {
                 bitmapPool = new BitmapPoolAdapter();
@@ -105,9 +175,13 @@
         }
 
         if (engine == null) {
-            engine = new Engine(memoryCache, diskCache, resizeService, diskCacheService);
+            engine = new Engine(memoryCache, diskCache, diskCacheService, sourceService);
         }
 
-        return new Glide(engine, requestQueue, memoryCache, bitmapPool, context);
+        if (decodeFormat == null) {
+            decodeFormat = DecodeFormat.DEFAULT;
+        }
+
+        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
     }
 }
\ No newline at end of file
diff --git a/library/src/main/java/com/bumptech/glide/ListPreloader.java b/library/src/main/java/com/bumptech/glide/ListPreloader.java
index a0bbd07..7d5158a 100644
--- a/library/src/main/java/com/bumptech/glide/ListPreloader.java
+++ b/library/src/main/java/com/bumptech/glide/ListPreloader.java
@@ -1,18 +1,17 @@
 package com.bumptech.glide;
 
-import android.annotation.TargetApi;
-import android.os.Build;
 import android.widget.AbsListView;
-import com.bumptech.glide.request.GlideAnimation;
-import com.bumptech.glide.request.target.BaseTarget;
 
-import java.util.ArrayDeque;
-import java.util.LinkedList;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.BaseTarget;
+import com.bumptech.glide.request.target.SizeReadyCallback;
+import com.bumptech.glide.util.Util;
+
 import java.util.List;
 import java.util.Queue;
 
 /**
- * Loads a few images ahead in the direction of scrolling in any {@link AbsListView} so that images are in the memory
+ * Loads a few resources ahead in the direction of scrolling in any {@link AbsListView} so that images are in the memory
  * cache just before the corresponding view in created in the list. Gives the appearance of an infinitely large image
  * cache, depending on scrolling speed, cpu speed, and cache size.
  *
@@ -45,7 +44,9 @@
     }
 
     @Override
-    public void onScrollStateChanged(AbsListView absListView, int i) { }
+    public void onScrollStateChanged(AbsListView absListView, int scrollState) {
+        // Do nothing.
+    }
 
     @Override
     public void onScroll(AbsListView absListView, int firstVisible, int visibleCount, int totalCount) {
@@ -59,7 +60,7 @@
     }
 
     /**
-     * Returns the dimensions of the view in the list where the images will be displayed.
+     * Returns the dimensions of the view in the list where the resources will be displayed.
      * <p>
      *     Note - The dimensions returned here must precisely match those of the view in the list.
      * </p>
@@ -69,22 +70,23 @@
     protected abstract int[] getDimensions(T item);
 
     /**
-     * Returns a list of all models that need to be loaded for the list to display adapter items start - end. A list of
-     * any size can be returned so there can be multiple models per adapter position.
+     * Returns a list of all models that need to be loaded for the list to display adapter items {@code start - end}.
+     * A list of any size can be returned so there can be multiple models per adapter position.
      *
-     * @param start The smallest adapter position. Will be >= 0 && < adapter.getCount() && <= end
-     * @param end The largest adapter position. Will be >= 0 && < adapter.getCount && >= start
-     * @return A non null list of all models for adapter positions between start and end.
+     * @param start The smallest adapter position. Will be {@code >= 0 && < adapter.getCount() && <= end}
+     * @param end The largest adapter position. Will be {@code >= 0 && < adapter.getCount && >= start}
+     * @return A non null list of all models for adapter positions between {@code start} and {@code end}.
      */
     protected abstract List<T> getItems(int start, int end);
 
     /**
-     * Returns a glide request for a given item. Must exactly match the request used to load the image in the list. The
-     * target and context will be provided by the preloader.
+     * Returns a glide request for a given item. Must exactly match the request used to load the resource in the list.
+     * The target and context will be provided by the preloader.
      *
      * @param item The model to load.
      * @return A non null {@link BitmapRequestBuilder}.
      */
+    @SuppressWarnings("rawtypes")
     protected abstract GenericRequestBuilder getRequestBuilder(T item);
 
     private void preload(int start, boolean increasing) {
@@ -126,6 +128,7 @@
         lastEnd = end;
     }
 
+    @SuppressWarnings("unchecked")
     private void preloadItem(List<T> items, int position) {
         final T item = items.get(position);
         final int[] dimensions = getDimensions(item);
@@ -140,16 +143,11 @@
         }
     }
 
-    private static class PreloadTargetQueue {
+    private static final class PreloadTargetQueue {
         private final Queue<PreloadTarget> queue;
 
-        @TargetApi(9)
-        private PreloadTargetQueue(int size) {
-            if (Build.VERSION.SDK_INT >= 9) {
-                queue = new ArrayDeque<PreloadTarget>(size);
-            } else {
-                queue = new LinkedList<PreloadTarget>();
-            }
+        public PreloadTargetQueue(int size) {
+            queue = Util.createQueue(size);
 
             for (int i = 0; i < size; i++) {
                 queue.offer(new PreloadTarget());
@@ -165,12 +163,12 @@
         }
     }
 
-    private static class PreloadTarget extends BaseTarget {
+    private static class PreloadTarget extends BaseTarget<Object> {
         private int photoHeight;
         private int photoWidth;
 
         @Override
-        public void onResourceReady(Object resource, GlideAnimation glideAnimation) {
+        public void onResourceReady(Object resource, GlideAnimation<? super Object> glideAnimation) {
             // Do nothing.
         }
 
@@ -178,6 +176,5 @@
         public void getSize(SizeReadyCallback cb) {
             cb.onSizeReady(photoWidth, photoHeight);
         }
-
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/MemoryCategory.java b/library/src/main/java/com/bumptech/glide/MemoryCategory.java
index 319c5f0..08127f4 100644
--- a/library/src/main/java/com/bumptech/glide/MemoryCategory.java
+++ b/library/src/main/java/com/bumptech/glide/MemoryCategory.java
@@ -1,8 +1,20 @@
 package com.bumptech.glide;
 
+/**
+ * An enum for dynamically modifying the amount of memory Glide is able to use.
+ */
 public enum MemoryCategory {
+    /**
+     * Tells Glide's memory cache and bitmap pool to use at most half of their initial maximum size.
+     */
     LOW(0.5f),
+    /**
+     * Tells Glide's memory cache and bitmap pool to use at most their initial maximum size.
+     */
     NORMAL(1f),
+    /**
+     * Tells Glide's memory cache and bitmap pool to use at most one and a half times their initial maximum size.
+     */
     HIGH(1.5f);
 
     private float multiplier;
@@ -11,6 +23,10 @@
         this.multiplier = multiplier;
     }
 
+    /**
+     * Returns the multiplier that should be applied to the initial maximum size of Glide's memory cache and bitmap
+     * pool.
+     */
     public float getMultiplier() {
         return multiplier;
     }
diff --git a/library/src/main/java/com/bumptech/glide/Priority.java b/library/src/main/java/com/bumptech/glide/Priority.java
index 90df8e2..316eae1 100644
--- a/library/src/main/java/com/bumptech/glide/Priority.java
+++ b/library/src/main/java/com/bumptech/glide/Priority.java
@@ -2,12 +2,12 @@
 
 /**
  * Priorities for completing loads. If more than one load is queued at a time, the load with the higher priority will be
- * started first. Priorities are condsidered best effort, there are no guarantees about the order in which loads will
+ * started first. Priorities are considered best effort, there are no guarantees about the order in which loads will
  * start or finish.
  */
 public enum Priority {
     IMMEDIATE,
     HIGH,
     NORMAL,
-    LOW,
+    LOW, priority,
 }
diff --git a/library/src/main/java/com/bumptech/glide/RequestManager.java b/library/src/main/java/com/bumptech/glide/RequestManager.java
index 0c68fde..071a91e 100644
--- a/library/src/main/java/com/bumptech/glide/RequestManager.java
+++ b/library/src/main/java/com/bumptech/glide/RequestManager.java
@@ -1,28 +1,27 @@
 package com.bumptech.glide;
 
-import android.app.Activity;
-import android.app.Fragment;
 import android.content.Context;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.ParcelFileDescriptor;
-import android.provider.MediaStore;
-import android.support.v4.app.FragmentActivity;
-import com.bumptech.glide.load.ResourceDecoder;
-import com.bumptech.glide.load.ResourceEncoder;
+
+import com.bumptech.glide.load.Key;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
 import com.bumptech.glide.load.model.ModelLoader;
-import com.bumptech.glide.load.model.ModelLoaderFactory;
 import com.bumptech.glide.load.model.file_descriptor.FileDescriptorModelLoader;
 import com.bumptech.glide.load.model.stream.MediaStoreStreamLoader;
 import com.bumptech.glide.load.model.stream.StreamByteArrayLoader;
-import com.bumptech.glide.load.model.stream.StreamFileLoader;
 import com.bumptech.glide.load.model.stream.StreamModelLoader;
-import com.bumptech.glide.load.model.stream.StreamResourceLoader;
-import com.bumptech.glide.load.model.stream.StreamStringLoader;
-import com.bumptech.glide.load.model.stream.StreamUriLoader;
 import com.bumptech.glide.manager.ConnectivityMonitor;
 import com.bumptech.glide.manager.ConnectivityMonitorFactory;
+import com.bumptech.glide.manager.Lifecycle;
+import com.bumptech.glide.manager.LifecycleListener;
 import com.bumptech.glide.manager.RequestTracker;
-import com.bumptech.glide.volley.VolleyUrlLoader;
+import com.bumptech.glide.signature.ApplicationVersionSignature;
+import com.bumptech.glide.signature.MediaStoreSignature;
+import com.bumptech.glide.signature.StringSignature;
+import com.bumptech.glide.util.Util;
 
 import java.io.File;
 import java.io.InputStream;
@@ -34,52 +33,112 @@
  * intelligently stop, start, and restart requests. Retrieve either by instantiating a new object, or to take advantage
  * built in Activity and Fragment lifecycle handling, use the static Glide.load methods with your Fragment or Activity.
  *
- * @see Glide#with(Activity)
- * @see Glide#with(FragmentActivity)
- * @see Glide#with(Fragment)
+ * @see Glide#with(android.app.Activity)
+ * @see Glide#with(android.support.v4.app.FragmentActivity)
+ * @see Glide#with(android.app.Fragment)
  * @see Glide#with(android.support.v4.app.Fragment)
  * @see Glide#with(Context)
  */
-public class RequestManager {
-    private final ConnectivityMonitor connectivityMonitor;
+public class RequestManager implements LifecycleListener {
     private final Context context;
+    private final Lifecycle lifecycle;
     private final RequestTracker requestTracker;
     private final Glide glide;
     private final OptionsApplier optionsApplier;
     private DefaultOptions options;
 
-    public RequestManager(Context context) {
-        this(context, new RequestTracker(), new ConnectivityMonitorFactory());
+    public RequestManager(Context context, Lifecycle lifecycle) {
+        this(context, lifecycle, new RequestTracker(), new ConnectivityMonitorFactory());
     }
 
-    RequestManager(Context context, RequestTracker requestTracker, ConnectivityMonitorFactory factory) {
-        this.context = context;
+    RequestManager(Context context, final Lifecycle lifecycle, RequestTracker requestTracker,
+            ConnectivityMonitorFactory factory) {
+        this.context = context.getApplicationContext();
+        this.lifecycle = lifecycle;
         this.requestTracker = requestTracker;
-        this.connectivityMonitor = factory.build(context, new RequestManagerConnectivityListener(requestTracker));
-        connectivityMonitor.register();
         this.glide = Glide.get(context);
         this.optionsApplier = new OptionsApplier();
+
+        ConnectivityMonitor connectivityMonitor = factory.build(context,
+                new RequestManagerConnectivityListener(requestTracker));
+
+        // If we're the application level request manager, we may be created on a background thread. In that case we
+        // cannot risk synchronously pausing or resuming requests, so we hack around the issue by delaying adding
+        // ourselves as a lifecycle listener by posting to the main thread. This should be entirely safe.
+        if (Util.isOnBackgroundThread()) {
+            new Handler(Looper.getMainLooper()).post(new Runnable() {
+                @Override
+                public void run() {
+                    lifecycle.addListener(RequestManager.this);
+                }
+            });
+        } else {
+            lifecycle.addListener(this);
+        }
+        lifecycle.addListener(connectivityMonitor);
     }
 
+    /**
+     * An interface that allows a default set of options to be applied to all requests started from an
+     * {@link com.bumptech.glide.RequestManager}.
+     */
     public interface DefaultOptions {
-        public <T> void apply(T model, GenericRequestBuilder<T, ?, ?, ?> requestBuilder);
+        /**
+         * Allows the implementor to apply some options to the given request.
+         *
+         * @param requestBuilder The request builder being used to construct the load.
+         * @param <T> The type of the model.
+         */
+        <T> void apply(GenericRequestBuilder<T, ?, ?, ?> requestBuilder);
     }
 
+    /**
+     * Sets an interface that can apply some default options to all Requests started using this {@link RequestManager}.
+     *
+     * <p>
+     *     Note - These options will be retained for the life the of this {@link com.bumptech.glide.RequestManager}
+     *     so be wary of using
+     *     {@link com.bumptech.glide.GenericRequestBuilder#listener(com.bumptech.glide.request.RequestListener)}} when
+     *     starting requests using an {@link android.content.Context} or {@link android.app.Application} to avoid
+     *     leaking memory. Any option that does not use an anonymous inner class is generally safe.
+     * </p>
+     *
+     * @param options The default options to apply to all requests.
+     */
     public void setDefaultOptions(DefaultOptions options) {
         this.options = options;
     }
 
     /**
+     * Returns true if loads for this {@link RequestManager} are currently paused.
+     *
+     * @see #pauseRequests()
+     * @see #resumeRequests()
+     */
+    public boolean isPaused() {
+        Util.assertMainThread();
+        return requestTracker.isPaused();
+    }
+
+    /**
      * Cancels any in progress loads, but does not clear resources of completed loads.
+     *
+     * @see #isPaused()
+     * @see #resumeRequests()
      */
     public void pauseRequests() {
+        Util.assertMainThread();
         requestTracker.pauseRequests();
     }
 
     /**
      * Restarts any loads that have not yet completed.
+     *
+     * @see #isPaused()
+     * @see #pauseRequests()
      */
     public void resumeRequests() {
+        Util.assertMainThread();
         requestTracker.resumeRequests();
     }
 
@@ -87,266 +146,448 @@
      * Lifecycle callback that registers for connectivity events (if the android.permission.ACCESS_NETWORK_STATE
      * permission is present) and restarts failed or paused requests.
      */
+    @Override
     public void onStart() {
         // onStart might not be called because this object may be created after the fragment/activity's onStart method.
-        connectivityMonitor.register();
-
-        requestTracker.resumeRequests();
+        resumeRequests();
     }
 
     /**
      * Lifecycle callback that unregisters for connectivity events (if the android.permission.ACCESS_NETWORK_STATE
      * permission is present) and pauses in progress loads.
      */
+    @Override
     public void onStop() {
-        connectivityMonitor.unregister();
-        requestTracker.pauseRequests();
+        pauseRequests();
     }
 
     /**
      * Lifecycle callback that cancels all in progress requests and clears and recycles resources for all completed
      * requests.
      */
+    @Override
     public void onDestroy() {
         requestTracker.clearRequests();
     }
 
     /**
-     * Use the given generic model loader to load the given generic data class.
+     * Returns a request builder that uses the given {@link com.bumptech.glide.load.model.ModelLoader} to fetch a
+     * generic data type.
+     *
      * <p>
-     *     Note that in most cases you will also need to specify an {@link ResourceDecoder} and an
-     *     {@link ResourceEncoder} for the load to complete successfully.
+     *     Warning - This is an experimental api that may change without a change in major version.
      * </p>
+     *
      * @param modelLoader The {@link ModelLoader} class to use to load the model.
      * @param dataClass The type of data the {@link ModelLoader} will load.
      * @param <A> The type of the model to be loaded.
      * @param <T> The type of the data to be loaded from the mode.
-     * @return A {@link GenericModelRequest} to set options for the load and ultimately the target to load the model
-     * into.
      */
     public <A, T> GenericModelRequest<A, T> using(ModelLoader<A, T> modelLoader, Class<T> dataClass) {
         return new GenericModelRequest<A, T>(modelLoader, dataClass);
     }
 
     /**
-     * Set the {@link ModelLoader} to use for for a new load where the model loader translates from a model to an
-     * {@link InputStream} resource for loading images.
+     * Returns a request builder that uses the given {@link com.bumptech.glide.load.model.stream.StreamModelLoader} to
+     * fetch an {@link InputStream} for loading images.
      *
      * @param modelLoader The model loader to use.
      * @param <T> The type of the model.
-     * @return A new {@link ImageModelRequest}.
      */
     public <T> ImageModelRequest<T> using(final StreamModelLoader<T> modelLoader) {
         return new ImageModelRequest<T>(modelLoader);
     }
 
     /**
-     * A convenience method to use a {@link StreamByteArrayLoader} to decode an image from a byte array.
+     * Returns a request builder that uses the given
+     * {@link com.bumptech.glide.load.model.stream.StreamByteArrayLoader} to fetch an {@link java.io.InputStream} for
+     * loading Bitmaps.
      *
      * @param modelLoader The byte array loader.
-     * @return A new {@link ImageModelRequest}.
      */
     public ImageModelRequest<byte[]> using(StreamByteArrayLoader modelLoader) {
         return new ImageModelRequest<byte[]>(modelLoader);
     }
 
     /**
-     * Set the {@link ModelLoader} to use for a new load where the model loader translates from a model to an
-     * {@link ParcelFileDescriptor} resource for loading video thumbnails.
+     * Returns a new request builder that uses the given {@link ModelLoader} to fetch a
+     * {@link ParcelFileDescriptor} for loading video thumbnails.
      *
      * @param modelLoader The model loader to use.
      * @param <T> The type of the model.
-     * @return A new {@link VideoModelRequest}.
      */
     public <T> VideoModelRequest<T> using(final FileDescriptorModelLoader<T> modelLoader) {
         return new VideoModelRequest<T>(modelLoader);
     }
 
     /**
-     * Use the {@link ModelLoaderFactory} currently registered for {@link String} to load the image represented by
-     * the given {@link String}. Defaults to {@link StreamStringLoader.Factory} and {@link StreamStringLoader} to
-     * load the given model.
+     * Returns a request builder to load the given {@link java.lang.String}.
+     * signature.
      *
-     * @see #using(StreamModelLoader)
+     * @see #fromString()
+     * @see #load(Object)
      *
-     * @param string The string representing the image. Must be either a path, or a uri handled by
-     *      {@link StreamUriLoader}
-     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
-     * into.
+     * @param string A file path, or a uri or url handled by {@link com.bumptech.glide.load.model.UriLoader}.
      */
     public DrawableTypeRequest<String> load(String string) {
-        return loadGeneric(string);
+        return (DrawableTypeRequest<String>) fromString().load(string);
     }
 
     /**
-     * Use the {@link ModelLoaderFactory} currently registered for {@link Uri} to load the image at the given uri.
-     * Defaults to {@link StreamUriLoader.Factory} and {@link StreamUriLoader}.
+     * Returns a request builder that loads data from {@link String}s using an empty signature.
      *
-     * @see #using(StreamModelLoader)
+     * <p>
+     *     Note - this method caches data using only the given String as the cache key. If the data is a Uri outside of
+     *     your control, or you otherwise expect the data represented by the given String to change without the String
+     *     identifier changing, Consider using
+     *     {@link com.bumptech.glide.GenericRequestBuilder#signature(com.bumptech.glide.load.Key)} to mixin a signature
+     *     you create that identifies the data currently at the given String that will invalidate the cache if that data
+     *     changes. Alternatively, using {@link com.bumptech.glide.load.engine.DiskCacheStrategy#NONE} and/or
+     *     {@link com.bumptech.glide.DrawableRequestBuilder#skipMemoryCache(boolean)} may be appropriate.
+     * </p>
      *
-     * @param uri The uri representing the image. Must be a uri handled by {@link StreamUriLoader}
-     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
-     * into.
+     * @see #from(Class)
+     * @see #load(String)
+     */
+    public DrawableTypeRequest<String> fromString() {
+        return loadGeneric(String.class);
+    }
+
+    /**
+     * Returns a request builder to load the given {@link Uri}.
+     *
+     * @see #fromUri()
+     * @see #load(Object)
+     *
+     * @param uri The Uri representing the image. Must be of a type handled by
+     * {@link com.bumptech.glide.load.model.UriLoader}.
      */
     public DrawableTypeRequest<Uri> load(Uri uri) {
-        return loadGeneric(uri);
+        return (DrawableTypeRequest<Uri>) fromUri().load(uri);
     }
 
     /**
-     * Use {@link MediaStore.Images.Thumbnails} and {@link MediaStore.Video.Thumbnails} to retrieve pre-generated
-     * thumbnails for the given uri. Falls back to the registered {@link ModelLoaderFactory} registered for {@link Uri}s
-     * if the given uri is not a media store uri or if no pre-generated thumbnail exists for the given uri. In addition,
-     * mixes the given mimeType, dateModified, and orientation into the cache key to detect and invalidate thumbnails
-     * if content is changed locally.
+     * Returns a request builder to load data from {@link android.net.Uri}s using no signature.
      *
+     * <p>
+     *     Note - this method caches data at Uris using only the Uri itself as the cache key. The data represented by
+     *     Uris from some content providers may change without the Uri changing, which means using this method
+     *     can lead to displaying stale data. Consider using
+     *     {@link com.bumptech.glide.GenericRequestBuilder#signature(com.bumptech.glide.load.Key)} to mixin a signature
+     *     you create based on the data at the given Uri that will invalidate the cache if that data changes.
+     *     Alternatively, using {@link com.bumptech.glide.load.engine.DiskCacheStrategy#NONE} and/or
+     *     {@link com.bumptech.glide.DrawableRequestBuilder#skipMemoryCache(boolean)} may be appropriate.
+     * </p>
+     *
+     * @see #from(Class)
+     * @see #loadFromMediaStore(android.net.Uri)
+     * @see #loadFromMediaStore(android.net.Uri, String, long, int)
+     * @see com.bumptech.glide.GenericRequestBuilder#signature(com.bumptech.glide.load.Key)
+     */
+    public DrawableTypeRequest<Uri> fromUri() {
+        return loadGeneric(Uri.class);
+    }
+
+    /**
+     * Returns a request builder that uses {@link android.provider.MediaStore.Images.Thumbnails} and
+     * {@link android.provider.MediaStore.Video.Thumbnails} to retrieve pre-generated thumbnails for the given uri if
+     * available and uses the given additional data to build a unique signature for cache invalidation.
+     *
+     * @see #loadFromMediaStore(android.net.Uri)
+     * @see #load(android.net.Uri)
+     * @see com.bumptech.glide.GenericRequestBuilder#signature(com.bumptech.glide.load.Key)
+     * @see com.bumptech.glide.signature.MediaStoreSignature
+     *
+     * @deprecated Use {@link #loadFromMediaStore(android.net.Uri)},
+     * {@link com.bumptech.glide.signature.MediaStoreSignature}, and
+     * {@link com.bumptech.glide.DrawableRequestBuilder#signature(com.bumptech.glide.load.Key)} instead. Scheduled to be
+     * removed in Glide 4.0.
      * @param uri The uri representing the media.
      * @param mimeType The mime type of the media store media. Ok to default to empty string "". See
-     *      {@link MediaStore.Images.ImageColumns#MIME_TYPE} or {@link MediaStore.Video.VideoColumns#MIME_TYPE}.
+     *      {@link android.provider.MediaStore.Images.ImageColumns#MIME_TYPE} or
+     *      {@link android.provider.MediaStore.Video.VideoColumns#MIME_TYPE}.
      * @param dateModified The date modified time of the media store media. Ok to default to 0. See
-     *      {@link MediaStore.Images.ImageColumns#DATE_MODIFIED} or {@link MediaStore.Video.VideoColumns#DATE_MODIFIED}.
+     *      {@link android.provider.MediaStore.Images.ImageColumns#DATE_MODIFIED} or
+     *      {@link android.provider.MediaStore.Video.VideoColumns#DATE_MODIFIED}.
      * @param orientation The orientation of the media store media. Ok to default to 0. See
-     *      {@link MediaStore.Images.ImageColumns#ORIENTATION}.
-     * @return A new {@link DrawableRequestBuilder} to set options for the load and ultimately the target to load the
-     *      uri into.
+     *      {@link android.provider.MediaStore.Images.ImageColumns#ORIENTATION}.
      */
+    @Deprecated
     public DrawableTypeRequest<Uri> loadFromMediaStore(Uri uri, String mimeType, long dateModified, int orientation) {
-        ModelLoader<Uri, InputStream> genericStreamLoader = Glide.buildStreamModelLoader(uri, context);
-        ModelLoader<Uri, InputStream> mediaStoreLoader = new MediaStoreStreamLoader(context, genericStreamLoader,
-                mimeType, dateModified, orientation);
-        ModelLoader<Uri, ParcelFileDescriptor> fileDescriptorModelLoader = Glide.buildFileDescriptorModelLoader(uri,
-                context);
-        return optionsApplier.apply(uri, new DrawableTypeRequest<Uri>(uri, mediaStoreLoader, fileDescriptorModelLoader,
-                context, glide, requestTracker, optionsApplier));
+        Key signature = new MediaStoreSignature(mimeType, dateModified, orientation);
+        return (DrawableTypeRequest<Uri>) loadFromMediaStore(uri).signature(signature);
     }
 
     /**
-     * Use the {@link ModelLoaderFactory} currently registered for {@link File} to load the image represented by the
-     * given {@link File}. Defaults to {@link StreamFileLoader.Factory} and {@link StreamFileLoader} to load the
-     * given model.
+     * Returns a request builder to load the given media store {@link android.net.Uri}.
      *
-     * @see #using(StreamModelLoader)
+     * @see #fromMediaStore()
+     * @see #load(Object)
+     *
+     * @param uri The uri representing the media.
+     */
+    public DrawableTypeRequest<Uri> loadFromMediaStore(Uri uri) {
+        return (DrawableTypeRequest<Uri>) fromMediaStore().load(uri);
+    }
+
+    /**
+     * Returns a request builder that uses {@link android.provider.MediaStore.Images.Thumbnails} and
+     * {@link android.provider.MediaStore.Video.Thumbnails} to retrieve pre-generated thumbnails for
+     * {@link android.net.Uri}s.
+     *
+     * <p>
+     *  Falls back to the registered {@link com.bumptech.glide.load.model.ModelLoaderFactory} registered for
+     *  {@link Uri}s if the given uri is not a media store uri or if no pre-generated thumbnail exists for the given
+     *  uri.
+     * </p>
+     *
+     * <p>
+     *     Note - This method by default caches data using the given Uri as the key. Since content in the media store
+     *     can change at any time, you should use
+     *     {@link com.bumptech.glide.GenericRequestBuilder#signature(com.bumptech.glide.load.Key)} to mix in some
+     *     additional data identifying the current state of the Uri, preferably using
+     *     {@link com.bumptech.glide.signature.MediaStoreSignature}. Alternatively consider avoiding the memory and
+     *     disk caches entirely using
+     *     {@link GenericRequestBuilder#diskCacheStrategy(com.bumptech.glide.load.engine.DiskCacheStrategy)}
+     *     and {@link com.bumptech.glide.load.engine.DiskCacheStrategy#NONE} and/or
+     *     {@link com.bumptech.glide.GenericRequestBuilder#skipMemoryCache(boolean)}.
+     * </p>
+     *
+     * @see #from(Class)
+     * @see #loadFromMediaStore(android.net.Uri, String, long, int)
+     * @see #load(android.net.Uri)
+     * @see com.bumptech.glide.signature.MediaStoreSignature
+     */
+    public DrawableTypeRequest<Uri> fromMediaStore() {
+        ModelLoader<Uri, InputStream> genericStreamLoader = Glide.buildStreamModelLoader(Uri.class, context);
+        ModelLoader<Uri, InputStream> mediaStoreLoader = new MediaStoreStreamLoader(context, genericStreamLoader);
+        ModelLoader<Uri, ParcelFileDescriptor> fileDescriptorModelLoader =
+                Glide.buildFileDescriptorModelLoader(Uri.class, context);
+
+        return optionsApplier.apply(new DrawableTypeRequest<Uri>(Uri.class, mediaStoreLoader,
+                fileDescriptorModelLoader, context, glide, requestTracker, lifecycle, optionsApplier));
+    }
+
+    /**
+     * Returns a request builder to load the given {@link File}.
+     *
+     * @see #fromFile()
+     * @see #load(Object)
      *
      * @param file The File containing the image
-     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
-     * into.
      */
     public DrawableTypeRequest<File> load(File file) {
-        return loadGeneric(file);
+        return (DrawableTypeRequest<File>) fromFile().load(file);
     }
 
     /**
-     * Use the {@link ModelLoaderFactory} currently registered for {@link Integer} to load the image represented by
-     * the given {@link Integer} resource id. Defaults to {@link StreamResourceLoader.Factory} and
-     * {@link StreamResourceLoader} to load the given model.
+     * Returns a request builder that uses the {@link com.bumptech.glide.load.model.ModelLoaderFactory} currently
+     * registered for {@link File} to load the image represented by the given {@link File}. Defaults to
+     * {@link com.bumptech.glide.load.model.stream.StreamFileLoader.Factory} and
+     * {@link com.bumptech.glide.load.model.stream.StreamFileLoader} to load images from {@link File}s.
      *
-     * @see #using(StreamModelLoader)
+     *  <p>
+     *     Note - this method caches data for Files using only the file path itself as the cache key. The data in the
+     *     File can change so using this method can lead to displaying stale data. If you expect the data in the File to
+     *     change, Consider using
+     *     {@link com.bumptech.glide.GenericRequestBuilder#signature(com.bumptech.glide.load.Key)} to mixin a signature
+     *     you create that identifies the data currently in the File that will invalidate the cache if that data
+     *     changes. Alternatively, using {@link com.bumptech.glide.load.engine.DiskCacheStrategy#NONE} and/or
+     *     {@link com.bumptech.glide.DrawableRequestBuilder#skipMemoryCache(boolean)} may be appropriate.
+     * </p>
+     *
+     * @see #load(java.io.File)
+     * @see #from(Class)
+     */
+    public DrawableTypeRequest<File> fromFile() {
+        return loadGeneric(File.class);
+    }
+
+    /**
+     * Returns a request builder to load the given resource id.
+     *
+     * @see #fromResource()
+     * @see #load(Object)
      *
      * @param resourceId the id of the resource containing the image
-     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
-     * into.
      */
     public DrawableTypeRequest<Integer> load(Integer resourceId) {
-        return loadGeneric(resourceId);
+        return (DrawableTypeRequest<Integer>) fromResource().load(resourceId);
     }
 
     /**
-     * Use the {@link ModelLoaderFactory} currently registered for the given model type to load the image
-     * represented by the given model.
+     * Returns a request builder that uses the {@link com.bumptech.glide.load.model.ModelLoaderFactory} currently
+     * registered for {@link Integer} to load the image represented by the given {@link Integer} resource id. Defaults
+     * to {@link com.bumptech.glide.load.model.stream.StreamResourceLoader.Factory} and
+     * {@link com.bumptech.glide.load.model.stream.StreamResourceLoader} to load resource id models.
      *
-     * @param model The model to load.
-     * @param <T> The type of the model to load.
-     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the image
-     * into.
+     * <p>
+     *     By default this method adds a version code based signature to the cache key used to cache this resource in
+     *     Glide. This signature is sufficient to guarantee that end users will see the most up to date versions of
+     *     your Drawables, but during development if you do not increment your version code before each install and
+     *     you replace a Drawable with different data without changing the Drawable name, you may see inconsistent
+     *     cached data. To get around this, consider using {@link com.bumptech.glide.load.engine.DiskCacheStrategy#NONE}
+     *     via {@link GenericRequestBuilder#diskCacheStrategy(com.bumptech.glide.load.engine.DiskCacheStrategy)}
+     *     during development, and re-enabling the default
+     *     {@link com.bumptech.glide.load.engine.DiskCacheStrategy#RESULT} for release builds.
+     * </p>
+     *
+     * @see #from(Class)
+     * @see #load(Integer)
+     * @see com.bumptech.glide.signature.ApplicationVersionSignature
+     * @see com.bumptech.glide.GenericRequestBuilder#signature(com.bumptech.glide.load.Key)
      */
-    @SuppressWarnings("unused")
-    public <T> DrawableTypeRequest<T> loadFromImage(T model) {
-        return loadGeneric(model);
+    public DrawableTypeRequest<Integer> fromResource() {
+        return (DrawableTypeRequest<Integer>) loadGeneric(Integer.class)
+                .signature(ApplicationVersionSignature.obtain(context));
     }
 
     /**
-     * Use the {@link ModelLoaderFactory} currently registered for {@link URL} to load the image represented by the
-     * given {@link URL}. Defaults to {@link VolleyUrlLoader.Factory} and {@link VolleyUrlLoader} to load the given
-     * model.
+     * Returns a request builder to load the given {@link URL}.
      *
-     * @see #using(StreamModelLoader)
+     * @see #fromUrl()
+     * @see #load(Object)
      *
+     * @deprecated The {@link java.net.URL} class has
+     * <a href="http://goo.gl/c4hHNu">a number of performance problems</a> and should generally be avoided when
+     * possible. Prefer {@link #load(android.net.Uri)} or {@link #load(String)}.
      * @param url The URL representing the image.
-     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the model
-     * into.
      */
-    public DrawableTypeRequest<URL> loadFromImage(URL url) {
-        return loadGeneric(url);
+    @Deprecated
+    public DrawableTypeRequest<URL> load(URL url) {
+        return (DrawableTypeRequest<URL>) fromUrl().load(url);
     }
 
     /**
-     * Use a new {@link StreamByteArrayLoader} to load an image from the given model.
+     * Returns a request builder that uses the {@link com.bumptech.glide.load.model.ModelLoaderFactory} currently
+     * registered for {@link URL} to load the image represented by the given {@link URL}. Defaults to
+     * {@link com.bumptech.glide.load.model.stream.HttpUrlGlideUrlLoader} and
+     * {@link com.bumptech.glide.load.data.HttpUrlFetcher} to load {@link java.net.URL} models.
      *
-     * @see #loadFromImage(byte[])
+     * @see #from(Class)
+     * @see #load(java.net.URL)
      *
+     * @deprecated The {@link java.net.URL} class has
+     * <a href="http://goo.gl/c4hHNu">a number of performance problems</a> and should generally be avoided when
+     * possible. Prefer {@link #load(android.net.Uri)} or {@link #load(String)}.
+     */
+    @Deprecated
+    public DrawableTypeRequest<URL> fromUrl() {
+        return loadGeneric(URL.class);
+    }
+
+    /**
+     * Returns a request builder that uses a {@link StreamByteArrayLoader} to load an image from the given byte array.
+     *
+     *
+     * <p>
+     *     Note - by default loads for bytes are not cached in either the memory or the disk cache.
+     * </p>
+     *
+     * @see #load(byte[])
+     *
+     * @deprecated Use {@link #load(byte[])} along with
+     * {@link com.bumptech.glide.GenericRequestBuilder#signature(com.bumptech.glide.load.Key)} instead. Scheduled to be
+     * removed in Glide 4.0.
      * @param model The data to load.
      * @param id A unique id that identifies the image represented by the model suitable for use as a cache key
-     *           (url, filepath etc). If there is no suitable id, use {@link #loadFromImage(byte[])} instaed.
-     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the image
-     * into.
+     *           (url, filepath etc). If there is no suitable id, use {@link #load(byte[])} instead.
      */
-    public DrawableTypeRequest<byte[]> loadFromImage(byte[] model, final String id) {
-        final StreamByteArrayLoader loader = new StreamByteArrayLoader(id);
-        return optionsApplier.apply(model,
-                new DrawableTypeRequest<byte[]>(model, loader, null, context, glide, requestTracker, optionsApplier));
+    @Deprecated
+    public DrawableTypeRequest<byte[]> load(byte[] model, final String id) {
+        return (DrawableTypeRequest<byte[]>) load(model).signature(new StringSignature(id));
     }
 
     /**
-     * Use a new {@link StreamByteArrayLoader} to load an image from the given model. Suitable when there is no
-     * simple id that represents the given data.
+     * Returns a request to load the given byte array.
+     *
+     * @see #fromBytes()
+     * @see #load(Object)
      *
      * @param model the data to load.
-     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the image
-     * into.
      */
-    public DrawableTypeRequest<byte[]> loadFromImage(byte[] model) {
-        return loadFromImage(model, UUID.randomUUID()
-                .toString());
+    public DrawableTypeRequest<byte[]> load(byte[] model) {
+        return (DrawableTypeRequest<byte[]>) fromBytes().load(model);
     }
 
     /**
-     * Use the {@link ModelLoaderFactory} currently registered for the given model type for
-     * {@link ParcelFileDescriptor}s to load a thumbnail for the video represented by the given model.
+     * Returns a request builder that uses {@link com.bumptech.glide.load.model.stream.StreamByteArrayLoader} to load
+     * images from byte arrays.
      *
-     * @param model The model to load.
-     * @param <T> The type of the model to load.
-     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the image
-     * into.
+     * <p>
+     *     Note - by default loads for bytes are not cached in either the memory or the disk cache.
+     * </p>
+     *
+     * @see #from(Class)
+     * @see #load(byte[])
      */
-    @SuppressWarnings("unused")
-    public <T> DrawableTypeRequest<T> loadFromVideo(T model) {
-        return loadGeneric(model);
+    public DrawableTypeRequest<byte[]> fromBytes() {
+        return (DrawableTypeRequest<byte[]>) loadGeneric(byte[].class)
+                .signature(new StringSignature(UUID.randomUUID().toString()))
+                .diskCacheStrategy(DiskCacheStrategy.NONE)
+                .skipMemoryCache(true /*skipMemoryCache*/);
     }
 
     /**
-     * Use the {@link ModelLoaderFactory}s currently registered for the given model type for
-     * {@link InputStream}s and {@link ParcelFileDescriptor}s to load a thumbnail from either the image or the video
-     * represented by the given model.
+     * Returns a request builder that uses the {@link com.bumptech.glide.load.model.ModelLoaderFactory}s currently
+     * registered for the given model class for {@link InputStream}s and {@link ParcelFileDescriptor}s to load a
+     * thumbnail from either the image or the video represented by the given model.
+     *
+     * <p>
+     *     Note - for maximum efficiency, consider using {@link #from(Class)}} to avoid repeatedly allocating builder
+     *     objects.
+     * </p>
+     *
+     * @see #from(Class)
      *
      * @param model The model the load.
      * @param <T> The type of the model to load.
-     * @return A {@link DrawableTypeRequest} to set options for the load and ultimately the target to load the image
-     * into.
      */
     public <T> DrawableTypeRequest<T> load(T model) {
-        return loadGeneric(model);
+        return (DrawableTypeRequest<T>) loadGeneric(getSafeClass(model)).load(model);
     }
 
-    private <T> DrawableTypeRequest<T> loadGeneric(T model) {
-        ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(model, context);
+    /**
+     * Returns a request builder that can be used for multiple loads that uses the
+     * {@link com.bumptech.glide.load.model.ModelLoaderFactory}s registered for the given model class for
+     * {@link java.io.InputStream}s and {@link android.os.ParcelFileDescriptor}s to load a thumbnail from objects of
+     * the given modelClass.
+     *
+     * <p>
+     *     Note - you must use {@link com.bumptech.glide.DrawableRequestBuilder#load(Object)}} to set a concrete model
+     *     to be loaded before calling
+     *     {@link com.bumptech.glide.DrawableRequestBuilder#into(com.bumptech.glide.request.target.Target)}. You may
+     *     also use this object for repeated loads by calling <code>request.load(model).into(target)</code>. You may
+     *     also adjust the options after calling {@link com.bumptech.glide.DrawableRequestBuilder#load(Object)}} and/or
+     *     {@link com.bumptech.glide.DrawableRequestBuilder#into(com.bumptech.glide.request.target.Target)}}. However,
+     *     keep in mind that any changes in options will apply to all future loads.
+     * </p>
+     *
+     * @param modelClass The class of model requests built by this class will load data from.
+     * @param <T> The type of the model.
+     */
+    public <T> DrawableTypeRequest<T> from(Class<T> modelClass) {
+        return loadGeneric(modelClass);
+    }
+
+    private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
+        ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
         ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
-                Glide.buildFileDescriptorModelLoader(model, context);
-        if (model != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
-            throw new IllegalArgumentException("Unknown type " + model + ". You must provide a Model of a type for"
+                Glide.buildFileDescriptorModelLoader(modelClass, context);
+        if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
+            throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for"
                     + " which there is a registered ModelLoader, if you are using a custom model, you must first call"
                     + " Glide#register with a ModelLoaderFactory for your custom model class");
         }
-        return optionsApplier.apply(model, new DrawableTypeRequest<T>(model, streamModelLoader,
-                fileDescriptorModelLoader, context, glide, requestTracker, optionsApplier));
+
+        return optionsApplier.apply(
+                new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
+                        glide, requestTracker, lifecycle, optionsApplier));
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> Class<T> getSafeClass(T model) {
+        return model != null ? (Class<T>) model.getClass() : null;
     }
 
     /**
@@ -355,16 +596,17 @@
      *
      * @param <T> The type of the model.
      */
-    public class VideoModelRequest<T> {
+    public final class VideoModelRequest<T> {
         private final ModelLoader<T, ParcelFileDescriptor> loader;
 
-        private VideoModelRequest(ModelLoader<T, ParcelFileDescriptor> loader) {
+        VideoModelRequest(ModelLoader<T, ParcelFileDescriptor> loader) {
             this.loader = loader;
         }
 
-        public BitmapTypeRequest<T> loadFromVideo(T model) {
-            return optionsApplier.apply(model, new BitmapTypeRequest<T>(context, model, null, loader, glide,
-                    requestTracker, optionsApplier));
+        public DrawableTypeRequest<T> load(T model) {
+            return (DrawableTypeRequest<T>) optionsApplier.apply(new DrawableTypeRequest<T>(getSafeClass(model), null,
+                    loader, context, glide, requestTracker, lifecycle, optionsApplier))
+                    .load(model);
         }
     }
 
@@ -374,69 +616,127 @@
      *
      * @param <T> The type of the model.
      */
-    public class ImageModelRequest<T> {
+    public final class ImageModelRequest<T> {
         private final ModelLoader<T, InputStream> loader;
 
-        private ImageModelRequest(ModelLoader<T, InputStream> loader) {
+        ImageModelRequest(ModelLoader<T, InputStream> loader) {
             this.loader = loader;
         }
 
+        /**
+         * Returns a request builder that uses the provided {@link com.bumptech.glide.load.model.ModelLoader} to load
+         * images from an {@link java.io.InputStream}s obtained from models of the given model class.
+         *
+         * @param modelClass The class of model to load images from.
+         */
+        public DrawableTypeRequest<T> from(Class<T> modelClass) {
+            return optionsApplier.apply(new DrawableTypeRequest<T>(modelClass, loader, null, context, glide,
+                    requestTracker, lifecycle, optionsApplier));
+        }
+
+        /**
+         * Returns a request builder that uses the provided {@link com.bumptech.glide.load.model.ModelLoader} to load
+         * an image from an {@link java.io.InputStream} obtained from the given model.
+         *
+         * @see #from(Class)
+         *
+         * @param model The model to load an image from.
+         */
         public DrawableTypeRequest<T> load(T model) {
-            return optionsApplier.apply(model, new DrawableTypeRequest<T>(model, loader, null, context, glide,
-                    requestTracker, optionsApplier));
+            return (DrawableTypeRequest<T>) from(getSafeClass(model)).load(model);
+        }
+    }
+
+    /**
+     * A helper class for building requests with custom {@link ModelLoader}s that requires the user to provide a
+     * specific model.
+     *
+     * @param <A> The type of the model.
+     * @param <T> The type of data the {@link com.bumptech.glide.load.model.ModelLoader} provides an
+     * {@link com.bumptech.glide.load.data.DataFetcher} to convert the model to.
+     */
+    public final class GenericModelRequest<A, T> {
+        private final ModelLoader<A, T> modelLoader;
+        private final Class<T> dataClass;
+
+        GenericModelRequest(ModelLoader<A, T> modelLoader, Class<T> dataClass) {
+            this.modelLoader = modelLoader;
+            this.dataClass = dataClass;
+        }
+
+        /**
+         * Sets the type of model that will be loaded.
+         *
+         * @param modelClass the class of model to use.
+         * @return A request builder
+         */
+        public GenericTypeRequest from(Class<A> modelClass) {
+            return new GenericTypeRequest(modelClass);
+        }
+
+        /**
+         * Sets the specific model that will be loaded.
+         *
+         * @param model The model to use.
+         * @return A request builder.
+         */
+        public GenericTypeRequest load(A model) {
+            return new GenericTypeRequest(model);
+        }
+
+        /**
+         * A helper class for building requests with custom {@link com.bumptech.glide.load.model.ModelLoader}s that
+         * requires the user to specify a specific resource class that will be loaded.
+         *
+         */
+        public final class GenericTypeRequest {
+            private final A model;
+            private final Class<A> modelClass;
+            private final boolean providedModel;
+
+            GenericTypeRequest(A model) {
+                providedModel = true;
+                this.model = model;
+                this.modelClass = getSafeClass(model);
+            }
+
+            GenericTypeRequest(Class<A> modelClass) {
+                providedModel = false;
+                this.model = null;
+                this.modelClass = modelClass;
+            }
+
+            /**
+             * Sets the resource class that will be loaded.
+             *
+             * @param resourceClass The class of the resource that will be loaded.
+             * @param <Z> The type of the resource that will be loaded.
+             * @return This request builder.
+             */
+            public <Z> GenericTranscodeRequest<A, T, Z> as(Class<Z> resourceClass) {
+                GenericTranscodeRequest<A, T, Z> result =
+                        optionsApplier.apply(new GenericTranscodeRequest<A, T, Z>(context, glide, modelClass,
+                                modelLoader, dataClass, resourceClass, requestTracker, lifecycle, optionsApplier));
+                if (providedModel) {
+                    result.load(model);
+                }
+                return result;
+            }
         }
     }
 
     class OptionsApplier {
 
-        public <A, X extends GenericRequestBuilder<A, ?, ?, ?>> X apply(A model, X builder) {
+        public <A, X extends GenericRequestBuilder<A, ?, ?, ?>> X apply(X builder) {
             if (options != null) {
-                options.apply(model, builder);
+                options.apply(builder);
             }
             return builder;
         }
-
-    }
-
-    /**
-     * A helper class for building requests with custom {@link ModelLoader}s that translate models to
-     * {@link InputStream} resources for loading images.
-     *
-     * @param <T> The type of the model.
-     */
-    public class GenericModelRequest<A, T> {
-        private final ModelLoader<A, T> modelLoader;
-        private final Class<T> dataClass;
-
-        private GenericModelRequest(ModelLoader<A, T> modelLoader, Class<T> dataClass) {
-            this.modelLoader = modelLoader;
-            this.dataClass = dataClass;
-        }
-
-        public GenericTypeRequest load(A model) {
-            return new GenericTypeRequest(model, modelLoader, dataClass);
-        }
-
-        public class GenericTypeRequest {
-            private final A model;
-            private final ModelLoader<A, T> modelLoader;
-            private final Class<T> dataClass;
-
-            private GenericTypeRequest(A model, ModelLoader<A, T> modelLoader, Class<T> dataClass) {
-                this.model = model;
-                this.modelLoader = modelLoader;
-                this.dataClass = dataClass;
-            }
-
-            public <Z> GenericTranscodeRequest<A, T, Z> as(Class<Z> resourceClass) {
-                return optionsApplier.apply(model, new GenericTranscodeRequest<A, T, Z>(context, glide, model,
-                        modelLoader, dataClass, resourceClass, requestTracker, optionsApplier));
-            }
-        }
     }
 
     private static class RequestManagerConnectivityListener implements ConnectivityMonitor.ConnectivityListener {
-        private RequestTracker requestTracker;
+        private final RequestTracker requestTracker;
 
         public RequestManagerConnectivityListener(RequestTracker requestTracker) {
             this.requestTracker = requestTracker;
diff --git a/library/src/main/java/com/bumptech/glide/load/CacheLoader.java b/library/src/main/java/com/bumptech/glide/load/CacheLoader.java
deleted file mode 100644
index d18789f..0000000
--- a/library/src/main/java/com/bumptech/glide/load/CacheLoader.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.bumptech.glide.load;
-
-import android.util.Log;
-import com.bumptech.glide.load.engine.Resource;
-import com.bumptech.glide.load.engine.cache.DiskCache;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public class CacheLoader {
-    private static final String TAG = "CacheLoader";
-    private DiskCache diskCache;
-
-    public CacheLoader(DiskCache diskCache) {
-        this.diskCache = diskCache;
-    }
-
-    public <Z> Resource<Z> load(Key key, ResourceDecoder<InputStream, Z> decoder, int width, int height) {
-        Resource<Z> result = null;
-        InputStream fromCache = diskCache.get(key);
-        if (fromCache != null) {
-            try {
-                result = decoder.decode(fromCache, width, height);
-            } catch (IOException e) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "Exception decoding image from cache", e);
-                }
-            }
-            if (result == null) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "Failed to decode image from cache or not present in cache");
-                }
-                diskCache.delete(key);
-            }
-        }
-        return result;
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/DecodeFormat.java b/library/src/main/java/com/bumptech/glide/load/DecodeFormat.java
index 433c67f..afb892a 100644
--- a/library/src/main/java/com/bumptech/glide/load/DecodeFormat.java
+++ b/library/src/main/java/com/bumptech/glide/load/DecodeFormat.java
@@ -1,29 +1,35 @@
 package com.bumptech.glide.load;
 
-import android.graphics.Bitmap;
-import com.bumptech.glide.load.resource.bitmap.BitmapDecoder;
+import android.os.Build;
 
 /**
- * Options for setting the value of {@link Bitmap#getConfig()} for {@link Bitmap}s returned by a {@link BitmapDecoder}.
+ * Options for setting the value of {@link android.graphics.Bitmap#getConfig()} for {@link android.graphics.Bitmap}s
+ * returned by a {@link com.bumptech.glide.load.resource.bitmap.BitmapDecoder}.
  *
  * <p>
- *     Note - In some cases it may not be possible to obey the requested setting, not all {@link BitmapDecoder}s support
- *     setting formats and certain images may not be able to be loaded as certain configurations. Therefore this class
- *     represents a preference rather than a requirement.
+ *     Note - In some cases it may not be possible to obey the requested setting, not all
+ *     {@link com.bumptech.glide.load.resource.bitmap.BitmapDecoder}s support setting formats and certain images may
+ *     not be able to be loaded as certain configurations. Therefore this class represents a preference rather than a
+ *     requirement.
  * </p>
  */
 public enum DecodeFormat {
     /**
-     * All bitmaps returned by the {@link BitmapDecoder} should return {@link Bitmap.Config#ARGB_8888} for
-     * {@link Bitmap#getConfig()}.
+     * All bitmaps returned by the {@link com.bumptech.glide.load.resource.bitmap.BitmapDecoder} should return
+     * {@link android.graphics.Bitmap.Config#ARGB_8888} for {@link android.graphics.Bitmap#getConfig()}.
      */
     ALWAYS_ARGB_8888,
 
     /**
      * Bitmaps decoded from image formats that support and/or use alpha (some types of PNGs, GIFs etc) should
-     * return {@link Bitmap.Config#ARGB_8888} for {@link Bitmap#getConfig()}. Bitmaps decoded from formats that don't
-     * support or use alpha should return {@link Bitmap.Config#RGB_565} for {@link Bitmap#getConfig()}.
+     * return {@link android.graphics.Bitmap.Config#ARGB_8888} for {@link android.graphics.Bitmap#getConfig()}. Bitmaps
+     * decoded from formats that don't support or use alpha should return
+     * {@link android.graphics.Bitmap.Config#RGB_565} for {@link android.graphics.Bitmap#getConfig()}.
      *
      */
-    PREFER_RGB_565,
+    PREFER_RGB_565;
+
+    /** The default value for DecodeFormat. */
+    public static final DecodeFormat DEFAULT = Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT
+            ? ALWAYS_ARGB_8888 : PREFER_RGB_565;
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/Encoder.java b/library/src/main/java/com/bumptech/glide/load/Encoder.java
index 7d18371..b81230c 100644
--- a/library/src/main/java/com/bumptech/glide/load/Encoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/Encoder.java
@@ -2,9 +2,30 @@
 
 import java.io.OutputStream;
 
+/**
+ * An interface for writing data to some persistent data store (i.e. a local File cache).
+ *
+ * @param <T> The type of the data that will be written.
+ */
 public interface Encoder<T> {
 
-    public boolean encode(T data, OutputStream os);
+    /**
+     * Writes the given data to the given output stream and returns True if the write completed successfully and
+     * should be committed.
+     *
+     * @param data The data to write.
+     * @param os The OutputStream to write the data to.
+     */
+    boolean encode(T data, OutputStream os);
 
-    public String getId();
+    /**
+     * Returns an ID identifying any transformation this encoder may apply to the given data that will be mixed in to
+     * the cache key.
+     *
+     * <p>
+     *     If the encoder does not transform the data in a way that significantly affects the cached result (ie performs
+     *     no unusual compression or downsampling) an empty string is an appropriate id.
+     * </p>
+     */
+    String getId();
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/Key.java b/library/src/main/java/com/bumptech/glide/load/Key.java
index 099aa29..4f614ed 100644
--- a/library/src/main/java/com/bumptech/glide/load/Key.java
+++ b/library/src/main/java/com/bumptech/glide/load/Key.java
@@ -4,16 +4,28 @@
 import java.security.MessageDigest;
 
 /**
- * A very generic interface that must implement {@link #equals(Object)} and {@link #hashCode()} to include a set of
- * uniquely identifying information for the object(s) represented by this key. Keys are used as cache keys so they must
- * be unique within a given dataset.
- *
+ * An interface that uniquely identifies some set of data. Implementations must implement {@link Object#equals(Object)}
+ * and {@link Object#hashCode()}. Implementations are generally expected to add all uniquely identifying information
+ * used in in {@link java.lang.Object#equals(Object)}} and {@link Object#hashCode()}} to the given
+ * {@link java.security.MessageDigest} in {@link #updateDiskCacheKey(java.security.MessageDigest)}}, although this
+ * requirement is not as strict for partial cache key signatures.
  */
 public interface Key {
+    String STRING_CHARSET_NAME = "UTF-8";
 
     /**
      * Adds all uniquely identifying information to the given digest.
+     *
+     * <p>
+     *     Note - Using {@link java.security.MessageDigest#reset()} inside of this method will result in undefined
+     *     behavior.
+     * </p>
      */
-    public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException;
+    void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException;
 
+    @Override
+    boolean equals(Object o);
+
+    @Override
+    int hashCode();
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/MultiTransformation.java b/library/src/main/java/com/bumptech/glide/load/MultiTransformation.java
index 7f04401..f5bd201 100644
--- a/library/src/main/java/com/bumptech/glide/load/MultiTransformation.java
+++ b/library/src/main/java/com/bumptech/glide/load/MultiTransformation.java
@@ -2,51 +2,43 @@
 
 import com.bumptech.glide.load.engine.Resource;
 
-import java.util.List;
+import java.util.Arrays;
+import java.util.Collection;
 
 /**
- * A transformation that applies an ordered array of one or more transformations to an image.
+ * A transformation that applies one or more transformations in iteration order to a resource.
+ *
+ * @param <T> The type of {@link com.bumptech.glide.load.engine.Resource} that will be transformed.
  */
 public class MultiTransformation<T> implements Transformation<T> {
-    private Transformation<T>[] transformations;
-    private List<Transformation<T>> transformationList;
+    private final Collection<? extends Transformation<T>> transformations;
     private String id;
 
+    @SafeVarargs
     public MultiTransformation(Transformation<T>... transformations) {
         if (transformations.length < 1) {
             throw new IllegalArgumentException("MultiTransformation must contain at least one Transformation");
         }
-        this.transformations = transformations;
+        this.transformations = Arrays.asList(transformations);
     }
 
-    public MultiTransformation(List<Transformation<T>> transformationList) {
+    public MultiTransformation(Collection<? extends Transformation<T>> transformationList) {
         if (transformationList.size() < 1) {
             throw new IllegalArgumentException("MultiTransformation must contain at least one Transformation");
         }
-        this.transformationList = transformationList;
+        this.transformations = transformationList;
     }
 
     @Override
     public Resource<T> transform(Resource<T> resource, int outWidth, int outHeight) {
         Resource<T> previous = resource;
 
-        if (transformations != null) {
-            for (Transformation<T> transformation : transformations) {
-                Resource<T> transformed = transformation.transform(previous, outWidth, outHeight);
-                if (transformed != previous && previous != resource && previous != null) {
-                    previous.recycle();
-                }
-                previous = transformed;
+        for (Transformation<T> transformation : transformations) {
+            Resource<T> transformed = transformation.transform(previous, outWidth, outHeight);
+            if (previous != null && !previous.equals(resource) && !previous.equals(transformed)) {
+                previous.recycle();
             }
-        } else {
-            for (Transformation<T> transformation : transformationList) {
-                 Resource<T> transformed = transformation.transform(previous, outWidth, outHeight);
-                if (transformed != previous && previous != resource && previous != null) {
-                    previous.recycle();
-                }
-                previous = transformed;
-            }
-
+            previous = transformed;
         }
         return previous;
     }
@@ -55,14 +47,8 @@
     public String getId() {
         if (id == null) {
             StringBuilder sb = new StringBuilder();
-            if (transformations != null) {
-                for (Transformation transformation : transformations) {
-                    sb.append(transformation.getId());
-                }
-            } else {
-                for (Transformation transformation : transformationList) {
-                    sb.append(transformation.getId());
-                }
+            for (Transformation<T> transformation : transformations) {
+                sb.append(transformation.getId());
             }
             id = sb.toString();
         }
diff --git a/library/src/main/java/com/bumptech/glide/load/ResourceDecoder.java b/library/src/main/java/com/bumptech/glide/load/ResourceDecoder.java
index 3e21364..3de450f 100644
--- a/library/src/main/java/com/bumptech/glide/load/ResourceDecoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/ResourceDecoder.java
@@ -5,13 +5,41 @@
 import java.io.IOException;
 
 /**
- * An interface for decoding resources
+ * An interface for decoding resources.
+ *
  * @param <T> The type the resource will be decoded from (File, InputStream etc).
- * @param <Z> The type of the decoded resource (Bitmap, Drawable etc:w
+ * @param <Z> The type of the decoded resource (Bitmap, Drawable etc).
  */
 public interface ResourceDecoder<T, Z> {
 
-    public Resource<Z> decode(T source, int width, int height) throws IOException;
+    /**
+     * Returns a decoded resource from the given data or null if no resource could be decoded.
+     * <p>
+     *     The {@code source} is managed by the caller, there's no need to close it.
+     *     The returned {@link Resource} will be {@link Resource#recycle() released} when the engine sees fit.
+     * </p>
+     * <p>
+     *     Note - The {@code width} and {@code height} arguments are hints only,
+     *     there is no requirement that the decoded resource exactly match the given dimensions.
+     *     A typical use case would be to use the target dimensions to determine
+     *     how much to downsample Bitmaps by to avoid overly large allocations.
+     * </p>
+     *
+     * @param source The data the resource should be decoded from.
+     * @param width The ideal width in pixels of the decoded resource.
+     * @param height The ideal height in pixels of the decoded resource.
+     * @throws IOException
+     */
+    Resource<Z> decode(T source, int width, int height) throws IOException;
 
-    public String getId();
+    /**
+     * Returns an ID identifying any transformation this decoder may apply to the given data that will be mixed in to
+     * the cache key.
+     *
+     * <p>
+     *     If the decoder does not transform the data in a way that significantly affects the cached
+     *     result (ie performs no downsampling) an empty string is an appropriate id.
+     * </p>
+     */
+    String getId();
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/ResourceEncoder.java b/library/src/main/java/com/bumptech/glide/load/ResourceEncoder.java
index c6b6b75..afa258b 100644
--- a/library/src/main/java/com/bumptech/glide/load/ResourceEncoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/ResourceEncoder.java
@@ -2,6 +2,11 @@
 
 import com.bumptech.glide.load.engine.Resource;
 
-public interface ResourceEncoder<T> extends Encoder<Resource<T>>{
-
+/**
+ * An interface for writing data from a resource to some persistent data store (i.e. a local File cache).
+ *
+ * @param <T> The type of the data contained by the resource.
+ */
+public interface ResourceEncoder<T> extends Encoder<Resource<T>> {
+    // specializing the generic arguments
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/SkipCache.java b/library/src/main/java/com/bumptech/glide/load/SkipCache.java
deleted file mode 100644
index d0945e1..0000000
--- a/library/src/main/java/com/bumptech/glide/load/SkipCache.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.bumptech.glide.load;
-
-import com.bumptech.glide.load.engine.Resource;
-
-import java.io.OutputStream;
-
-public class SkipCache<T> implements ResourceEncoder<T> {
-    private static final SkipCache SKIP_CACHE = new SkipCache();
-
-    @SuppressWarnings("unchecked")
-    public static <T> SkipCache<T> get() {
-        return SKIP_CACHE;
-    }
-
-    @Override
-    public boolean encode(Resource resource, OutputStream os) {
-        return false;
-    }
-
-    @Override
-    public String getId() {
-        return "SkipCache.com.bumptech.glide.load";
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/Transformation.java b/library/src/main/java/com/bumptech/glide/load/Transformation.java
index 0b097f7..ae7f3ff 100644
--- a/library/src/main/java/com/bumptech/glide/load/Transformation.java
+++ b/library/src/main/java/com/bumptech/glide/load/Transformation.java
@@ -3,28 +3,41 @@
 import com.bumptech.glide.load.engine.Resource;
 
 /**
- * A class for performing an arbitrary transformation on a bitmap
+ * A class for performing an arbitrary transformation on a resource.
+ *
  * @param <T> The type of the resource being transformed.
  */
 public interface Transformation<T> {
 
     /**
-     * Transform the given bitmap. It is also acceptable to simply return the given bitmap if no transformation is
-     * required.
+     * Transforms the given resource and returns the transformed resource.
      *
-     * @param resource The resource to transform
-     * @param outWidth The width of the view or target the bitmap will be displayed in
-     * @param outHeight The height of the view or target the bitmap will be displayed in
-     * @return The transformed bitmap
+     * <p>
+     *     Note - If the original resource object is not returned, the original resource will be recycled and it's
+     *     internal resources may be reused. This means it is not safe to rely on the original resource or any internal
+     *     state of the original resource in any new resource that is created. Usually this shouldn't occur, but if
+     *     absolutely necessary either the original resource object can be returned with modified internal state, or
+     *     the data in the original resource can be copied into the transformed resource.
+     * </p>
+     *
+     * @param resource The resource to transform.
+     * @param outWidth The width of the view or target the resource will be displayed in.
+     * @param outHeight The height of the view or target the resource will be displayed in.
+     * @return The transformed resource.
      */
-    public abstract Resource<T> transform(Resource<T> resource, int outWidth, int outHeight);
+    Resource<T> transform(Resource<T> resource, int outWidth, int outHeight);
 
     /**
      * A method to get a unique identifier for this particular transformation that can be used as part of a cache key.
      * The fully qualified class name for this class is appropriate if written out, but getClass().getName() is not
      * because the name may be changed by proguard.
      *
-     * @return A string that uniquely identifies this transformation from other transformations
+     * <p>
+     *     If this transformation does not affect the data that will be stored in cache, returning an empty string here
+     *     is acceptable.
+     * </p>
+     *
+     * @return A string that uniquely identifies this transformation.
      */
-    public String getId();
+    String getId();
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/UnitTransformation.java b/library/src/main/java/com/bumptech/glide/load/UnitTransformation.java
deleted file mode 100644
index 475f4db..0000000
--- a/library/src/main/java/com/bumptech/glide/load/UnitTransformation.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.bumptech.glide.load;
-
-import com.bumptech.glide.load.engine.Resource;
-
-/**
- * A noop Transformation that simply returns the given resource.
- */
-public class UnitTransformation<T> implements Transformation<T> {
-    private static final UnitTransformation TRANSFORMATION = new UnitTransformation();
-
-    @SuppressWarnings("unchecked")
-    public static <T> UnitTransformation<T> get() {
-        return TRANSFORMATION;
-    }
-
-    @Override
-    public Resource<T> transform(Resource<T> resource, int outWidth, int outHeight) {
-        return resource;
-    }
-
-    @Override
-    public String getId() {
-        return "";
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/data/AssetPathFetcher.java b/library/src/main/java/com/bumptech/glide/load/data/AssetPathFetcher.java
new file mode 100644
index 0000000..7c9e622
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/data/AssetPathFetcher.java
@@ -0,0 +1,73 @@
+package com.bumptech.glide.load.data;
+
+import android.content.res.AssetManager;
+import android.util.Log;
+
+import com.bumptech.glide.Priority;
+
+import java.io.IOException;
+
+/**
+ * An abstract class for obtaining data for an asset path using an {@link android.content.res.AssetManager}.
+ *
+ * @param <T> The type of data obtained from the asset path (InputStream, FileDescriptor etc).
+ */
+public abstract class AssetPathFetcher<T> implements DataFetcher<T> {
+    private static final String TAG = "AssetUriFetcher";
+    private final String assetPath;
+    private final AssetManager assetManager;
+    private T data;
+
+    public AssetPathFetcher(AssetManager assetManager, String assetPath) {
+        this.assetManager = assetManager;
+        this.assetPath = assetPath;
+    }
+
+    @Override
+    public T loadData(Priority priority) throws Exception {
+        data = loadResource(assetManager, assetPath);
+        return data;
+    }
+
+    @Override
+    public void cleanup() {
+        if (data == null) {
+            return;
+        }
+        try {
+            close(data);
+        } catch (IOException e) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "Failed to close data", e);
+            }
+        }
+
+    }
+
+    @Override
+    public String getId() {
+        return assetPath;
+    }
+
+    @Override
+    public void cancel() {
+        // Do nothing.
+    }
+
+    /**
+     * Opens the given asset path with the given {@link android.content.res.AssetManager} and returns the conrete data
+     * type returned by the AssetManager.
+     *
+     * @param assetManager An AssetManager to use to open the given path.
+     * @param path A string path pointing to a resource in assets to open.
+     */
+    protected abstract T loadResource(AssetManager assetManager, String path) throws IOException;
+
+    /**
+     * Closes the concrete data type if necessary.
+     *
+     * @param data The data to close.
+     * @throws IOException
+     */
+    protected abstract void close(T data) throws IOException;
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/data/ByteArrayFetcher.java b/library/src/main/java/com/bumptech/glide/load/data/ByteArrayFetcher.java
index 5f8d5cc..ff4269c 100644
--- a/library/src/main/java/com/bumptech/glide/load/data/ByteArrayFetcher.java
+++ b/library/src/main/java/com/bumptech/glide/load/data/ByteArrayFetcher.java
@@ -19,13 +19,13 @@
     }
 
     @Override
-    public InputStream loadData(Priority priority) throws Exception {
+    public InputStream loadData(Priority priority) {
         return new ByteArrayInputStream(bytes);
     }
 
     @Override
     public void cleanup() {
-        // Do nothing.
+        // Do nothing. It's safe to leave a ByteArrayInputStream open.
     }
 
     @Override
diff --git a/library/src/main/java/com/bumptech/glide/load/data/DataFetcher.java b/library/src/main/java/com/bumptech/glide/load/data/DataFetcher.java
index e589535..e192827 100644
--- a/library/src/main/java/com/bumptech/glide/load/data/DataFetcher.java
+++ b/library/src/main/java/com/bumptech/glide/load/data/DataFetcher.java
@@ -1,16 +1,14 @@
 package com.bumptech.glide.load.data;
 
 import com.bumptech.glide.Priority;
-import com.bumptech.glide.load.ResourceDecoder;
-import com.bumptech.glide.load.model.ModelLoader;
 
 /**
- * A base class for lazily and retrieving data that can be used to load a resource. A new instance is created per image
- * load by {@link ModelLoader}. {@link #loadData(Priority)} may or may not be called for any given load depending on
- * whether or not the corresponding image is cached. Cancel also may or may not be called. If
- * {@link #loadData(Priority)} is called, then so {@link #cleanup()} will be called.
+ * An interface for lazily retrieving data that can be used to load a resource. A new instance is created per
+ * resource load by {@link com.bumptech.glide.load.model.ModelLoader}. {@link #loadData(Priority)} may or may not be
+ * called for any given load depending on whether or not the corresponding resource is cached. Cancel also may or may
+ * not be called. If {@link #loadData(Priority)} is called, then so {@link #cleanup()} will be called.
  *
- * @param <T> The type of data to be loaded.
+ * @param <T> The type of data to be loaded (InputStream, byte[], File etc).
  */
 public interface DataFetcher<T> {
 
@@ -20,27 +18,43 @@
      * must be thread safe since this method will be called from a thread in a
      * {@link java.util.concurrent.ExecutorService} that may have more than one background thread.
      *
-     * This method will only be called when the corresponding image is not in the cache.
+     * This method will only be called when the corresponding resource is not in the cache.
+     *
+     * <p>
+     *     Note - this method will be run on a background thread so blocking I/O is safe.
+     * </p>
      *
      * @param priority The priority with which the request should be completed.
+     * @see #cleanup() where the data retuned will be cleaned up
      */
-    public T loadData(Priority priority) throws Exception;
+    T loadData(Priority priority) throws Exception;
 
     /**
      * Cleanup or recycle any resources used by this data fetcher. This method will be called in a finally block
-     * after the data returned by {@link #loadData(Priority)} has been decoded by the {@link ResourceDecoder}.
+     * after the data returned by {@link #loadData(Priority)} has been decoded by the
+     * {@link com.bumptech.glide.load.ResourceDecoder}.
+     *
+     * <p>
+     *     Note - this method will be run on a background thread so blocking I/O is safe.
+     * </p>
+     *
      */
-    public void cleanup();
+    void cleanup();
 
     /**
      * Returns a string uniquely identifying the data that this fetcher will fetch including the specific size.
      *
      * <p>
      *     A hash of the bytes of the data that will be fetched is the ideal id but since that is in many cases
-     *     impractical and not performant, urls, file paths, and uris are normally sufficient.
+     *     impractical, urls, file paths, and uris are normally sufficient.
+     * </p>
+     *
+     * <p>
+     *     Note - this method will be run on the main thread so it should not perform blocking operations and should
+     *     finish quickly.
      * </p>
      */
-    public String getId();
+    String getId();
 
     /**
      * A method that will be called when a load is no longer relevant and has been cancelled. This method does not need
@@ -49,9 +63,14 @@
      *
      * <p>
      *  The best way to use this method is to cancel any loads that have not yet started, but allow those that are in
-     *  process to finish since its we typically will want to display the same image in a different view in
+     *  process to finish since its we typically will want to display the same resource in a different view in
      *  the near future.
      * </p>
+     *
+     * <p>
+     *     Note - this method will be run on the main thread so it should not perform blocking operations and should
+     *     finish quickly.
+     * </p>
      */
-    public void cancel();
+    void cancel();
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/data/FileDescriptorAssetPathFetcher.java b/library/src/main/java/com/bumptech/glide/load/data/FileDescriptorAssetPathFetcher.java
new file mode 100644
index 0000000..16e3213
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/data/FileDescriptorAssetPathFetcher.java
@@ -0,0 +1,25 @@
+package com.bumptech.glide.load.data;
+
+import android.content.res.AssetManager;
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+
+/**
+ * Fetches an {@link android.os.ParcelFileDescriptor} for an asset path.
+ */
+public class FileDescriptorAssetPathFetcher extends AssetPathFetcher<ParcelFileDescriptor> {
+    public FileDescriptorAssetPathFetcher(AssetManager assetManager, String assetPath) {
+        super(assetManager, assetPath);
+    }
+
+    @Override
+    protected ParcelFileDescriptor loadResource(AssetManager assetManager, String path) throws IOException {
+        return assetManager.openFd(path).getParcelFileDescriptor();
+    }
+
+    @Override
+    protected void close(ParcelFileDescriptor data) throws IOException {
+        data.close();
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/data/FileDescriptorLocalUriFetcher.java b/library/src/main/java/com/bumptech/glide/load/data/FileDescriptorLocalUriFetcher.java
index e069890..335b4d7 100644
--- a/library/src/main/java/com/bumptech/glide/load/data/FileDescriptorLocalUriFetcher.java
+++ b/library/src/main/java/com/bumptech/glide/load/data/FileDescriptorLocalUriFetcher.java
@@ -6,7 +6,11 @@
 import android.os.ParcelFileDescriptor;
 
 import java.io.FileNotFoundException;
+import java.io.IOException;
 
+/**
+ * Fetches an {@link android.os.ParcelFileDescriptor} for a local {@link android.net.Uri}.
+ */
 public class FileDescriptorLocalUriFetcher extends LocalUriFetcher<ParcelFileDescriptor> {
     public FileDescriptorLocalUriFetcher(Context context, Uri uri) {
         super(context, uri);
@@ -16,4 +20,9 @@
     protected ParcelFileDescriptor loadResource(Uri uri, ContentResolver contentResolver) throws FileNotFoundException {
         return contentResolver.openAssetFileDescriptor(uri, "r").getParcelFileDescriptor();
     }
+
+    @Override
+    protected void close(ParcelFileDescriptor data) throws IOException {
+        data.close();
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/data/HttpUrlFetcher.java b/library/src/main/java/com/bumptech/glide/load/data/HttpUrlFetcher.java
new file mode 100644
index 0000000..7662eb2
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/data/HttpUrlFetcher.java
@@ -0,0 +1,123 @@
+package com.bumptech.glide.load.data;
+
+import android.text.TextUtils;
+
+import com.bumptech.glide.Priority;
+import com.bumptech.glide.load.model.GlideUrl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+/**
+ * A DataFetcher that retrieves an {@link java.io.InputStream} for a Url.
+ */
+public class HttpUrlFetcher implements DataFetcher<InputStream> {
+    private static final int MAXIMUM_REDIRECTS = 5;
+    private static final HttpUrlConnectionFactory DEFAULT_CONNECTION_FACTORY = new DefaultHttpUrlConnectionFactory();
+
+    private final GlideUrl glideUrl;
+    private final HttpUrlConnectionFactory connectionFactory;
+
+    private HttpURLConnection urlConnection;
+    private InputStream stream;
+    private volatile boolean isCancelled;
+
+    public HttpUrlFetcher(GlideUrl glideUrl) {
+        this(glideUrl, DEFAULT_CONNECTION_FACTORY);
+    }
+
+    // Visible for testing.
+    HttpUrlFetcher(GlideUrl glideUrl, HttpUrlConnectionFactory connectionFactory) {
+        this.glideUrl = glideUrl;
+        this.connectionFactory = connectionFactory;
+    }
+
+    @Override
+    public InputStream loadData(Priority priority) throws Exception {
+        return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/);
+    }
+
+    private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl) throws IOException {
+        if (redirects >= MAXIMUM_REDIRECTS) {
+            throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
+        } else {
+            // Comparing the URLs using .equals performs additional network I/O and is generally broken.
+            // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
+            try {
+                if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
+                    throw new IOException("In re-direct loop");
+                }
+            } catch (URISyntaxException e) {
+                // Do nothing, this is best effort.
+            }
+        }
+        urlConnection = connectionFactory.build(url);
+        urlConnection.setConnectTimeout(2500);
+        urlConnection.setReadTimeout(2500);
+        urlConnection.setUseCaches(false);
+        urlConnection.setDoInput(true);
+
+        // Connect explicitly to avoid errors in decoders if connection fails.
+        urlConnection.connect();
+        if (isCancelled) {
+            return null;
+        }
+        final int statusCode = urlConnection.getResponseCode();
+        if (statusCode / 100 == 2) {
+            stream = urlConnection.getInputStream();
+            return stream;
+        } else if (statusCode / 100 == 3) {
+            String redirectUrlString = urlConnection.getHeaderField("Location");
+            if (TextUtils.isEmpty(redirectUrlString)) {
+                throw new IOException("Received empty or null redirect url");
+            }
+            URL redirectUrl = new URL(url, redirectUrlString);
+            return loadDataWithRedirects(redirectUrl, redirects + 1, url);
+        } else {
+            if (statusCode == -1) {
+                throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
+            }
+            throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
+        }
+    }
+
+    @Override
+    public void cleanup() {
+        if (stream != null) {
+            try {
+                stream.close();
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+        if (urlConnection != null) {
+            urlConnection.disconnect();
+        }
+    }
+
+    @Override
+    public String getId() {
+        return glideUrl.toString();
+    }
+
+    @Override
+    public void cancel() {
+        // TODO: we should consider disconnecting the url connection here, but we can't do so directly because cancel is
+        // often called on the main thread.
+        isCancelled = true;
+    }
+
+    interface HttpUrlConnectionFactory {
+        HttpURLConnection build(URL url) throws IOException;
+    }
+
+    private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory {
+        @Override
+        public HttpURLConnection build(URL url) throws IOException {
+            return (HttpURLConnection) url.openConnection();
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/data/LocalUriFetcher.java b/library/src/main/java/com/bumptech/glide/load/data/LocalUriFetcher.java
index d9cbda0..8c3a999 100644
--- a/library/src/main/java/com/bumptech/glide/load/data/LocalUriFetcher.java
+++ b/library/src/main/java/com/bumptech/glide/load/data/LocalUriFetcher.java
@@ -4,20 +4,23 @@
 import android.content.Context;
 import android.net.Uri;
 import android.util.Log;
+
 import com.bumptech.glide.Priority;
 
-import java.io.Closeable;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.lang.ref.WeakReference;
 
 /**
+ * A DataFetcher that uses an {@link android.content.ContentResolver} to load data from a {@link android.net.Uri}
+ * pointing to a local resource.
  *
+ * @param <T> The type of data that will obtained for the given uri (For example, {@link java.io.InputStream} or
+ * {@link android.os.ParcelFileDescriptor}.
  */
-public abstract class LocalUriFetcher<T extends Closeable> implements DataFetcher<T> {
+public abstract class LocalUriFetcher<T> implements DataFetcher<T> {
     private static final String TAG = "LocalUriFetcher";
-    private final WeakReference<Context> contextRef;
     private final Uri uri;
+    private final Context context;
     private T data;
 
     /**
@@ -31,16 +34,12 @@
      *            {@link ContentResolver#openInputStream(android.net.Uri)}
      */
     public LocalUriFetcher(Context context, Uri uri) {
-        contextRef = new WeakReference<Context>(context);
+        this.context = context.getApplicationContext();
         this.uri = uri;
     }
 
     @Override
     public final T loadData(Priority priority) throws Exception {
-        Context context = contextRef.get();
-        if (context == null) {
-            throw new NullPointerException("Context has been cleared in LocalUriFetcher uri: " + uri);
-        }
         ContentResolver contentResolver = context.getContentResolver();
         data = loadResource(uri, contentResolver);
         return data;
@@ -50,7 +49,7 @@
     public void cleanup() {
         if (data != null) {
             try {
-                data.close();
+                close(data);
             } catch (IOException e) {
                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
                     Log.v(TAG, "failed to close data", e);
@@ -70,6 +69,25 @@
         return uri.toString();
     }
 
+
+    /**
+     * Returns a concrete data type from the given {@link android.net.Uri} using the given
+     * {@link android.content.ContentResolver}.
+     *
+     * @throws FileNotFoundException
+     */
     protected abstract T loadResource(Uri uri, ContentResolver contentResolver) throws FileNotFoundException;
+
+    /**
+     * Closes the concrete data type if necessary.
+     *
+     * <p>
+     *     Note - We can't rely on the closeable interface because it was added after our min API level. See issue #157.
+     * </p>
+     *
+     * @param data The data to close.
+     * @throws IOException
+     */
+    protected abstract void close(T data) throws IOException;
 }
 
diff --git a/library/src/main/java/com/bumptech/glide/load/data/MediaStoreThumbFetcher.java b/library/src/main/java/com/bumptech/glide/load/data/MediaStoreThumbFetcher.java
index b014c7c..b9b622a 100644
--- a/library/src/main/java/com/bumptech/glide/load/data/MediaStoreThumbFetcher.java
+++ b/library/src/main/java/com/bumptech/glide/load/data/MediaStoreThumbFetcher.java
@@ -6,6 +6,7 @@
 import android.net.Uri;
 import android.provider.MediaStore;
 import android.text.TextUtils;
+
 import com.bumptech.glide.Priority;
 
 import java.io.File;
@@ -13,6 +14,13 @@
 import java.io.IOException;
 import java.io.InputStream;
 
+/**
+ * A DataFetcher that retrieves an {@link java.io.InputStream} for a local Uri that may or may not be for a resource
+ * in the media store. If the local Uri is for a resource in the media store and the size requested is less than or
+ * equal to the media store thumbnail size, preferentially attempts to fetch data for the pre-generated media store
+ * thumbs using {@link android.provider.MediaStore.Images.Thumbnails} and
+ * {@link android.provider.MediaStore.Video.Thumbnails}.
+ */
 public class MediaStoreThumbFetcher implements DataFetcher<InputStream> {
     private static final int MINI_WIDTH = 512;
     private static final int MINI_HEIGHT = 384;
@@ -23,28 +31,21 @@
     private final DataFetcher<InputStream> defaultFetcher;
     private final int width;
     private final int height;
-    private final long dateModified;
-    private final int orientation;
     private final ThumbnailStreamOpenerFactory factory;
     private InputStream inputStream;
-    private String mimeType;
 
     public MediaStoreThumbFetcher(Context context, Uri mediaStoreUri, DataFetcher<InputStream> defaultFetcher,
-            int width, int height, String mimeType, long dateModified, int orientation) {
-        this(context, mediaStoreUri, defaultFetcher, width, height, mimeType, dateModified, orientation,
-                DEFAULT_FACTORY);
+            int width, int height) {
+        this(context, mediaStoreUri, defaultFetcher, width, height, DEFAULT_FACTORY);
     }
 
     MediaStoreThumbFetcher(Context context, Uri mediaStoreUri, DataFetcher<InputStream> defaultFetcher, int width,
-            int height, String mimeType, long dateModified, int orientation, ThumbnailStreamOpenerFactory factory) {
+            int height, ThumbnailStreamOpenerFactory factory) {
         this.context = context;
         this.mediaStoreUri = mediaStoreUri;
         this.defaultFetcher = defaultFetcher;
         this.width = width;
         this.height = height;
-        this.mimeType = mimeType;
-        this.dateModified = dateModified;
-        this.orientation = orientation;
         this.factory = factory;
     }
 
@@ -77,7 +78,7 @@
 
     @Override
     public String getId() {
-        return mediaStoreUri + mimeType + String.valueOf(dateModified) + String.valueOf(orientation);
+        return mediaStoreUri.toString();
     }
 
     @Override
@@ -110,7 +111,7 @@
     }
 
     interface ThumbnailQuery {
-        public Cursor query(Context context, Uri uri);
+        Cursor query(Context context, Uri uri);
     }
 
     static class ThumbnailStreamOpener {
diff --git a/library/src/main/java/com/bumptech/glide/load/data/StreamAssetPathFetcher.java b/library/src/main/java/com/bumptech/glide/load/data/StreamAssetPathFetcher.java
new file mode 100644
index 0000000..4d1a540
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/data/StreamAssetPathFetcher.java
@@ -0,0 +1,25 @@
+package com.bumptech.glide.load.data;
+
+import android.content.res.AssetManager;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Fetches an {@link java.io.InputStream} for an asset path.
+ */
+public class StreamAssetPathFetcher extends AssetPathFetcher<InputStream> {
+    public StreamAssetPathFetcher(AssetManager assetManager, String assetPath) {
+        super(assetManager, assetPath);
+    }
+
+    @Override
+    protected InputStream loadResource(AssetManager assetManager, String path) throws IOException {
+        return assetManager.open(path);
+    }
+
+    @Override
+    protected void close(InputStream data) throws IOException {
+        data.close();
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/data/StreamLocalUriFetcher.java b/library/src/main/java/com/bumptech/glide/load/data/StreamLocalUriFetcher.java
index 2a1e275..d933de1 100644
--- a/library/src/main/java/com/bumptech/glide/load/data/StreamLocalUriFetcher.java
+++ b/library/src/main/java/com/bumptech/glide/load/data/StreamLocalUriFetcher.java
@@ -5,8 +5,12 @@
 import android.net.Uri;
 
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 
+/**
+ * Fetches an {@link java.io.InputStream} for a local {@link android.net.Uri}.
+ */
 public class StreamLocalUriFetcher extends LocalUriFetcher<InputStream> {
     public StreamLocalUriFetcher(Context context, Uri uri) {
         super(context, uri);
@@ -16,4 +20,9 @@
     protected InputStream loadResource(Uri uri, ContentResolver contentResolver) throws FileNotFoundException {
         return contentResolver.openInputStream(uri);
     }
+
+    @Override
+    protected void close(InputStream data) throws IOException {
+        data.close();
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/CacheLoader.java b/library/src/main/java/com/bumptech/glide/load/engine/CacheLoader.java
new file mode 100644
index 0000000..eb4304f
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/engine/CacheLoader.java
@@ -0,0 +1,42 @@
+package com.bumptech.glide.load.engine;
+
+import android.util.Log;
+
+import com.bumptech.glide.load.Key;
+import com.bumptech.glide.load.ResourceDecoder;
+import com.bumptech.glide.load.engine.cache.DiskCache;
+
+import java.io.File;
+import java.io.IOException;
+
+class CacheLoader {
+    private static final String TAG = "CacheLoader";
+    private final DiskCache diskCache;
+
+    public CacheLoader(DiskCache diskCache) {
+        this.diskCache = diskCache;
+    }
+
+    public <Z> Resource<Z> load(Key key, ResourceDecoder<File, Z> decoder, int width, int height) {
+        File fromCache = diskCache.get(key);
+        if (fromCache == null) {
+            return null;
+        }
+
+        Resource<Z> result = null;
+        try {
+            result = decoder.decode(fromCache, width, height);
+        } catch (IOException e) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Exception decoding image from cache", e);
+            }
+        }
+        if (result == null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Failed to decode image from cache or not present in cache");
+            }
+            diskCache.delete(key);
+        }
+        return result;
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/DecodeJob.java b/library/src/main/java/com/bumptech/glide/load/engine/DecodeJob.java
new file mode 100644
index 0000000..f3d9c00
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/engine/DecodeJob.java
@@ -0,0 +1,292 @@
+package com.bumptech.glide.load.engine;
+
+import android.util.Log;
+
+import com.bumptech.glide.Priority;
+import com.bumptech.glide.load.Encoder;
+import com.bumptech.glide.load.Key;
+import com.bumptech.glide.load.Transformation;
+import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.engine.cache.DiskCache;
+import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
+import com.bumptech.glide.provider.DataLoadProvider;
+import com.bumptech.glide.util.LogTime;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * A class responsible for decoding resources either from cached data or from the original source and applying
+ * transformations and transcodes.
+ *
+ * @param <A> The type of the source data the resource can be decoded from.
+ * @param <T> The type of resource that will be decoded.
+ * @param <Z> The type of resource that will be transcoded from the decoded and transformed resource.
+ */
+class DecodeJob<A, T, Z> {
+    private static final String TAG = "DecodeJob";
+    private static final FileOpener DEFAULT_FILE_OPENER = new FileOpener();
+
+    private final EngineKey resultKey;
+    private final int width;
+    private final int height;
+    private final DataFetcher<A> fetcher;
+    private final DataLoadProvider<A, T> loadProvider;
+    private final Transformation<T> transformation;
+    private final ResourceTranscoder<T, Z> transcoder;
+    private final DiskCacheStrategy diskCacheStrategy;
+    private final DiskCache diskCache;
+    private final Priority priority;
+    private final FileOpener fileOpener;
+
+    private volatile boolean isCancelled;
+
+    public DecodeJob(EngineKey resultKey, int width, int height, DataFetcher<A> fetcher,
+            DataLoadProvider<A, T> loadProvider, Transformation<T> transformation, ResourceTranscoder<T, Z> transcoder,
+            DiskCache diskCache, DiskCacheStrategy diskCacheStrategy, Priority priority) {
+        this(resultKey, width, height, fetcher, loadProvider, transformation, transcoder, diskCache, diskCacheStrategy,
+                priority, DEFAULT_FILE_OPENER);
+    }
+
+    // Visible for testing.
+    DecodeJob(EngineKey resultKey, int width, int height, DataFetcher<A> fetcher,
+            DataLoadProvider<A, T> loadProvider, Transformation<T> transformation, ResourceTranscoder<T, Z> transcoder,
+            DiskCache diskCache, DiskCacheStrategy diskCacheStrategy, Priority priority, FileOpener fileOpener) {
+        this.resultKey = resultKey;
+        this.width = width;
+        this.height = height;
+        this.fetcher = fetcher;
+        this.loadProvider = loadProvider;
+        this.transformation = transformation;
+        this.transcoder = transcoder;
+        this.diskCacheStrategy = diskCacheStrategy;
+        this.diskCache = diskCache;
+        this.priority = priority;
+        this.fileOpener = fileOpener;
+    }
+
+    /**
+     * Returns a transcoded resource decoded from transformed resource data in the disk cache, or null if no such
+     * resource exists.
+     *
+     * @throws Exception
+     */
+    public Resource<Z> decodeResultFromCache() throws Exception {
+        if (!diskCacheStrategy.cacheResult()) {
+            return null;
+        }
+
+        long startTime = LogTime.getLogTime();
+        Resource<T> transformed = loadFromCache(resultKey);
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            logWithTimeAndKey("Decoded transformed from cache", startTime);
+        }
+        startTime = LogTime.getLogTime();
+        Resource<Z> result = transcode(transformed);
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            logWithTimeAndKey("Transcoded transformed from cache", startTime);
+        }
+        return result;
+    }
+
+    /**
+     * Returns a transformed and transcoded resource decoded from source data in the disk cache, or null if no such
+     * resource exists.
+     *
+     * @throws Exception
+     */
+    public Resource<Z> decodeSourceFromCache() throws Exception {
+        if (!diskCacheStrategy.cacheSource()) {
+            return null;
+        }
+
+        long startTime = LogTime.getLogTime();
+        Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            logWithTimeAndKey("Decoded source from cache", startTime);
+        }
+        return transformEncodeAndTranscode(decoded);
+    }
+
+    /**
+     * Returns a transformed and transcoded resource decoded from source data, or null if no source data could be
+     * obtained or no resource could be decoded.
+     *
+     * <p>
+     *     Depending on the {@link com.bumptech.glide.load.engine.DiskCacheStrategy} used, source data is either decoded
+     *     directly or first written to the disk cache and then decoded from the disk cache.
+     * </p>
+     *
+     * @throws Exception
+     */
+    public Resource<Z> decodeFromSource() throws Exception {
+        Resource<T> decoded = decodeSource();
+        return transformEncodeAndTranscode(decoded);
+    }
+
+    public void cancel() {
+        fetcher.cancel();
+        isCancelled = true;
+    }
+
+    private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
+        long startTime = LogTime.getLogTime();
+        Resource<T> transformed = transform(decoded);
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            logWithTimeAndKey("Transformed resource from source", startTime);
+        }
+
+        writeTransformedToCache(transformed);
+
+        startTime = LogTime.getLogTime();
+        Resource<Z> result = transcode(transformed);
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            logWithTimeAndKey("Transcoded transformed from source", startTime);
+        }
+        return result;
+    }
+
+    private void writeTransformedToCache(Resource<T> transformed) {
+        if (transformed == null || !diskCacheStrategy.cacheResult()) {
+            return;
+        }
+        long startTime = LogTime.getLogTime();
+        SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed);
+        diskCache.put(resultKey, writer);
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            logWithTimeAndKey("Wrote transformed from source to cache", startTime);
+        }
+    }
+
+    private Resource<T> decodeSource() throws Exception {
+        Resource<T> decoded = null;
+        try {
+            long startTime = LogTime.getLogTime();
+            final A data = fetcher.loadData(priority);
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                logWithTimeAndKey("Fetched data", startTime);
+            }
+            if (isCancelled) {
+                return null;
+            }
+            decoded = decodeFromSourceData(data);
+        } finally {
+            fetcher.cleanup();
+        }
+        return decoded;
+    }
+
+    private Resource<T> decodeFromSourceData(A data) throws IOException {
+        final Resource<T> decoded;
+        if (diskCacheStrategy.cacheSource()) {
+            decoded = cacheAndDecodeSourceData(data);
+        } else {
+            long startTime = LogTime.getLogTime();
+            decoded = loadProvider.getSourceDecoder().decode(data, width, height);
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                logWithTimeAndKey("Decoded from source", startTime);
+            }
+        }
+        return decoded;
+    }
+
+    private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
+        long startTime = LogTime.getLogTime();
+        SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);
+        diskCache.put(resultKey.getOriginalKey(), writer);
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            logWithTimeAndKey("Wrote source to cache", startTime);
+        }
+
+        startTime = LogTime.getLogTime();
+        Resource<T> result = loadFromCache(resultKey.getOriginalKey());
+        if (Log.isLoggable(TAG, Log.VERBOSE) && result != null) {
+            logWithTimeAndKey("Decoded source from cache", startTime);
+        }
+        return result;
+    }
+
+    private Resource<T> loadFromCache(Key key) throws IOException {
+        File cacheFile = diskCache.get(key);
+        if (cacheFile == null) {
+            return null;
+        }
+
+        Resource<T> result = null;
+        try {
+            result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
+        } finally {
+            if (result == null) {
+                diskCache.delete(key);
+            }
+        }
+        return result;
+    }
+
+    private Resource<T> transform(Resource<T> decoded) {
+        if (decoded == null) {
+            return null;
+        }
+
+        Resource<T> transformed = transformation.transform(decoded, width, height);
+        if (!decoded.equals(transformed)) {
+            decoded.recycle();
+        }
+        return transformed;
+    }
+
+    private Resource<Z> transcode(Resource<T> transformed) {
+        if (transformed == null) {
+            return null;
+        }
+        return transcoder.transcode(transformed);
+    }
+
+    private void logWithTimeAndKey(String message, long startTime) {
+        Log.v(TAG, message + " in " + LogTime.getElapsedMillis(startTime) + resultKey);
+    }
+
+    class SourceWriter<DataType> implements DiskCache.Writer {
+
+        private final Encoder<DataType> encoder;
+        private final DataType data;
+
+        public SourceWriter(Encoder<DataType> encoder, DataType data) {
+            this.encoder = encoder;
+            this.data = data;
+        }
+
+        @Override
+        public boolean write(File file) {
+            boolean success = false;
+            OutputStream os = null;
+            try {
+                os = fileOpener.open(file);
+                success = encoder.encode(data, os);
+            } catch (FileNotFoundException e) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Failed to find file to write to disk cache", e);
+                }
+            } finally {
+                if (os != null) {
+                    try {
+                        os.close();
+                    } catch (IOException e) {
+                        // Do nothing.
+                    }
+                }
+            }
+            return success;
+        }
+    }
+
+    static class FileOpener {
+        public OutputStream open(File file) throws FileNotFoundException {
+            return new BufferedOutputStream(new FileOutputStream(file));
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/DefaultResourceRunnerFactory.java b/library/src/main/java/com/bumptech/glide/load/engine/DefaultResourceRunnerFactory.java
deleted file mode 100644
index 1b2141a..0000000
--- a/library/src/main/java/com/bumptech/glide/load/engine/DefaultResourceRunnerFactory.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.bumptech.glide.load.engine;
-
-import android.os.Handler;
-import com.bumptech.glide.load.CacheLoader;
-import com.bumptech.glide.Priority;
-import com.bumptech.glide.load.Encoder;
-import com.bumptech.glide.load.ResourceDecoder;
-import com.bumptech.glide.load.ResourceEncoder;
-import com.bumptech.glide.load.Transformation;
-import com.bumptech.glide.load.data.DataFetcher;
-import com.bumptech.glide.load.engine.cache.DiskCache;
-import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
-
-import java.io.InputStream;
-import java.util.concurrent.ExecutorService;
-
-class DefaultResourceRunnerFactory implements ResourceRunnerFactory {
-    private final CacheLoader cacheLoader;
-    private DiskCache diskCache;
-    private Handler mainHandler;
-    private ExecutorService diskCacheService;
-    private ExecutorService service;
-
-    public DefaultResourceRunnerFactory(DiskCache diskCache, Handler mainHandler,
-            ExecutorService diskCacheService, ExecutorService resizeService) {
-        this.diskCache = diskCache;
-        this.mainHandler = mainHandler;
-        this.diskCacheService = diskCacheService;
-        this.service = resizeService;
-        cacheLoader = new CacheLoader(diskCache);
-    }
-
-    @Override
-    public <T, Z, R> ResourceRunner<Z, R> build(EngineKey key, int width, int height,
-            ResourceDecoder<InputStream, Z> cacheDecoder, DataFetcher<T> fetcher, boolean cacheSource,
-            Encoder<T> sourceEncoder, ResourceDecoder<T, Z> decoder, Transformation<Z> transformation,
-            ResourceEncoder<Z> encoder, ResourceTranscoder<Z, R> transcoder, Priority priority,
-            boolean isMemoryCacheable, EngineJobListener listener) {
-
-        EngineJob engineJob = new EngineJob(key, mainHandler, isMemoryCacheable, listener);
-
-        SourceResourceRunner<T, Z, R> sourceRunner = new SourceResourceRunner<T, Z, R>(key, width, height, cacheLoader,
-                cacheDecoder, fetcher, cacheSource, sourceEncoder, decoder, transformation, encoder, transcoder,
-                diskCache, priority, engineJob);
-
-        return new ResourceRunner<Z, R>(key, width, height, cacheLoader, cacheDecoder, transformation,
-                transcoder, sourceRunner, diskCacheService, service, engineJob, priority);
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/DiskCacheStrategy.java b/library/src/main/java/com/bumptech/glide/load/engine/DiskCacheStrategy.java
new file mode 100644
index 0000000..a9c79de
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/engine/DiskCacheStrategy.java
@@ -0,0 +1,37 @@
+package com.bumptech.glide.load.engine;
+
+/**
+ * Set of available caching strategies for media.
+ */
+public enum DiskCacheStrategy {
+    /** Caches with both {@link #SOURCE} and {@link #RESULT}. */
+    ALL(true, true),
+    /** Saves no data to cache. */
+    NONE(false, false),
+    /** Saves just the original data to cache. */
+    SOURCE(true, false),
+    /** Saves the media item after all transformations to cache. */
+    RESULT(false, true);
+
+    private final boolean cacheSource;
+    private final boolean cacheResult;
+
+    DiskCacheStrategy(boolean cacheSource, boolean cacheResult) {
+        this.cacheSource = cacheSource;
+        this.cacheResult = cacheResult;
+    }
+
+    /**
+     * Returns true if this request should cache the original unmodified data.
+     */
+    public boolean cacheSource() {
+        return cacheSource;
+    }
+
+    /**
+     * Returns true if this request should cache the final transformed result.
+     */
+    public boolean cacheResult() {
+        return cacheResult;
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/Engine.java b/library/src/main/java/com/bumptech/glide/load/engine/Engine.java
index a38dee4..1e7a9e7 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/Engine.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/Engine.java
@@ -1,39 +1,44 @@
 package com.bumptech.glide.load.engine;
 
-import android.os.Handler;
 import android.os.Looper;
 import android.os.MessageQueue;
 import android.util.Log;
 
 import com.bumptech.glide.Priority;
-import com.bumptech.glide.load.Encoder;
 import com.bumptech.glide.load.Key;
-import com.bumptech.glide.load.ResourceDecoder;
-import com.bumptech.glide.load.ResourceEncoder;
 import com.bumptech.glide.load.Transformation;
 import com.bumptech.glide.load.data.DataFetcher;
 import com.bumptech.glide.load.engine.cache.DiskCache;
 import com.bumptech.glide.load.engine.cache.MemoryCache;
 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
+import com.bumptech.glide.provider.DataLoadProvider;
 import com.bumptech.glide.request.ResourceCallback;
 import com.bumptech.glide.util.LogTime;
+import com.bumptech.glide.util.Util;
 
-import java.io.InputStream;
 import java.lang.ref.ReferenceQueue;
 import java.lang.ref.WeakReference;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
 
-public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, Resource.ResourceListener {
+/**
+ * Responsible for starting loads and managing active and cached resources.
+ */
+public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {
     private static final String TAG = "Engine";
-    private final Map<Key, ResourceRunner> runners;
-    private final ResourceRunnerFactory factory;
+    private final Map<Key, EngineJob> jobs;
     private final EngineKeyFactory keyFactory;
     private final MemoryCache cache;
-    private final Map<Key, WeakReference<Resource>> activeResources;
-    private final ReferenceQueue<Resource> resourceReferenceQueue;
+    private final DiskCache diskCache;
+    private final EngineJobFactory engineJobFactory;
+    private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
+    private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue;
+    private final ResourceRecycler resourceRecycler;
 
+    /**
+     * Allows a request to indicate it no longer is interested in a given load.
+     */
     public static class LoadStatus {
         private final EngineJob engineJob;
         private final ResourceCallback cb;
@@ -48,18 +53,21 @@
         }
     }
 
-    public Engine(MemoryCache memoryCache, DiskCache diskCache, ExecutorService resizeService,
-            ExecutorService diskCacheService) {
-        this(null, memoryCache, diskCache, resizeService, diskCacheService, null, null, null);
+    public Engine(MemoryCache memoryCache, DiskCache diskCache, ExecutorService diskCacheService,
+            ExecutorService sourceService) {
+        this(memoryCache, diskCache, diskCacheService, sourceService, null, null, null, null, null);
     }
 
-    Engine(ResourceRunnerFactory factory, MemoryCache cache, DiskCache diskCache, ExecutorService resizeService,
-            ExecutorService diskCacheService, Map<Key, ResourceRunner> runners, EngineKeyFactory keyFactory,
-            Map<Key, WeakReference<Resource>> activeResources) {
+    // Visible for testing.
+    Engine(MemoryCache cache, DiskCache diskCache, ExecutorService diskCacheService, ExecutorService sourceService,
+            Map<Key, EngineJob> jobs, EngineKeyFactory keyFactory,
+            Map<Key, WeakReference<EngineResource<?>>> activeResources, EngineJobFactory engineJobFactory,
+            ResourceRecycler resourceRecycler) {
         this.cache = cache;
+        this.diskCache = diskCache;
 
         if (activeResources == null) {
-            activeResources = new HashMap<Key, WeakReference<Resource>>();
+            activeResources = new HashMap<Key, WeakReference<EngineResource<?>>>();
         }
         this.activeResources = activeResources;
 
@@ -68,66 +76,97 @@
         }
         this.keyFactory = keyFactory;
 
-        if (runners == null) {
-            runners = new HashMap<Key, ResourceRunner>();
+        if (jobs == null) {
+            jobs = new HashMap<Key, EngineJob>();
         }
-        this.runners = runners;
+        this.jobs = jobs;
 
-        if (factory == null) {
-            factory = new DefaultResourceRunnerFactory(diskCache, new Handler(Looper.getMainLooper()),
-                    diskCacheService, resizeService);
+        if (engineJobFactory == null) {
+            engineJobFactory = new EngineJobFactory(diskCacheService, sourceService, this);
         }
-        this.factory = factory;
+        this.engineJobFactory = engineJobFactory;
 
-        resourceReferenceQueue = new ReferenceQueue<Resource>();
+        if (resourceRecycler == null) {
+            resourceRecycler = new ResourceRecycler();
+        }
+        this.resourceRecycler = resourceRecycler;
+
+        resourceReferenceQueue = new ReferenceQueue<EngineResource<?>>();
         MessageQueue queue = Looper.myQueue();
         queue.addIdleHandler(new RefQueueIdleHandler(activeResources, resourceReferenceQueue));
         cache.setResourceRemovedListener(this);
     }
 
     /**
-     * @param cacheDecoder
-     * @param fetcher
-     * @param decoder
-     * @param encoder
-     * @param transcoder
-     * @param priority
-     * @param <T>          The type of data the resource will be decoded from.
-     * @param <Z>          The type of the resource that will be decoded.
-     * @param <R>          The type of the resource that will be transcoded from the decoded resource.
+     * Starts a load for the given arguments. Must be called on the main thread.
+     *
+     * <p>
+     *     The flow for any request is as follows:
+     *     <ul>
+     *         <li>Check the memory cache and provide the cached resource if present</li>
+     *         <li>Check the current set of actively used resources and return the active resource if present</li>
+     *         <li>Check the current set of in progress loads and add the cb to the in progress load if present</li>
+     *         <li>Start a new load</li>
+     *     </ul>
+     * </p>
+     *
+     * <p>
+     *     Active resources are those that have been provided to at least one request and have not yet been released.
+     *     Once all consumers of a resource have released that resource, the resource then goes to cache. If the
+     *     resource is ever returned to a new consumer from cache, it is re-added to the active resources. If the
+     *     resource is evicted from the cache, its resources are recycled and re-used if possible and the resource is
+     *     discarded. There is no strict requirement that consumers release their resources so active resources are
+     *     held weakly.
+     * </p>
+     *
+     * @param signature A non-null unique key to be mixed into the cache key that identifies the version of the data to
+     *                  be loaded.
+     * @param width The target width in pixels of the desired resource.
+     * @param height The target height in pixels of the desired resource.
+     * @param fetcher The fetcher to use to retrieve data not in the disk cache.
+     * @param loadProvider The load provider containing various encoders and decoders use to decode and encode data.
+     * @param transformation The transformation to use to transform the decoded resource.
+     * @param transcoder The transcoder to use to transcode the decoded and transformed resource.
+     * @param priority The priority with which the request should run.
+     * @param isMemoryCacheable True if the transcoded resource can be cached in memory.
+     * @param diskCacheStrategy The strategy to use that determines what type of data, if any,
+     *                          will be cached in the local disk cache.
+     * @param cb The callback that will be called when the load completes.
+     *
+     * @param <T> The type of data the resource will be decoded from.
+     * @param <Z> The type of the resource that will be decoded.
+     * @param <R> The type of the resource that will be transcoded from the decoded resource.
      */
-    public <T, Z, R> LoadStatus load(int width, int height, ResourceDecoder<InputStream, Z> cacheDecoder,
-            DataFetcher<T> fetcher, boolean cacheSource, Encoder<T> sourceEncoder,
-            ResourceDecoder<T, Z> decoder, Transformation<Z> transformation, ResourceEncoder<Z> encoder,
-            ResourceTranscoder<Z, R> transcoder, Priority priority, boolean isMemoryCacheable, ResourceCallback cb) {
+    public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
+            DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
+            Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
+        Util.assertMainThread();
         long startTime = LogTime.getLogTime();
 
         final String id = fetcher.getId();
-        EngineKey key = keyFactory.buildKey(id, width, height, cacheDecoder, decoder, transformation, encoder,
-                transcoder, sourceEncoder);
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "loading: " + key);
-        }
+        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
+                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
+                transcoder, loadProvider.getSourceEncoder());
 
-        Resource cached = cache.remove(key);
+        EngineResource<?> cached = getFromCache(key);
         if (cached != null) {
-            cached.acquire(1);
+            cached.acquire();
             activeResources.put(key, new ResourceWeakReference(key, cached, resourceReferenceQueue));
             cb.onResourceReady(cached);
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "loaded resource from cache in " + LogTime.getElapsedMillis(startTime));
+                logWithTimeAndKey("Loaded resource from cache", startTime, key);
             }
             return null;
         }
 
-        WeakReference<Resource> activeRef = activeResources.get(key);
+        WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
         if (activeRef != null) {
-            Resource active = activeRef.get();
+            EngineResource<?> active = activeRef.get();
             if (active != null) {
-                active.acquire(1);
+                active.acquire();
                 cb.onResourceReady(active);
                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "loaded resource from active resources in " + LogTime.getElapsedMillis(startTime));
+                    logWithTimeAndKey("Loaded resource from active resources", startTime, key);
                 }
                 return null;
             } else {
@@ -135,85 +174,108 @@
             }
         }
 
-        ResourceRunner current = runners.get(key);
+        EngineJob current = jobs.get(key);
         if (current != null) {
-            EngineJob job = current.getJob();
-            job.addCallback(cb);
+            current.addCallback(cb);
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "added to existing load in " + LogTime.getElapsedMillis(startTime));
+                logWithTimeAndKey("Added to existing load", startTime, key);
             }
-            return new LoadStatus(cb, job);
+            return new LoadStatus(cb, current);
         }
 
-        long start = LogTime.getLogTime();
-        ResourceRunner<Z, R> runner = factory.build(key, width, height, cacheDecoder, fetcher, cacheSource,
-                sourceEncoder, decoder, transformation, encoder, transcoder, priority, isMemoryCacheable, this);
-        runner.getJob().addCallback(cb);
-        runners.put(key, runner);
-        runner.queue();
+        EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
+        DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
+                transcoder, diskCache, diskCacheStrategy, priority);
+        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
+        jobs.put(key, engineJob);
+        engineJob.addCallback(cb);
+        engineJob.start(runnable);
+
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "queued new load in " + LogTime.getElapsedMillis(start));
-            Log.v(TAG, "finished load in engine in " + LogTime.getElapsedMillis(startTime));
+            logWithTimeAndKey("Started new load", startTime, key);
         }
-        return new LoadStatus(cb, runner.getJob());
+        return new LoadStatus(cb, engineJob);
     }
 
+    private static void logWithTimeAndKey(String log, long startTime, Key key) {
+        Log.v(TAG, log + " in " + LogTime.getElapsedMillis(startTime) + "ms, key: " + key);
+    }
+
+    @SuppressWarnings("unchecked")
+    private EngineResource<?> getFromCache(Key key) {
+        Resource<?> cached = cache.remove(key);
+
+        final EngineResource result;
+        if (cached == null) {
+            result = null;
+        } else if (cached instanceof EngineResource) {
+            // Save an object allocation if we've cached an EngineResource (the typical case).
+            result = (EngineResource) cached;
+        } else {
+            result = new EngineResource(cached, true /*isCacheable*/);
+        }
+        return result;
+    }
+
+    public void release(Resource resource) {
+        if (resource instanceof EngineResource) {
+            ((EngineResource) resource).release();
+        } else {
+            throw new IllegalArgumentException("Cannot release anything but an EngineResource");
+        }
+    }
+
+    @SuppressWarnings("unchecked")
     @Override
-    public void onEngineJobComplete(Key key, Resource resource) {
+    public void onEngineJobComplete(Key key, EngineResource<?> resource) {
         // A null resource indicates that the load failed, usually due to an exception.
         if (resource != null) {
             resource.setResourceListener(key, this);
             activeResources.put(key, new ResourceWeakReference(key, resource, resourceReferenceQueue));
         }
-        runners.remove(key);
+        // TODO: should this check that the engine job is still current?
+        jobs.remove(key);
     }
 
     @Override
-    public void onEngineJobCancelled(Key key) {
-        ResourceRunner runner = runners.remove(key);
-        runner.cancel();
-    }
-
-    @Override
-    public void onResourceRemoved(Resource resource) {
-        resource.recycle();
-    }
-
-    @Override
-    public void onResourceReleased(Key cacheKey, Resource resource) {
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "released: " + cacheKey);
+    public void onEngineJobCancelled(EngineJob engineJob, Key key) {
+        EngineJob current = jobs.get(key);
+        if (engineJob.equals(current)) {
+            jobs.remove(key);
         }
+    }
+
+    @Override
+    public void onResourceRemoved(final Resource<?> resource) {
+        resourceRecycler.recycle(resource);
+    }
+
+    @Override
+    public void onResourceReleased(Key cacheKey, EngineResource resource) {
         activeResources.remove(cacheKey);
         if (resource.isCacheable()) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "recaching: " + cacheKey);
-            }
             cache.put(cacheKey, resource);
         } else {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "recycling: " + cacheKey);
-            }
-            resource.recycle();
+            resourceRecycler.recycle(resource);
         }
     }
 
-    private static class ResourceWeakReference extends WeakReference<Resource> {
-        public final Object resource;
-        public final Key key;
+    private static class ResourceWeakReference extends WeakReference<EngineResource<?>> {
+        private final Key key;
 
-        public ResourceWeakReference(Key key, Resource r, ReferenceQueue<? super Resource> q) {
+        public ResourceWeakReference(Key key, EngineResource<?> r, ReferenceQueue<? super EngineResource<?>> q) {
             super(r, q);
             this.key = key;
-            resource = r.get();
         }
     }
 
+    // Responsible for cleaning up the active resource map by remove weak references that have been cleared.
     private static class RefQueueIdleHandler implements MessageQueue.IdleHandler {
-        private Map<Key, WeakReference<Resource>> activeResources;
-        private ReferenceQueue<Resource> queue;
+        private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
+        private final ReferenceQueue<EngineResource<?>> queue;
 
-        public RefQueueIdleHandler(Map<Key, WeakReference<Resource>> activeResources, ReferenceQueue<Resource> queue) {
+        public RefQueueIdleHandler(Map<Key, WeakReference<EngineResource<?>>> activeResources,
+                ReferenceQueue<EngineResource<?>> queue) {
             this.activeResources = activeResources;
             this.queue = queue;
         }
@@ -223,12 +285,27 @@
             ResourceWeakReference ref = (ResourceWeakReference) queue.poll();
             if (ref != null) {
                 activeResources.remove(ref.key);
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "Maybe leaked a resource: " + ref.resource);
-                }
             }
 
             return true;
         }
     }
+
+    // Visible for testing.
+    static class EngineJobFactory {
+        private final ExecutorService diskCacheService;
+        private final ExecutorService sourceService;
+        private final EngineJobListener listener;
+
+        public EngineJobFactory(ExecutorService diskCacheService, ExecutorService sourceService,
+                EngineJobListener listener) {
+            this.diskCacheService = diskCacheService;
+            this.sourceService = sourceService;
+            this.listener = listener;
+        }
+
+        public EngineJob build(Key key, boolean isMemoryCacheable) {
+            return new EngineJob(key, diskCacheService, sourceService, isMemoryCacheable, listener);
+        }
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/EngineJob.java b/library/src/main/java/com/bumptech/glide/load/engine/EngineJob.java
index ddf7996..0692631 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/EngineJob.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/EngineJob.java
@@ -1,63 +1,128 @@
 package com.bumptech.glide.load.engine;
 
 import android.os.Handler;
-import android.util.Log;
+import android.os.Message;
+
 import com.bumptech.glide.load.Key;
 import com.bumptech.glide.request.ResourceCallback;
-import com.bumptech.glide.util.LogTime;
+import com.bumptech.glide.util.Util;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 
-public class EngineJob implements ResourceCallback {
-    private static final String TAG = "EngineJob";
-    private boolean isCacheable;
+/**
+ * A class that manages a load by adding and removing callbacks for for the load and notifying callbacks when the
+ * load completes.
+ */
+class EngineJob implements EngineRunnable.EngineRunnableManager {
+    private static final EngineResourceFactory DEFAULT_FACTORY = new EngineResourceFactory();
+    private static final Handler MAIN_THREAD_HANDLER = new Handler(new MainThreadCallback());
+
+    private static final int MSG_COMPLETE = 1;
+    private static final int MSG_EXCEPTION = 2;
+
+    private final List<ResourceCallback> cbs = new ArrayList<ResourceCallback>();
+    private final EngineResourceFactory engineResourceFactory;
     private final EngineJobListener listener;
-    private Key key;
-    private Handler mainHandler;
-    private List<ResourceCallback> cbs;
-    private ResourceCallback cb;
-    private boolean isCancelled;
-    private boolean isComplete;
+    private final Key key;
+    private final ExecutorService diskCacheService;
+    private final ExecutorService sourceService;
+    private final boolean isCacheable;
 
-    public EngineJob(Key key, Handler mainHandler, boolean isCacheable, EngineJobListener listener) {
+    private boolean isCancelled;
+    // Either resource or exception (particularly exception) may be returned to us null, so use booleans to track if
+    // we've received them instead of relying on them to be non-null. See issue #180.
+    private Resource<?> resource;
+    private boolean hasResource;
+    private Exception exception;
+    private boolean hasException;
+    // A set of callbacks that are removed while we're notifying other callbacks of a change in status.
+    private Set<ResourceCallback> ignoredCallbacks;
+    private EngineRunnable engineRunnable;
+    private EngineResource<?> engineResource;
+
+    private volatile Future<?> future;
+
+    public EngineJob(Key key, ExecutorService diskCacheService, ExecutorService sourceService, boolean isCacheable,
+            EngineJobListener listener) {
+        this(key, diskCacheService, sourceService, isCacheable, listener, DEFAULT_FACTORY);
+    }
+
+    public EngineJob(Key key, ExecutorService diskCacheService, ExecutorService sourceService, boolean isCacheable,
+            EngineJobListener listener, EngineResourceFactory engineResourceFactory) {
         this.key = key;
+        this.diskCacheService = diskCacheService;
+        this.sourceService = sourceService;
         this.isCacheable = isCacheable;
         this.listener = listener;
-        this.mainHandler = mainHandler;
+        this.engineResourceFactory = engineResourceFactory;
+    }
+
+    public void start(EngineRunnable engineRunnable) {
+        this.engineRunnable = engineRunnable;
+        future = diskCacheService.submit(engineRunnable);
+    }
+
+    @Override
+    public void submitForSource(EngineRunnable runnable) {
+        future = sourceService.submit(runnable);
     }
 
     public void addCallback(ResourceCallback cb) {
-        if (this.cb == null) {
-            this.cb = cb;
+        Util.assertMainThread();
+        if (hasResource) {
+            cb.onResourceReady(engineResource);
+        } else if (hasException) {
+            cb.onException(exception);
         } else {
-            if (cbs == null) {
-                cbs = new ArrayList<ResourceCallback>(2);
-                cbs.add(this.cb);
-            }
             cbs.add(cb);
         }
     }
 
     public void removeCallback(ResourceCallback cb) {
-        if (cbs != null) {
+        Util.assertMainThread();
+        if (hasResource || hasException) {
+            addIgnoredCallback(cb);
+        } else {
             cbs.remove(cb);
-            if (cbs.size() == 0) {
+            if (cbs.isEmpty()) {
                 cancel();
             }
-        } else if (this.cb == cb) {
-            this.cb = null;
-            cancel();
         }
     }
 
+    // We cannot remove callbacks while notifying our list of callbacks directly because doing so would cause a
+    // ConcurrentModificationException. However, we need to obey the cancellation request such that if notifying a
+    // callback early in the callbacks list cancels a callback later in the request list, the cancellation for the later
+    // request is still obeyed. Using a set of ignored callbacks allows us to avoid the exception while still meeting
+    // the requirement.
+    private void addIgnoredCallback(ResourceCallback cb) {
+        if (ignoredCallbacks == null) {
+            ignoredCallbacks = new HashSet<ResourceCallback>();
+        }
+        ignoredCallbacks.add(cb);
+    }
+
+    private boolean isInIgnoredCallbacks(ResourceCallback cb) {
+        return ignoredCallbacks != null && ignoredCallbacks.contains(cb);
+    }
+
     // Exposed for testing.
     void cancel() {
-        if (isComplete || isCancelled) {
+        if (hasException || hasResource || isCancelled) {
             return;
         }
+        engineRunnable.cancel();
+        Future currentFuture = future;
+        if (currentFuture != null) {
+            currentFuture.cancel(true);
+        }
         isCancelled = true;
-        listener.onEngineJobCancelled(key);
+        listener.onEngineJobCancelled(this, key);
     }
 
     // Exposed for testing.
@@ -66,71 +131,81 @@
     }
 
     @Override
-    public void onResourceReady(final Resource resource) {
-        final long start = LogTime.getLogTime();
-        mainHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "Posted to main thread in onResourceReady in " + LogTime.getElapsedMillis(start)
-                            + " cancelled: " + isCancelled);
-                }
-                if (isCancelled) {
-                    resource.recycle();
-                    return;
-                }
-                resource.setCacheable(isCacheable);
-                isComplete = true;
+    public void onResourceReady(final Resource<?> resource) {
+        this.resource = resource;
+        MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
+    }
 
-                // Hold on to resource for duration of request so we don't recycle it in the middle of notifying if it
-                // synchronously released by one of the callbacks.
-                resource.acquire(1);
-                listener.onEngineJobComplete(key, resource);
-                if (cbs != null) {
-                    resource.acquire(cbs.size());
-                    for (ResourceCallback cb : cbs) {
-                        cb.onResourceReady(resource);
-                    }
-                } else {
-                    resource.acquire(1);
-                    cb.onResourceReady(resource);
-                }
-                // Our request is complete, so we can release the resource.
-                resource.release();
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "Finished resource ready in " + LogTime.getElapsedMillis(start));
-                }
+    private void handleResultOnMainThread() {
+        if (isCancelled) {
+            resource.recycle();
+            return;
+        } else if (cbs.isEmpty()) {
+            throw new IllegalStateException("Received a resource without any callbacks to notify");
+        }
+        engineResource = engineResourceFactory.build(resource, isCacheable);
+        hasResource = true;
+
+        // Hold on to resource for duration of request so we don't recycle it in the middle of notifying if it
+        // synchronously released by one of the callbacks.
+        engineResource.acquire();
+        listener.onEngineJobComplete(key, engineResource);
+
+        for (ResourceCallback cb : cbs) {
+            if (!isInIgnoredCallbacks(cb)) {
+                engineResource.acquire();
+                cb.onResourceReady(engineResource);
             }
-        });
+        }
+        // Our request is complete, so we can release the resource.
+        engineResource.release();
     }
 
     @Override
     public void onException(final Exception e) {
-        final long start = LogTime.getLogTime();
-        mainHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "posted to main thread in onException in " + LogTime.getElapsedMillis(start)
-                            + " cancelled: " + isCancelled);
-                }
-                if (isCancelled) {
-                    return;
-                }
-                isComplete = true;
+        this.exception = e;
+        MAIN_THREAD_HANDLER.obtainMessage(MSG_EXCEPTION, this).sendToTarget();
+    }
 
-                listener.onEngineJobComplete(key, null);
-                if (cbs != null) {
-                    for (ResourceCallback cb : cbs) {
-                        cb.onException(e);
-                    }
-                } else {
-                    cb.onException(e);
-                }
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "finished onException in " + LogTime.getElapsedMillis(start));
-                }
+    private void handleExceptionOnMainThread() {
+        if (isCancelled) {
+            return;
+        } else if (cbs.isEmpty()) {
+            throw new IllegalStateException("Received an exception without any callbacks to notify");
+        }
+        hasException = true;
+
+        listener.onEngineJobComplete(key, null);
+
+        for (ResourceCallback cb : cbs) {
+            if (!isInIgnoredCallbacks(cb)) {
+                cb.onException(exception);
             }
-        });
+        }
+    }
+
+    // Visible for testing.
+    static class EngineResourceFactory {
+        public <R> EngineResource<R> build(Resource<R> resource, boolean isMemoryCacheable) {
+            return new EngineResource<R>(resource, isMemoryCacheable);
+        }
+    }
+
+    private static class MainThreadCallback implements Handler.Callback {
+
+        @Override
+        public boolean handleMessage(Message message) {
+            if (MSG_COMPLETE == message.what || MSG_EXCEPTION == message.what) {
+                EngineJob job = (EngineJob) message.obj;
+                if (MSG_COMPLETE == message.what) {
+                    job.handleResultOnMainThread();
+                } else {
+                    job.handleExceptionOnMainThread();
+                }
+                return true;
+            }
+
+            return false;
+        }
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/EngineJobListener.java b/library/src/main/java/com/bumptech/glide/load/engine/EngineJobListener.java
index e0c1641..e143b22 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/EngineJobListener.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/EngineJobListener.java
@@ -2,9 +2,9 @@
 
 import com.bumptech.glide.load.Key;
 
-public interface EngineJobListener {
+interface EngineJobListener {
 
-    public void onEngineJobComplete(Key key, Resource resource);
+    void onEngineJobComplete(Key key, EngineResource<?> resource);
 
-    public void onEngineJobCancelled(Key key);
+    void onEngineJobCancelled(EngineJob engineJob, Key key);
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/EngineKey.java b/library/src/main/java/com/bumptech/glide/load/engine/EngineKey.java
index 19830c8..7cbbe6f 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/EngineKey.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/EngineKey.java
@@ -11,9 +11,8 @@
 import java.nio.ByteBuffer;
 import java.security.MessageDigest;
 
-public class EngineKey implements Key {
-    private static final String FORMAT = "UTF-8";
-
+@SuppressWarnings("rawtypes")
+class EngineKey implements Key {
     private final String id;
     private final int width;
     private final int height;
@@ -21,16 +20,18 @@
     private final ResourceDecoder decoder;
     private final Transformation transformation;
     private final ResourceEncoder encoder;
-    private ResourceTranscoder transcoder;
-    private Encoder sourceEncoder;
+    private final ResourceTranscoder transcoder;
+    private final Encoder sourceEncoder;
+    private final Key signature;
     private String stringKey;
     private int hashCode;
-    private OriginalEngineKey originalKey;
+    private Key originalKey;
 
-    public EngineKey(String id, int width, int height, ResourceDecoder cacheDecoder, ResourceDecoder decoder,
-            Transformation transformation, ResourceEncoder encoder, ResourceTranscoder transcoder,
-            Encoder sourceEncoder) {
+    public EngineKey(String id, Key signature, int width, int height, ResourceDecoder cacheDecoder,
+            ResourceDecoder decoder, Transformation transformation, ResourceEncoder encoder,
+            ResourceTranscoder transcoder, Encoder sourceEncoder) {
         this.id = id;
+        this.signature = signature;
         this.width = width;
         this.height = height;
         this.cacheDecoder = cacheDecoder;
@@ -43,7 +44,7 @@
 
     public Key getOriginalKey() {
         if (originalKey == null) {
-            originalKey = new OriginalEngineKey(id);
+            originalKey = new OriginalKey(id, signature);
         }
         return originalKey;
     }
@@ -61,39 +62,53 @@
 
         if (!id.equals(engineKey.id)) {
             return false;
+        } else if (!signature.equals(engineKey.signature)) {
+            return false;
         } else if (height != engineKey.height) {
             return false;
         } else if (width != engineKey.width) {
             return false;
-        } else  if (!transformation.getId().equals(engineKey.transformation.getId())) {
+        } else if (transformation == null ^ engineKey.transformation == null) {
             return false;
-        } else if (!decoder.getId().equals(engineKey.decoder.getId())) {
+        } else if (transformation != null && !transformation.getId().equals(engineKey.transformation.getId())) {
             return false;
-        } else if (!cacheDecoder.getId().equals(engineKey.cacheDecoder.getId())) {
+        } else if (decoder == null ^ engineKey.decoder == null) {
             return false;
-        } else if (!encoder.getId().equals(engineKey.encoder.getId())) {
+        } else if (decoder != null && !decoder.getId().equals(engineKey.decoder.getId())) {
             return false;
-        } else if (!transcoder.getId().equals(engineKey.transcoder.getId())) {
+        } else if (cacheDecoder == null ^ engineKey.cacheDecoder == null) {
             return false;
-        } else if (!sourceEncoder.getId().equals(engineKey.sourceEncoder.getId())) {
+        } else if (cacheDecoder != null && !cacheDecoder.getId().equals(engineKey.cacheDecoder.getId())) {
             return false;
-        } else {
-            return true;
+        } else if (encoder == null ^ engineKey.encoder == null) {
+            return false;
+        } else if (encoder != null && !encoder.getId().equals(engineKey.encoder.getId())) {
+            return false;
+        } else if (transcoder == null ^ engineKey.transcoder == null) {
+            return false;
+        } else if (transcoder != null && !transcoder.getId().equals(engineKey.transcoder.getId())) {
+            return false;
+        } else if (sourceEncoder == null ^ engineKey.sourceEncoder == null) {
+            return false;
+        } else if (sourceEncoder != null && !sourceEncoder.getId().equals(engineKey.sourceEncoder.getId())) {
+            return false;
         }
+        return true;
     }
 
     @Override
     public int hashCode() {
         if (hashCode == 0) {
             hashCode = id.hashCode();
+            hashCode = 31 * hashCode + signature.hashCode();
             hashCode = 31 * hashCode + width;
             hashCode = 31 * hashCode + height;
-            hashCode = 31 * hashCode + cacheDecoder.getId().hashCode();
-            hashCode = 31 * hashCode + decoder.getId().hashCode();
-            hashCode = 31 * hashCode + transformation.getId().hashCode();
-            hashCode = 31 * hashCode + encoder.getId().hashCode();
-            hashCode = 31 * hashCode + transcoder.getId().hashCode();
-            hashCode = 31 * hashCode + sourceEncoder.getId().hashCode();
+            hashCode = 31 * hashCode + (cacheDecoder   != null ? cacheDecoder  .getId().hashCode() : 0);
+            hashCode = 31 * hashCode + (decoder        != null ? decoder       .getId().hashCode() : 0);
+            hashCode = 31 * hashCode + (transformation != null ? transformation.getId().hashCode() : 0);
+            hashCode = 31 * hashCode + (encoder        != null ? encoder       .getId().hashCode() : 0);
+            hashCode = 31 * hashCode + (transcoder     != null ? transcoder    .getId().hashCode() : 0);
+            hashCode = 31 * hashCode + (sourceEncoder  != null ? sourceEncoder .getId().hashCode() : 0);
         }
         return hashCode;
     }
@@ -103,14 +118,15 @@
         if (stringKey == null) {
             stringKey = new StringBuilder()
                 .append(id)
+                .append(signature)
                 .append(width)
                 .append(height)
-                .append(cacheDecoder.getId())
-                .append(decoder.getId())
-                .append(transformation.getId())
-                .append(encoder.getId())
-                .append(transcoder.getId())
-                .append(sourceEncoder)
+                .append(cacheDecoder   != null ? cacheDecoder  .getId() : "")
+                .append(decoder        != null ? decoder       .getId() : "")
+                .append(transformation != null ? transformation.getId() : "")
+                .append(encoder        != null ? encoder       .getId() : "")
+                .append(transcoder     != null ? transcoder    .getId() : "")
+                .append(sourceEncoder  != null ? sourceEncoder .getId() : "")
                 .toString();
         }
         return stringKey;
@@ -122,12 +138,14 @@
                 .putInt(width)
                 .putInt(height)
                 .array();
-        messageDigest.update(id.getBytes(FORMAT));
+        signature.updateDiskCacheKey(messageDigest);
+        messageDigest.update(id.getBytes(STRING_CHARSET_NAME));
         messageDigest.update(dimensions);
-        messageDigest.update(cacheDecoder.getId().getBytes(FORMAT));
-        messageDigest.update(decoder.getId().getBytes(FORMAT));
-        messageDigest.update(transformation.getId().getBytes(FORMAT));
-        messageDigest.update(encoder.getId().getBytes(FORMAT));
-        messageDigest.update(sourceEncoder.getId().getBytes(FORMAT));
+        messageDigest.update((cacheDecoder   != null ? cacheDecoder  .getId() : "").getBytes(STRING_CHARSET_NAME));
+        messageDigest.update((decoder        != null ? decoder       .getId() : "").getBytes(STRING_CHARSET_NAME));
+        messageDigest.update((transformation != null ? transformation.getId() : "").getBytes(STRING_CHARSET_NAME));
+        messageDigest.update((encoder        != null ? encoder       .getId() : "").getBytes(STRING_CHARSET_NAME));
+        // The Transcoder is not included in the disk cache key because its result is not cached.
+        messageDigest.update((sourceEncoder  != null ? sourceEncoder .getId() : "").getBytes(STRING_CHARSET_NAME));
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/EngineKeyFactory.java b/library/src/main/java/com/bumptech/glide/load/engine/EngineKeyFactory.java
index c2b8627..029266a 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/EngineKeyFactory.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/EngineKeyFactory.java
@@ -1,18 +1,20 @@
 package com.bumptech.glide.load.engine;
 
 import com.bumptech.glide.load.Encoder;
+import com.bumptech.glide.load.Key;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.ResourceEncoder;
 import com.bumptech.glide.load.Transformation;
 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
 
-public class EngineKeyFactory {
+class EngineKeyFactory {
 
-    public EngineKey buildKey(String id, int width, int height, ResourceDecoder cacheDecoder,
+    @SuppressWarnings("rawtypes")
+    public EngineKey buildKey(String id, Key signature, int width, int height, ResourceDecoder cacheDecoder,
             ResourceDecoder sourceDecoder, Transformation transformation, ResourceEncoder encoder,
             ResourceTranscoder transcoder, Encoder sourceEncoder) {
-        return new EngineKey(id, width, height, cacheDecoder, sourceDecoder, transformation, encoder, transcoder,
-                sourceEncoder);
+        return new EngineKey(id, signature, width, height, cacheDecoder, sourceDecoder, transformation, encoder,
+                transcoder, sourceEncoder);
     }
 
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/EngineResource.java b/library/src/main/java/com/bumptech/glide/load/engine/EngineResource.java
new file mode 100644
index 0000000..49bf096
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/engine/EngineResource.java
@@ -0,0 +1,103 @@
+package com.bumptech.glide.load.engine;
+
+import android.os.Looper;
+
+import com.bumptech.glide.load.Key;
+
+/**
+ * A wrapper resource that allows reference counting a wrapped {@link com.bumptech.glide.load.engine.Resource}
+ * interface.
+ *
+ * @param <Z> The type of data returned by the wrapped {@link Resource}.
+ */
+class EngineResource<Z> implements Resource<Z> {
+    private final Resource<Z> resource;
+    private final boolean isCacheable;
+    private ResourceListener listener;
+    private Key key;
+    private int acquired;
+    private boolean isRecycled;
+
+    interface ResourceListener {
+        void onResourceReleased(Key key, EngineResource<?> resource);
+    }
+
+    EngineResource(Resource<Z> toWrap, boolean isCacheable) {
+        if (toWrap == null) {
+            throw new NullPointerException("Wrapped resource must not be null");
+        }
+        resource = toWrap;
+        this.isCacheable = isCacheable;
+    }
+
+    void setResourceListener(Key key, ResourceListener listener) {
+        this.key = key;
+        this.listener = listener;
+    }
+
+    boolean isCacheable() {
+        return isCacheable;
+    }
+
+    @Override
+    public Z get() {
+        return resource.get();
+    }
+
+    @Override
+    public int getSize() {
+        return resource.getSize();
+    }
+
+    @Override
+    public void recycle() {
+        if (acquired > 0) {
+            throw new IllegalStateException("Cannot recycle a resource while it is still acquired");
+        }
+        if (isRecycled) {
+            throw new IllegalStateException("Cannot recycle a resource that has already been recycled");
+        }
+        isRecycled = true;
+        resource.recycle();
+    }
+
+    /**
+     * Increments the number of consumers using the wrapped resource. Must be called on the main thread.
+     *
+     * <p>
+     *     This must be called with a number corresponding to the number of new consumers each time new consumers
+     *     begin using the wrapped resource. It is always safer to call acquire more often than necessary. Generally
+     *     external users should never call this method, the framework will take care of this for you.
+     * </p>
+     */
+    void acquire() {
+        if (isRecycled) {
+            throw new IllegalStateException("Cannot acquire a recycled resource");
+        }
+        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
+            throw new IllegalThreadStateException("Must call acquire on the main thread");
+        }
+        ++acquired;
+    }
+
+    /**
+     * Decrements the number of consumers using the wrapped resource. Must be called on the main thread.
+     *
+     * <p>
+     *     This must only be called when a consumer that called the {@link #acquire()} method is now done with the
+     *     resource. Generally external users should never callthis method, the framework will take care of this for
+     *     you.
+     * </p>
+     */
+    void release() {
+        if (acquired <= 0) {
+            throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
+        }
+        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
+            throw new IllegalThreadStateException("Must call release on the main thread");
+        }
+        if (--acquired == 0) {
+            listener.onResourceReleased(key, this);
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/EngineRunnable.java b/library/src/main/java/com/bumptech/glide/load/engine/EngineRunnable.java
new file mode 100644
index 0000000..98ec65e
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/engine/EngineRunnable.java
@@ -0,0 +1,140 @@
+package com.bumptech.glide.load.engine;
+
+import android.util.Log;
+
+import com.bumptech.glide.Priority;
+import com.bumptech.glide.load.engine.executor.Prioritized;
+import com.bumptech.glide.request.ResourceCallback;
+
+/**
+ * A runnable class responsible for using an {@link com.bumptech.glide.load.engine.DecodeJob} to decode resources on a
+ * background thread in two stages.
+ *
+ * <p>
+ *     In the first stage, this class attempts to decode a resource
+ *     from cache, first using transformed data and then using source data. If no resource can be decoded from cache,
+ *     this class then requests to be posted again. During the second stage this class then attempts to use the
+ *     {@link com.bumptech.glide.load.engine.DecodeJob} to decode data directly from the original source.
+ * </p>
+ *
+ * <p>
+ *     Using two stages with a re-post in between allows us to run fast disk cache decodes on one thread and slow source
+ *     fetches on a second pool so that loads for local data are never blocked waiting for loads for remote data to
+ *     complete.
+ * </p>
+ */
+class EngineRunnable implements Runnable, Prioritized {
+    private static final String TAG = "EngineRunnable";
+
+    private final Priority priority;
+    private final EngineRunnableManager manager;
+    private final DecodeJob<?, ?, ?> decodeJob;
+
+    private Stage stage;
+
+    private volatile boolean isCancelled;
+
+    public EngineRunnable(EngineRunnableManager manager, DecodeJob<?, ?, ?> decodeJob, Priority priority) {
+        this.manager = manager;
+        this.decodeJob = decodeJob;
+        this.stage = Stage.CACHE;
+        this.priority = priority;
+    }
+
+    public void cancel() {
+        isCancelled = true;
+        decodeJob.cancel();
+    }
+
+    @Override
+    public void run() {
+        if (isCancelled) {
+            return;
+        }
+
+        Exception exception = null;
+        Resource<?> resource = null;
+        try {
+            resource = decode();
+        } catch (Exception e) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "Exception decoding", e);
+            }
+            exception = e;
+        }
+
+        if (isCancelled) {
+            if (resource != null) {
+                resource.recycle();
+            }
+            return;
+        }
+
+        if (resource == null) {
+            onLoadFailed(exception);
+        } else {
+            onLoadComplete(resource);
+        }
+    }
+
+    private boolean isDecodingFromCache() {
+        return stage == Stage.CACHE;
+    }
+
+    private void onLoadComplete(Resource resource) {
+        manager.onResourceReady(resource);
+    }
+
+    private void onLoadFailed(Exception e) {
+        if (isDecodingFromCache()) {
+            stage = Stage.SOURCE;
+            manager.submitForSource(this);
+        } else {
+            manager.onException(e);
+        }
+    }
+
+    private Resource<?> decode() throws Exception {
+        if (isDecodingFromCache()) {
+            return decodeFromCache();
+        } else {
+            return decodeFromSource();
+        }
+    }
+
+    private Resource<?> decodeFromCache() throws Exception {
+        Resource<?> result = null;
+        try {
+            result = decodeJob.decodeResultFromCache();
+        } catch (Exception e) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Exception decoding result from cache: " + e);
+            }
+        }
+
+        if (result == null) {
+            result = decodeJob.decodeSourceFromCache();
+        }
+        return result;
+    }
+
+    private Resource<?> decodeFromSource() throws Exception {
+        return decodeJob.decodeFromSource();
+    }
+
+    @Override
+    public int getPriority() {
+        return priority.ordinal();
+    }
+
+    private enum Stage {
+        /** Attempting to decode resource from cache. */
+        CACHE,
+        /** Attempting to decode resource from source data. */
+        SOURCE
+    }
+
+    interface EngineRunnableManager extends ResourceCallback {
+        void submitForSource(EngineRunnable runnable);
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/OriginalEngineKey.java b/library/src/main/java/com/bumptech/glide/load/engine/OriginalEngineKey.java
deleted file mode 100644
index 0be82b6..0000000
--- a/library/src/main/java/com/bumptech/glide/load/engine/OriginalEngineKey.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.bumptech.glide.load.engine;
-
-import com.bumptech.glide.load.Key;
-
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-
-public class OriginalEngineKey implements Key {
-    private String id;
-
-    public OriginalEngineKey(String id) {
-        this.id = id;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof OriginalEngineKey)) {
-            return false;
-        }
-
-        OriginalEngineKey that = (OriginalEngineKey) o;
-
-        if (!id.equals(that.id)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        return id.hashCode();
-    }
-
-    @Override
-    public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException {
-        messageDigest.update(id.getBytes("UTF-8"));
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/OriginalKey.java b/library/src/main/java/com/bumptech/glide/load/engine/OriginalKey.java
new file mode 100644
index 0000000..d52e7b6
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/engine/OriginalKey.java
@@ -0,0 +1,54 @@
+package com.bumptech.glide.load.engine;
+
+import com.bumptech.glide.load.Key;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+
+/**
+ * A class for keeping track of the cache key of the original data + any requested signature.
+ */
+class OriginalKey implements Key {
+
+    private final String id;
+    private final Key signature;
+
+    public OriginalKey(String id, Key signature) {
+        this.id = id;
+        this.signature = signature;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        OriginalKey that = (OriginalKey) o;
+
+        if (!id.equals(that.id)) {
+            return false;
+        }
+        if (!signature.equals(that.signature)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = id.hashCode();
+        result = 31 * result + signature.hashCode();
+        return result;
+    }
+
+    @Override
+    public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException {
+        messageDigest.update(id.getBytes(STRING_CHARSET_NAME));
+        signature.updateDiskCacheKey(messageDigest);
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/Resource.java b/library/src/main/java/com/bumptech/glide/load/engine/Resource.java
index 8340f07..febe945 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/Resource.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/Resource.java
@@ -1,79 +1,48 @@
 package com.bumptech.glide.load.engine;
 
-import android.os.Looper;
-import com.bumptech.glide.load.Key;
 
 /**
- * A generic resource that handles reference counting so resources can safely be reused.
- * <p>
- *     Public methods are non final only to allow for mocking, subclasses must only override abstract methods.
- * </p>
+ * A resource interface that wraps a particular type so that it can be pooled and reused.
  *
  * @param <Z> The type of resource wrapped by this class.
  */
-public abstract class Resource<Z> {
-    private volatile int acquired;
-    private volatile boolean isRecycled;
-    private ResourceListener listener;
-    private Key key;
-    private boolean isCacheable;
+public interface Resource<Z> {
 
-    interface ResourceListener {
-        public void onResourceReleased(Key key, Resource resource);
-    }
+    /**
+     * Returns an instance of the wrapped resource.
+     * <p>
+     *     Note - This does not have to be the same instance of the wrapped resource class and in fact it is often
+     *     appropriate to return a new instance for each call. For example,
+     *     {@link android.graphics.drawable.Drawable Drawable}s should only be used by a single
+     *     {@link android.view.View View} at a time so each call to this method for Resources that wrap
+     *     {@link android.graphics.drawable.Drawable Drawable}s should always return a new
+     *     {@link android.graphics.drawable.Drawable Drawable}.
+     * </p>
+     */
+    Z get();
 
-    public abstract Z get();
+    /**
+     * Returns the size in bytes of the wrapped resource to use to determine how much of the memory cache this resource
+     * uses.
+     */
+    int getSize();
 
-    public abstract int getSize();
-
-    protected abstract void recycleInternal();
-
-    void setResourceListener(Key key, ResourceListener listener) {
-        this.key = key;
-        this.listener = listener;
-    }
-
-    void setCacheable(boolean isCacheable) {
-        this.isCacheable = isCacheable;
-    }
-
-    boolean isCacheable() {
-        return isCacheable;
-    }
-
-    public void recycle() {
-        if (acquired > 0) {
-            throw new IllegalStateException("Cannot recycle a resource while it is still acquired");
-        }
-        if (isRecycled) {
-            throw new IllegalStateException("Cannot recycle a resource that has already been recycled");
-        }
-        isRecycled = true;
-        recycleInternal();
-    }
-
-    public void acquire(int times) {
-        if (isRecycled) {
-            throw new IllegalStateException("Cannot acquire a recycled resource");
-        }
-        if (times <= 0) {
-            throw new IllegalArgumentException("Must acquire a number of times >= 0");
-        }
-        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
-            throw new IllegalThreadStateException("Must call acquire on the main thread");
-        }
-        acquired += times;
-    }
-
-    public void release() {
-        if (acquired <= 0) {
-            throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
-        }
-        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
-            throw new IllegalThreadStateException("Must call release on the main thread");
-        }
-        if (--acquired == 0) {
-            listener.onResourceReleased(key, this);
-        }
-    }
+     /**
+     * Cleans up and recycles internal resources.
+     *
+     * <p>
+     *     It is only safe to call this method if there are no current resource consumers and if this method has not
+     *     yet been called. Typically this occurs at one of two times:
+     *     <ul>
+     *         <li>During a resource load when the resource is transformed or transcoded before any consumer have
+     *         ever had access to this resource</li>
+     *         <li>After all consumers have released this resource and it has been evicted from the cache</li>
+     *     </ul>
+     *
+     *     For most users of this class, the only time this method should ever be called is during transformations or
+     *     transcoders, the framework will call this method when all consumers have released this resource and it has
+     *     been evicted from the cache.
+     * </p>
+     */
+    void recycle();
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/ResourceRecycler.java b/library/src/main/java/com/bumptech/glide/load/engine/ResourceRecycler.java
new file mode 100644
index 0000000..c30376c
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/engine/ResourceRecycler.java
@@ -0,0 +1,42 @@
+package com.bumptech.glide.load.engine;
+
+import android.os.Handler;
+import android.os.Message;
+
+import com.bumptech.glide.util.Util;
+
+/**
+ * A class that can safely recycle recursive resources.
+ */
+class ResourceRecycler {
+    private boolean isRecycling;
+    private final Handler handler = new Handler(new ResourceRecyclerCallback());
+
+    public void recycle(Resource<?> resource) {
+        Util.assertMainThread();
+
+        if (isRecycling) {
+            // If a resource has sub-resources, releasing a sub resource can cause it's parent to be synchronously
+            // evicted which leads to a recycle loop when the parent releases it's children. Posting breaks this loop.
+            handler.obtainMessage(ResourceRecyclerCallback.RECYCLE_RESOURCE, resource).sendToTarget();
+        } else {
+            isRecycling = true;
+            resource.recycle();
+            isRecycling = false;
+        }
+    }
+
+    private static class ResourceRecyclerCallback implements Handler.Callback {
+        public static final int RECYCLE_RESOURCE = 1;
+
+        @Override
+        public boolean handleMessage(Message message) {
+            if (message.what == RECYCLE_RESOURCE) {
+                Resource resource = (Resource) message.obj;
+                resource.recycle();
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/ResourceRunner.java b/library/src/main/java/com/bumptech/glide/load/engine/ResourceRunner.java
deleted file mode 100644
index d0323b8..0000000
--- a/library/src/main/java/com/bumptech/glide/load/engine/ResourceRunner.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package com.bumptech.glide.load.engine;
-
-import android.os.SystemClock;
-import android.util.Log;
-import com.bumptech.glide.load.CacheLoader;
-import com.bumptech.glide.Priority;
-import com.bumptech.glide.load.ResourceDecoder;
-import com.bumptech.glide.load.Transformation;
-import com.bumptech.glide.load.engine.executor.Prioritized;
-import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
-
-import java.io.InputStream;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-
-/**
- *
- * @param <Z> The type of the resource that will be decoded.
- * @param <R> the type of the resource the decoded resource will be transcoded to.
- */
-public class ResourceRunner<Z, R> implements Runnable, Prioritized {
-    private static final String TAG = "ResourceRunner";
-
-    private final EngineKey key;
-    private final Transformation<Z> transformation;
-    private final ResourceTranscoder<Z, R> transcoder;
-    private final SourceResourceRunner sourceRunner;
-    private final EngineJob job;
-    private final Priority priority;
-    private final ResourceDecoder<InputStream, Z> cacheDecoder;
-    private final int width;
-    private final int height;
-    private final CacheLoader cacheLoader;
-    private final ExecutorService diskCacheService;
-    private final ExecutorService resizeService;
-    private volatile Future<?> future;
-    private volatile boolean isCancelled;
-
-    public ResourceRunner(EngineKey key, int width, int height, CacheLoader cacheLoader,
-            ResourceDecoder<InputStream, Z> cacheDecoder, Transformation<Z> transformation,
-            ResourceTranscoder<Z, R> transcoder, SourceResourceRunner sourceRunner, ExecutorService diskCacheService,
-            ExecutorService resizeService, EngineJob job, Priority priority) {
-        this.key = key;
-        this.width = width;
-        this.height = height;
-        this.cacheLoader = cacheLoader;
-        this.cacheDecoder = cacheDecoder;
-        this.transformation = transformation;
-        this.transcoder = transcoder;
-        this.sourceRunner = sourceRunner;
-        this.diskCacheService = diskCacheService;
-        this.resizeService = resizeService;
-        this.job = job;
-        this.priority = priority;
-    }
-
-    public EngineJob getJob() {
-        return job;
-    }
-
-    public void cancel() {
-        isCancelled = true;
-        if (future != null) {
-            future.cancel(false);
-        }
-        sourceRunner.cancel();
-    }
-
-    public void queue() {
-        future = diskCacheService.submit(this);
-    }
-
-    @Override
-    public void run() {
-        if (isCancelled) {
-            return;
-        }
-
-        long start = SystemClock.currentThreadTimeMillis();
-        Resource<Z> fromCache = cacheLoader.load(key, cacheDecoder, width, height);
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "loaded from disk cache in " + (SystemClock.currentThreadTimeMillis() - start));
-        }
-        if (fromCache != null) {
-            Resource<Z> transformed = transformation.transform(fromCache, width, height);
-            if (transformed != fromCache) {
-                fromCache.recycle();
-            }
-            Resource<R> transcoded = transcoder.transcode(transformed);
-            job.onResourceReady(transcoded);
-        } else {
-            future = resizeService.submit(sourceRunner);
-        }
-    }
-
-    @Override
-    public int getPriority() {
-        return priority.ordinal();
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/ResourceRunnerFactory.java b/library/src/main/java/com/bumptech/glide/load/engine/ResourceRunnerFactory.java
deleted file mode 100644
index 4276599..0000000
--- a/library/src/main/java/com/bumptech/glide/load/engine/ResourceRunnerFactory.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.bumptech.glide.load.engine;
-
-import com.bumptech.glide.Priority;
-import com.bumptech.glide.load.Encoder;
-import com.bumptech.glide.load.ResourceDecoder;
-import com.bumptech.glide.load.ResourceEncoder;
-import com.bumptech.glide.load.Transformation;
-import com.bumptech.glide.load.data.DataFetcher;
-import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
-
-import java.io.InputStream;
-
-interface ResourceRunnerFactory {
-    /**
-     *
-     * @param key
-     * @param cacheDecoder
-     * @param fetcher
-     * @param decoder
-     * @param transformation
-     * @param encoder
-     * @param priority
-     * @param <T> The type of data the resource will be decoded from.
-     * @param <Z> The type of the resource that will be decoded.
-     * @param <R> The type of the resource that will be transcoded to from the decoded resource.
-     * @return
-     */
-    public <T, Z, R> ResourceRunner<Z, R> build(EngineKey key, int width, int height,
-            ResourceDecoder<InputStream, Z> cacheDecoder, DataFetcher<T> fetcher, boolean cacheSource,
-            Encoder<T> sourceEncoder, ResourceDecoder<T, Z> decoder, Transformation<Z> transformation,
-            ResourceEncoder<Z> encoder, ResourceTranscoder<Z, R> transcoder, Priority priority,
-            boolean isMemoryCacheable, EngineJobListener listener);
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/SourceResourceRunner.java b/library/src/main/java/com/bumptech/glide/load/engine/SourceResourceRunner.java
deleted file mode 100644
index c562311..0000000
--- a/library/src/main/java/com/bumptech/glide/load/engine/SourceResourceRunner.java
+++ /dev/null
@@ -1,163 +0,0 @@
-package com.bumptech.glide.load.engine;
-
-import android.os.SystemClock;
-import android.util.Log;
-import com.bumptech.glide.load.CacheLoader;
-import com.bumptech.glide.Priority;
-import com.bumptech.glide.load.Encoder;
-import com.bumptech.glide.load.ResourceDecoder;
-import com.bumptech.glide.load.ResourceEncoder;
-import com.bumptech.glide.load.Transformation;
-import com.bumptech.glide.load.data.DataFetcher;
-import com.bumptech.glide.load.engine.cache.DiskCache;
-import com.bumptech.glide.load.engine.executor.Prioritized;
-import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
-import com.bumptech.glide.request.ResourceCallback;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- *
- * @param <T> The type of the data the resource will be decoded from.
- * @param <Z> The type of the resource that will be decoded.
- * @param <R> The type of the resource that will be transcoded to from the decoded resource.
- */
-public class SourceResourceRunner<T, Z, R> implements Runnable, DiskCache.Writer, Prioritized {
-    private static final String TAG = "SourceRunner";
-    private final EngineKey key;
-    private final int width;
-    private final int height;
-    private final CacheLoader cacheLoader;
-    private final ResourceDecoder<InputStream, Z> cacheDecoder;
-    private final DataFetcher<T> fetcher;
-    private final boolean cacheSource;
-    private final Encoder<T> sourceEncoder;
-    private final ResourceDecoder<T, Z> decoder;
-    private final Transformation<Z> transformation;
-    private final ResourceEncoder<Z> encoder;
-    private final ResourceTranscoder<Z, R> transcoder;
-    private final DiskCache diskCache;
-    private final Priority priority;
-    private final ResourceCallback cb;
-
-    private Resource<Z> result;
-    private volatile boolean isCancelled;
-
-    public SourceResourceRunner(EngineKey key, int width, int height, CacheLoader cacheLoader,
-            ResourceDecoder<InputStream, Z> cacheDecoder, DataFetcher<T> dataFetcher, boolean cacheSource,
-            Encoder<T> sourceEncoder, ResourceDecoder<T, Z> decoder, Transformation<Z> transformation,
-            ResourceEncoder<Z> encoder, ResourceTranscoder<Z, R> transcoder, DiskCache diskCache, Priority priority,
-            ResourceCallback cb) {
-        this.key = key;
-        this.width = width;
-        this.height = height;
-        this.cacheLoader = cacheLoader;
-        this.cacheDecoder = cacheDecoder;
-        this.fetcher = dataFetcher;
-        this.cacheSource = cacheSource;
-        this.sourceEncoder = sourceEncoder;
-        this.decoder = decoder;
-        this.transformation = transformation;
-        this.encoder = encoder;
-        this.transcoder = transcoder;
-        this.diskCache = diskCache;
-        this.priority = priority;
-        this.cb = cb;
-    }
-
-    public void cancel() {
-        isCancelled = true;
-        if (fetcher != null) {
-            fetcher.cancel();
-        }
-    }
-
-    @Override
-    public void run() {
-        if (isCancelled) {
-            return;
-        }
-
-        try {
-            long start = SystemClock.currentThreadTimeMillis();
-            Resource<Z> decoded = cacheLoader.load(key.getOriginalKey(), cacheDecoder, width, height);
-
-            if (decoded == null) {
-                decoded = decodeFromSource();
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "Decoded from source in " + (SystemClock.currentThreadTimeMillis() - start) + " cache");
-                    start = SystemClock.currentThreadTimeMillis();
-                }
-            }
-
-            if (decoded != null) {
-                Resource<Z> transformed = transformation.transform(decoded, width, height);
-                if (decoded != transformed) {
-                    decoded.recycle();
-                }
-                result = transformed;
-            }
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "transformed in " + (SystemClock.currentThreadTimeMillis() - start));
-            }
-
-            if (result != null) {
-                diskCache.put(key, this);
-                start = SystemClock.currentThreadTimeMillis();
-                Resource<R> transcoded = transcoder.transcode(result);
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.d(TAG, "transcoded in " + (SystemClock.currentThreadTimeMillis() - start));
-                }
-                cb.onResourceReady(transcoded);
-            } else {
-                cb.onException(null);
-            }
-
-        } catch (Exception e) {
-            cb.onException(e);
-        }
-    }
-
-    private Resource<Z> encodeSourceAndDecodeFromCache(final T data) {
-        diskCache.put(key.getOriginalKey(), new DiskCache.Writer() {
-            @Override
-            public boolean write(OutputStream os) {
-                return sourceEncoder.encode(data, os);
-            }
-        });
-        return cacheLoader.load(key.getOriginalKey(), cacheDecoder, width, height);
-    }
-
-    private Resource<Z> decodeFromSource() throws Exception {
-        try {
-            final T data = fetcher.loadData(priority);
-            if (data != null) {
-                if (cacheSource) {
-                    return encodeSourceAndDecodeFromCache(data);
-                } else {
-                    return decoder.decode(data, width, height);
-                }
-            }
-        } finally {
-            fetcher.cleanup();
-        }
-
-        return null;
-    }
-
-    @Override
-    public boolean write(OutputStream os) {
-        long start = SystemClock.currentThreadTimeMillis();
-        boolean success = encoder.encode(result, os);
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "wrote to disk cache in " + (SystemClock.currentThreadTimeMillis() - start));
-        }
-        return success;
-    }
-
-    @Override
-    public int getPriority() {
-        return priority.ordinal();
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/AttributeStrategy.java b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/AttributeStrategy.java
index cfaf4db..eebd1fd 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/AttributeStrategy.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/AttributeStrategy.java
@@ -1,6 +1,7 @@
 package com.bumptech.glide.load.engine.bitmap_recycle;
 
 import android.graphics.Bitmap;
+
 import com.bumptech.glide.util.Util;
 
 /**
@@ -40,7 +41,7 @@
 
     @Override
     public int getSize(Bitmap bitmap) {
-        return Util.getSize(bitmap);
+        return Util.getBitmapByteSize(bitmap);
     }
 
     @Override
@@ -56,7 +57,8 @@
         return "[" + width + "x" + height + "], " + config;
     }
 
-    private static class KeyPool extends BaseKeyPool<Key> {
+    // Visible for testing.
+    static class KeyPool extends BaseKeyPool<Key> {
         public Key get(int width, int height, Bitmap.Config config) {
             Key result = get();
             result.init(width, height, config);
@@ -69,7 +71,8 @@
         }
     }
 
-    private static class Key implements Poolable {
+    // Visible for testing.
+    static class Key implements Poolable {
         private final KeyPool pool;
         private int width;
         private int height;
@@ -88,16 +91,13 @@
 
         @Override
         public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-
-            Key key = (Key) o;
-
-            if (height != key.height) return false;
-            if (width != key.width) return false;
-            if (config != key.config) return false;
-
-            return true;
+            if (o instanceof Key) {
+                Key other = (Key) o;
+                return width == other.width
+                        && height == other.height
+                        && config == other.config;
+            }
+            return false;
         }
 
         @Override
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BaseKeyPool.java b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BaseKeyPool.java
index cc4a710..bfd8d37 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BaseKeyPool.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BaseKeyPool.java
@@ -1,22 +1,12 @@
 package com.bumptech.glide.load.engine.bitmap_recycle;
 
-import android.os.Build;
+import com.bumptech.glide.util.Util;
 
-import java.util.ArrayDeque;
-import java.util.LinkedList;
 import java.util.Queue;
 
 abstract class BaseKeyPool<T extends Poolable> {
     private static final int MAX_SIZE = 20;
-    private final Queue<T> keyPool;
-
-    public BaseKeyPool() {
-        if (Build.VERSION.SDK_INT >= 9) {
-            keyPool = new ArrayDeque<T>(MAX_SIZE);
-        } else {
-            keyPool = new LinkedList<T>();
-        }
-    }
+    private final Queue<T> keyPool = Util.createQueue(MAX_SIZE);
 
     protected T get() {
         T result = keyPool.poll();
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapPool.java b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapPool.java
index f6a575f..0390a66 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapPool.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapPool.java
@@ -2,14 +2,113 @@
 
 import android.graphics.Bitmap;
 
+/**
+ * An interface for a pool that allows users to reuse {@link android.graphics.Bitmap} objects.
+ */
 public interface BitmapPool {
-    public void setSizeMultiplier(float sizeMultiplier);
 
-    public boolean put(Bitmap bitmap);
+    /**
+     * Returns the current maximum size of the pool in bytes.
+     */
+    int getMaxSize();
 
-    public Bitmap get(int width, int height, Bitmap.Config config);
+    /**
+     * Multiplies the initial size of the pool by the given multipler to dynamically and synchronously allow users to
+     * adjust the size of the pool.
+     *
+     * <p>
+     *     If the current total size of the pool is larger than the max size after the given multiplier is applied,
+     *     {@link Bitmap}s should be evicted until the pool is smaller than the new max size.
+     * </p>
+     *
+     * @param sizeMultiplier The size multiplier to apply between 0 and 1.
+     */
+    void setSizeMultiplier(float sizeMultiplier);
 
-    public void clearMemory();
+    /**
+     * Adds the given {@link android.graphics.Bitmap} and returns {@code true} if the {@link android.graphics.Bitmap}
+     * was eligible to be added and {@code false} otherwise.
+     *
+     * <p>
+     *     Note - If the {@link android.graphics.Bitmap} is rejected (this method returns false) then it is the caller's
+     *     responsibility to call {@link android.graphics.Bitmap#recycle()}.
+     * </p>
+     *
+     * <p>
+     *     Note - This method will return {@code true} if the given {@link android.graphics.Bitmap} is synchronously
+     *     evicted after being accepted. The only time this method will return {@code false} is if the
+     *     {@link android.graphics.Bitmap} is not eligible to be added to the pool (either it is not mutable or it is
+     *     larger than the max pool size).
+     * </p>
+     *
+     * @see android.graphics.Bitmap#isMutable()
+     * @see android.graphics.Bitmap#recycle()
+     *
+     * @param bitmap The {@link android.graphics.Bitmap} to attempt to add.
+     */
+    boolean put(Bitmap bitmap);
 
-    public void trimMemory(int level);
+    /**
+     * Returns a {@link android.graphics.Bitmap} of exactly the given width, height, and configuration, and containing
+     * only transparent pixels or null if no such {@link android.graphics.Bitmap} could be obtained from the pool.
+     *
+     * <p>
+     *     Because this method erases all pixels in the {@link Bitmap}, this method is slightly slower than
+     *     {@link #getDirty(int, int, android.graphics.Bitmap.Config)}. If the {@link android.graphics.Bitmap} is being
+     *     obtained to be used in {@link android.graphics.BitmapFactory} or in any other case where every pixel in the
+     *     {@link android.graphics.Bitmap} will always be overwritten or cleared,
+     *     {@link #getDirty(int, int, android.graphics.Bitmap.Config)} will be faster. When in doubt, use this method
+     *     to ensure correctness.
+     * </p>
+     *
+     * <pre>
+     *     Implementations can should clear out every returned Bitmap using the following:
+     *
+     * {@code
+     * bitmap.eraseColor(Color.TRANSPARENT);
+     * }
+     * </pre>
+     *
+     * @see #getDirty(int, int, android.graphics.Bitmap.Config)
+     *
+     * @param width The width in pixels of the desired {@link android.graphics.Bitmap}.
+     * @param height The height in pixels of the desired {@link android.graphics.Bitmap}.
+     * @param config The {@link android.graphics.Bitmap.Config} of the desired {@link android.graphics.Bitmap}.
+     */
+    Bitmap get(int width, int height, Bitmap.Config config);
+
+    /**
+     * Identical to {@link #get(int, int, android.graphics.Bitmap.Config)} except that any returned non-null
+     * {@link android.graphics.Bitmap} may <em>not</em> have been erased and may contain random data.
+     *
+     * <p>
+     *     Although this method is slightly more efficient than {@link #get(int, int, android.graphics.Bitmap.Config)}
+     *     it should be used with caution and only when the caller is sure that they are going to erase the
+     *     {@link android.graphics.Bitmap} entirely before writing new data to it.
+     * </p>
+     *
+     * @see #get(int, int, android.graphics.Bitmap.Config)
+     *
+     * @param width The width in pixels of the desired {@link android.graphics.Bitmap}.
+     * @param height The height in pixels of the desired {@link android.graphics.Bitmap}.
+     * @param config The {@link android.graphics.Bitmap.Config} of the desired {@link android.graphics.Bitmap}.
+     * @return A {@link android.graphics.Bitmap} with exactly the given width, height, and config potentially containing
+     * random image data or null if no such {@link android.graphics.Bitmap} could be obtained from the pool.
+     */
+    Bitmap getDirty(int width, int height, Bitmap.Config config);
+
+    /**
+     * Removes all {@link android.graphics.Bitmap}s from the pool.
+     */
+    void clearMemory();
+
+    /**
+     * Reduces the size of the cache by evicting items based on the given level.
+     *
+     * @see android.content.ComponentCallbacks2
+     *
+     * @param level The level from {@link android.content.ComponentCallbacks2} to use to determine how many
+     * {@link android.graphics.Bitmap}s to evict.
+     */
+    void trimMemory(int level);
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapPoolAdapter.java b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapPoolAdapter.java
index 3df0d74..ebffc1b 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapPoolAdapter.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapPoolAdapter.java
@@ -2,9 +2,19 @@
 
 import android.graphics.Bitmap;
 
+/**
+ * An {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool BitmapPool} implementation that rejects all
+ * {@link android.graphics.Bitmap Bitmap}s added to it and always returns {@code null} from get.
+ */
 public class BitmapPoolAdapter implements BitmapPool {
     @Override
+    public int getMaxSize() {
+        return 0;
+    }
+
+    @Override
     public void setSizeMultiplier(float sizeMultiplier) {
+        // Do nothing.
     }
 
     @Override
@@ -18,8 +28,17 @@
     }
 
     @Override
-    public void clearMemory() { }
+    public Bitmap getDirty(int width, int height, Bitmap.Config config) {
+        return null;
+    }
 
     @Override
-    public void trimMemory(int level) { }
+    public void clearMemory() {
+        // Do nothing.
+    }
+
+    @Override
+    public void trimMemory(int level) {
+        // Do nothing.
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapReferenceCounter.java b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapReferenceCounter.java
deleted file mode 100644
index f9651a8..0000000
--- a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapReferenceCounter.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.bumptech.glide.load.engine.bitmap_recycle;
-
-import android.graphics.Bitmap;
-
-public interface BitmapReferenceCounter {
-
-    public void acquireBitmap(Bitmap bitmap);
-
-    public void releaseBitmap(Bitmap bitmap);
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapReferenceCounterAdapter.java b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapReferenceCounterAdapter.java
deleted file mode 100644
index d097390..0000000
--- a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/BitmapReferenceCounterAdapter.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.bumptech.glide.load.engine.bitmap_recycle;
-
-import android.graphics.Bitmap;
-
-public class BitmapReferenceCounterAdapter implements BitmapReferenceCounter {
-    @Override
-    public void acquireBitmap(Bitmap bitmap) { }
-
-    @Override
-    public void releaseBitmap(Bitmap bitmap) { }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/GroupedLinkedMap.java b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/GroupedLinkedMap.java
index 032501c..cbf4f8a 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/GroupedLinkedMap.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/GroupedLinkedMap.java
@@ -49,7 +49,7 @@
     public V removeLast() {
         LinkedEntry<K, V> last = head.prev;
 
-        while (last != head) {
+        while (!last.equals(head)) {
             V removed = last.removeLast();
             if (removed != null) {
                 return removed;
@@ -71,18 +71,18 @@
 
     @Override
     public String toString() {
-        String result = "GroupedLinkedMap( ";
+        StringBuilder sb = new StringBuilder("GroupedLinkedMap( ");
         LinkedEntry<K, V> current = head.next;
         boolean hadAtLeastOneItem = false;
-        while (current != head) {
+        while (!current.equals(head)) {
             hadAtLeastOneItem = true;
-            result += "{" + current.key + ":" + current.size() + "}, ";
+            sb.append('{').append(current.key).append(':').append(current.size()).append("}, ");
             current = current.next;
         }
         if (hadAtLeastOneItem) {
-            result = result.substring(0, result.length() - 2);
+            sb.delete(sb.length() - 2, sb.length());
         }
-        return result + " )";
+        return sb.append(" )").toString();
     }
 
     // Make the entry the most recently used item.
@@ -101,12 +101,12 @@
         updateEntry(entry);
     }
 
-    private static void updateEntry(LinkedEntry entry) {
+    private static <K, V> void updateEntry(LinkedEntry<K, V> entry) {
         entry.next.prev = entry;
         entry.prev.next = entry;
     }
 
-    private static void removeEntry(LinkedEntry entry) {
+    private static <K, V> void removeEntry(LinkedEntry<K, V> entry) {
         entry.prev.next = entry.next;
         entry.next.prev = entry.prev;
     }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPool.java b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPool.java
index 2c53e2f..79d8c6c 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPool.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPool.java
@@ -1,19 +1,32 @@
 package com.bumptech.glide.load.engine.bitmap_recycle;
 
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.os.Build;
 import android.util.Log;
 
-import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
-import static android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
+/**
+ * An {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} implementation that uses an
+ * {@link com.bumptech.glide.load.engine.bitmap_recycle.LruPoolStrategy} to bucket {@link Bitmap}s and then uses an LRU
+ * eviction policy to evict {@link android.graphics.Bitmap}s from the least recently used bucket in order to keep
+ * the pool below a given maximum size limit.
+ */
 public class LruBitmapPool implements BitmapPool {
     private static final String TAG = "LruBitmapPool";
-    private final LruPoolStrategy strategy;
+    private static final Bitmap.Config DEFAULT_CONFIG = Bitmap.Config.ARGB_8888;
 
+    private final LruPoolStrategy strategy;
     private final int initialMaxSize;
+    private final BitmapTracker tracker;
+
     private int maxSize;
-    private int currentSize = 0;
+    private int currentSize;
     private int hits;
     private int misses;
     private int puts;
@@ -24,20 +37,25 @@
         this.initialMaxSize = maxSize;
         this.maxSize = maxSize;
         this.strategy = strategy;
+        this.tracker = new NullBitmapTracker();
     }
 
+    /**
+     * Constructor for LruBitmapPool.
+     *
+     * @param maxSize The initial maximum size of the pool in bytes.
+     */
     public LruBitmapPool(int maxSize) {
-        initialMaxSize = maxSize;
-        this.maxSize = maxSize;
-        if (Build.VERSION.SDK_INT >= 19) {
-            strategy = new SizeStrategy();
-        } else {
-            strategy = new AttributeStrategy();
-        }
+        this(maxSize, getDefaultStrategy());
     }
 
     @Override
-    public void setSizeMultiplier(float sizeMultiplier) {
+    public int getMaxSize() {
+        return maxSize;
+    }
+
+    @Override
+    public synchronized void setSizeMultiplier(float sizeMultiplier) {
         maxSize = Math.round(initialMaxSize * sizeMultiplier);
         evict();
     }
@@ -45,17 +63,22 @@
     @Override
     public synchronized boolean put(Bitmap bitmap) {
         if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "Reject bitmap from pool=" + strategy.logBitmap(bitmap) + " is mutable="
+                        + bitmap.isMutable());
+            }
             return false;
         }
 
         final int size = strategy.getSize(bitmap);
         strategy.put(bitmap);
+        tracker.add(bitmap);
 
         puts++;
         currentSize += size;
 
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));
         }
         dump();
 
@@ -69,7 +92,23 @@
 
     @Override
     public synchronized Bitmap get(int width, int height, Bitmap.Config config) {
-        final Bitmap result = strategy.get(width, height, config);
+        Bitmap result = getDirty(width, height, config);
+        if (result != null) {
+            // Bitmaps in the pool contain random data that in some cases must be cleared for an image to be rendered
+            // correctly. we shouldn't force all consumers to independently erase the contents individually, so we do so
+            // here. See issue #131.
+            result.eraseColor(Color.TRANSPARENT);
+        }
+
+        return result;
+    }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+    @Override
+    public synchronized Bitmap getDirty(int width, int height, Bitmap.Config config) {
+        // Config will be null for non public config types, which can lead to transformations naively passing in
+        // null as the requested config here. See issue #194.
+        final Bitmap result = strategy.get(width, height, config != null ? config : DEFAULT_CONFIG);
         if (result == null) {
             if (Log.isLoggable(TAG, Log.DEBUG)) {
                 Log.d(TAG, "Missing bitmap=" + strategy.logBitmap(width, height, config));
@@ -78,9 +117,13 @@
         } else {
             hits++;
             currentSize -= strategy.getSize(result);
+            tracker.remove(result);
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
+                result.setHasAlpha(true);
+            }
         }
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "Get bitmap=" + strategy.logBitmap(width, height, config));
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "Get bitmap=" + strategy.logBitmap(width, height, config));
         }
         dump();
 
@@ -92,18 +135,20 @@
         trimToSize(0);
     }
 
+    @SuppressLint("InlinedApi")
     @Override
     public void trimMemory(int level) {
-        if (level >= TRIM_MEMORY_MODERATE) {
+        if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
             clearMemory();
-        } else if (level >= TRIM_MEMORY_BACKGROUND) {
+        } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
             trimToSize(maxSize / 2);
         }
     }
 
-    private void trimToSize(int size) {
+    private synchronized void trimToSize(int size) {
         while (currentSize > size) {
             final Bitmap removed = strategy.removeLast();
+            tracker.remove(removed);
             currentSize -= strategy.getSize(removed);
             removed.recycle();
             evictions++;
@@ -116,8 +161,58 @@
 
     private void dump() {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "Hits=" + hits + " misses=" + misses + " puts=" + puts + " evictions=" + evictions + " currentSize="
-                    + currentSize + " maxSize=" + maxSize + "\nStrategy=" + strategy);
+            Log.v(TAG, "Hits=" + hits + " misses=" + misses + " puts=" + puts + " evictions=" + evictions
+                    + " currentSize=" + currentSize + " maxSize=" + maxSize + "\nStrategy=" + strategy);
+        }
+    }
+
+    private static LruPoolStrategy getDefaultStrategy() {
+        final LruPoolStrategy strategy;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            strategy = new SizeStrategy();
+        } else {
+            strategy = new AttributeStrategy();
+        }
+        return strategy;
+    }
+
+    private interface BitmapTracker {
+        void add(Bitmap bitmap);
+        void remove(Bitmap bitmap);
+    }
+
+    @SuppressWarnings("unused")
+    // Only used for debugging
+    private static class ThrowingBitmapTracker implements BitmapTracker {
+        private final Set<Bitmap> bitmaps = Collections.synchronizedSet(new HashSet<Bitmap>());
+
+        @Override
+        public void add(Bitmap bitmap) {
+            if (bitmaps.contains(bitmap)) {
+                throw new IllegalStateException("Can't add already added bitmap: " + bitmap + " [" + bitmap.getWidth()
+                        + "x" + bitmap.getHeight() + "]");
+            }
+            bitmaps.add(bitmap);
+        }
+
+        @Override
+        public void remove(Bitmap bitmap) {
+            if (!bitmaps.contains(bitmap)) {
+                throw new IllegalStateException("Cannot remove bitmap not in tracker");
+            }
+            bitmaps.remove(bitmap);
+        }
+    }
+
+    private static class NullBitmapTracker implements BitmapTracker {
+        @Override
+        public void add(Bitmap bitmap) {
+            // Do nothing.
+        }
+
+        @Override
+        public void remove(Bitmap bitmap) {
+            // Do nothing.
         }
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruPoolStrategy.java b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruPoolStrategy.java
index e674915..d52e695 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruPoolStrategy.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/LruPoolStrategy.java
@@ -3,10 +3,10 @@
 import android.graphics.Bitmap;
 
 interface LruPoolStrategy {
-    public void put(Bitmap bitmap);
-    public Bitmap get(int width, int height, Bitmap.Config config);
-    public Bitmap removeLast();
-    public String logBitmap(Bitmap bitmap);
-    public String logBitmap(int width, int height, Bitmap.Config config);
-    public int getSize(Bitmap bitmap);
+    void put(Bitmap bitmap);
+    Bitmap get(int width, int height, Bitmap.Config config);
+    Bitmap removeLast();
+    String logBitmap(Bitmap bitmap);
+    String logBitmap(int width, int height, Bitmap.Config config);
+    int getSize(Bitmap bitmap);
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/Poolable.java b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/Poolable.java
index 48ad36e..28e96ba 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/Poolable.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/Poolable.java
@@ -1,5 +1,5 @@
 package com.bumptech.glide.load.engine.bitmap_recycle;
 
 interface Poolable {
-    public void offer();
+    void offer();
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/SerialBitmapReferenceCounter.java b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/SerialBitmapReferenceCounter.java
deleted file mode 100644
index 972e5b9..0000000
--- a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/SerialBitmapReferenceCounter.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.bumptech.glide.load.engine.bitmap_recycle;
-
-import android.graphics.Bitmap;
-
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-public class SerialBitmapReferenceCounter implements BitmapReferenceCounter {
-
-    private static class InnerTrackerPool {
-        private LinkedList<InnerTracker> pool = new LinkedList<InnerTracker>();
-
-        public InnerTracker get() {
-            InnerTracker result = pool.poll();
-            if (result == null) {
-                result = new InnerTracker();
-            }
-
-            return result;
-        }
-
-        public void release(InnerTracker innerTracker) {
-            pool.offer(innerTracker);
-        }
-    }
-
-    private static class InnerTracker {
-        private int refs = 0;
-
-        public void acquire() {
-            refs++;
-        }
-
-        public boolean release() {
-            refs--;
-
-            return refs == 0;
-        }
-    }
-
-    private final Map<Bitmap, InnerTracker> counter = new WeakHashMap<Bitmap, InnerTracker>();
-    private final BitmapPool target;
-    private final InnerTrackerPool pool = new InnerTrackerPool();
-
-    public SerialBitmapReferenceCounter(BitmapPool target) {
-        this.target = target;
-    }
-
-    private void initBitmap(Bitmap toInit) {
-        final InnerTracker tracker = counter.get(toInit);
-        if (tracker == null) {
-            counter.put(toInit, pool.get());
-        }
-    }
-
-    @Override
-    public void acquireBitmap(Bitmap bitmap) {
-        initBitmap(bitmap);
-        counter.get(bitmap).acquire();
-    }
-
-    @Override
-    public void releaseBitmap(Bitmap bitmap) {
-        final InnerTracker tracker = counter.get(bitmap);
-        if (tracker.release()) {
-            recycle(tracker, bitmap);
-        }
-    }
-
-    private void recycle(InnerTracker tracker, Bitmap bitmap) {
-        if (!target.put(bitmap)) {
-            bitmap.recycle();
-        }
-        counter.remove(bitmap);
-        pool.release(tracker);
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/SizeStrategy.java b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/SizeStrategy.java
index 2ed98d0..2716d33 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/SizeStrategy.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/bitmap_recycle/SizeStrategy.java
@@ -2,23 +2,27 @@
 
 import android.annotation.TargetApi;
 import android.graphics.Bitmap;
+import android.os.Build;
+
+import com.bumptech.glide.util.Util;
 
 import java.util.TreeMap;
 
 /**
- * A strategy for reusing bitmaps that relies on {@link Bitmap#reconfigure(int, int, Bitmap.Config)}. Requires KitKat
- * (API 19) or higher.
+ * A strategy for reusing bitmaps that relies on {@link Bitmap#reconfigure(int, int, Bitmap.Config)}.
+ * Requires {@link Build.VERSION_CODES#KITKAT KitKat} (API {@value Build.VERSION_CODES#KITKAT}) or higher.
  */
-@TargetApi(19)
+@TargetApi(Build.VERSION_CODES.KITKAT)
 class SizeStrategy implements LruPoolStrategy {
-    private static final int MAX_SIZE_MULTIPLE = 4;
+    private static final int MAX_SIZE_MULTIPLE = 8;
     private final KeyPool keyPool = new KeyPool();
     private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<Key, Bitmap>();
-    private final TreeMap<Integer, Integer> sortedSizes = new TreeMap<Integer, Integer>();
+    private final TreeMap<Integer, Integer> sortedSizes = new PrettyPrintTreeMap<Integer, Integer>();
 
     @Override
     public void put(Bitmap bitmap) {
-        final Key key = keyPool.get(bitmap.getAllocationByteCount());
+        int size = Util.getBitmapByteSize(bitmap);
+        final Key key = keyPool.get(size);
 
         groupedMap.put(key, bitmap);
 
@@ -28,7 +32,7 @@
 
     @Override
     public Bitmap get(int width, int height, Bitmap.Config config) {
-        final int size = getSize(width, height, config);
+        final int size = Util.getBitmapByteSize(width, height, config);
         Key key = keyPool.get(size);
 
         Integer possibleSize = sortedSizes.ceilingKey(size);
@@ -51,7 +55,7 @@
     public Bitmap removeLast() {
         Bitmap removed = groupedMap.removeLast();
         if (removed != null) {
-            final int removedSize = removed.getAllocationByteCount();
+            final int removedSize = Util.getBitmapByteSize(removed);
             decrementBitmapOfSize(removedSize);
         }
         return removed;
@@ -73,59 +77,51 @@
 
     @Override
     public String logBitmap(int width, int height, Bitmap.Config config) {
-        return getBitmapString(getSize(width, height, config));
+        int size = Util.getBitmapByteSize(width, height, config);
+        return getBitmapString(size);
     }
 
     @Override
     public int getSize(Bitmap bitmap) {
-        return bitmap.getAllocationByteCount();
+        return Util.getBitmapByteSize(bitmap);
     }
 
     @Override
     public String toString() {
-        String result = "SizeStrategy:\n  " + groupedMap + "\n  SortedSizes( ";
-        boolean hadAtLeastOneKey = false;
-        for (Integer size : sortedSizes.keySet()) {
-            hadAtLeastOneKey = true;
-            result += "{" + getBitmapString(size) + ":" + sortedSizes.get(size) + "}, ";
+        return "SizeStrategy:\n  "
+                + groupedMap + "\n"
+                + "  SortedSizes" + sortedSizes;
+    }
+
+    private static class PrettyPrintTreeMap<K, V> extends TreeMap<K, V> {
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("( ");
+            for (Entry<K, V> entry : entrySet()) {
+                sb.append('{').append(entry.getKey()).append(':').append(entry.getValue()).append("}, ");
+            }
+            final String result;
+            if (!isEmpty()) {
+                result = sb.substring(0, sb.length() - 2);
+            } else {
+                result = sb.toString();
+            }
+            return result + " )";
         }
-        if (hadAtLeastOneKey) {
-            result = result.substring(0, result.length() - 2);
-        }
-        return result + " )";
     }
 
     private static String getBitmapString(Bitmap bitmap) {
-        return getBitmapString(bitmap.getAllocationByteCount());
+        int size = Util.getBitmapByteSize(bitmap);
+        return getBitmapString(size);
     }
 
     private static String getBitmapString(int size) {
         return "[" + size + "]";
     }
 
-    private static int getSize(int width, int height, Bitmap.Config config) {
-        return width * height * getBytesPerPixel(config);
-    }
-
-    private static int getBytesPerPixel(Bitmap.Config config) {
-        switch (config) {
-            case ARGB_8888:
-                return 4;
-            case RGB_565:
-                return 2;
-            case ARGB_4444:
-                return 2;
-            case ALPHA_8:
-                return 1;
-            default:
-                // We only use this to calculate sizes to get, so choosing 4 bytes per pixel is conservative and
-                // probably forces us to get a larger bitmap than we really need. Since we can't tell for sure, probably
-                // better safe than sorry.
-                return 4;
-        }
-    }
-
-    private static class KeyPool extends BaseKeyPool<Key> {
+    // Visible for testing.
+    static class KeyPool extends BaseKeyPool<Key> {
 
         public Key get(int size) {
             Key result = get();
@@ -139,11 +135,12 @@
         }
     }
 
-    private static class Key implements Poolable {
+    // Visible for testing.
+    static final class Key implements Poolable {
         private final KeyPool pool;
         private int size;
 
-        private Key(KeyPool pool) {
+        Key(KeyPool pool) {
             this.pool = pool;
         }
 
@@ -153,12 +150,11 @@
 
         @Override
         public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-
-            Key key = (Key) o;
-
-            return size == key.size;
+            if (o instanceof Key) {
+                Key other = (Key) o;
+                return size == other.size;
+            }
+            return false;
         }
 
         @Override
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/cache/DiskCache.java b/library/src/main/java/com/bumptech/glide/load/engine/cache/DiskCache.java
index aa27758..c456249 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/cache/DiskCache.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/cache/DiskCache.java
@@ -2,48 +2,51 @@
 
 import com.bumptech.glide.load.Key;
 
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.io.File;
 
 /**
- * An interface for writing to and reading from a disk cache
+ * An interface for writing to and reading from a disk cache.
  */
 public interface DiskCache {
     /**
-     * An interface to actually write data to a key in
-     * the disk cache
+     * An interface to actually write data to a key in the disk cache.
      */
-    public interface Writer {
+    interface Writer {
         /**
-         * Writes data to the output stream and returns true if the write was successful and should be committed, and
+         * Writes data to the file and returns true if the write was successful and should be committed, and
          * false if the write should be aborted.
          *
-         * @param os The output stream the Writer should write to.
+         * @param file The File the Writer should write to.
          */
-        public boolean write(OutputStream os);
+        boolean write(File file);
     }
 
     /**
-     * Get an InputStream for the value at the given key.
+     * Get the cache for the value at the given key.
      *
-     * @param key The key in the cache
-     * @return An InputStream representing the data at key at the time get is called
+     * <p>
+     *     Note - This is potentially dangerous, someone may write a new value to the file at any point in timeand we
+     *     won't know about it.
+     * </p>
+     *
+     * @param key The key in the cache.
+     * @return An InputStream representing the data at key at the time get is called.
      */
-    public InputStream get(Key key);
+    File get(Key key);
 
     /**
-     * Write to a key in the cache. {@link Writer} is used so that the cache implementation
-     * can perform actions after the write finishes, like commit (via atomic file rename).
+     * Write to a key in the cache. {@link Writer} is used so that the cache implementation can perform actions after
+     * the write finishes, like commit (via atomic file rename).
      *
-     * @param key The key to write to
-     * @param writer An interface that will write data given an OutputStream for the key
+     * @param key The key to write to.
+     * @param writer An interface that will write data given an OutputStream for the key.
      */
-    public void put(Key key, Writer writer);
+    void put(Key key, Writer writer);
 
     /**
-     * Remove the key and value from the cache
+     * Remove the key and value from the cache.
      *
-     * @param key The key to remove
+     * @param key The key to remove.
      */
-    public void delete(Key key);
+    void delete(Key key);
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/cache/DiskCacheAdapter.java b/library/src/main/java/com/bumptech/glide/load/engine/cache/DiskCacheAdapter.java
index 4b01e20..7027b05 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/cache/DiskCacheAdapter.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/cache/DiskCacheAdapter.java
@@ -2,17 +2,25 @@
 
 import com.bumptech.glide.load.Key;
 
-import java.io.InputStream;
+import java.io.File;
 
+/**
+ * A simple class that returns null for all gets and ignores all writes.
+ */
 public class DiskCacheAdapter implements DiskCache {
     @Override
-    public InputStream get(Key key) {
+    public File get(Key key) {
+        // no op, default for overriders
         return null;
     }
 
     @Override
-    public void put(Key key, Writer writer) { }
+    public void put(Key key, Writer writer) {
+        // no op, default for overriders
+    }
 
     @Override
-    public void delete(Key key) { }
+    public void delete(Key key) {
+        // no op, default for overriders
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/cache/DiskLruCacheWrapper.java b/library/src/main/java/com/bumptech/glide/load/engine/cache/DiskLruCacheWrapper.java
index 496f30f..1264868 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/cache/DiskLruCacheWrapper.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/cache/DiskLruCacheWrapper.java
@@ -5,13 +5,12 @@
 package com.bumptech.glide.load.engine.cache;
 
 import android.util.Log;
+
+import com.bumptech.glide.disklrucache.DiskLruCache;
 import com.bumptech.glide.load.Key;
-import com.jakewharton.disklrucache.DiskLruCache;
 
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 
 /**
  * The default DiskCache implementation. There must be no more than one active instance for a given
@@ -24,8 +23,12 @@
 
     private static final int APP_VERSION = 1;
     private static final int VALUE_COUNT = 1;
-    private static DiskLruCacheWrapper WRAPPER = null;
+    private static DiskLruCacheWrapper wrapper = null;
+
     private final SafeKeyGenerator safeKeyGenerator;
+    private final File directory;
+    private final int maxSize;
+    private DiskLruCache diskLruCache;
 
     /**
      * Get a DiskCache in the given directory and size. If a disk cache has alread been created with
@@ -36,18 +39,14 @@
      * @param maxSize The max size for the disk cache
      * @return The new disk cache with the given arguments, or the current cache if one already exists
      */
-    public synchronized static DiskCache get(File directory, int maxSize) {
-        if (WRAPPER == null) {
-            WRAPPER = new DiskLruCacheWrapper(directory, maxSize);
+    public static synchronized DiskCache get(File directory, int maxSize) {
+        // TODO calling twice with different arguments makes it return the cache for the same directory, it's public!
+        if (wrapper == null) {
+            wrapper = new DiskLruCacheWrapper(directory, maxSize);
         }
-        return WRAPPER;
+        return wrapper;
     }
 
-    private final File directory;
-    private final int maxSize;
-
-    private DiskLruCache diskLruCache;
-
     protected DiskLruCacheWrapper(File directory, int maxSize) {
         this.directory = directory;
         this.maxSize = maxSize;
@@ -62,16 +61,16 @@
     }
 
     @Override
-    public InputStream get(Key key) {
+    public File get(Key key) {
         String safeKey = safeKeyGenerator.getSafeKey(key);
-        InputStream result = null;
+        File result = null;
         try {
             //It is possible that the there will be a put in between these two gets. If so that shouldn't be a problem
             //because we will always put the same value at the same key so our input streams will still represent
             //the same data
-            final DiskLruCache.Snapshot snapshot = getDiskCache().get(safeKey);
-            if (snapshot != null) {
-                result = snapshot.getInputStream(0);
+            final DiskLruCache.Value value = getDiskCache().get(safeKey);
+            if (value != null) {
+                result = value.getFile(0);
             }
         } catch (IOException e) {
             if (Log.isLoggable(TAG, Log.WARN)) {
@@ -86,21 +85,15 @@
         String safeKey = safeKeyGenerator.getSafeKey(key);
         try {
             DiskLruCache.Editor editor = getDiskCache().edit(safeKey);
-            //editor will be null if there are two concurrent puts
-            //worst case just silently fail
+            // Editor will be null if there are two concurrent puts. In the worst case we will just silently fail.
             if (editor != null) {
-                boolean success = false;
-                OutputStream os = null;
                 try {
-                    os = editor.newOutputStream(0);
-                    success = writer.write(os);
-                } finally {
-                    if (os != null) {
-                        os.close();
+                    File file = editor.getFile(0);
+                    if (writer.write(file)) {
+                        editor.commit();
                     }
-                }
-                if (success) {
-                    editor.commit();
+                } finally {
+                    editor.abortUnlessCommitted();
                 }
             }
         } catch (IOException e) {
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/cache/LruResourceCache.java b/library/src/main/java/com/bumptech/glide/load/engine/cache/LruResourceCache.java
index 3a3cc3f..ffe5a6f 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/cache/LruResourceCache.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/cache/LruResourceCache.java
@@ -1,15 +1,22 @@
 package com.bumptech.glide.load.engine.cache;
 
+import android.annotation.SuppressLint;
+
 import com.bumptech.glide.load.Key;
 import com.bumptech.glide.load.engine.Resource;
 import com.bumptech.glide.util.LruCache;
 
-import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
-import static android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE;
-
-public class LruResourceCache extends LruCache<Key, Resource> implements MemoryCache {
+/**
+ * An LRU in memory cache for {@link com.bumptech.glide.load.engine.Resource}s.
+ */
+public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
     private ResourceRemovedListener listener;
 
+    /**
+     * Constructor for LruResourceCache.
+     *
+     * @param size The maximum size in bytes the in memory cache can use.
+     */
     public LruResourceCache(int size) {
         super(size);
     }
@@ -20,27 +27,28 @@
     }
 
     @Override
-    protected void onItemRemoved(Key key, Resource item) {
+    protected void onItemEvicted(Key key, Resource<?> item) {
         if (listener != null) {
             listener.onResourceRemoved(item);
         }
     }
 
     @Override
-    protected int getSize(Resource item) {
+    protected int getSize(Resource<?> item) {
         return item.getSize();
     }
 
+    @SuppressLint("InlinedApi")
+    @Override
     public void trimMemory(int level) {
-        if (level >= TRIM_MEMORY_MODERATE) {
+        if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
             // Nearing middle of list of cached background apps
             // Evict our entire bitmap cache
             clearMemory();
-        } else if (level >= TRIM_MEMORY_BACKGROUND) {
+        } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
             // Entering list of cached background apps
             // Evict oldest half of our bitmap cache
             trimToSize(getCurrentSize() / 2);
         }
-
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/cache/MemoryCache.java b/library/src/main/java/com/bumptech/glide/load/engine/cache/MemoryCache.java
index 23c7d77..f350f71 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/cache/MemoryCache.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/cache/MemoryCache.java
@@ -1,21 +1,30 @@
 package com.bumptech.glide.load.engine.cache;
 
-import android.content.ComponentCallbacks2;
 import com.bumptech.glide.load.Key;
 import com.bumptech.glide.load.engine.Resource;
 
 /**
- * An interface for adding and removing resources from an in memory cache
+ * An interface for adding and removing resources from an in memory cache.
  */
 public interface MemoryCache {
     /**
      * An interface that will be called whenever a bitmap is removed from the cache.
      */
-    public interface ResourceRemovedListener {
-        public void onResourceRemoved(Resource removed);
+    interface ResourceRemovedListener {
+        void onResourceRemoved(Resource<?> removed);
     }
 
     /**
+     * Returns the sum of the sizes of all the contents of the cache in bytes.
+     */
+    int getCurrentSize();
+
+    /**
+     * Returns the current maximum size in bytes of the cache.
+     */
+    int getMaxSize();
+
+    /**
      * Adjust the maximum size of the cache by multiplying the original size of the cache by the given multiplier.
      *
      * <p>
@@ -25,36 +34,40 @@
      *
      * @param multiplier A size multiplier >= 0.
      */
-    public void setSizeMultiplier(float multiplier);
+    void setSizeMultiplier(float multiplier);
 
     /**
      * Removes the value for the given key and returns it if present or null otherwise.
+     *
      * @param key The key.
      */
-    public Resource remove(Key key);
+    Resource<?> remove(Key key);
 
     /**
-     * Add bitmap to the cache with the given key
-     * @param key The key to retrieve the bitmap
-     * @param resource The {@link Resource} to store
-     * @return The old value of key (null if key is not in map)
+     * Add bitmap to the cache with the given key.
+     *
+     * @param key The key to retrieve the bitmap.
+     * @param resource The {@link com.bumptech.glide.load.engine.EngineResource} to store.
+     * @return The old value of key (null if key is not in map).
      */
-    public Resource put(Key key, Resource resource);
+    Resource<?> put(Key key, Resource<?> resource);
 
     /**
-     * Set the listener to be called when a bitmap is removed from the cache
-     * @param listener The listener
+     * Set the listener to be called when a bitmap is removed from the cache.
+     *
+     * @param listener The listener.
      */
-    public void setResourceRemovedListener(ResourceRemovedListener listener);
+    void setResourceRemovedListener(ResourceRemovedListener listener);
 
     /**
      * Evict all items from the memory cache.
      */
-    public void clearMemory();
+    void clearMemory();
 
     /**
      * Trim the memory cache to the appropriate level. Typically called on the callback onTrimMemory.
-     * @param level This integer represents a trim level as specified in {@link ComponentCallbacks2}
+     *
+     * @param level This integer represents a trim level as specified in {@link android.content.ComponentCallbacks2}.
      */
-    public void trimMemory(int level);
+    void trimMemory(int level);
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/cache/MemoryCacheAdapter.java b/library/src/main/java/com/bumptech/glide/load/engine/cache/MemoryCacheAdapter.java
index ab5ad84..9fe4aeb 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/cache/MemoryCacheAdapter.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/cache/MemoryCacheAdapter.java
@@ -3,22 +3,35 @@
 import com.bumptech.glide.load.Key;
 import com.bumptech.glide.load.engine.Resource;
 
+/**
+ * A simple class that ignores all puts and returns null for all gets.
+ */
 public class MemoryCacheAdapter implements MemoryCache {
 
     private ResourceRemovedListener listener;
 
     @Override
+    public int getCurrentSize() {
+        return 0;
+    }
+
+    @Override
+    public int getMaxSize() {
+        return 0;
+    }
+
+    @Override
     public void setSizeMultiplier(float multiplier) {
         // Do nothing.
     }
 
     @Override
-    public Resource remove(Key key) {
+    public Resource<?> remove(Key key) {
         return null;
     }
 
     @Override
-    public Resource put(Key key, Resource resource) {
+    public Resource<?> put(Key key, Resource<?> resource) {
         listener.onResourceRemoved(resource);
         return null;
     }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/cache/MemorySizeCalculator.java b/library/src/main/java/com/bumptech/glide/load/engine/cache/MemorySizeCalculator.java
index b863b56..d3f9d75 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/cache/MemorySizeCalculator.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/cache/MemorySizeCalculator.java
@@ -7,6 +7,10 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 
+/**
+ * A calculator that tries to intelligently determine cache sizes for a given device based on some constants and the
+ * devices screen density, width, and height.
+ */
 public class MemorySizeCalculator {
     private static final String TAG = "MemorySizeCalculator";
 
@@ -20,8 +24,8 @@
     private final int memoryCacheSize;
 
     interface ScreenDimensions {
-        public int getWidthPixels();
-        public int getHeightPixels();
+        int getWidthPixels();
+        int getHeightPixels();
     }
 
     public MemorySizeCalculator(Context context) {
@@ -55,10 +59,16 @@
         }
     }
 
+    /**
+     * Returns the recommended memory cache size for the device it is run on in bytes.
+     */
     public int getMemoryCacheSize() {
         return memoryCacheSize;
     }
 
+    /**
+     * Returns the recommended bitmap pool size for the device it is run on in bytes.
+     */
     public int getBitmapPoolSize() {
         return bitmapPoolSize;
     }
@@ -66,22 +76,23 @@
     private static int getMaxSize(ActivityManager activityManager) {
         final int memoryClassBytes = activityManager.getMemoryClass() * 1024 * 1024;
         final boolean isLowMemoryDevice = isLowMemoryDevice(activityManager);
-        return Math.round(memoryClassBytes *
-                (isLowMemoryDevice ? LOW_MEMORY_MAX_SIZE_MULTIPLIER : MAX_SIZE_MULTIPLIER));
+        return Math.round(memoryClassBytes
+                * (isLowMemoryDevice ? LOW_MEMORY_MAX_SIZE_MULTIPLIER : MAX_SIZE_MULTIPLIER));
     }
 
     private static int toMb(int bytes) {
         return bytes / (1024 * 1024);
     }
 
-    @TargetApi(19)
+    @TargetApi(Build.VERSION_CODES.KITKAT)
     private static boolean isLowMemoryDevice(ActivityManager activityManager) {
         final int sdkInt = Build.VERSION.SDK_INT;
-        return sdkInt < 11 || (sdkInt >= 19 && activityManager.isLowRamDevice());
+        return sdkInt < Build.VERSION_CODES.HONEYCOMB
+                || (sdkInt >= Build.VERSION_CODES.KITKAT && activityManager.isLowRamDevice());
     }
 
     private static class DisplayMetricsScreenDimensions implements ScreenDimensions {
-        private DisplayMetrics displayMetrics;
+        private final DisplayMetrics displayMetrics;
 
         public DisplayMetricsScreenDimensions(DisplayMetrics displayMetrics) {
             this.displayMetrics = displayMetrics;
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/cache/SafeKeyGenerator.java b/library/src/main/java/com/bumptech/glide/load/engine/cache/SafeKeyGenerator.java
index 2b7a658..a2594f7 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/cache/SafeKeyGenerator.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/cache/SafeKeyGenerator.java
@@ -8,7 +8,10 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 
-public class SafeKeyGenerator {
+/**
+ * A class that generates and caches safe and unique string file names from {@link com.bumptech.glide.load.Key}s.
+ */
+class SafeKeyGenerator {
     private final LruCache<Key, String> loadIdToSafeHash = new LruCache<Key, String>(1000);
 
     public String getSafeKey(Key key) {
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/cache/StringKey.java b/library/src/main/java/com/bumptech/glide/load/engine/cache/StringKey.java
deleted file mode 100644
index 41afd9f..0000000
--- a/library/src/main/java/com/bumptech/glide/load/engine/cache/StringKey.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.bumptech.glide.load.engine.cache;
-
-import com.bumptech.glide.load.Key;
-
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-
-public class StringKey implements Key {
-    private String key;
-
-    public StringKey(String key) {
-        this.key = key;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        StringKey stringKey = (StringKey) o;
-
-        if (!key.equals(stringKey.key)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        return key.hashCode();
-    }
-
-    @Override
-    public String toString() {
-        return key;
-    }
-
-    @Override
-    public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException {
-        messageDigest.update(key.getBytes("UTF-8"));
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/executor/FifoPriorityThreadPoolExecutor.java b/library/src/main/java/com/bumptech/glide/load/engine/executor/FifoPriorityThreadPoolExecutor.java
index a451cac..db0a128 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/executor/FifoPriorityThreadPoolExecutor.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/executor/FifoPriorityThreadPoolExecutor.java
@@ -18,7 +18,9 @@
     AtomicInteger ordering = new AtomicInteger();
 
     /**
-     * Constructor to build a fixed thread pool with the given pool size using {@link DefaultThreadFactory}.
+     * Constructor to build a fixed thread pool with the given pool size using
+     * {@link com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor.DefaultThreadFactory}.
+     *
      * @param poolSize The number of threads.
      */
     public FifoPriorityThreadPoolExecutor(int poolSize) {
@@ -32,14 +34,18 @@
 
     @Override
     protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
-        return new FifoPriorityLoadTask<T>(runnable, value, ordering.getAndIncrement());
+        return new LoadTask<T>(runnable, value, ordering.getAndIncrement());
     }
 
+    /**
+     * A {@link java.util.concurrent.ThreadFactory} that builds threads with priority
+     * {@link android.os.Process#THREAD_PRIORITY_BACKGROUND}.
+     */
     public static class DefaultThreadFactory implements ThreadFactory {
         int threadNum = 0;
         @Override
         public Thread newThread(Runnable runnable) {
-            final Thread result = new Thread(runnable, "image-manager-resize-" + threadNum) {
+            final Thread result = new Thread(runnable, "fifo-pool-thread-" + threadNum) {
                 @Override
                 public void run() {
                     android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
@@ -51,24 +57,42 @@
         }
     }
 
-    private static class FifoPriorityLoadTask<T> extends FutureTask<T> implements Comparable<FifoPriorityLoadTask> {
+    // Visible for testing.
+    static class LoadTask<T> extends FutureTask<T> implements Comparable<LoadTask<?>> {
         private final int priority;
         private final int order;
 
-        public FifoPriorityLoadTask(Runnable runnable, T result, int order) {
+        public LoadTask(Runnable runnable, T result, int order) {
             super(runnable, result);
             if (!(runnable instanceof Prioritized)) {
-                throw new IllegalArgumentException("FifoPriorityThreadPoolExecutor must be given Runnables that " +
-                        "implement Prioritized");
+                throw new IllegalArgumentException("FifoPriorityThreadPoolExecutor must be given Runnables that "
+                        + "implement Prioritized");
             }
             priority = ((Prioritized) runnable).getPriority();
             this.order = order;
         }
 
+        @SuppressWarnings("unchecked")
         @Override
-        public int compareTo(FifoPriorityLoadTask loadTask) {
+        public boolean equals(Object o) {
+            if (o instanceof LoadTask) {
+                LoadTask<Object> other = (LoadTask<Object>) o;
+                return order == other.order && priority == other.priority;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = priority;
+            result = 31 * result + order;
+            return result;
+        }
+
+        @Override
+        public int compareTo(LoadTask<?> loadTask) {
             int result = priority - loadTask.priority;
-            if (result == 0 && loadTask != this) {
+            if (result == 0) {
                 result = order - loadTask.order;
             }
             return result;
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/executor/Prioritized.java b/library/src/main/java/com/bumptech/glide/load/engine/executor/Prioritized.java
index 45ecdbe..6e19d2e 100644
--- a/library/src/main/java/com/bumptech/glide/load/engine/executor/Prioritized.java
+++ b/library/src/main/java/com/bumptech/glide/load/engine/executor/Prioritized.java
@@ -8,5 +8,5 @@
     /**
      * Returns the priority of this task.
      */
-    public int getPriority();
+    int getPriority();
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillRunner.java b/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillRunner.java
new file mode 100644
index 0000000..6af89c1
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFillRunner.java
@@ -0,0 +1,161 @@
+package com.bumptech.glide.load.engine.prefill;
+
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.bumptech.glide.load.Key;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.engine.cache.MemoryCache;
+import com.bumptech.glide.load.resource.bitmap.BitmapResource;
+import com.bumptech.glide.util.Util;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class that allocates {@link android.graphics.Bitmap Bitmaps} to make sure that the
+ * {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} is pre-populated.
+ *
+ * <p>By posting to the main thread with backoffs, we try to avoid ANRs when the garbage collector gets into a state
+ * where a high percentage of {@link Bitmap} allocations trigger a stop the world GC. We try to detect whether or not a
+ * GC has occurred by only allowing our allocator to run for a limited number of milliseconds. Since the allocations
+ * themselves very fast, a GC is the most likely reason for a substantial delay. If we detect our allocator has run for
+ * more than our limit, we assume a GC has occurred, stop the current allocations, and try again after a delay.
+ */
+final class BitmapPreFillRunner implements Runnable {
+    private static final String TAG = "PreFillRunner";
+    private static final Clock DEFAULT_CLOCK = new Clock();
+
+    /**
+     * The maximum number of millis we can run before posting. Set to match and detect the duration of non concurrent
+     * GCs.
+     */
+    static final long MAX_DURATION_MS = 32;
+
+    /**
+     * The amount of time in ms we wait before continuing to allocate after the first GC is detected.
+     */
+    static final long INITIAL_BACKOFF_MS = 40;
+
+    /**
+     * The amount by which the current backoff time is multiplied each time we detect a GC.
+     */
+    static final int BACKOFF_RATIO = 4;
+
+    /**
+     * The maximum amount of time in ms we wait before continuing to allocate.
+     */
+    static final long MAX_BACKOFF_MS = TimeUnit.SECONDS.toMillis(1);
+
+    private final BitmapPool bitmapPool;
+    private final MemoryCache memoryCache;
+    private final PreFillQueue toPrefill;
+    private final Clock clock;
+    private final Set<PreFillType> seenTypes = new HashSet<PreFillType>();
+    private final Handler handler;
+
+    private long currentDelay = INITIAL_BACKOFF_MS;
+    private boolean isCancelled;
+
+    public BitmapPreFillRunner(BitmapPool bitmapPool, MemoryCache memoryCache, PreFillQueue allocationOrder) {
+        this(bitmapPool, memoryCache, allocationOrder, DEFAULT_CLOCK, new Handler(Looper.getMainLooper()));
+    }
+
+    // Visible for testing.
+    BitmapPreFillRunner(BitmapPool bitmapPool, MemoryCache memoryCache, PreFillQueue allocationOrder, Clock clock,
+            Handler handler) {
+        this.bitmapPool = bitmapPool;
+        this.memoryCache = memoryCache;
+        this.toPrefill = allocationOrder;
+        this.clock = clock;
+        this.handler = handler;
+    }
+
+    public void cancel() {
+        isCancelled = true;
+    }
+
+    /**
+     * Attempts to allocate {@link android.graphics.Bitmap}s and returns {@code true} if there are more
+     * {@link android.graphics.Bitmap}s to allocate and {@code false} otherwise.
+     */
+    private boolean allocate() {
+        long start = clock.now();
+        while (!toPrefill.isEmpty() && !isGcDetected(start)) {
+            PreFillType toAllocate = toPrefill.remove();
+            Bitmap bitmap = Bitmap.createBitmap(toAllocate.getWidth(), toAllocate.getHeight(),
+                    toAllocate.getConfig());
+
+            // Don't over fill the memory cache to avoid evicting useful resources, but make sure it's not empty so
+            // we use all available space.
+            if (getFreeMemoryCacheBytes() >= Util.getBitmapByteSize(bitmap)) {
+                memoryCache.put(new UniqueKey(), BitmapResource.obtain(bitmap, bitmapPool));
+            } else {
+                addToBitmapPool(toAllocate, bitmap);
+            }
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "allocated [" + toAllocate.getWidth() + "x" + toAllocate.getHeight() + "] "
+                        + toAllocate.getConfig() + " size: " + Util.getBitmapByteSize(bitmap));
+            }
+        }
+
+        return !isCancelled && !toPrefill.isEmpty();
+    }
+
+    private boolean isGcDetected(long startTimeMs) {
+        return clock.now() - startTimeMs >= MAX_DURATION_MS;
+    }
+
+    private int getFreeMemoryCacheBytes() {
+        return memoryCache.getMaxSize() - memoryCache.getCurrentSize();
+    }
+
+    private void addToBitmapPool(PreFillType toAllocate, Bitmap bitmap) {
+        // The pool may not move sizes to the front of the LRU on put. Do a get here to make sure the size we're adding
+        // is at the front of the queue so that the Bitmap we're adding won't be evicted immediately.
+        if (seenTypes.add(toAllocate)) {
+          Bitmap fromPool = bitmapPool.get(toAllocate.getWidth(), toAllocate.getHeight(),
+              toAllocate.getConfig());
+            if (fromPool != null) {
+                bitmapPool.put(fromPool);
+            }
+        }
+
+        bitmapPool.put(bitmap);
+    }
+
+    @Override
+    public void run() {
+        if (allocate()) {
+            handler.postDelayed(this, getNextDelay());
+        }
+    }
+
+    private long getNextDelay() {
+        long result = currentDelay;
+        currentDelay = Math.min(currentDelay * BACKOFF_RATIO, MAX_BACKOFF_MS);
+        return result;
+    }
+
+    private static class UniqueKey implements Key {
+
+        @Override
+        public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException {
+            // Do nothing.
+        }
+    }
+
+    // Visible for testing.
+    static class Clock {
+        public long now() {
+            return SystemClock.currentThreadTimeMillis();
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFiller.java b/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFiller.java
new file mode 100644
index 0000000..f2e9acc
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/engine/prefill/BitmapPreFiller.java
@@ -0,0 +1,80 @@
+package com.bumptech.glide.load.engine.prefill;
+
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.Looper;
+
+import com.bumptech.glide.load.DecodeFormat;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.engine.cache.MemoryCache;
+import com.bumptech.glide.util.Util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class for pre-filling {@link android.graphics.Bitmap Bitmaps} in a
+ * {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool}.
+ */
+public final class BitmapPreFiller {
+
+    private final MemoryCache memoryCache;
+    private final BitmapPool bitmapPool;
+    private final DecodeFormat defaultFormat;
+    private final Handler handler = new Handler(Looper.getMainLooper());
+
+    private BitmapPreFillRunner current;
+
+    public BitmapPreFiller(MemoryCache memoryCache, BitmapPool bitmapPool, DecodeFormat defaultFormat) {
+        this.memoryCache = memoryCache;
+        this.bitmapPool = bitmapPool;
+        this.defaultFormat = defaultFormat;
+    }
+
+    public void preFill(PreFillType.Builder... bitmapAttributeBuilders) {
+        if (current != null) {
+            current.cancel();
+        }
+
+        PreFillType[] bitmapAttributes = new PreFillType[bitmapAttributeBuilders.length];
+        for (int i = 0; i < bitmapAttributeBuilders.length; i++) {
+            PreFillType.Builder builder = bitmapAttributeBuilders[i];
+            if (builder.getConfig() == null) {
+                builder.setConfig(defaultFormat == DecodeFormat.ALWAYS_ARGB_8888
+                        ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
+            }
+            bitmapAttributes[i] = builder.build();
+        }
+
+        PreFillQueue allocationOrder = generateAllocationOrder(bitmapAttributes);
+        current = new BitmapPreFillRunner(bitmapPool, memoryCache, allocationOrder);
+        handler.post(current);
+    }
+
+    // Visible for testing.
+    PreFillQueue generateAllocationOrder(PreFillType[] preFillSizes) {
+        final int maxSize = memoryCache.getMaxSize() - memoryCache.getCurrentSize() + bitmapPool.getMaxSize();
+
+        int totalWeight = 0;
+        for (PreFillType size : preFillSizes) {
+            totalWeight += size.getWeight();
+        }
+
+        final float bytesPerWeight = maxSize / (float) totalWeight;
+
+        Map<PreFillType, Integer> attributeToCount = new HashMap<PreFillType, Integer>();
+        for (PreFillType size : preFillSizes) {
+            int bytesForSize = Math.round(bytesPerWeight * size.getWeight());
+            int bytesPerBitmap = getSizeInBytes(size);
+            int bitmapsForSize = bytesForSize / bytesPerBitmap;
+            attributeToCount.put(size, bitmapsForSize);
+        }
+
+        return new PreFillQueue(attributeToCount);
+    }
+
+    private static int getSizeInBytes(PreFillType size) {
+        return Util.getBitmapByteSize(size.getWidth(), size.getHeight(), size.getConfig());
+    }
+}
+
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/prefill/PreFillQueue.java b/library/src/main/java/com/bumptech/glide/load/engine/prefill/PreFillQueue.java
new file mode 100644
index 0000000..f88032a
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/engine/prefill/PreFillQueue.java
@@ -0,0 +1,49 @@
+package com.bumptech.glide.load.engine.prefill;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+final class PreFillQueue {
+
+    private final Map<PreFillType, Integer> bitmapsPerType;
+    private final List<PreFillType> keyList;
+    private int bitmapsRemaining;
+    private int keyIndex;
+
+    public PreFillQueue(Map<PreFillType, Integer> bitmapsPerType) {
+        this.bitmapsPerType = bitmapsPerType;
+        // We don't particularly care about the initial order.
+        keyList = new ArrayList<PreFillType>(bitmapsPerType.keySet());
+
+        for (Integer count : bitmapsPerType.values()) {
+            bitmapsRemaining += count;
+        }
+    }
+
+    public PreFillType remove() {
+        PreFillType result = keyList.get(keyIndex);
+
+        Integer countForResult = bitmapsPerType.get(result);
+        if (countForResult == 1) {
+            bitmapsPerType.remove(result);
+            keyList.remove(keyIndex);
+        } else {
+            bitmapsPerType.put(result, countForResult - 1);
+        }
+        bitmapsRemaining--;
+
+        // Avoid divide by 0.
+        keyIndex =  keyList.isEmpty() ? 0 : (keyIndex + 1) % keyList.size();
+
+        return result;
+    }
+
+    public int getSize() {
+        return bitmapsRemaining;
+    }
+
+    public boolean isEmpty() {
+        return bitmapsRemaining == 0;
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/engine/prefill/PreFillType.java b/library/src/main/java/com/bumptech/glide/load/engine/prefill/PreFillType.java
new file mode 100644
index 0000000..5037b35
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/engine/prefill/PreFillType.java
@@ -0,0 +1,172 @@
+package com.bumptech.glide.load.engine.prefill;
+
+import android.graphics.Bitmap;
+
+/**
+ * A container for a set of options used to pre-fill a {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool}
+ * with {@link Bitmap Bitmaps} of a single size and configuration.
+ */
+public final class PreFillType {
+    // Visible for testing.
+    static final Bitmap.Config DEFAULT_CONFIG = Bitmap.Config.RGB_565;
+    private final int width;
+    private final int height;
+    private final Bitmap.Config config;
+    private final int weight;
+
+    /**
+     * Constructor for a single type of {@link android.graphics.Bitmap}.
+     *
+     * @param width The width in pixels of the {@link android.graphics.Bitmap Bitmaps} to
+     *              pre-fill.
+     * @param height The height in pixels of the {@link android.graphics.Bitmap Bitmaps} to
+     *               pre-fill.
+     * @param config The {@link android.graphics.Bitmap.Config} of the {@link android.graphics.Bitmap Bitmaps} to
+     *               pre-fill.
+     * @param weight An integer indicating how to balance pre-filling this size and configuration of
+     * {@link android.graphics.Bitmap} against any other sizes/configurations that may be being pre-filled.
+     */
+    PreFillType(int width, int height, Bitmap.Config config, int weight) {
+        if (config == null) {
+            throw new NullPointerException("Config must not be null");
+        }
+
+        this.width = width;
+        this.height = height;
+        this.config = config;
+        this.weight = weight;
+    }
+
+    /**
+     * Returns the width in pixels of the {@link android.graphics.Bitmap Bitmaps}.
+     */
+    int getWidth() {
+        return width;
+    }
+
+    /**
+     * Returns the height in pixels of the {@link android.graphics.Bitmap Bitmaps}.
+     */
+    int getHeight() {
+        return height;
+    }
+
+    /**
+     * Returns the {@link android.graphics.Bitmap.Config} of the {@link android.graphics.Bitmap Bitmaps}.
+     */
+    Bitmap.Config getConfig() {
+        return config;
+    }
+
+    /**
+     * Returns the weight of the {@link android.graphics.Bitmap Bitmaps} of this type.
+     */
+    int getWeight() {
+        return weight;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof PreFillType) {
+            PreFillType other = (PreFillType) o;
+            return height == other.height
+                    && width == other.width
+                    && weight == other.weight
+                    && config == other.config;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = width;
+        result = 31 * result + height;
+        result = 31 * result + config.hashCode();
+        result = 31 * result + weight;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "PreFillSize{"
+                + "width=" + width
+                + ", height=" + height
+                + ", config=" + config
+                + ", weight=" + weight
+                + '}';
+    }
+
+    /**
+     * Builder for {@link PreFillType}.
+     */
+    public static class Builder {
+        private final int width;
+        private final int height;
+
+        private Bitmap.Config config;
+        private int weight = 1;
+
+        /**
+         * Constructor for a builder that uses the given size as the width and height of the Bitmaps to prefill.
+         * @param size The width and height in pixels of the Bitmaps to prefill.
+         */
+        public Builder(int size) {
+            this(size, size);
+        }
+
+        /**
+         * Constructor for a builder that uses the given dimensions as the dimensions of the Bitmaps to prefill.
+         * @param width The width in pixels of the Bitmaps to prefill.
+         * @param height The height in pixels of the Bitmaps to prefill.
+         */
+        public Builder(int width, int height) {
+            if (width <= 0) {
+                throw new IllegalArgumentException("Width must be > 0");
+            }
+            if (height <= 0) {
+                throw new IllegalArgumentException("Height must be > 0");
+            }
+            this.width = width;
+            this.height = height;
+        }
+
+        /**
+         * Sets the {@link android.graphics.Bitmap.Config} for the Bitmaps to pre-fill.
+         * @param config The config to use, or null to use Glide's default.
+         * @return This builder.
+         */
+        public Builder setConfig(Bitmap.Config config) {
+            this.config = config;
+            return this;
+        }
+
+        /**
+         * Returns the current {@link android.graphics.Bitmap.Config}.
+         */
+        Bitmap.Config getConfig() {
+            return config;
+        }
+
+        /**
+         * Sets the weight to use to balance how many Bitmaps of this type are prefilled relative to the other requested
+         * types.
+         * @param weight An integer indicating how to balance pre-filling this size and configuration of
+         * {@link android.graphics.Bitmap} against any other sizes/configurations that may be being pre-filled.
+         * @return This builder.
+         */
+        public Builder setWeight(int weight) {
+            if (weight <= 0) {
+                throw new IllegalArgumentException("Weight must be > 0");
+            }
+            this.weight = weight;
+            return this;
+        }
+
+        /**
+         * Returns a new {@link PreFillType}.
+         */
+        PreFillType build() {
+            return new PreFillType(width, height, config, weight);
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/model/AssetUriParser.java b/library/src/main/java/com/bumptech/glide/load/model/AssetUriParser.java
new file mode 100644
index 0000000..a7861e6
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/model/AssetUriParser.java
@@ -0,0 +1,36 @@
+package com.bumptech.glide.load.model;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+
+/**
+ * A utility class for parsing Asset uris that look like: file:///android_asset/some/path/in/assets/folder.
+ */
+final class AssetUriParser {
+    private static final String ASSET_PATH_SEGMENT = "android_asset";
+    private static final String ASSET_PREFIX = ContentResolver.SCHEME_FILE + ":///" + ASSET_PATH_SEGMENT + "/";
+    private static final int ASSET_PREFIX_LENGTH = ASSET_PREFIX.length();
+
+    private AssetUriParser() {
+        // Utility constructor.
+    }
+
+    /**
+     * Returns true if the given {@link android.net.Uri} matches the asset uri pattern.
+     */
+    public static boolean isAssetUri(Uri uri) {
+        return ContentResolver.SCHEME_FILE.equals(uri.getScheme()) && !uri.getPathSegments().isEmpty()
+                && ASSET_PATH_SEGMENT.equals(uri.getPathSegments().get(0));
+    }
+
+    /**
+     * Returns the string path for the given asset uri.
+     *
+     * <p>
+     *     Assumes the given {@link android.net.Uri} is in fact an asset uri.
+     * </p>
+     */
+    public static String toAssetPath(Uri uri) {
+        return uri.toString().substring(ASSET_PREFIX_LENGTH);
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/model/FileLoader.java b/library/src/main/java/com/bumptech/glide/load/model/FileLoader.java
index ae061fc..87d9517 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/FileLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/FileLoader.java
@@ -1,12 +1,16 @@
 package com.bumptech.glide.load.model;
 
 import android.net.Uri;
+
 import com.bumptech.glide.load.data.DataFetcher;
 
 import java.io.File;
 
 /**
- * A simple model loader for {@link File}
+ * A simple model loader for loading data from {@link File}s.
+ *
+ * @param <T> The type of data loaded from the given {@link java.io.File} ({@link java.io.InputStream} or
+ *           {@link java.io.FileDescriptor} etc).
  */
 public class FileLoader<T> implements ModelLoader<File, T> {
 
diff --git a/library/src/main/java/com/bumptech/glide/load/model/GenericLoaderFactory.java b/library/src/main/java/com/bumptech/glide/load/model/GenericLoaderFactory.java
index 70be4de..9900322 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/GenericLoaderFactory.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/GenericLoaderFactory.java
@@ -1,19 +1,22 @@
 package com.bumptech.glide.load.model;
 
 import android.content.Context;
+
 import com.bumptech.glide.load.data.DataFetcher;
 
 import java.util.HashMap;
 import java.util.Map;
 
 /**
- * Maintain a map of model class to factory to retrieve a {@link ModelLoaderFactory} and/or a {@link ModelLoader}
+ * Maintains a map of model class to factory to retrieve a {@link ModelLoaderFactory} and/or a {@link ModelLoader}
  * for a given model type.
  */
+@SuppressWarnings({ "rawtypes", "unchecked" })
+// this is a general class capable of handling any generic combination
 public class GenericLoaderFactory {
-    private Map<Class, Map<Class, ModelLoaderFactory>> modelClassToResourceFactories =
+    private final Map<Class/*T*/, Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/>> modelClassToResourceFactories =
             new HashMap<Class, Map<Class, ModelLoaderFactory>>();
-    private Map<Class, Map<Class, ModelLoader>> cachedModelLoaders =
+    private final Map<Class/*T*/, Map<Class/*Y*/, ModelLoader/*T, Y*/>> cachedModelLoaders =
             new HashMap<Class, Map<Class, ModelLoader>>();
 
     private static final ModelLoader NULL_MODEL_LOADER = new ModelLoader() {
@@ -28,6 +31,12 @@
         }
     };
 
+    private final Context context;
+
+    public GenericLoaderFactory(Context context) {
+       this.context = context.getApplicationContext();
+    }
+
     /**
      * Removes and returns the registered {@link ModelLoaderFactory} for the given model and resource classes. Returns
      * null if no such factory is registered. Clears all cached model loaders.
@@ -37,12 +46,11 @@
      * @param <T> The type of the model the class.
      * @param <Y> The type of the resource class.
      */
-    @SuppressWarnings("unchecked")
-    public <T, Y> ModelLoaderFactory<T, Y> unregister(Class<T> modelClass, Class<Y> resourceClass) {
+    public synchronized <T, Y> ModelLoaderFactory<T, Y> unregister(Class<T> modelClass, Class<Y> resourceClass) {
         cachedModelLoaders.clear();
 
-        ModelLoaderFactory<T, Y> result = null;
-        Map<Class, ModelLoaderFactory> resourceToFactories = modelClassToResourceFactories.get(modelClass);
+        ModelLoaderFactory/*T, Y*/ result = null;
+        Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/> resourceToFactories = modelClassToResourceFactories.get(modelClass);
         if (resourceToFactories != null) {
             result = resourceToFactories.remove(resourceClass);
         }
@@ -60,24 +68,23 @@
      * @param <T> The type of the model.
      * @param <Y> The type of the resource.
      */
-    @SuppressWarnings("unchecked")
-    public <T, Y> ModelLoaderFactory<T, Y> register(Class<T> modelClass, Class<Y> resourceClass,
+    public synchronized <T, Y> ModelLoaderFactory<T, Y> register(Class<T> modelClass, Class<Y> resourceClass,
             ModelLoaderFactory<T, Y> factory) {
         cachedModelLoaders.clear();
 
-        Map<Class, ModelLoaderFactory> resourceToFactories = modelClassToResourceFactories.get(modelClass);
+        Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/> resourceToFactories = modelClassToResourceFactories.get(modelClass);
         if (resourceToFactories == null) {
-            resourceToFactories = new HashMap<Class, ModelLoaderFactory>();
+            resourceToFactories = new HashMap<Class/*Y*/, ModelLoaderFactory/*T, Y*/>();
             modelClassToResourceFactories.put(modelClass, resourceToFactories);
         }
 
-        ModelLoaderFactory<T, Y> previous = resourceToFactories.put(resourceClass, factory);
+        ModelLoaderFactory/*T, Y*/ previous = resourceToFactories.put(resourceClass, factory);
 
         if (previous != null) {
             // This factory may be being used by another model. We don't want to say it has been removed unless we
             // know it has been removed for all models.
-            for (Map<Class, ModelLoaderFactory> currentResourceToFactories : modelClassToResourceFactories.values()) {
-                if (currentResourceToFactories.containsValue(previous)) {
+            for (Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/> factories : modelClassToResourceFactories.values()) {
+                if (factories.containsValue(previous)) {
                     previous = null;
                     break;
                 }
@@ -92,17 +99,35 @@
      * {@link ModelLoader} or building a new a new {@link ModelLoader} using registered {@link ModelLoaderFactory}s.
      * Returns null if no {@link ModelLoaderFactory} is registered for the given classes.
      *
+     * @deprecated Use {@link #buildModelLoader(Class, Class)} instead. Scheduled to be removed in Glide 4.0.
+     * @param modelClass The model class.
+     * @param resourceClass The resource class.
+     * @param context Unused
+     * @param <T> The type of the model.
+     * @param <Y> The type of the resource.
+     */
+    @Deprecated
+    public synchronized <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass,
+            Context context) {
+        return buildModelLoader(modelClass, resourceClass);
+    }
+
+    /**
+     * Returns a {@link ModelLoader} for the given model and resource classes by either returning a cached
+     * {@link ModelLoader} or building a new a new {@link ModelLoader} using registered {@link ModelLoaderFactory}s.
+     * Returns null if no {@link ModelLoaderFactory} is registered for the given classes.
+     *
      * @param modelClass The model class.
      * @param resourceClass The resource class.
      * @param <T> The type of the model.
      * @param <Y> The type of the resource.
      */
-    public <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass, Context context) {
+    public synchronized <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass) {
         ModelLoader<T, Y> result = getCachedLoader(modelClass, resourceClass);
         if (result != null) {
-            // We've already tried to create a model loader and can't with the currently registered set of factories, but
-            // we can't use null to demonstrate that failure because model loaders that haven't been requested yet will
-            // be null in the cache. To avoid this, we use a special signal model loader.
+            // We've already tried to create a model loader and can't with the currently registered set of factories,
+            // but we can't use null to demonstrate that failure because model loaders that haven't been requested
+            // yet will be null in the cache. To avoid this, we use a special signal model loader.
             if (NULL_MODEL_LOADER.equals(result)) {
                 return null;
             } else {
@@ -121,24 +146,22 @@
         return result;
     }
 
-    @SuppressWarnings("unchecked")
     private <T, Y> void cacheNullLoader(Class<T> modelClass, Class<Y> resourceClass) {
         cacheModelLoader(modelClass, resourceClass, NULL_MODEL_LOADER);
     }
 
     private <T, Y> void cacheModelLoader(Class<T> modelClass, Class<Y> resourceClass, ModelLoader<T, Y> modelLoader) {
-        Map<Class, ModelLoader> resourceToLoaders = cachedModelLoaders.get(modelClass);
+        Map<Class/*Y*/, ModelLoader/*T, Y*/> resourceToLoaders = cachedModelLoaders.get(modelClass);
         if (resourceToLoaders == null) {
-            resourceToLoaders = new HashMap<Class, ModelLoader>();
+            resourceToLoaders = new HashMap<Class/*Y*/, ModelLoader/*T, Y*/>();
             cachedModelLoaders.put(modelClass, resourceToLoaders);
         }
         resourceToLoaders.put(resourceClass, modelLoader);
     }
 
-    @SuppressWarnings("unchecked")
     private <T, Y> ModelLoader<T, Y> getCachedLoader(Class<T> modelClass, Class<Y> resourceClass) {
-        Map<Class, ModelLoader> resourceToLoaders = cachedModelLoaders.get(modelClass);
-        ModelLoader result = null;
+        Map<Class/*Y*/, ModelLoader/*T, Y*/> resourceToLoaders = cachedModelLoaders.get(modelClass);
+        ModelLoader/*T, Y*/ result = null;
         if (resourceToLoaders != null) {
             result = resourceToLoaders.get(resourceClass);
         }
@@ -146,24 +169,22 @@
         return result;
     }
 
-    @SuppressWarnings("unchecked")
     private <T, Y> ModelLoaderFactory<T, Y> getFactory(Class<T> modelClass, Class<Y> resourceClass) {
-        Map<Class, ModelLoaderFactory> resourceToFactories = modelClassToResourceFactories.get(modelClass);
-        ModelLoaderFactory result = null;
+        Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/> resourceToFactories = modelClassToResourceFactories.get(modelClass);
+        ModelLoaderFactory/*T, Y*/ result = null;
         if (resourceToFactories != null) {
             result = resourceToFactories.get(resourceClass);
         }
 
-
         if (result == null) {
-            for (Class registeredModelClass : modelClassToResourceFactories.keySet()) {
+            for (Class<? super T> registeredModelClass : modelClassToResourceFactories.keySet()) {
                 // This accounts for model subclasses, our map only works for exact matches. We should however still
                 // match a subclass of a model with a factory for a super class of that model if if there isn't a
                 // factory for that particular subclass. Uris are a great example of when this happens, most uris
                 // are actually subclasses for Uri, but we'd generally rather load them all with the same factory rather
                 // than trying to register for each subclass individually.
                 if (registeredModelClass.isAssignableFrom(modelClass)) {
-                    Map<Class, ModelLoaderFactory> currentResourceToFactories =
+                    Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/> currentResourceToFactories =
                             modelClassToResourceFactories.get(registeredModelClass);
                     if (currentResourceToFactories != null) {
                         result = currentResourceToFactories.get(resourceClass);
diff --git a/library/src/main/java/com/bumptech/glide/load/model/GlideUrl.java b/library/src/main/java/com/bumptech/glide/load/model/GlideUrl.java
index decfac8..3fb16f7 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/GlideUrl.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/GlideUrl.java
@@ -1,35 +1,65 @@
 package com.bumptech.glide.load.model;
 
+import android.net.Uri;
 import android.text.TextUtils;
 
 import java.net.MalformedURLException;
 import java.net.URL;
 
 /**
- * This is a simple wrapper for strings representing http/https urls. new URL() is an excessively expensive operation
- * that may be unnecessary if the class loading the image from the url doesn't actually require a URL object.
+ * A wrapper for strings representing http/https URLs responsible for ensuring URLs are properly escaped and avoiding
+ * unnecessary URL instantiations for loaders that require only string urls rather than URL objects.
  *
- * Users wishing to replace the class for handling urls must register a factory using GlideUrl.
+ * <p>
+ *  Users wishing to replace the class for handling URLs must register a factory using GlideUrl.
+ * </p>
+ *
+ * <p>
+ *     To obtain a properly escaped URL, call {@link #toURL()}. To obtain a properly escaped string URL, call
+ *     {@link #toURL()} and then {@link java.net.URL#toString()}.
+ * </p>
  */
 public class GlideUrl {
+    private static final String ALLOWED_URI_CHARS = "@#&=*+-_.,:!?()/~'%";
+
+    private final URL url;
     private String stringUrl;
-    private URL url;
+
+    private URL safeUrl;
 
     public GlideUrl(URL url) {
+        if (url == null) {
+            throw new IllegalArgumentException("URL must not be null!");
+        }
         this.url = url;
         stringUrl = null;
     }
 
     public GlideUrl(String url) {
+        if (TextUtils.isEmpty(url)) {
+            throw new IllegalArgumentException("String url must not be empty or null: " + url);
+        }
         this.stringUrl = url;
         this.url = null;
     }
 
+
     public URL toURL() throws MalformedURLException {
-        if (url == null) {
-            url = new URL(stringUrl);
+        return getSafeUrl();
+    }
+
+    // See http://stackoverflow.com/questions/3286067/url-encoding-in-android. Although the answer using URI would work,
+    // using it would require both decoding and encoding each string which is more complicated, slower and generates
+    // more objects than the solution below. See also issue #133.
+    private URL getSafeUrl() throws MalformedURLException {
+        if (safeUrl != null) {
+            return safeUrl;
         }
-        return url;
+        String unsafe = toString();
+        String safe = Uri.encode(unsafe, ALLOWED_URI_CHARS);
+
+        safeUrl = new URL(safe);
+        return safeUrl;
     }
 
     @Override
@@ -49,28 +79,11 @@
             return false;
         }
 
-        GlideUrl glideUrl = (GlideUrl) o;
-        if (stringUrl != null) {
-            if (glideUrl.stringUrl != null) {
-                return stringUrl.equals(glideUrl.stringUrl);
-            } else {
-                return stringUrl.equals(glideUrl.url.toString());
-            }
-        } else {
-            if (glideUrl.stringUrl != null) {
-                return url.toString().equals(glideUrl.stringUrl);
-            } else {
-                return url.equals(glideUrl.url);
-            }
-        }
+        return toString().equals(o.toString());
     }
 
     @Override
     public int hashCode() {
-        if (stringUrl != null) {
-            return stringUrl.hashCode();
-        } else {
-            return url.toString().hashCode();
-        }
+        return toString().hashCode();
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/model/ImageVideoModelLoader.java b/library/src/main/java/com/bumptech/glide/load/model/ImageVideoModelLoader.java
index dcad640..3d057ba 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/ImageVideoModelLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/ImageVideoModelLoader.java
@@ -2,11 +2,21 @@
 
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
+
 import com.bumptech.glide.Priority;
 import com.bumptech.glide.load.data.DataFetcher;
 
 import java.io.InputStream;
 
+/**
+ * A wrapper model loader that provides both an {@link java.io.InputStream} and a
+ * {@link android.os.ParcelFileDescriptor} for a given model type by wrapping an
+ * {@link com.bumptech.glide.load.model.ModelLoader} for {@link java.io.InputStream}s for the given model type and an
+ * {@link com.bumptech.glide.load.model.ModelLoader} for {@link android.os.ParcelFileDescriptor} for the given model
+ * type.
+ *
+ * @param <A> The model type.
+ */
 public class ImageVideoModelLoader<A> implements ModelLoader<A, ImageVideoWrapper> {
     private static final String TAG = "IVML";
 
@@ -32,10 +42,15 @@
         if (fileDescriptorLoader != null) {
             fileDescriptorFetcher = fileDescriptorLoader.getResourceFetcher(model, width, height);
         }
-        return new ImageVideoFetcher(streamFetcher, fileDescriptorFetcher);
+
+        if (streamFetcher != null || fileDescriptorFetcher != null) {
+            return new ImageVideoFetcher(streamFetcher, fileDescriptorFetcher);
+        } else {
+            return null;
+        }
     }
 
-    public static class ImageVideoFetcher implements DataFetcher<ImageVideoWrapper> {
+    static class ImageVideoFetcher implements DataFetcher<ImageVideoWrapper> {
         private final DataFetcher<InputStream> streamFetcher;
         private final DataFetcher<ParcelFileDescriptor> fileDescriptorFetcher;
 
@@ -44,6 +59,9 @@
             this.streamFetcher = streamFetcher;
             this.fileDescriptorFetcher = fileDescriptorFetcher;
         }
+
+        @SuppressWarnings("resource")
+        // @see ModelLoader.loadData
         @Override
         public ImageVideoWrapper loadData(Priority priority) throws Exception {
             InputStream is = null;
diff --git a/library/src/main/java/com/bumptech/glide/load/model/ImageVideoWrapper.java b/library/src/main/java/com/bumptech/glide/load/model/ImageVideoWrapper.java
index e8dedcc..85ee4d3 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/ImageVideoWrapper.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/ImageVideoWrapper.java
@@ -4,6 +4,9 @@
 
 import java.io.InputStream;
 
+/**
+ * A simple wrapper that wraps an {@link java.io.InputStream} and/or an {@link android.os.ParcelFileDescriptor}.
+ */
 public class ImageVideoWrapper {
     private final InputStream streamData;
     private final ParcelFileDescriptor fileDescriptor;
diff --git a/library/src/main/java/com/bumptech/glide/load/model/ImageVideoWrapperEncoder.java b/library/src/main/java/com/bumptech/glide/load/model/ImageVideoWrapperEncoder.java
index abdb938..2d9c59a 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/ImageVideoWrapperEncoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/ImageVideoWrapperEncoder.java
@@ -1,11 +1,17 @@
 package com.bumptech.glide.load.model;
 
 import android.os.ParcelFileDescriptor;
+
 import com.bumptech.glide.load.Encoder;
 
 import java.io.InputStream;
 import java.io.OutputStream;
 
+/**
+ * A source encoder that writes a {@link com.bumptech.glide.load.model.ImageVideoWrapper} to disk by preferentially
+ * writing data from the wrapper's {@link java.io.InputStream} and falling back to the wrapper's
+ * {@link android.os.ParcelFileDescriptor} if the {@link java.io.InputStream} isn't available.
+ */
 public class ImageVideoWrapperEncoder implements Encoder<ImageVideoWrapper> {
     private final Encoder<InputStream> streamEncoder;
     private final Encoder<ParcelFileDescriptor> fileDescriptorEncoder;
diff --git a/library/src/main/java/com/bumptech/glide/load/model/ModelCache.java b/library/src/main/java/com/bumptech/glide/load/model/ModelCache.java
index c77cd6f..55c0de0 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/ModelCache.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/ModelCache.java
@@ -1,8 +1,8 @@
 package com.bumptech.glide.load.model;
 
 import com.bumptech.glide.util.LruCache;
+import com.bumptech.glide.util.Util;
 
-import java.util.ArrayDeque;
 import java.util.Queue;
 
 /**
@@ -10,19 +10,67 @@
  * model, width and height. For a loader that takes a model and returns a url, the cache could be used to safely memoize
  * url creation based on the width and height of the view.
  *
- * @param <A> Some Model type that implements equals and hashcode.
+ * @param <A> Some Model type that implements {@link #equals} and {@link #hashCode}.
  * @param <B> Some useful type that may be expensive to create (URL, file path, etc).
- * //TODO: fix this.
  */
 public class ModelCache<A, B> {
     private static final int DEFAULT_SIZE = 250;
 
-    private static class ModelKey<A> {
-        private static final Queue<ModelKey> KEY_QUEUE = new ArrayDeque<ModelKey>();
+    private final LruCache<ModelKey<A>, B> cache;
 
-        @SuppressWarnings("unchecked")
-        public static <A> ModelKey<A> get(A model, int width, int height) {
-            ModelKey<A> modelKey = KEY_QUEUE.poll();
+    public ModelCache() {
+        this(DEFAULT_SIZE);
+    }
+
+    public ModelCache(int size) {
+        cache = new LruCache<ModelKey<A>, B>(size) {
+            @Override
+            protected void onItemEvicted(ModelKey<A> key, B item) {
+                key.release();
+            }
+        };
+    }
+
+    /**
+     * Get a value.
+     *
+     * @param model The model.
+     * @param width The width in pixels of the view the image is being loaded into.
+     * @param height The height in pixels of the view the image is being loaded into.
+     *
+     * @return The cached result, or null.
+     */
+    public B get(A model, int width, int height) {
+        ModelKey<A> key = ModelKey.get(model, width, height);
+        B result = cache.get(key);
+        key.release();
+        return result;
+    }
+
+    /**
+     * Add a value.
+     *
+     * @param model The model.
+     * @param width The width in pixels of the view the image is being loaded into.
+     * @param height The height in pixels of the view the image is being loaded into.
+     * @param value The value to store.
+     */
+    public void put(A model, int width, int height, B value) {
+        ModelKey<A> key = ModelKey.get(model, width, height);
+        cache.put(key, value);
+    }
+
+    // Visible for testing.
+    static final class ModelKey<A> {
+        private static final Queue<ModelKey<?>> KEY_QUEUE = Util.createQueue(0);
+
+        private int height;
+        private int width;
+        private A model;
+
+        static <A> ModelKey<A> get(A model, int width, int height) {
+            @SuppressWarnings("unchecked")
+            ModelKey<A> modelKey = (ModelKey<A>) KEY_QUEUE.poll();
             if (modelKey == null) {
                 modelKey = new ModelKey<A>();
             }
@@ -31,10 +79,6 @@
             return modelKey;
         }
 
-        private int height;
-        private int width;
-        private A model;
-
         private ModelKey() {  }
 
         private void init(A model, int width, int height) {
@@ -49,16 +93,11 @@
 
         @Override
         public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-
-            ModelKey modelKey = (ModelKey) o;
-
-            if (height != modelKey.height) return false;
-            if (width != modelKey.width) return false;
-            if (!model.equals(modelKey.model)) return false;
-
-            return true;
+            if (o instanceof ModelKey) {
+                ModelKey other = (ModelKey) o;
+                return width == other.width && height == other.height && model.equals(other.model);
+            }
+            return false;
         }
 
         @Override
@@ -69,48 +108,4 @@
             return result;
         }
     }
-
-    private final LruCache<ModelKey<A>, B> cache;
-
-    public ModelCache() {
-        this(DEFAULT_SIZE);
-    }
-
-    public ModelCache(int size) {
-        cache = new LruCache<ModelKey<A>, B>(size) {
-            @Override
-            protected void onItemRemoved(ModelKey<A> key, B item) {
-                key.release();
-            }
-        };
-    }
-
-    /**
-     * Get a value.
-     *
-     * @param model The model.
-     * @param width The width of the view the image is being loaded into.
-     * @param height The height of the view the image is being loaded into.
-     *
-     * @return The cached result, or null.
-     */
-    public B get(A model, int width, int height) {
-        ModelKey<A> key = ModelKey.get(model, width, height);
-        B result = cache.get(key);
-        key.release();
-        return result;
-    }
-
-    /**
-     * Add a value.
-     *
-     * @param model The model.
-     * @param width The width of the view the image is being loaded into.
-     * @param height The height of the view the image is being loaded into.
-     * @param value The value to store.
-     */
-    public void put(A model, int width, int height, B value) {
-        ModelKey<A> key = ModelKey.get(model, width, height);
-        cache.put(key, value);
-    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/model/ModelLoader.java b/library/src/main/java/com/bumptech/glide/load/model/ModelLoader.java
index aced71e..164f1d7 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/ModelLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/ModelLoader.java
@@ -1,49 +1,46 @@
 package com.bumptech.glide.load.model;
 
-import com.bumptech.glide.load.model.stream.StreamStringLoader;
 import com.bumptech.glide.load.data.DataFetcher;
-import com.bumptech.glide.load.resource.bitmap.BitmapDecoder;
 
 /**
- * An interface for translating an arbitrarily complex data model into a concrete data type that can be used by an
- * {@link DataFetcher} to obtain the data for an image represented by the model.
+ * A factory interface for translating an arbitrarily complex data model into a concrete data type that can be used
+ * by an {@link DataFetcher} to obtain the data for a resource represented by the model.
  *
- * This interface has two objectives:
- *   1. To translate a specific data model into something that can be used by a decoder to load a Bitmap.
+ * <p>
+ *  This interface has two objectives:
+ *   1. To translate a specific model into a data type that can be decoded into a resource.
  *
- *   2. To allow a data model to be combined with the dimensions of the view to fetch an image of a specific size.
+ *   2. To allow a model to be combined with the dimensions of the view to fetch a resource of a specific size.
  *
  *      This not only avoids having to duplicate dimensions in xml and in your code in order to determine the size of a
  *      view on devices with different densities, but also allows you to use layout weights or otherwise
- *      programatically set the dimensions of the view without forcing you to fetch a generic image size.
+ *      programatically set the dimensions of the view without forcing you to fetch a generic resource size.
  *
- *      The smaller the image you fetch, the less bandwidth and battery life you use, and the lower your memory
- *      footprint per image.
+ *      The smaller the resource you fetch, the less bandwidth and battery life you use, and the lower your memory
+ *      footprint per resource.
+ *</p>
  *
- *
- * @param <T> The type of the model
- * @param <Y> The type of the data that can be used by a {@link BitmapDecoder}
- *           to decode a Bitmap.
+ * @param <T> The type of the model.
+ * @param <Y> The type of the data that can be used by a {@link com.bumptech.glide.load.ResourceDecoder} to decode a
+ *           resource.
  */
 public interface ModelLoader<T, Y> {
 
     /**
-     * Obtain an {@link DataFetcher} that can load the data required to decode the
-     * image represented by this model. The {@link DataFetcher} will not be used if
-     * the image is already cached.
+     * Obtains an {@link DataFetcher} that can fetch the data required to decode the resource represented by this model.
+     * The {@link DataFetcher} will not be used if the resource is already cached.
      *
      * <p>
-     *     Note - If the {@link StreamStringLoader} in any way retains a reference a context, either directly or as an
-     *     anonymous inner class, that context may be leaked. The leak will only be an issue if this load can run for a
-     *     long time or indefinitely (because of a particularly slow or paused/failed download for example).
+     *     Note - If no valid data fetcher can be returned (for example if a model has a null URL), then it is
+     *     acceptable to return a null data fetcher from this method. Doing so will be treated any other failure or
+     *     exception during the load process.
      * </p>
      *
-     *
-     * @param model The model representing the image
-     * @param width The width of the view the image will be loaded into
-     * @param height The height of the view the image will be loaded into
-     * @return A {@link DataFetcher} that can obtain the data for the image if the
-     *          image is not cached.
+     * @param model The model representing the resource.
+     * @param width The width in pixels of the view or target the resource will be loaded into
+     * @param height The height in pixels of the view or target the resource will be loaded into
+     * @return A {@link DataFetcher} that can obtain the data the resource can be decoded from if the resource is not
+     * cached, or null if no valid {@link com.bumptech.glide.load.data.DataFetcher} could be constructed.
      */
-    public DataFetcher<Y> getResourceFetcher(T model, int width, int height);
+    DataFetcher<Y> getResourceFetcher(T model, int width, int height);
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/model/ModelLoaderFactory.java b/library/src/main/java/com/bumptech/glide/load/model/ModelLoaderFactory.java
index 8d74af3..10f3a33 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/ModelLoaderFactory.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/ModelLoaderFactory.java
@@ -7,6 +7,10 @@
  * retain {@link Context} or any other objects that cannot be retained for the life of the application. ModelLoaders
  * will not be retained statically so it is safe for any ModelLoader built by this factory to retain a reference to a
  * {@link Context}.
+ *
+ * @param <T> The type of the model the {@link com.bumptech.glide.load.model.ModelLoader}s built by this factory
+ *           can handle
+ * @param <Y> The type of data the {@link com.bumptech.glide.load.model.ModelLoader}s built by this factory can load.
  */
 public interface ModelLoaderFactory<T, Y> {
 
@@ -18,10 +22,10 @@
      *                  this factory's {@link ModelLoader} may depend on
      * @return A new {@link ModelLoader}
      */
-    public ModelLoader<T, Y> build(Context context, GenericLoaderFactory factories);
+    ModelLoader<T, Y> build(Context context, GenericLoaderFactory factories);
 
     /**
-     * A lifecycle method that will be called when this factory is about to replaced
+     * A lifecycle method that will be called when this factory is about to replaced.
      */
-    public void teardown();
+    void teardown();
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/model/NullEncoder.java b/library/src/main/java/com/bumptech/glide/load/model/NullEncoder.java
deleted file mode 100644
index 7e2f373..0000000
--- a/library/src/main/java/com/bumptech/glide/load/model/NullEncoder.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.bumptech.glide.load.model;
-
-import com.bumptech.glide.load.Encoder;
-
-import java.io.OutputStream;
-
-public class NullEncoder<T> implements Encoder<T> {
-    private static final NullEncoder NULL_ENCODER = new NullEncoder();
-
-    @SuppressWarnings("unchecked")
-    public static <T> NullEncoder<T> get() {
-        return NULL_ENCODER;
-    }
-
-    @Override
-    public boolean encode(T data, OutputStream os) {
-        return false;
-    }
-
-    @Override
-    public String getId() {
-        return "";
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/model/ResourceLoader.java b/library/src/main/java/com/bumptech/glide/load/model/ResourceLoader.java
index 5691d1e..efc2bd6 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/ResourceLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/ResourceLoader.java
@@ -1,25 +1,39 @@
 package com.bumptech.glide.load.model;
 
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.Resources;
 import android.net.Uri;
+
 import com.bumptech.glide.load.data.DataFetcher;
 
 /**
- * A model loader for handling resources. Model must be a resource id in the package of the given context.
+ * A model loader for handling Android resource files. Model must be an Android resource id in the package of the given
+ * context.
+ *
+ * @param <T> The type of data that will be loaded for the given android resource.
  */
 public class ResourceLoader<T> implements ModelLoader<Integer, T> {
 
     private final ModelLoader<Uri, T> uriLoader;
-    private final Context context;
+    private final Resources resources;
 
     public ResourceLoader(Context context, ModelLoader<Uri, T> uriLoader) {
-        this.context = context;
+        this(context.getResources(), uriLoader);
+    }
+
+    public ResourceLoader(Resources resources, ModelLoader<Uri, T> uriLoader) {
+        this.resources = resources;
         this.uriLoader = uriLoader;
     }
 
     @Override
     public DataFetcher<T> getResourceFetcher(Integer model, int width, int height) {
-        Uri uri = Uri.parse("android.resource://" + context.getPackageName() + "/" + model.toString());
+        Uri uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+                + resources.getResourcePackageName(model) + '/'
+                + resources.getResourceTypeName(model) + '/'
+                + resources.getResourceEntryName(model));
+
         return uriLoader.getResourceFetcher(uri, width, height);
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/model/StreamEncoder.java b/library/src/main/java/com/bumptech/glide/load/model/StreamEncoder.java
index 2788061..1022754 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/StreamEncoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/StreamEncoder.java
@@ -1,5 +1,7 @@
 package com.bumptech.glide.load.model;
 
+import android.util.Log;
+
 import com.bumptech.glide.load.Encoder;
 import com.bumptech.glide.util.ByteArrayPool;
 
@@ -7,22 +9,29 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 
+/**
+ * An {@link com.bumptech.glide.load.Encoder} that can write an {@link java.io.InputStream} to disk.
+ */
 public class StreamEncoder implements Encoder<InputStream> {
+    private static final String TAG = "StreamEncoder";
 
     @Override
     public boolean encode(InputStream data, OutputStream os) {
         byte[] buffer = ByteArrayPool.get().getBytes();
-        int read;
         try {
+            int read;
             while ((read = data.read(buffer)) != -1) {
                     os.write(buffer, 0, read);
             }
+            return true;
         } catch (IOException e) {
-            e.printStackTrace();
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Failed to encode data onto the OutputStream", e);
+            }
             return false;
+        } finally {
+            ByteArrayPool.get().releaseBytes(buffer);
         }
-        ByteArrayPool.get().releaseBytes(buffer);
-        return true;
     }
 
     @Override
diff --git a/library/src/main/java/com/bumptech/glide/load/model/StringLoader.java b/library/src/main/java/com/bumptech/glide/load/model/StringLoader.java
index 630c168..efdf666 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/StringLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/StringLoader.java
@@ -1,6 +1,7 @@
 package com.bumptech.glide.load.model;
 
 import android.net.Uri;
+
 import com.bumptech.glide.load.data.DataFetcher;
 
 import java.io.File;
@@ -8,6 +9,8 @@
 /**
  * A model loader for handling certain string models. Handles paths, urls, and any uri string with a scheme handled by
  * {@link android.content.ContentResolver#openInputStream(Uri)}.
+ *
+ * @param <T> The type of data that will be loaded from the given {@link java.lang.String}.
  */
 public class StringLoader<T> implements ModelLoader<String, T> {
     private final ModelLoader<Uri, T> uriLoader;
@@ -18,12 +21,21 @@
 
     @Override
     public DataFetcher<T> getResourceFetcher(String model, int width, int height) {
-        Uri uri = Uri.parse(model);
-
-        final String scheme = uri.getScheme();
-        if (scheme == null) {
-            uri = Uri.fromFile(new File(model));
+        Uri uri;
+        if (model.startsWith("/")) {
+            uri = toFileUri(model);
+        } else {
+            uri = Uri.parse(model);
+            final String scheme = uri.getScheme();
+            if (scheme == null) {
+                uri = toFileUri(model);
+            }
         }
+
         return uriLoader.getResourceFetcher(uri, width, height);
     }
+
+    private static Uri toFileUri(String path) {
+        return Uri.fromFile(new File(path));
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/model/UriLoader.java b/library/src/main/java/com/bumptech/glide/load/model/UriLoader.java
index b516aca..30db4a6 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/UriLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/UriLoader.java
@@ -3,9 +3,17 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.net.Uri;
+
 import com.bumptech.glide.load.data.DataFetcher;
 
-public abstract class UriLoader<T> implements ModelLoader<Uri, T>{
+/**
+ * A base ModelLoader for {@link android.net.Uri}s that handles local {@link android.net.Uri}s directly and routes
+ * remote {@link android.net.Uri}s to a wrapped {@link com.bumptech.glide.load.model.ModelLoader} that handles
+ * {@link com.bumptech.glide.load.model.GlideUrl}s.
+ *
+ * @param <T> The type of data that will be retrieved for {@link android.net.Uri}s.
+ */
+public abstract class UriLoader<T> implements ModelLoader<Uri, T> {
     private final Context context;
     private final ModelLoader<GlideUrl, T> urlLoader;
 
@@ -20,7 +28,12 @@
 
         DataFetcher<T> result = null;
         if (isLocalUri(scheme)) {
-            result = getLocalUriFetcher(context, model);
+            if (AssetUriParser.isAssetUri(model)) {
+                String path = AssetUriParser.toAssetPath(model);
+                result = getAssetPathFetcher(context, path);
+            } else {
+                result = getLocalUriFetcher(context, model);
+            }
         } else if (urlLoader != null && ("http".equals(scheme) || "https".equals(scheme))) {
             result = urlLoader.getResourceFetcher(new GlideUrl(model.toString()), width, height);
         }
@@ -30,7 +43,9 @@
 
     protected abstract DataFetcher<T> getLocalUriFetcher(Context context, Uri uri);
 
-    private boolean isLocalUri(String scheme) {
+    protected abstract DataFetcher<T> getAssetPathFetcher(Context context, String path);
+
+    private static boolean isLocalUri(String scheme) {
         return ContentResolver.SCHEME_FILE.equals(scheme)
                 || ContentResolver.SCHEME_CONTENT.equals(scheme)
                 || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme);
diff --git a/library/src/main/java/com/bumptech/glide/load/model/UrlLoader.java b/library/src/main/java/com/bumptech/glide/load/model/UrlLoader.java
index 4f2dbb3..6d29b9b 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/UrlLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/UrlLoader.java
@@ -4,6 +4,13 @@
 
 import java.net.URL;
 
+/**
+ * A wrapper class that translates {@link java.net.URL} objects into {@link com.bumptech.glide.load.model.GlideUrl}
+ * objects and then uses the wrapped {@link com.bumptech.glide.load.model.ModelLoader} for
+ * {@link com.bumptech.glide.load.model.GlideUrl}s to load the data.
+ *
+ * @param <T> The type of data that will be loaded from the {@link java.net.URL}s.
+ */
 public class UrlLoader<T> implements ModelLoader<URL, T> {
     private final ModelLoader<GlideUrl, T> glideUrlLoader;
 
diff --git a/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorFileLoader.java b/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorFileLoader.java
index 872cd63..2431df3 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorFileLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorFileLoader.java
@@ -3,6 +3,7 @@
 import android.content.Context;
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
+
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.load.model.FileLoader;
 import com.bumptech.glide.load.model.GenericLoaderFactory;
@@ -12,19 +13,24 @@
 import java.io.File;
 
 /**
- * A {@link ModelLoader} For translating {@link File} models into {@link ParcelFileDescriptor} resources.
+ * A {@link ModelLoader} For translating {@link File} models into {@link ParcelFileDescriptor} data.
  */
-public class FileDescriptorFileLoader extends FileLoader<ParcelFileDescriptor> implements FileDescriptorModelLoader<File> {
+public class FileDescriptorFileLoader extends FileLoader<ParcelFileDescriptor>
+        implements FileDescriptorModelLoader<File> {
 
+    /**
+     * The default {@link com.bumptech.glide.load.model.ModelLoaderFactory} for
+     * {@link com.bumptech.glide.load.model.file_descriptor.FileDescriptorFileLoader}s.
+     */
     public static class Factory implements ModelLoaderFactory<File, ParcelFileDescriptor> {
         @Override
         public ModelLoader<File, ParcelFileDescriptor> build(Context context, GenericLoaderFactory factories) {
-            return new FileDescriptorFileLoader(factories.buildModelLoader(Uri.class, ParcelFileDescriptor.class,
-                    context));
+            return new FileDescriptorFileLoader(factories.buildModelLoader(Uri.class, ParcelFileDescriptor.class));
         }
 
         @Override
         public void teardown() {
+            // Do nothing.
         }
     }
 
diff --git a/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorModelLoader.java b/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorModelLoader.java
index 133d927..c645fdc 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorModelLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorModelLoader.java
@@ -1,14 +1,14 @@
 package com.bumptech.glide.load.model.file_descriptor;
 
 import android.os.ParcelFileDescriptor;
+
 import com.bumptech.glide.load.model.ModelLoader;
 
-import java.io.File;
-
 /**
- * A base class for {@link ModelLoader}s that translate models into {@link File}s.
+ * A base class for {@link ModelLoader}s that translate models into {@link java.io.File}s.
  *
- * @param <T> The type of the model that will be translated into an {@link File}.
+ * @param <T> The type of the model that will be translated into an {@link java.io.File}.
  */
 public interface FileDescriptorModelLoader<T> extends ModelLoader<T, ParcelFileDescriptor> {
+    // specializing the generic arguments
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorResourceLoader.java b/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorResourceLoader.java
index 9472e36..0f107bd1 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorResourceLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorResourceLoader.java
@@ -3,6 +3,7 @@
 import android.content.Context;
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
+
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.load.model.GenericLoaderFactory;
 import com.bumptech.glide.load.model.ModelLoader;
@@ -10,21 +11,26 @@
 import com.bumptech.glide.load.model.ResourceLoader;
 
 /**
- * A {@link ModelLoader} For translating android resource id models into {@link ParcelFileDescriptor} resources.
+ * A {@link ModelLoader} For translating android resource id models into {@link ParcelFileDescriptor} data.
  */
 public class FileDescriptorResourceLoader extends ResourceLoader<ParcelFileDescriptor>
         implements FileDescriptorModelLoader<Integer> {
 
+    /**
+     * The default factory for {@link com.bumptech.glide.load.model.file_descriptor.FileDescriptorResourceLoader}s.
+     */
     public static class Factory implements ModelLoaderFactory<Integer, ParcelFileDescriptor> {
 
         @Override
         public ModelLoader<Integer, ParcelFileDescriptor> build(Context context, GenericLoaderFactory factories) {
-            return new FileDescriptorResourceLoader(context, factories.buildModelLoader(Uri.class, ParcelFileDescriptor.class,
-                    context));
+            return new FileDescriptorResourceLoader(context, factories.buildModelLoader(Uri.class,
+                    ParcelFileDescriptor.class));
         }
 
         @Override
-        public void teardown() { }
+        public void teardown() {
+            // Do nothing.
+        }
     }
 
     public FileDescriptorResourceLoader(Context context) {
diff --git a/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorStringLoader.java b/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorStringLoader.java
index af8c535..c6f335f 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorStringLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorStringLoader.java
@@ -3,6 +3,7 @@
 import android.content.Context;
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
+
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.load.model.GenericLoaderFactory;
 import com.bumptech.glide.load.model.ModelLoader;
@@ -11,20 +12,24 @@
 
 /**
  * A {@link ModelLoader} For translating {@link String} models, such as file paths, into {@link ParcelFileDescriptor}
- * resources.
+ * data.
  */
 public class FileDescriptorStringLoader extends StringLoader<ParcelFileDescriptor>
-        implements FileDescriptorModelLoader<String>{
+        implements FileDescriptorModelLoader<String> {
 
+    /**
+     * The default factory for {@link com.bumptech.glide.load.model.file_descriptor.FileDescriptorStringLoader}s.
+     */
     public static class Factory implements ModelLoaderFactory<String, ParcelFileDescriptor> {
         @Override
         public ModelLoader<String, ParcelFileDescriptor> build(Context context, GenericLoaderFactory factories) {
-            return new FileDescriptorStringLoader(factories.buildModelLoader(Uri.class, ParcelFileDescriptor.class,
-                    context));
+            return new FileDescriptorStringLoader(factories.buildModelLoader(Uri.class, ParcelFileDescriptor.class));
         }
 
         @Override
-        public void teardown() { }
+        public void teardown() {
+            // Do nothing.
+        }
     }
 
     public FileDescriptorStringLoader(Context context) {
diff --git a/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorUriLoader.java b/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorUriLoader.java
index cab36ff..d950461 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorUriLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/file_descriptor/FileDescriptorUriLoader.java
@@ -3,29 +3,36 @@
 import android.content.Context;
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
+
 import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.data.FileDescriptorAssetPathFetcher;
+import com.bumptech.glide.load.data.FileDescriptorLocalUriFetcher;
 import com.bumptech.glide.load.model.GenericLoaderFactory;
 import com.bumptech.glide.load.model.GlideUrl;
 import com.bumptech.glide.load.model.ModelLoader;
 import com.bumptech.glide.load.model.ModelLoaderFactory;
 import com.bumptech.glide.load.model.UriLoader;
-import com.bumptech.glide.load.data.DataFetcher;
-import com.bumptech.glide.load.data.FileDescriptorLocalUriFetcher;
 
 /**
- * A {@link ModelLoader} For translating {@link Uri} models for local uris into {@link ParcelFileDescriptor} resources.
+ * A {@link ModelLoader} For translating {@link Uri} models for local uris into {@link ParcelFileDescriptor} data.
  */
 public class FileDescriptorUriLoader extends UriLoader<ParcelFileDescriptor> implements FileDescriptorModelLoader<Uri> {
 
+    /**
+     * The default factory for {@link com.bumptech.glide.load.model.file_descriptor.FileDescriptorUriLoader}s.
+     */
     public static class Factory implements ModelLoaderFactory<Uri, ParcelFileDescriptor> {
         @Override
         public ModelLoader<Uri, ParcelFileDescriptor> build(Context context, GenericLoaderFactory factories) {
-            return new FileDescriptorUriLoader(context, factories.buildModelLoader(GlideUrl.class, ParcelFileDescriptor.class,
-                    context));
+            return new FileDescriptorUriLoader(context, factories.buildModelLoader(GlideUrl.class,
+                    ParcelFileDescriptor.class));
         }
 
         @Override
-        public void teardown() { }
+        public void teardown() {
+            // Do nothing.
+        }
     }
 
     public FileDescriptorUriLoader(Context context) {
@@ -40,4 +47,9 @@
     protected DataFetcher<ParcelFileDescriptor> getLocalUriFetcher(Context context, Uri uri) {
         return new FileDescriptorLocalUriFetcher(context, uri);
     }
+
+    @Override
+    protected DataFetcher<ParcelFileDescriptor> getAssetPathFetcher(Context context, String assetPath) {
+        return new FileDescriptorAssetPathFetcher(context.getApplicationContext().getAssets(), assetPath);
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/model/stream/BaseGlideUrlLoader.java b/library/src/main/java/com/bumptech/glide/load/model/stream/BaseGlideUrlLoader.java
index 4840a38..a319c34 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/stream/BaseGlideUrlLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/stream/BaseGlideUrlLoader.java
@@ -1,19 +1,21 @@
 package com.bumptech.glide.load.model.stream;
 
 import android.content.Context;
+import android.text.TextUtils;
+
 import com.bumptech.glide.Glide;
-import com.bumptech.glide.load.model.ModelCache;
-import com.bumptech.glide.load.model.GlideUrl;
-import com.bumptech.glide.load.model.ModelLoader;
 import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.model.GlideUrl;
+import com.bumptech.glide.load.model.ModelCache;
+import com.bumptech.glide.load.model.ModelLoader;
 
 import java.io.InputStream;
 
 /**
  * A base class for loading images over http/https. Can be subclassed for use with any model that can be translated
- * in to an image.
+ * in to {@link java.io.InputStream} data.
  *
- * @param <T> The type of the model
+ * @param <T> The type of the model.
  */
 public abstract class BaseGlideUrlLoader<T> implements StreamModelLoader<T> {
     private final ModelLoader<GlideUrl, InputStream> concreteLoader;
@@ -27,7 +29,6 @@
         this(Glide.buildModelLoader(GlideUrl.class, InputStream.class, context), modelCache);
     }
 
-    @SuppressWarnings("unused")
     public BaseGlideUrlLoader(ModelLoader<GlideUrl, InputStream> concreteLoader) {
         this(concreteLoader, null);
     }
@@ -46,6 +47,10 @@
 
         if (result == null) {
             String stringURL = getUrl(model, width, height);
+            if (TextUtils.isEmpty(stringURL)) {
+               return null;
+            }
+
             result = new GlideUrl(stringURL);
 
             if (modelCache != null) {
@@ -57,12 +62,12 @@
     }
 
     /**
-     * Get a valid url http:// or https:// for the given model and dimensions as a string
+     * Get a valid url http:// or https:// for the given model and dimensions as a string.
      *
-     * @param model The model
-     * @param width The width of the view/target the image will be loaded into
-     * @param height The height of the view/target the image will be loaded into
-     * @return The String url
+     * @param model The model.
+     * @param width The width in pixels of the view/target the image will be loaded into.
+     * @param height The height in pixels of the view/target the image will be loaded into.
+     * @return The String url.
      */
     protected abstract String getUrl(T model, int width, int height);
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/model/stream/HttpUrlGlideUrlLoader.java b/library/src/main/java/com/bumptech/glide/load/model/stream/HttpUrlGlideUrlLoader.java
new file mode 100644
index 0000000..3ee3060
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/model/stream/HttpUrlGlideUrlLoader.java
@@ -0,0 +1,61 @@
+package com.bumptech.glide.load.model.stream;
+
+import android.content.Context;
+
+import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.data.HttpUrlFetcher;
+import com.bumptech.glide.load.model.GenericLoaderFactory;
+import com.bumptech.glide.load.model.GlideUrl;
+import com.bumptech.glide.load.model.ModelCache;
+import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.load.model.ModelLoaderFactory;
+
+import java.io.InputStream;
+
+/**
+ * An {@link com.bumptech.glide.load.model.ModelLoader} for translating {@link com.bumptech.glide.load.model.GlideUrl}
+ * (http/https URLS) into {@link java.io.InputStream} data.
+ */
+public class HttpUrlGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
+
+    private final ModelCache<GlideUrl, GlideUrl> modelCache;
+
+    /**
+     * The default factory for {@link com.bumptech.glide.load.model.stream.HttpUrlGlideUrlLoader}s.
+     */
+    public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
+        private final ModelCache<GlideUrl, GlideUrl> modelCache = new ModelCache<GlideUrl, GlideUrl>(500);
+
+        @Override
+        public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
+            return new HttpUrlGlideUrlLoader(modelCache);
+        }
+
+        @Override
+        public void teardown() {
+            // Do nothing.
+        }
+    }
+
+    public HttpUrlGlideUrlLoader() {
+        this(null);
+    }
+
+    public HttpUrlGlideUrlLoader(ModelCache<GlideUrl, GlideUrl> modelCache) {
+        this.modelCache = modelCache;
+    }
+
+    @Override
+    public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
+        // GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time spent parsing urls.
+        GlideUrl url = model;
+        if (modelCache != null) {
+            url = modelCache.get(model, 0, 0);
+            if (url == null) {
+                modelCache.put(model, 0, 0, model);
+                url = model;
+            }
+        }
+        return new HttpUrlFetcher(url);
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/model/stream/MediaStoreStreamLoader.java b/library/src/main/java/com/bumptech/glide/load/model/stream/MediaStoreStreamLoader.java
index c9ee29d..980997a 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/stream/MediaStoreStreamLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/stream/MediaStoreStreamLoader.java
@@ -2,31 +2,33 @@
 
 import android.content.Context;
 import android.net.Uri;
+
 import com.bumptech.glide.load.data.DataFetcher;
 import com.bumptech.glide.load.data.MediaStoreThumbFetcher;
 import com.bumptech.glide.load.model.ModelLoader;
 
 import java.io.InputStream;
 
+/**
+ * An {@link com.bumptech.glide.load.model.ModelLoader} that can use media store uris to open pre-generated thumbnails
+ * from the media store using {@link android.provider.MediaStore.Images.Thumbnails} and
+ * {@link android.provider.MediaStore.Video.Thumbnails} if the requested size is less than or equal to the media store
+ * thumbnail size. If the given uri is not a media store uri or if the desired dimensions are too large,
+ * it falls back to the wrapped {@link com.bumptech.glide.load.model.ModelLoader} to load the
+ * {@link java.io.InputStream} data.
+ */
 public class MediaStoreStreamLoader implements ModelLoader<Uri, InputStream> {
     private final Context context;
     private final ModelLoader<Uri, InputStream> uriLoader;
-    private String mimeType;
-    private final long dateModified;
-    private final int orientation;
 
-    public MediaStoreStreamLoader(Context context, ModelLoader<Uri, InputStream> uriLoader, String mimeType,
-            long dateModified, int orientation) {
+    public MediaStoreStreamLoader(Context context, ModelLoader<Uri, InputStream> uriLoader) {
         this.context = context;
         this.uriLoader = uriLoader;
-        this.mimeType = mimeType;
-        this.dateModified = dateModified;
-        this.orientation = orientation;
     }
 
     @Override
     public DataFetcher<InputStream> getResourceFetcher(Uri model, int width, int height) {
         return new MediaStoreThumbFetcher(context, model, uriLoader.getResourceFetcher(model, width, height), width,
-                height, mimeType, dateModified, orientation);
+                height);
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamByteArrayLoader.java b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamByteArrayLoader.java
index 7b741ed..3f0277d 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamByteArrayLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamByteArrayLoader.java
@@ -1,22 +1,31 @@
 package com.bumptech.glide.load.model.stream;
 
+import android.content.Context;
+
 import com.bumptech.glide.load.data.ByteArrayFetcher;
 import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.model.GenericLoaderFactory;
+import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.load.model.ModelLoaderFactory;
 
 import java.io.InputStream;
-import java.util.UUID;
 
 /**
  * A base class to convert byte arrays to input streams so they can be decoded. This class is abstract because there is
  * no simple/quick way to generate an id from the bytes themselves, so subclass must include an id.
  */
 public class StreamByteArrayLoader implements StreamModelLoader<byte[]> {
-    private String id;
+    private final String id;
 
     public StreamByteArrayLoader() {
-        this(UUID.randomUUID().toString());
+        this("");
     }
 
+    /**
+     * @deprecated Use {@link com.bumptech.glide.GenericRequestBuilder#signature(com.bumptech.glide.load.Key)}
+     * and the empty constructor instead. Scheduled to be removed in Glide 4.0.
+     */
+    @Deprecated
     public StreamByteArrayLoader(String id) {
         this.id = id;
     }
@@ -25,4 +34,20 @@
     public DataFetcher<InputStream> getResourceFetcher(byte[] model, int width, int height) {
         return new ByteArrayFetcher(model, id);
     }
+
+    /**
+     * Factory for {@link com.bumptech.glide.load.model.stream.StreamByteArrayLoader}.
+     */
+    public static class Factory implements ModelLoaderFactory<byte[], InputStream> {
+
+        @Override
+        public ModelLoader<byte[], InputStream> build(Context context, GenericLoaderFactory factories) {
+            return new StreamByteArrayLoader();
+        }
+
+        @Override
+        public void teardown() {
+            // Do nothing.
+        }
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamFileLoader.java b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamFileLoader.java
index c0107be..1aca470 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamFileLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamFileLoader.java
@@ -2,6 +2,7 @@
 
 import android.content.Context;
 import android.net.Uri;
+
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.load.model.FileLoader;
 import com.bumptech.glide.load.model.GenericLoaderFactory;
@@ -12,18 +13,23 @@
 import java.io.InputStream;
 
 /**
- * A {@link ModelLoader} For translating {@link File} models for local uris into {@link InputStream} resources.
+ * A {@link ModelLoader} For translating {@link File} models for local uris into {@link InputStream} data.
  */
 public class StreamFileLoader extends FileLoader<InputStream> implements StreamModelLoader<File> {
 
+    /**
+     * The default factory for {@link com.bumptech.glide.load.model.stream.StreamFileLoader}s.
+     */
     public static class Factory implements ModelLoaderFactory<File, InputStream> {
         @Override
         public ModelLoader<File, InputStream> build(Context context, GenericLoaderFactory factories) {
-            return new StreamFileLoader(factories.buildModelLoader(Uri.class, InputStream.class, context));
+            return new StreamFileLoader(factories.buildModelLoader(Uri.class, InputStream.class));
         }
 
         @Override
-        public void teardown() { }
+        public void teardown() {
+            // Do nothing.
+        }
     }
 
     public StreamFileLoader(Context context) {
diff --git a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamModelLoader.java b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamModelLoader.java
index 589e8ac..fb203b9 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamModelLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamModelLoader.java
@@ -9,4 +9,6 @@
  *
  * @param <T> The type of the model that will be translated into an {@link InputStream}.
  */
-public interface StreamModelLoader<T> extends ModelLoader<T, InputStream> { }
+public interface StreamModelLoader<T> extends ModelLoader<T, InputStream> {
+    // specializing the generic arguments
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamResourceLoader.java b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamResourceLoader.java
index 74e182a..a382557 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamResourceLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamResourceLoader.java
@@ -2,6 +2,7 @@
 
 import android.content.Context;
 import android.net.Uri;
+
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.load.model.GenericLoaderFactory;
 import com.bumptech.glide.load.model.ModelLoader;
@@ -11,19 +12,24 @@
 import java.io.InputStream;
 
 /**
- * A {@link ModelLoader} For translating android resource id models for local uris into {@link InputStream} resources.
+ * A {@link ModelLoader} For translating android resource id models for local uris into {@link InputStream} data.
  */
 public class StreamResourceLoader extends ResourceLoader<InputStream> implements StreamModelLoader<Integer> {
 
+    /**
+     * The default factory for {@link com.bumptech.glide.load.model.stream.StreamResourceLoader}s.
+     */
     public static class Factory implements ModelLoaderFactory<Integer, InputStream> {
 
         @Override
         public ModelLoader<Integer, InputStream> build(Context context, GenericLoaderFactory factories) {
-            return new StreamResourceLoader(context, factories.buildModelLoader(Uri.class, InputStream.class, context));
+            return new StreamResourceLoader(context, factories.buildModelLoader(Uri.class, InputStream.class));
         }
 
         @Override
-        public void teardown() { }
+        public void teardown() {
+            // Do nothing.
+        }
     }
 
     public StreamResourceLoader(Context context) {
diff --git a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamStringLoader.java b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamStringLoader.java
index 5be8568..80bbb7a 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamStringLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamStringLoader.java
@@ -2,6 +2,7 @@
 
 import android.content.Context;
 import android.net.Uri;
+
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.load.model.GenericLoaderFactory;
 import com.bumptech.glide.load.model.ModelLoader;
@@ -11,19 +12,24 @@
 import java.io.InputStream;
 
 /**
- * A {@link ModelLoader} For translating {@link String} models, such as file paths or remote urls, into
- * {@link InputStream} resources.
+ * A {@link ModelLoader} for translating {@link String} models, such as file paths or remote urls, into
+ * {@link InputStream} data.
  */
 public class StreamStringLoader extends StringLoader<InputStream> implements StreamModelLoader<String> {
 
+    /**
+     * The default factory for {@link com.bumptech.glide.load.model.stream.StreamStringLoader}s.
+     */
     public static class Factory implements ModelLoaderFactory<String, InputStream> {
         @Override
         public ModelLoader<String, InputStream> build(Context context, GenericLoaderFactory factories) {
-            return new StreamStringLoader(factories.buildModelLoader(Uri.class, InputStream.class, context));
+            return new StreamStringLoader(factories.buildModelLoader(Uri.class, InputStream.class));
         }
 
         @Override
-        public void teardown() { }
+        public void teardown() {
+            // Do nothing.
+        }
     }
 
     public StreamStringLoader(Context context) {
diff --git a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamUriLoader.java b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamUriLoader.java
index 7d23143..086f4c4 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamUriLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamUriLoader.java
@@ -2,33 +2,40 @@
 
 import android.content.Context;
 import android.net.Uri;
+
 import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.data.StreamAssetPathFetcher;
+import com.bumptech.glide.load.data.StreamLocalUriFetcher;
 import com.bumptech.glide.load.model.GenericLoaderFactory;
 import com.bumptech.glide.load.model.GlideUrl;
 import com.bumptech.glide.load.model.ModelLoader;
 import com.bumptech.glide.load.model.ModelLoaderFactory;
 import com.bumptech.glide.load.model.UriLoader;
-import com.bumptech.glide.load.data.DataFetcher;
-import com.bumptech.glide.load.data.StreamLocalUriFetcher;
 
 import java.io.InputStream;
 
 /**
- * A {@link ModelLoader} for translating uri models into {@link InputStream} resources. Capable of handling 'http',
+ * A {@link ModelLoader} for translating uri models into {@link InputStream} data. Capable of handling 'http',
  * 'https', 'android.resource', 'content', and 'file' schemes. Unsupported schemes will throw an exception in
  * {@link #getResourceFetcher(Uri, int, int)}.
  */
 public class StreamUriLoader extends UriLoader<InputStream> implements StreamModelLoader<Uri> {
 
+    /**
+     * THe default factory for {@link com.bumptech.glide.load.model.stream.StreamUriLoader}s.
+     */
     public static class Factory implements ModelLoaderFactory<Uri, InputStream> {
 
         @Override
         public ModelLoader<Uri, InputStream> build(Context context, GenericLoaderFactory factories) {
-            return new StreamUriLoader(context, factories.buildModelLoader(GlideUrl.class, InputStream.class, context));
+            return new StreamUriLoader(context, factories.buildModelLoader(GlideUrl.class, InputStream.class));
         }
 
         @Override
-        public void teardown() { }
+        public void teardown() {
+            // Do nothing.
+        }
     }
 
     public StreamUriLoader(Context context) {
@@ -43,4 +50,9 @@
     protected DataFetcher<InputStream> getLocalUriFetcher(Context context, Uri uri) {
         return new StreamLocalUriFetcher(context, uri);
     }
+
+    @Override
+    protected DataFetcher<InputStream> getAssetPathFetcher(Context context, String assetPath) {
+        return new StreamAssetPathFetcher(context.getApplicationContext().getAssets(), assetPath);
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamUrlLoader.java b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamUrlLoader.java
index 28b31e5..ec6a207 100644
--- a/library/src/main/java/com/bumptech/glide/load/model/stream/StreamUrlLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/model/stream/StreamUrlLoader.java
@@ -1,8 +1,9 @@
 package com.bumptech.glide.load.model.stream;
 
 import android.content.Context;
-import com.bumptech.glide.load.model.GlideUrl;
+
 import com.bumptech.glide.load.model.GenericLoaderFactory;
+import com.bumptech.glide.load.model.GlideUrl;
 import com.bumptech.glide.load.model.ModelLoader;
 import com.bumptech.glide.load.model.ModelLoaderFactory;
 import com.bumptech.glide.load.model.UrlLoader;
@@ -10,16 +11,26 @@
 import java.io.InputStream;
 import java.net.URL;
 
+/**
+ * A wrapper class that translates {@link java.net.URL} objects into {@link com.bumptech.glide.load.model.GlideUrl}
+ * objects and then uses the wrapped {@link com.bumptech.glide.load.model.ModelLoader} for
+ * {@link com.bumptech.glide.load.model.GlideUrl}s to load the {@link java.io.InputStream} data.
+ */
 public class StreamUrlLoader extends UrlLoader<InputStream> {
 
+    /**
+     * The default factory for {@link com.bumptech.glide.load.model.stream.StreamUrlLoader}s.
+     */
     public static class Factory implements ModelLoaderFactory<URL, InputStream> {
         @Override
         public ModelLoader<URL, InputStream> build(Context context, GenericLoaderFactory factories) {
-            return new StreamUrlLoader(factories.buildModelLoader(GlideUrl.class, InputStream.class, context));
+            return new StreamUrlLoader(factories.buildModelLoader(GlideUrl.class, InputStream.class));
         }
 
         @Override
-        public void teardown() { }
+        public void teardown() {
+            // Do nothing.
+        }
     }
 
     public StreamUrlLoader(ModelLoader<GlideUrl, InputStream> glideUrlLoader) {
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/NullDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/NullDecoder.java
index 5579274..5481502 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/NullDecoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/NullDecoder.java
@@ -3,18 +3,28 @@
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.engine.Resource;
 
-import java.io.IOException;
-
+/**
+ * A simple {@link com.bumptech.glide.load.ResourceDecoder} that always returns null.
+ *
+ * @param <T> The type of the data that will be ignored by this class.
+ * @param <Z> The type of the decoded resource that will always be null.
+ */
 public class NullDecoder<T, Z> implements ResourceDecoder<T, Z> {
-    private static final NullDecoder NULL_DECODER = new NullDecoder();
+    private static final NullDecoder<?, ?> NULL_DECODER = new NullDecoder<Object, Object>();
 
+    /**
+     * Returns an instance of the NullDecoder for the given types.
+     *
+     * @param <T> The data type.
+     * @param <Z> The resource type.
+     */
     @SuppressWarnings("unchecked")
     public static <T, Z> NullDecoder<T, Z> get() {
-        return NULL_DECODER;
+        return (NullDecoder<T, Z>) NULL_DECODER;
     }
 
     @Override
-    public Resource<Z> decode(T source, int width, int height) throws IOException {
+    public Resource<Z> decode(T source, int width, int height) {
         return null;
     }
 
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/NullEncoder.java b/library/src/main/java/com/bumptech/glide/load/resource/NullEncoder.java
new file mode 100644
index 0000000..15de403
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/NullEncoder.java
@@ -0,0 +1,35 @@
+package com.bumptech.glide.load.resource;
+
+import com.bumptech.glide.load.Encoder;
+
+import java.io.OutputStream;
+
+/**
+ * A simple {@link com.bumptech.glide.load.Encoder} that never writes data.
+ *
+ * @param <T> type discarded by this Encoder
+ */
+public class NullEncoder<T> implements Encoder<T> {
+    private static final NullEncoder<?> NULL_ENCODER = new NullEncoder<Object>();
+
+    /**
+     * Returns an Encoder for the given data type.
+     *
+     * @param <T> The type of data to be written (or not in this case).
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Encoder<T> get() {
+        return (Encoder<T>) NULL_ENCODER;
+
+    }
+
+    @Override
+    public boolean encode(T data, OutputStream os) {
+        return false;
+    }
+
+    @Override
+    public String getId() {
+        return "";
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/NullResourceEncoder.java b/library/src/main/java/com/bumptech/glide/load/resource/NullResourceEncoder.java
new file mode 100644
index 0000000..8ea6115
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/NullResourceEncoder.java
@@ -0,0 +1,35 @@
+package com.bumptech.glide.load.resource;
+
+import com.bumptech.glide.load.ResourceEncoder;
+import com.bumptech.glide.load.engine.Resource;
+
+import java.io.OutputStream;
+
+/**
+ * A simple {@link com.bumptech.glide.load.ResourceEncoder} that never writes data.
+ *
+ * @param <T> The type of the resource that will always fail to be encoded.
+ */
+public class NullResourceEncoder<T> implements ResourceEncoder<T> {
+    private static final NullResourceEncoder<?> NULL_ENCODER = new NullResourceEncoder<Object>();
+
+    /**
+     * Returns a NullResourceEncoder for the given type.
+     *
+     * @param <T> The type of data to be written (or in this case not written).
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> NullResourceEncoder<T> get() {
+        return (NullResourceEncoder<T>) NULL_ENCODER;
+    }
+
+    @Override
+    public boolean encode(Resource<T> data, OutputStream os) {
+        return false;
+    }
+
+    @Override
+    public String getId() {
+        return "";
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/SimpleResource.java b/library/src/main/java/com/bumptech/glide/load/resource/SimpleResource.java
new file mode 100644
index 0000000..33ccbb8
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/SimpleResource.java
@@ -0,0 +1,36 @@
+package com.bumptech.glide.load.resource;
+
+import com.bumptech.glide.load.engine.Resource;
+
+/**
+ * Simple wrapper for an arbitrary object which helps to satisfy some of the glide engine's contracts.
+ * <b>Suggested usages only include resource object which don't have size and cannot be recycled/closed.</b>
+ *
+ * @param <T> type of the wrapped resource
+ */
+// TODO: there isn't much point in caching these...
+public class SimpleResource<T> implements Resource<T> {
+    protected final T data;
+
+    public SimpleResource(T data) {
+        if (data == null) {
+            throw new NullPointerException("Data must not be null");
+        }
+        this.data = data;
+    }
+
+    @Override
+    public final T get() {
+        return data;
+    }
+
+    @Override
+    public final int getSize() {
+        return 1;
+    }
+
+    @Override
+    public void recycle() {
+        // no op
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/UnitTransformation.java b/library/src/main/java/com/bumptech/glide/load/resource/UnitTransformation.java
new file mode 100644
index 0000000..e4c1aa6
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/UnitTransformation.java
@@ -0,0 +1,33 @@
+package com.bumptech.glide.load.resource;
+
+import com.bumptech.glide.load.Transformation;
+import com.bumptech.glide.load.engine.Resource;
+
+/**
+ * A noop Transformation that simply returns the given resource.
+ *
+ * @param <T> The type of the resource that will always be returned unmodified.
+ */
+public class UnitTransformation<T> implements Transformation<T> {
+    private static final Transformation<?> TRANSFORMATION = new UnitTransformation<Object>();
+
+    /**
+     * Returns a UnitTransformation for the given type.
+     *
+     * @param <T> The type of the resource to be transformed.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> UnitTransformation<T> get() {
+        return (UnitTransformation<T>) TRANSFORMATION;
+    }
+
+    @Override
+    public Resource<T> transform(Resource<T> resource, int outWidth, int outHeight) {
+        return resource;
+    }
+
+    @Override
+    public String getId() {
+        return "";
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapDecoder.java
index d786e2a..36c4df1 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapDecoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapDecoder.java
@@ -1,8 +1,9 @@
 package com.bumptech.glide.load.resource.bitmap;
 
 import android.graphics.Bitmap;
-import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+
 import com.bumptech.glide.load.DecodeFormat;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 
 /**
  * A bitmap decoder for a given resource type.
@@ -13,7 +14,7 @@
     /**
      * Returns a decoded bitmap for a given resource and target dimensions.
      *
-     * @param resource The resource to decode.
+     * @param resource The resource to decode, managed by the caller, no need to clean it up.
      * @param bitmapPool A bitmap pool that can be used to reuse bitmaps during the load. Any bitmaps created or
      *                   obtained from the pool other than the bitmap returned by this method should be returned to the
      *                   pool.
@@ -21,11 +22,18 @@
      * @param outHeight The target height for the returned bitmap (need not match exactly).
      * @param decodeFormat The desired configuration for the returned bitmap.
      */
-    public Bitmap decode(T resource, BitmapPool bitmapPool, int outWidth, int outHeight, DecodeFormat decodeFormat)
+    Bitmap decode(T resource, BitmapPool bitmapPool, int outWidth, int outHeight, DecodeFormat decodeFormat)
             throws Exception;
 
     /**
      * Returns some unique String id that distinguishes this decoder from any other decoder.
+     *
+     * <p>
+     *     This method can return the empty string if for all practical purposes it applies no transformations to the
+     *     data while loading the resource. For {@link android.graphics.Bitmap}s this would mean at a minimum doing no
+     *     downsampling and also probably always producing {@link android.graphics.Bitmap}s with
+     *     {@link android.graphics.Bitmap.Config#ARGB_8888} as their config.
+     * </p>
      */
-    public String getId();
+    String getId();
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapDrawableResource.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapDrawableResource.java
index 126253d..9d062e1 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapDrawableResource.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapDrawableResource.java
@@ -1,31 +1,35 @@
 package com.bumptech.glide.load.resource.bitmap;
 
 import android.graphics.drawable.BitmapDrawable;
-import com.bumptech.glide.load.engine.Resource;
+
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.drawable.DrawableResource;
 import com.bumptech.glide.util.Util;
 
-public class BitmapDrawableResource extends Resource<BitmapDrawable> {
-    private BitmapDrawable drawable;
-    private BitmapPool bitmapPool;
+/**
+ * A {@link com.bumptech.glide.load.engine.Resource} that wraps an {@link android.graphics.drawable.BitmapDrawable}
+ * <p>
+ *     This class ensures that every call to {@link #get()}} always returns a new
+ *     {@link android.graphics.drawable.BitmapDrawable} to avoid rendering issues if used in multiple views and
+ *     is also responsible for returning the underlying {@link android.graphics.Bitmap} to the given
+ *     {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} when the resource is recycled.
+ * </p>
+ */
+public class BitmapDrawableResource extends DrawableResource<BitmapDrawable> {
+    private final BitmapPool bitmapPool;
 
     public BitmapDrawableResource(BitmapDrawable drawable, BitmapPool bitmapPool) {
-        this.drawable = drawable;
+        super(drawable);
         this.bitmapPool = bitmapPool;
     }
 
     @Override
-    public BitmapDrawable get() {
-        return drawable;
-    }
-
-    @Override
     public int getSize() {
-        return Util.getSize(drawable.getBitmap());
+        return Util.getBitmapByteSize(drawable.getBitmap());
     }
 
     @Override
-    protected void recycleInternal() {
+    public void recycle() {
         bitmapPool.put(drawable.getBitmap());
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapEncoder.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapEncoder.java
index 145aee7..ab6d8df 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapEncoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapEncoder.java
@@ -1,17 +1,35 @@
 package com.bumptech.glide.load.resource.bitmap;
 
 import android.graphics.Bitmap;
-import com.bumptech.glide.load.engine.Resource;
+import android.util.Log;
+
 import com.bumptech.glide.load.ResourceEncoder;
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.util.LogTime;
+import com.bumptech.glide.util.Util;
 
 import java.io.OutputStream;
 
+/**
+ * An {@link com.bumptech.glide.load.ResourceEncoder} that writes {@link android.graphics.Bitmap}s to
+ * {@link java.io.OutputStream}s.
+ *
+ * <p>
+ *     {@link android.graphics.Bitmap}s that return true from {@link android.graphics.Bitmap#hasAlpha()}} are written
+ *     using {@link android.graphics.Bitmap.CompressFormat#PNG} to preserve alpha and all other bitmaps are written
+ *     using {@link android.graphics.Bitmap.CompressFormat#JPEG}.
+ * </p>
+ *
+ * @see android.graphics.Bitmap#compress(android.graphics.Bitmap.CompressFormat, int, java.io.OutputStream)
+ */
 public class BitmapEncoder implements ResourceEncoder<Bitmap> {
+    private static final String TAG = "BitmapEncoder";
+    private static final int DEFAULT_COMPRESSION_QUALITY = 90;
     private Bitmap.CompressFormat compressFormat;
     private int quality;
 
     public BitmapEncoder() {
-        this(null, 70);
+        this(null, DEFAULT_COMPRESSION_QUALITY);
     }
 
     public BitmapEncoder(Bitmap.CompressFormat compressFormat, int quality) {
@@ -22,7 +40,14 @@
     @Override
     public boolean encode(Resource<Bitmap> resource, OutputStream os) {
         final Bitmap bitmap = resource.get();
-        bitmap.compress(getFormat(bitmap), quality, os);
+
+        long start = LogTime.getLogTime();
+        Bitmap.CompressFormat format = getFormat(bitmap);
+        bitmap.compress(format, quality, os);
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "Compressed with type: " + format + " of size " + Util.getBitmapByteSize(bitmap) + " in "
+                    + LogTime.getElapsedMillis(start));
+        }
         return true;
     }
 
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapResource.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapResource.java
index e72268b..10785c6 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapResource.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapResource.java
@@ -1,15 +1,40 @@
 package com.bumptech.glide.load.resource.bitmap;
 
 import android.graphics.Bitmap;
+
 import com.bumptech.glide.load.engine.Resource;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 import com.bumptech.glide.util.Util;
 
-public class BitmapResource extends Resource<Bitmap> {
-    private Bitmap bitmap;
-    private BitmapPool bitmapPool;
+/**
+ * A resource wrapping a {@link android.graphics.Bitmap} object.
+ */
+public class BitmapResource implements Resource<Bitmap> {
+    private final Bitmap bitmap;
+    private final BitmapPool bitmapPool;
+
+    /**
+     * Returns a new {@link BitmapResource} wrapping the given {@link Bitmap} if the Bitmap is non-null or null if the
+     * given Bitmap is null.
+     *
+     * @param bitmap A Bitmap.
+     * @param bitmapPool A non-null {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool}.
+     */
+    public static BitmapResource obtain(Bitmap bitmap, BitmapPool bitmapPool) {
+        if (bitmap == null) {
+            return null;
+        } else {
+            return new BitmapResource(bitmap, bitmapPool);
+        }
+    }
 
     public BitmapResource(Bitmap bitmap, BitmapPool bitmapPool) {
+        if (bitmap == null) {
+            throw new NullPointerException("Bitmap must not be null");
+        }
+        if (bitmapPool == null) {
+            throw new NullPointerException("BitmapPool must not be null");
+        }
         this.bitmap = bitmap;
         this.bitmapPool = bitmapPool;
     }
@@ -21,11 +46,11 @@
 
     @Override
     public int getSize() {
-        return Util.getSize(bitmap);
+        return Util.getBitmapByteSize(bitmap);
     }
 
     @Override
-    public void recycleInternal() {
+    public void recycle() {
         if (!bitmapPool.put(bitmap)) {
             bitmap.recycle();
         }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapTransformation.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapTransformation.java
new file mode 100644
index 0000000..c3624b1
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapTransformation.java
@@ -0,0 +1,75 @@
+package com.bumptech.glide.load.resource.bitmap;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.Transformation;
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+
+/**
+ * A simple {@link com.bumptech.glide.load.Transformation} for transforming {@link android.graphics.Bitmap}s that
+ * abstracts away dealing with {@link com.bumptech.glide.load.engine.Resource} objects for subclasses.
+ *
+ * Use cases will look something like this:
+ * <pre>
+ * <code>
+ * public class FillSpace extends BaseBitmapTransformation {
+ *     {@literal @Override}
+ *     public Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
+ *         if (toTransform.getWidth() == outWidth && toTransform.getHeight() == outHeight) {
+ *             return toTransform;
+ *         }
+ *
+ *         return Bitmap.createScaledBitmap(toTransform, outWidth, outHeight, true);
+ *     }
+ * }
+ * </code>
+ * </pre>
+ */
+public abstract class BitmapTransformation implements Transformation<Bitmap> {
+
+    private BitmapPool bitmapPool;
+
+    public BitmapTransformation(Context context) {
+        this(Glide.get(context).getBitmapPool());
+    }
+
+    public BitmapTransformation(BitmapPool bitmapPool) {
+        this.bitmapPool = bitmapPool;
+    }
+
+    @Override
+    public final Resource<Bitmap> transform(Resource<Bitmap> resource, int outWidth, int outHeight) {
+        if (outWidth <= 0 || outHeight <= 0) {
+            throw new IllegalArgumentException("Cannot apply transformation on width: " + outWidth + " or height: "
+                    + outHeight + " less than or equal to zero");
+        }
+        Bitmap toTransform = resource.get();
+        Bitmap transformed = transform(bitmapPool, toTransform, outWidth, outHeight);
+
+        final Resource<Bitmap> result;
+        if (toTransform.equals(transformed)) {
+            result = resource;
+        } else {
+            result = BitmapResource.obtain(transformed, bitmapPool);
+        }
+
+        return result;
+    }
+
+    /**
+     * Transforms the given {@link android.graphics.Bitmap} based on the given dimensions and returns the transformed
+     * result.
+     *
+     * @param pool A {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} that can be used to obtain and
+     *             return intermediate {@link Bitmap}s used in this transformation. For every
+     *             {@link android.graphics.Bitmap} obtained from the pool during this transformation, a
+     *             {@link android.graphics.Bitmap} must also be returned.
+     * @param toTransform The {@link android.graphics.Bitmap} to transform.
+     * @param outWidth The ideal width of the transformed bitmap (does not need to match exactly).
+     * @param outHeight The ideal height of the transformed bitmap (does not need to match exactly).
+     */
+    protected abstract Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight);
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/CenterCrop.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/CenterCrop.java
index 28135ba..62eda0f 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/CenterCrop.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/CenterCrop.java
@@ -1,9 +1,9 @@
 package com.bumptech.glide.load.resource.bitmap;
 
+import android.content.Context;
 import android.graphics.Bitmap;
-import com.bumptech.glide.load.engine.Resource;
+
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
-import com.bumptech.glide.load.Transformation;
 
 /**
  * Scale the image so that either the width of the image matches the given width and the height of the image is
@@ -11,31 +11,27 @@
  *
  * Does not maintain the image's aspect ratio
  */
-public class CenterCrop implements Transformation<Bitmap> {
-    private BitmapPool pool;
+public class CenterCrop extends BitmapTransformation {
 
-    public CenterCrop(BitmapPool pool) {
-        this.pool = pool;
+    public CenterCrop(Context context) {
+        super(context);
     }
 
-    @Override
-    public Resource<Bitmap> transform(Resource<Bitmap> resource, int outWidth, int outHeight) {
-        if (outWidth <= 0 || outHeight <= 0) {
-            throw new IllegalArgumentException("Cannot center crop image to width=" + outWidth + " and height="
-                    + outHeight);
-        }
+    public CenterCrop(BitmapPool bitmapPool) {
+        super(bitmapPool);
+    }
 
-        final Bitmap toReuse = pool.get(outWidth, outHeight, resource.get().getConfig());
-        Bitmap transformed = TransformationUtils.centerCrop(toReuse, resource.get(), outWidth, outHeight);
+    // Bitmap doesn't implement equals, so == and .equals are equivalent here.
+    @SuppressWarnings("PMD.CompareObjectsWithEquals")
+    @Override
+    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
+        final Bitmap toReuse = pool.get(outWidth, outHeight, toTransform.getConfig() != null
+                ? toTransform.getConfig() : Bitmap.Config.ARGB_8888);
+        Bitmap transformed = TransformationUtils.centerCrop(toReuse, toTransform, outWidth, outHeight);
         if (toReuse != null && toReuse != transformed && !pool.put(toReuse)) {
             toReuse.recycle();
         }
-
-        if (transformed == resource.get()) {
-            return resource;
-        } else {
-            return new BitmapResource(transformed, pool);
-        }
+        return transformed;
     }
 
     @Override
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java
index b4e6e6d..904a7c4 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java
@@ -5,13 +5,15 @@
 import android.graphics.BitmapFactory;
 import android.os.Build;
 import android.util.Log;
+
 import com.bumptech.glide.load.DecodeFormat;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 import com.bumptech.glide.util.ByteArrayPool;
+import com.bumptech.glide.util.ExceptionCatchingInputStream;
+import com.bumptech.glide.util.Util;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.ArrayDeque;
 import java.util.EnumSet;
 import java.util.Queue;
 import java.util.Set;
@@ -22,49 +24,16 @@
 public abstract class Downsampler implements BitmapDecoder<InputStream> {
     private static final String TAG = "Downsampler";
 
-    private static final boolean CAN_RECYCLE = Build.VERSION.SDK_INT >= 11;
     private static final Set<ImageHeaderParser.ImageType> TYPES_THAT_USE_POOL = EnumSet.of(
             ImageHeaderParser.ImageType.JPEG, ImageHeaderParser.ImageType.PNG_A, ImageHeaderParser.ImageType.PNG);
 
-    private static final Queue<BitmapFactory.Options> OPTIONS_QUEUE = new ArrayDeque<BitmapFactory.Options>();
-
-    @TargetApi(11)
-    private static synchronized BitmapFactory.Options getDefaultOptions() {
-        BitmapFactory.Options decodeBitmapOptions = OPTIONS_QUEUE.poll();
-        if (decodeBitmapOptions == null) {
-            decodeBitmapOptions = new BitmapFactory.Options();
-            resetOptions(decodeBitmapOptions);
-        }
-
-        return decodeBitmapOptions;
-    }
-
-    private static void releaseOptions(BitmapFactory.Options decodeBitmapOptions) {
-        resetOptions(decodeBitmapOptions);
-        OPTIONS_QUEUE.offer(decodeBitmapOptions);
-    }
-
-    @TargetApi(11)
-    private static void resetOptions(BitmapFactory.Options decodeBitmapOptions) {
-        decodeBitmapOptions.inTempStorage = null;
-        decodeBitmapOptions.inDither = false;
-        decodeBitmapOptions.inScaled = false;
-        decodeBitmapOptions.inSampleSize = 1;
-        decodeBitmapOptions.inPreferredConfig = null;
-        decodeBitmapOptions.inJustDecodeBounds = false;
-
-        if (CAN_RECYCLE)  {
-            decodeBitmapOptions.inBitmap = null;
-            decodeBitmapOptions.inMutable = true;
-        }
-    }
+    private static final Queue<BitmapFactory.Options> OPTIONS_QUEUE = Util.createQueue(0);
 
     /**
      * Load and scale the image uniformly (maintaining the image's aspect ratio) so that the dimensions of the image
      * will be greater than or equal to the given width and height.
-     *
      */
-    public static Downsampler AT_LEAST = new Downsampler() {
+    public static final Downsampler AT_LEAST = new Downsampler() {
         @Override
         protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) {
             return Math.min(inHeight / outHeight, inWidth / outWidth);
@@ -81,7 +50,7 @@
      * will be less than or equal to the given width and height.
      *
      */
-    public static Downsampler AT_MOST = new Downsampler() {
+    public static final Downsampler AT_MOST = new Downsampler() {
         @Override
         protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) {
             return Math.max(inHeight / outHeight, inWidth / outWidth);
@@ -94,10 +63,9 @@
     };
 
     /**
-     * Load the image at its original size
-     *
+     * Load the image at its original size.
      */
-    public static Downsampler NONE = new Downsampler() {
+    public static final Downsampler NONE = new Downsampler() {
         @Override
         protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) {
             return 0;
@@ -118,122 +86,172 @@
      * Load the image for the given InputStream. If a recycled Bitmap whose dimensions exactly match those of the image
      * for the given InputStream is available, the operation is much less expensive in terms of memory.
      *
-     * Note - this method will throw an exception of a Bitmap with dimensions not matching those of the image for the
-     * given InputStream is provided.
+     * <p>
+     *     Note - this method will throw an exception of a Bitmap with dimensions not matching
+     *     those of the image for the given InputStream is provided.
+     * </p>
      *
-     * @param is An InputStream to the data for the image
-     * @param pool A pool of recycled bitmaps
-     * @param outWidth The width the final image should be close to
-     * @param outHeight The height the final image should be close to
-     * @return A new bitmap containing the image from the given InputStream, or recycle if recycle is not null
+     * @param is An {@link InputStream} to the data for the image.
+     * @param pool A pool of recycled bitmaps.
+     * @param outWidth The width the final image should be close to.
+     * @param outHeight The height the final image should be close to.
+     * @return A new bitmap containing the image from the given InputStream, or recycle if recycle is not null.
      */
+    @SuppressWarnings("resource")
+    // see BitmapDecoder.decode
     @Override
     public Bitmap decode(InputStream is, BitmapPool pool, int outWidth, int outHeight, DecodeFormat decodeFormat) {
         final ByteArrayPool byteArrayPool = ByteArrayPool.get();
-        byte[] bytesForOptions = byteArrayPool.getBytes();
-        byte[] bytesForStream = byteArrayPool.getBytes();
-        RecyclableBufferedInputStream bis = new RecyclableBufferedInputStream(is, bytesForStream);
-        bis.mark(MARK_POSITION);
-        int orientation = 0;
-        try {
-            orientation = new ImageHeaderParser(bis).getOrientation();
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-        try {
-            bis.reset();
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-
+        final byte[] bytesForOptions = byteArrayPool.getBytes();
+        final byte[] bytesForStream = byteArrayPool.getBytes();
         final BitmapFactory.Options options = getDefaultOptions();
-        options.inTempStorage = bytesForOptions;
 
-        final int[] inDimens = getDimensions(bis, options);
-        final int inWidth = inDimens[0];
-        final int inHeight = inDimens[1];
+        // TODO(#126): when the framework handles exceptions better, consider removing.
+        final ExceptionCatchingInputStream stream =
+                ExceptionCatchingInputStream.obtain(new RecyclableBufferedInputStream(is, bytesForStream));
+        try {
+            stream.mark(MARK_POSITION);
+            int orientation = 0;
+            try {
+                orientation = new ImageHeaderParser(stream).getOrientation();
+            } catch (IOException e) {
+                if (Log.isLoggable(TAG, Log.WARN)) {
+                    Log.w(TAG, "Cannot determine the image orientation from header", e);
+                }
+            } finally {
+                try {
+                    stream.reset();
+                } catch (IOException e) {
+                    if (Log.isLoggable(TAG, Log.WARN)) {
+                        Log.w(TAG, "Cannot reset the input stream", e);
+                    }
+                }
+            }
 
-        final int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
-        final int sampleSize;
+            options.inTempStorage = bytesForOptions;
+
+            final int[] inDimens = getDimensions(stream, options);
+            final int inWidth = inDimens[0];
+            final int inHeight = inDimens[1];
+
+            final int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
+            final int sampleSize = getRoundedSampleSize(degreesToRotate, inWidth, inHeight, outWidth, outHeight);
+
+            final Bitmap downsampled =
+                    downsampleWithSize(stream, options, pool, inWidth, inHeight, sampleSize,
+                            decodeFormat);
+
+            // BitmapFactory swallows exceptions during decodes and in some cases when inBitmap is non null, may catch
+            // and log a stack trace but still return a non null bitmap. To avoid displaying partially decoded bitmaps,
+            // we catch exceptions reading from the stream in our ExceptionCatchingInputStream and throw them here.
+            final Exception streamException = stream.getException();
+            if (streamException != null) {
+                throw new RuntimeException(streamException);
+            }
+
+            Bitmap rotated = null;
+            if (downsampled != null) {
+                rotated = TransformationUtils.rotateImageExif(downsampled, pool, orientation);
+
+                if (!downsampled.equals(rotated) && !pool.put(downsampled)) {
+                    downsampled.recycle();
+                }
+            }
+
+            return rotated;
+        } finally {
+            byteArrayPool.releaseBytes(bytesForOptions);
+            byteArrayPool.releaseBytes(bytesForStream);
+            stream.release();
+            releaseOptions(options);
+        }
+    }
+
+    private int getRoundedSampleSize(int degreesToRotate, int inWidth, int inHeight, int outWidth, int outHeight) {
+        final int exactSampleSize;
         if (degreesToRotate == 90 || degreesToRotate == 270) {
             // If we're rotating the image +-90 degrees, we need to downsample accordingly so the image width is
             // decreased to near our target's height and the image height is decreased to near our target width.
-            sampleSize = getSampleSize(inHeight, inWidth, outWidth, outHeight);
+            exactSampleSize = getSampleSize(inHeight, inWidth, outWidth, outHeight);
         } else {
-            sampleSize = getSampleSize(inWidth, inHeight, outWidth, outHeight);
+            exactSampleSize = getSampleSize(inWidth, inHeight, outWidth, outHeight);
         }
 
-        final Bitmap downsampled = downsampleWithSize(bis, options, pool, inWidth, inHeight, sampleSize, decodeFormat);
+        // BitmapFactory only accepts powers of 2, so it will round down to the nearest power of two that is less than
+        // or equal to the sample size we provide. Because we need to estimate the final image width and height to
+        // re-use Bitmaps, we mirror BitmapFactory's calculation here. For bug, see issue #224. For algorithm see
+        // http://stackoverflow.com/a/17379704/800716.
+        final int powerOfTwoSampleSize = exactSampleSize == 0 ? 0 : Integer.highestOneBit(exactSampleSize - 1);
 
-        Bitmap rotated = null;
-        if (downsampled != null) {
-            rotated = TransformationUtils.rotateImageExif(downsampled, pool, orientation);
-
-            if (downsampled != rotated && !pool.put(downsampled)) {
-                downsampled.recycle();
-            }
-        }
-
-        byteArrayPool.releaseBytes(bytesForOptions);
-        byteArrayPool.releaseBytes(bytesForStream);
-        releaseOptions(options);
-        return rotated;
+        // Although functionally equivalent to 0 for BitmapFactory, 1 is a safer default for our code than 0.
+        return Math.max(1, powerOfTwoSampleSize);
     }
 
-    protected Bitmap downsampleWithSize(RecyclableBufferedInputStream bis, BitmapFactory.Options options,
-            BitmapPool pool, int inWidth, int inHeight, int sampleSize, DecodeFormat decodeFormat) {
+    private Bitmap downsampleWithSize(ExceptionCatchingInputStream is, BitmapFactory.Options options, BitmapPool pool,
+            int inWidth, int inHeight, int sampleSize, DecodeFormat decodeFormat) {
         // Prior to KitKat, the inBitmap size must exactly match the size of the bitmap we're decoding.
-        Bitmap.Config config = getConfig(bis, decodeFormat);
+        Bitmap.Config config = getConfig(is, decodeFormat);
         options.inSampleSize = sampleSize;
         options.inPreferredConfig = config;
-        if (options.inSampleSize == 1 || Build.VERSION.SDK_INT >= 19) {
-            if (shouldUsePool(bis)) {
-                setInBitmap(options, pool.get(inWidth, inHeight, config));
-            }
+        if ((options.inSampleSize == 1 || Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) && shouldUsePool(is)) {
+            int targetWidth = (int) Math.ceil(inWidth / (double) sampleSize);
+            int targetHeight = (int) Math.ceil(inHeight / (double) sampleSize);
+            // BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe.
+            setInBitmap(options, pool.getDirty(targetWidth, targetHeight, config));
         }
-        return decodeStream(bis, options);
+        return decodeStream(is, options);
     }
 
-    private boolean shouldUsePool(RecyclableBufferedInputStream bis) {
+    private static boolean shouldUsePool(InputStream is) {
         // On KitKat+, any bitmap can be used to decode any other bitmap.
-        if (Build.VERSION.SDK_INT >= 19) {
+        if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) {
             return true;
         }
 
-        bis.mark(1024);
+        is.mark(1024);
         try {
-            final ImageHeaderParser.ImageType type = new ImageHeaderParser(bis).getType();
+            final ImageHeaderParser.ImageType type = new ImageHeaderParser(is).getType();
             // cannot reuse bitmaps when decoding images that are not PNG or JPG.
             // look at : https://groups.google.com/forum/#!msg/android-developers/Mp0MFVFi1Fo/e8ZQ9FGdWdEJ
             return TYPES_THAT_USE_POOL.contains(type);
         } catch (IOException e) {
-            e.printStackTrace();
+            if (Log.isLoggable(TAG, Log.WARN)) {
+                Log.w(TAG, "Cannot determine the image type from header", e);
+            }
         } finally {
             try {
-                bis.reset();
+                is.reset();
             } catch (IOException e) {
-                e.printStackTrace();
+                if (Log.isLoggable(TAG, Log.WARN)) {
+                    Log.w(TAG, "Cannot reset the input stream", e);
+                }
             }
         }
         return false;
     }
 
-    private Bitmap.Config getConfig(RecyclableBufferedInputStream bis, DecodeFormat format) {
-        if (format == DecodeFormat.ALWAYS_ARGB_8888) {
+    private static Bitmap.Config getConfig(InputStream is, DecodeFormat format) {
+        // Changing configs can cause skewing on 4.1, see issue #128.
+        if (format == DecodeFormat.ALWAYS_ARGB_8888 || Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
             return Bitmap.Config.ARGB_8888;
         }
 
         boolean hasAlpha = false;
-        bis.mark(1024); //we probably only need 25, but this is safer (particularly since the buffer size is > 1024)
+        // We probably only need 25, but this is safer (particularly since the buffer size is > 1024).
+        is.mark(1024);
         try {
-            hasAlpha = new ImageHeaderParser(bis).hasAlpha();
+            hasAlpha = new ImageHeaderParser(is).hasAlpha();
         } catch (IOException e) {
-            e.printStackTrace();
+            if (Log.isLoggable(TAG, Log.WARN)) {
+                Log.w(TAG, "Cannot determine whether the image has alpha or not from header for format " + format, e);
+            }
         } finally {
             try {
-                bis.reset();
+                is.reset();
             } catch (IOException e) {
-                e.printStackTrace();
+                if (Log.isLoggable(TAG, Log.WARN)) {
+                    Log.w(TAG, "Cannot reset the input stream", e);
+                }
             }
         }
 
@@ -244,50 +262,53 @@
      * Determine the amount of downsampling to use for a load given the dimensions of the image to be downsampled and
      * the dimensions of the view/target the image will be displayed in.
      *
-     * @see BitmapFactory.Options#inSampleSize
+     * @see android.graphics.BitmapFactory.Options#inSampleSize
      *
-     * @param inWidth The width of the image to be downsampled
-     * @param inHeight The height of the image to be downsampled
-     * @param outWidth The width of the view/target the image will be displayed in
-     * @param outHeight The height of the view/target the imag will be displayed in
-     * @return An integer to pass in to {@link BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, android.graphics.BitmapFactory.Options)}
+     * @param inWidth The width of the image to be downsampled.
+     * @param inHeight The height of the image to be downsampled.
+     * @param outWidth The width of the view/target the image will be displayed in.
+     * @param outHeight The height of the view/target the imag will be displayed in.
+     * @return An integer to pass in to {@link BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect,
+     *          android.graphics.BitmapFactory.Options)}.
      */
     protected abstract int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight);
 
     /**
-     * A method for getting the dimensions of an image from the given InputStream
+     * A method for getting the dimensions of an image from the given InputStream.
      *
-     * @param bis The InputStream representing the image
-     * @param options The options to pass to {@link BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, android.graphics.BitmapFactory.Options)}
-     * @return an array containing the dimensions of the image in the form {width, height}
+     * @param is The InputStream representing the image.
+     * @param options The options to pass to
+     *          {@link BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect,
+     *              android.graphics.BitmapFactory.Options)}.
+     * @return an array containing the dimensions of the image in the form {width, height}.
      */
-    public int[] getDimensions(RecyclableBufferedInputStream bis, BitmapFactory.Options options) {
+    public int[] getDimensions(ExceptionCatchingInputStream is, BitmapFactory.Options options) {
         options.inJustDecodeBounds = true;
-        decodeStream(bis, options);
+        decodeStream(is, options);
         options.inJustDecodeBounds = false;
         return new int[] { options.outWidth, options.outHeight };
     }
 
-
-    private Bitmap decodeStream(RecyclableBufferedInputStream bis, BitmapFactory.Options options) {
+    private static Bitmap decodeStream(ExceptionCatchingInputStream is, BitmapFactory.Options options) {
          if (options.inJustDecodeBounds) {
-             bis.mark(MARK_POSITION); //this is large, but jpeg headers are not size bounded so we need
-                         //something large enough to minimize the possibility of not being able to fit
-                         //enough of the header in the buffer to get the image size so that we don't fail
-                         //to load images. The BufferedInputStream will create a new buffer of 2x the
-                         //original size each time we use up the buffer space without passing the mark so
-                         //this is a maximum bound on the buffer size, not a default. Most of the time we
-                         //won't go past our pre-allocated 16kb
+             // This is large, but jpeg headers are not size bounded so we need something large enough to minimize
+             // the possibility of not being able to fit enough of the header in the buffer to get the image size so
+             // that we don't fail to load images. The BufferedInputStream will create a new buffer of 2x the
+             // original size each time we use up the buffer space without passing the mark so this is a maximum
+             // bound on the buffer size, not a default. Most of the time we won't go past our pre-allocated 16kb.
+             is.mark(MARK_POSITION);
+         } else {
+             // Once we've read the image header, we no longer need to allow the buffer to expand in size. To avoid
+             // unnecessary allocations reading image data, we fix the mark limit so that it is no larger than our
+             // current buffer size here. See issue #225.
+             is.fixMarkLimit();
          }
 
-        final Bitmap result = BitmapFactory.decodeStream(bis, null, options);
+        final Bitmap result = BitmapFactory.decodeStream(is, null, options);
 
         try {
             if (options.inJustDecodeBounds) {
-                bis.reset();
-                bis.clearMark();
-            } else {
-                bis.close();
+                is.reset();
             }
         } catch (IOException e) {
             if (Log.isLoggable(TAG, Log.ERROR)) {
@@ -299,10 +320,46 @@
         return result;
     }
 
-    @TargetApi(11)
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
     private static void setInBitmap(BitmapFactory.Options options, Bitmap recycled) {
-        if (Build.VERSION.SDK_INT >= 11) {
+        if (Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT) {
             options.inBitmap = recycled;
         }
     }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    private static synchronized BitmapFactory.Options getDefaultOptions() {
+        BitmapFactory.Options decodeBitmapOptions;
+        synchronized (OPTIONS_QUEUE) {
+            decodeBitmapOptions = OPTIONS_QUEUE.poll();
+        }
+        if (decodeBitmapOptions == null) {
+            decodeBitmapOptions = new BitmapFactory.Options();
+            resetOptions(decodeBitmapOptions);
+        }
+
+        return decodeBitmapOptions;
+    }
+
+    private static void releaseOptions(BitmapFactory.Options decodeBitmapOptions) {
+        resetOptions(decodeBitmapOptions);
+        synchronized (OPTIONS_QUEUE) {
+            OPTIONS_QUEUE.offer(decodeBitmapOptions);
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    private static void resetOptions(BitmapFactory.Options decodeBitmapOptions) {
+        decodeBitmapOptions.inTempStorage = null;
+        decodeBitmapOptions.inDither = false;
+        decodeBitmapOptions.inScaled = false;
+        decodeBitmapOptions.inSampleSize = 1;
+        decodeBitmapOptions.inPreferredConfig = null;
+        decodeBitmapOptions.inJustDecodeBounds = false;
+
+        if (Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT)  {
+            decodeBitmapOptions.inBitmap = null;
+            decodeBitmapOptions.inMutable = true;
+        }
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/FileDescriptorBitmapDataLoadProvider.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/FileDescriptorBitmapDataLoadProvider.java
index e313bb0..af27bc6 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/FileDescriptorBitmapDataLoadProvider.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/FileDescriptorBitmapDataLoadProvider.java
@@ -2,30 +2,37 @@
 
 import android.graphics.Bitmap;
 import android.os.ParcelFileDescriptor;
-import com.bumptech.glide.DataLoadProvider;
+
+import com.bumptech.glide.load.DecodeFormat;
 import com.bumptech.glide.load.Encoder;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.ResourceEncoder;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
-import com.bumptech.glide.load.model.NullEncoder;
+import com.bumptech.glide.load.resource.NullEncoder;
+import com.bumptech.glide.load.resource.file.FileToStreamDecoder;
+import com.bumptech.glide.provider.DataLoadProvider;
 
-import java.io.InputStream;
+import java.io.File;
 
+/**
+ * An {@link com.bumptech.glide.provider.DataLoadProvider} that provides classes for decoding and encoding
+ * {@link android.graphics.Bitmap}s from {@link android.os.ParcelFileDescriptor} data.
+ */
 public class FileDescriptorBitmapDataLoadProvider implements DataLoadProvider<ParcelFileDescriptor, Bitmap> {
-    private final StreamBitmapDecoder cacheDecoder;
+    private final ResourceDecoder<File, Bitmap> cacheDecoder;
     private final FileDescriptorBitmapDecoder sourceDecoder;
     private final BitmapEncoder encoder;
-    private final NullEncoder<ParcelFileDescriptor> sourceEncoder;
+    private final Encoder<ParcelFileDescriptor> sourceEncoder;
 
-    public FileDescriptorBitmapDataLoadProvider(BitmapPool bitmapPool) {
-        cacheDecoder = new StreamBitmapDecoder(bitmapPool);
-        sourceDecoder = new FileDescriptorBitmapDecoder(bitmapPool);
+    public FileDescriptorBitmapDataLoadProvider(BitmapPool bitmapPool, DecodeFormat decodeFormat) {
+        cacheDecoder = new FileToStreamDecoder<Bitmap>(new StreamBitmapDecoder(bitmapPool, decodeFormat));
+        sourceDecoder = new FileDescriptorBitmapDecoder(bitmapPool, decodeFormat);
         encoder = new BitmapEncoder();
         sourceEncoder = NullEncoder.get();
     }
 
     @Override
-    public ResourceDecoder<InputStream, Bitmap> getCacheDecoder() {
+    public ResourceDecoder<File, Bitmap> getCacheDecoder() {
         return cacheDecoder;
     }
 
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/FileDescriptorBitmapDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/FileDescriptorBitmapDecoder.java
index fb056d4..f6f2bfb 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/FileDescriptorBitmapDecoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/FileDescriptorBitmapDecoder.java
@@ -1,8 +1,10 @@
 package com.bumptech.glide.load.resource.bitmap;
 
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.os.ParcelFileDescriptor;
 
+import com.bumptech.glide.Glide;
 import com.bumptech.glide.load.DecodeFormat;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.engine.Resource;
@@ -10,13 +12,25 @@
 
 import java.io.IOException;
 
+/**
+ * An {@link com.bumptech.glide.load.ResourceDecoder} for decoding {@link android.graphics.Bitmap}s from
+ * {@link android.os.ParcelFileDescriptor} data.
+ */
 public class FileDescriptorBitmapDecoder implements ResourceDecoder<ParcelFileDescriptor, Bitmap> {
     private final VideoBitmapDecoder bitmapDecoder;
     private final BitmapPool bitmapPool;
     private DecodeFormat decodeFormat;
 
-    public FileDescriptorBitmapDecoder(BitmapPool bitmapPool) {
-        this(new VideoBitmapDecoder(), bitmapPool, DecodeFormat.ALWAYS_ARGB_8888);
+    public FileDescriptorBitmapDecoder(Context context) {
+        this(Glide.get(context).getBitmapPool(), DecodeFormat.DEFAULT);
+    }
+
+    public FileDescriptorBitmapDecoder(Context context, DecodeFormat decodeFormat) {
+        this(Glide.get(context).getBitmapPool(), decodeFormat);
+    }
+
+    public FileDescriptorBitmapDecoder(BitmapPool bitmapPool, DecodeFormat decodeFormat) {
+        this(new VideoBitmapDecoder(), bitmapPool, decodeFormat);
     }
 
     public FileDescriptorBitmapDecoder(VideoBitmapDecoder bitmapDecoder, BitmapPool bitmapPool,
@@ -29,11 +43,7 @@
     @Override
     public Resource<Bitmap> decode(ParcelFileDescriptor source, int width, int height) throws IOException {
         Bitmap bitmap = bitmapDecoder.decode(source, bitmapPool, width, height, decodeFormat);
-        if (bitmap == null) {
-            return null;
-        } else {
-            return new BitmapResource(bitmap, bitmapPool);
-        }
+        return BitmapResource.obtain(bitmap, bitmapPool);
     }
 
     @Override
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/FitCenter.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/FitCenter.java
index e8d1197..0d50ec0 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/FitCenter.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/FitCenter.java
@@ -1,33 +1,27 @@
 package com.bumptech.glide.load.resource.bitmap;
 
+import android.content.Context;
 import android.graphics.Bitmap;
-import com.bumptech.glide.load.engine.Resource;
+
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
-import com.bumptech.glide.load.Transformation;
 
 /**
- * Scale the image uniformly (maintaining the image's aspect ratio) so that one of the dimensions of the image
- * will be equal to the given dimension and the other will be less than the given dimension
+ * Scales the image uniformly (maintaining the image's aspect ratio) so that one of the dimensions of the image
+ * will be equal to the given dimension and the other will be less than the given dimension.
  */
-public class FitCenter implements Transformation<Bitmap> {
-    private BitmapPool pool;
+public class FitCenter extends BitmapTransformation {
 
-    public FitCenter(BitmapPool pool) {
-        this.pool = pool;
+    public FitCenter(Context context) {
+        super(context);
+    }
+
+    public FitCenter(BitmapPool bitmapPool) {
+        super(bitmapPool);
     }
 
     @Override
-    public Resource<Bitmap> transform(Resource<Bitmap> resource, int outWidth, int outHeight) {
-        if (outWidth <= 0 || outHeight <= 0) {
-            throw new IllegalArgumentException("Cannot fit center image to within width=" + outWidth + " or height="
-                    + outHeight);
-        }
-        Bitmap transformed = TransformationUtils.fitCenter(resource.get(), pool, outWidth, outHeight);
-        if (transformed == resource.get()) {
-            return resource;
-        } else {
-            return new BitmapResource(transformed, pool);
-        }
+    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
+        return TransformationUtils.fitCenter(toTransform, pool, outWidth, outHeight);
     }
 
     @Override
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/GlideBitmapDrawable.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/GlideBitmapDrawable.java
new file mode 100644
index 0000000..27e0518
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/GlideBitmapDrawable.java
@@ -0,0 +1,192 @@
+package com.bumptech.glide.load.resource.bitmap;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+
+/**
+ * A static {@link com.bumptech.glide.load.resource.drawable.GlideDrawable} for displaying a single image.
+ */
+public class GlideBitmapDrawable extends GlideDrawable {
+    private final Rect destRect = new Rect();
+    private int width;
+    private int height;
+    private boolean applyGravity;
+    private boolean mutated;
+    private BitmapState state;
+
+    public GlideBitmapDrawable(Resources res, Bitmap bitmap) {
+        this(res, new BitmapState(bitmap));
+    }
+
+    GlideBitmapDrawable(Resources res, BitmapState state) {
+        if (state == null) {
+            throw new NullPointerException("BitmapState must not be null");
+        }
+
+        this.state = state;
+        final int targetDensity;
+        if (res != null) {
+            final int density = res.getDisplayMetrics().densityDpi;
+            targetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
+            state.targetDensity = targetDensity;
+        } else {
+            targetDensity = state.targetDensity;
+        }
+        width = state.bitmap.getScaledWidth(targetDensity);
+        height = state.bitmap.getScaledHeight(targetDensity);
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return width;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return height;
+    }
+
+    @Override
+    public boolean isAnimated() {
+        return false;
+    }
+
+    @Override
+    public void setLoopCount(int loopCount) {
+        // Do nothing.
+    }
+
+    @Override
+    public void start() {
+        // Do nothing.
+    }
+
+    @Override
+    public void stop() {
+        // Do nothing.
+    }
+
+    @Override
+    public boolean isRunning() {
+        return false;
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        applyGravity = true;
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        return state;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (applyGravity) {
+            Gravity.apply(BitmapState.GRAVITY, width, height, getBounds(), destRect);
+            applyGravity = false;
+        }
+        canvas.drawBitmap(state.bitmap, null, destRect, state.paint);
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        int currentAlpha = state.paint.getAlpha();
+        if (currentAlpha != alpha) {
+            state.setAlpha(alpha);
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        state.setColorFilter(colorFilter);
+        invalidateSelf();
+    }
+
+    @Override
+    public int getOpacity() {
+        Bitmap bm = state.bitmap;
+        return bm == null || bm.hasAlpha() || state.paint.getAlpha() < 255
+                ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mutated && super.mutate() == this) {
+            state = new BitmapState(state);
+            mutated = true;
+        }
+        return this;
+    }
+
+    public Bitmap getBitmap() {
+        return state.bitmap;
+    }
+
+    static class BitmapState extends ConstantState {
+        private static final int DEFAULT_PAINT_FLAGS = Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
+        private static final Paint DEFAULT_PAINT = new Paint(DEFAULT_PAINT_FLAGS);
+        private static final int GRAVITY = Gravity.FILL;
+
+        final Bitmap bitmap;
+
+        int targetDensity;
+        Paint paint = DEFAULT_PAINT;
+
+        public BitmapState(Bitmap bitmap) {
+            this.bitmap = bitmap;
+        }
+
+
+        BitmapState(BitmapState other) {
+            this(other.bitmap);
+            targetDensity = other.targetDensity;
+        }
+
+        void setColorFilter(ColorFilter colorFilter) {
+            mutatePaint();
+            paint.setColorFilter(colorFilter);
+        }
+
+        void setAlpha(int alpha) {
+            mutatePaint();
+            paint.setAlpha(alpha);
+        }
+
+        // We want to create a new Paint object so we can mutate it safely.
+        @SuppressWarnings("PMD.CompareObjectsWithEquals")
+        void mutatePaint() {
+            if (DEFAULT_PAINT == paint) {
+                paint = new Paint(DEFAULT_PAINT_FLAGS);
+            }
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new GlideBitmapDrawable(null, this);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new GlideBitmapDrawable(res, this);
+        }
+
+        @Override
+        public int getChangingConfigurations() {
+            return 0;
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/GlideBitmapDrawableResource.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/GlideBitmapDrawableResource.java
new file mode 100644
index 0000000..91b211e
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/GlideBitmapDrawableResource.java
@@ -0,0 +1,27 @@
+package com.bumptech.glide.load.resource.bitmap;
+
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.drawable.DrawableResource;
+import com.bumptech.glide.util.Util;
+
+/**
+ * A resource wrapper for {@link com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawable}.
+ */
+public class GlideBitmapDrawableResource extends DrawableResource<GlideBitmapDrawable> {
+    private final BitmapPool bitmapPool;
+
+    public GlideBitmapDrawableResource(GlideBitmapDrawable drawable, BitmapPool bitmapPool) {
+        super(drawable);
+        this.bitmapPool = bitmapPool;
+    }
+
+    @Override
+    public int getSize() {
+        return Util.getBitmapByteSize(drawable.getBitmap());
+    }
+
+    @Override
+    public void recycle() {
+        bitmapPool.put(drawable.getBitmap());
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ImageHeaderParser.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ImageHeaderParser.java
index 76eac9c..cd78781 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ImageHeaderParser.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ImageHeaderParser.java
@@ -1,34 +1,38 @@
 package com.bumptech.glide.load.resource.bitmap;
 
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
 import static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.GIF;
 import static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.JPEG;
 import static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.PNG;
 import static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.PNG_A;
 import static com.bumptech.glide.load.resource.bitmap.ImageHeaderParser.ImageType.UNKNOWN;
 
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
 /**
- * A class for parsing the exif orientation from an InputStream for an image. Handles jpegs and tiffs.
+ * A class for parsing the exif orientation and other data from an image header.
  */
 public class ImageHeaderParser {
     private static final String TAG = "ImageHeaderParser";
 
+    /**
+     * The format of the image data including whether or not the image may include transparent pixels.
+     */
     public static enum ImageType {
-        /** GIF type */
+        /** GIF type. */
         GIF(true),
-        /** JPG type */
+        /** JPG type. */
         JPEG(false),
-        /** PNG type with alpha */
+        /** PNG type with alpha. */
         PNG_A(true),
-        /** PNG type without alpha */
+        /** PNG type without alpha. */
         PNG(false),
-        /** Unrecognized type */
+        /** Unrecognized type. */
         UNKNOWN(false);
         private final boolean hasAlpha;
 
@@ -44,22 +48,31 @@
     private static final int GIF_HEADER = 0x474946;
     private static final int PNG_HEADER = 0x89504E47;
     private static final int EXIF_MAGIC_NUMBER = 0xFFD8;
-    private static final int MOTOROLA_TIFF_MAGIC_NUMBER = 0x4D4D;  // "MM"
-    private static final int INTEL_TIFF_MAGIC_NUMBER = 0x4949;     // "II"
+    // "MM".
+    private static final int MOTOROLA_TIFF_MAGIC_NUMBER = 0x4D4D;
+    // "II".
+    private static final int INTEL_TIFF_MAGIC_NUMBER = 0x4949;
     private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0";
-
+    private static final byte[] JPEG_EXIF_SEGMENT_PREAMBLE_BYTES;
     private static final int SEGMENT_SOS = 0xDA;
     private static final int MARKER_EOI = 0xD9;
-
     private static final int SEGMENT_START_ID = 0xFF;
     private static final int EXIF_SEGMENT_TYPE = 0xE1;
-
     private static final int ORIENTATION_TAG_TYPE = 0x0112;
-
     private static final int[] BYTES_PER_FORMAT = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 };
 
     private final StreamReader streamReader;
 
+    static {
+        byte[] bytes = new byte[0];
+        try {
+            bytes = JPEG_EXIF_SEGMENT_PREAMBLE.getBytes("UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            // Ignore.
+        }
+        JPEG_EXIF_SEGMENT_PREAMBLE_BYTES = bytes;
+    }
+
     public ImageHeaderParser(InputStream is) {
         streamReader = new StreamReader(is);
     }
@@ -73,21 +86,24 @@
     public ImageType getType() throws IOException {
         int firstByte = streamReader.getUInt8();
 
-        if (firstByte == EXIF_MAGIC_NUMBER >> 8) { //JPEG
+        // JPEG.
+        if (firstByte == EXIF_MAGIC_NUMBER >> 8) {
             return JPEG;
         }
 
         final int firstTwoBytes = firstByte << 8 & 0xFF00 | streamReader.getUInt8() & 0xFF;
         final int firstFourBytes = firstTwoBytes << 16 & 0xFFFF0000 | streamReader.getUInt16() & 0xFFFF;
-        if (firstFourBytes == PNG_HEADER) { //PNG
-            //see: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha-color-type
+        // PNG.
+        if (firstFourBytes == PNG_HEADER) {
+            // See: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha-color-type
             streamReader.skip(25 - 4);
             int alpha = streamReader.getByte();
             // A RGB indexed PNG can also have transparency. Better safe than sorry!
             return alpha >= 3 ? PNG_A : PNG;
         }
 
-        if (firstFourBytes >> 8 == GIF_HEADER) { //GIF from first 3 bytes
+        // GIF from first 3 bytes.
+        if (firstFourBytes >> 8 == GIF_HEADER) {
             return GIF;
         }
 
@@ -108,9 +124,19 @@
             return -1;
         } else {
             byte[] exifData = getExifSegment();
-            if (exifData != null && exifData.length >= JPEG_EXIF_SEGMENT_PREAMBLE.length()
-                    && new String(exifData, 0, JPEG_EXIF_SEGMENT_PREAMBLE.length())
-                        .equalsIgnoreCase(JPEG_EXIF_SEGMENT_PREAMBLE)) {
+            boolean hasJpegExifPreamble = exifData != null
+                    && exifData.length >= JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length;
+
+            if (hasJpegExifPreamble) {
+                for (int i = 0; i < JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length; i++) {
+                    if (exifData[i] != JPEG_EXIF_SEGMENT_PREAMBLE_BYTES[i]) {
+                        hasJpegExifPreamble = false;
+                        break;
+                    }
+                }
+            }
+
+            if (hasJpegExifPreamble) {
                 return parseExifSegment(new RandomAccessReader(exifData));
             } else {
                 return -1;
@@ -142,7 +168,8 @@
                 return null;
             }
 
-            segmentLength = streamReader.getUInt16() - 2; //segment length includes bytes for segment length
+            // Segment length includes bytes for segment length.
+            segmentLength = streamReader.getUInt16() - 2;
 
             if (segmentType != EXIF_SEGMENT_TYPE) {
                 if (segmentLength != streamReader.skip(segmentLength)) {
@@ -166,13 +193,12 @@
         }
     }
 
-    private int parseExifSegment(RandomAccessReader segmentData) {
-
+    private static int parseExifSegment(RandomAccessReader segmentData) {
         final int headerOffsetSize = JPEG_EXIF_SEGMENT_PREAMBLE.length();
 
         short byteOrderIdentifier = segmentData.getInt16(headerOffsetSize);
         final ByteOrder byteOrder;
-        if (byteOrderIdentifier == MOTOROLA_TIFF_MAGIC_NUMBER) { //
+        if (byteOrderIdentifier == MOTOROLA_TIFF_MAGIC_NUMBER) {
             byteOrder = ByteOrder.BIG_ENDIAN;
         } else if (byteOrderIdentifier == INTEL_TIFF_MAGIC_NUMBER) {
             byteOrder = ByteOrder.LITTLE_ENDIAN;
@@ -194,13 +220,15 @@
 
             tagType = segmentData.getInt16(tagOffset);
 
-            if (tagType != ORIENTATION_TAG_TYPE) { //we only want orientation
+            // We only want orientation.
+            if (tagType != ORIENTATION_TAG_TYPE) {
                 continue;
             }
 
             formatCode = segmentData.getInt16(tagOffset + 2);
 
-            if (formatCode < 1 || formatCode > 12) { //12 is max format code
+            // 12 is max format code.
+            if (formatCode < 1 || formatCode > 12) {
                 if (Log.isLoggable(TAG, Log.DEBUG)) {
                     Log.d(TAG, "Got invalid format code = " + formatCode);
                 }
@@ -254,13 +282,13 @@
     }
 
     private static int calcTagOffset(int ifdOffset, int tagIndex) {
-        return ifdOffset + 2 + (12 * tagIndex);
+        return ifdOffset + 2 + 12 * tagIndex;
     }
 
-    private boolean handles(int imageMagicNumber) {
-        return (imageMagicNumber & EXIF_MAGIC_NUMBER) == EXIF_MAGIC_NUMBER ||
-                imageMagicNumber == MOTOROLA_TIFF_MAGIC_NUMBER ||
-                imageMagicNumber == INTEL_TIFF_MAGIC_NUMBER;
+    private static boolean handles(int imageMagicNumber) {
+        return (imageMagicNumber & EXIF_MAGIC_NUMBER) == EXIF_MAGIC_NUMBER
+                || imageMagicNumber == MOTOROLA_TIFF_MAGIC_NUMBER
+                || imageMagicNumber == INTEL_TIFF_MAGIC_NUMBER;
     }
 
     private static class RandomAccessReader {
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ImageVideoBitmapDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ImageVideoBitmapDecoder.java
index 53436d6..58cb603 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ImageVideoBitmapDecoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ImageVideoBitmapDecoder.java
@@ -3,13 +3,20 @@
 import android.graphics.Bitmap;
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
-import com.bumptech.glide.load.engine.Resource;
+
 import com.bumptech.glide.load.ResourceDecoder;
+import com.bumptech.glide.load.engine.Resource;
 import com.bumptech.glide.load.model.ImageVideoWrapper;
 
 import java.io.IOException;
 import java.io.InputStream;
 
+/**
+ * A {@link ResourceDecoder} that decodes {@link ImageVideoWrapper}s using
+ * a wrapped {@link ResourceDecoder} for {@link InputStream}s
+ * and a wrapped {@link ResourceDecoder} for {@link ParcelFileDescriptor}s.
+ * The {@link InputStream} data in the {@link ImageVideoWrapper} is always preferred.
+ */
 public class ImageVideoBitmapDecoder implements ResourceDecoder<ImageVideoWrapper, Bitmap> {
     private static final String TAG = "ImageVideoDecoder";
     private final ResourceDecoder<InputStream, Bitmap> streamDecoder;
@@ -21,13 +28,15 @@
         this.fileDescriptorDecoder = fileDescriptorDecoder;
     }
 
+    @SuppressWarnings("resource")
+    // @see ResourceDecoder.decode
     @Override
     public Resource<Bitmap> decode(ImageVideoWrapper source, int width, int height) throws IOException {
         Resource<Bitmap> result = null;
         InputStream is = source.getStream();
         if (is != null) {
             try {
-                result = streamDecoder.decode(source.getStream(), width, height);
+                result = streamDecoder.decode(is, width, height);
             } catch (IOException e) {
                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
                     Log.v(TAG, "Failed to load image from stream, trying FileDescriptor", e);
@@ -38,7 +47,7 @@
         if (result == null) {
             ParcelFileDescriptor fileDescriptor = source.getFileDescriptor();
             if (fileDescriptor != null) {
-                result = fileDescriptorDecoder.decode(source.getFileDescriptor(), width, height);
+                result = fileDescriptorDecoder.decode(fileDescriptor, width, height);
             }
         }
         return result;
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ImageVideoDataLoadProvider.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ImageVideoDataLoadProvider.java
index 632fb6f..002a48f 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ImageVideoDataLoadProvider.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ImageVideoDataLoadProvider.java
@@ -2,35 +2,39 @@
 
 import android.graphics.Bitmap;
 import android.os.ParcelFileDescriptor;
-import com.bumptech.glide.DataLoadProvider;
+
 import com.bumptech.glide.load.Encoder;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.ResourceEncoder;
-import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 import com.bumptech.glide.load.model.ImageVideoWrapper;
 import com.bumptech.glide.load.model.ImageVideoWrapperEncoder;
-import com.bumptech.glide.load.model.NullEncoder;
-import com.bumptech.glide.load.model.StreamEncoder;
+import com.bumptech.glide.provider.DataLoadProvider;
 
+import java.io.File;
 import java.io.InputStream;
 
+/**
+ * A {@link com.bumptech.glide.provider.DataLoadProvider} for loading either an {@link java.io.InputStream} or an
+ * {@link android.os.ParcelFileDescriptor} as an {@link android.graphics.Bitmap}.
+ */
 public class ImageVideoDataLoadProvider implements DataLoadProvider<ImageVideoWrapper, Bitmap> {
     private final ImageVideoBitmapDecoder sourceDecoder;
-    private final StreamBitmapDecoder cacheDecoder;
-    private final BitmapEncoder encoder;
+    private final ResourceDecoder<File, Bitmap> cacheDecoder;
+    private final ResourceEncoder<Bitmap> encoder;
     private final ImageVideoWrapperEncoder sourceEncoder;
 
-    public ImageVideoDataLoadProvider(BitmapPool bitmapPool) {
-        encoder = new BitmapEncoder();
-        Encoder<ParcelFileDescriptor> fileDescriptorEncoder = NullEncoder.get();
-        sourceEncoder = new ImageVideoWrapperEncoder(new StreamEncoder(), fileDescriptorEncoder);
-        cacheDecoder = new StreamBitmapDecoder(bitmapPool);
-        sourceDecoder = new ImageVideoBitmapDecoder(cacheDecoder,
-                new FileDescriptorBitmapDecoder(bitmapPool));
+    public ImageVideoDataLoadProvider(DataLoadProvider<InputStream, Bitmap> streamBitmapProvider,
+            DataLoadProvider<ParcelFileDescriptor, Bitmap> fileDescriptorBitmapProvider) {
+        encoder = streamBitmapProvider.getEncoder();
+        sourceEncoder = new ImageVideoWrapperEncoder(streamBitmapProvider.getSourceEncoder(),
+                fileDescriptorBitmapProvider.getSourceEncoder());
+        cacheDecoder = streamBitmapProvider.getCacheDecoder();
+        sourceDecoder = new ImageVideoBitmapDecoder(streamBitmapProvider.getSourceDecoder(),
+                fileDescriptorBitmapProvider.getSourceDecoder());
     }
 
     @Override
-    public ResourceDecoder<InputStream, Bitmap> getCacheDecoder() {
+    public ResourceDecoder<File, Bitmap> getCacheDecoder() {
         return cacheDecoder;
     }
 
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/RecyclableBufferedInputStream.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/RecyclableBufferedInputStream.java
index ed592b2..8699022 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/RecyclableBufferedInputStream.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/RecyclableBufferedInputStream.java
@@ -5,71 +5,66 @@
  *  contributor license agreements.  See the NOTICE file distributed with
  *  this work for additional information regarding copyright ownership.
  *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the &quot;License&quot;); you may not use this file except in compliance with
+ *  (the "License"); you may not use this file except in compliance with
  *  the License.  You may obtain a copy of the License at
  *
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+ *  distributed under the License is distributed on an "AS IS" BASIS,
  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
 
+import android.util.Log;
 
 import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
 /**
- * Wraps an existing {@link InputStream} and &lt;em&gt;buffers&lt;/em&gt; the input.
+ * Wraps an existing {@link InputStream} and <em>buffers</em> the input.
  * Expensive interaction with the underlying input stream is minimized, since
  * most (smaller) requests can be satisfied by accessing the buffer alone. The
  * drawback is that some extra space is required to hold the buffer and that
  * copying takes place when filling that buffer, but this is usually outweighed
  * by the performance benefits.
  *
- * &lt;p/&gt;A typical application pattern for the class looks like this:&lt;p/&gt;
+ * <p>A typical application pattern for the class looks like this:</p>
  *
- * &lt;pre&gt;
- * BufferedInputStream buf = new BufferedInputStream(new FileInputStream(&amp;quot;file.java&amp;quot;));
- * &lt;/pre&gt;
- *
+ * <pre>
+ * BufferedInputStream buf = new BufferedInputStream(new FileInputStream("file.java"));
+ * </pre>
  */
 public class RecyclableBufferedInputStream extends FilterInputStream {
-
-    public static class InvalidMarkException extends RuntimeException {
-        public InvalidMarkException(String detailMessage) {
-            super(detailMessage);
-        }
-    }
+    private static final String TAG = "BufferedIs";
 
     /**
      * The buffer containing the current bytes read from the target InputStream.
      */
-    protected volatile byte[] buf;
+    private volatile byte[] buf;
 
     /**
      * The total number of bytes inside the byte array {@code buf}.
      */
-    protected int count;
+    private int count;
 
     /**
      * The current limit, which when passed, invalidates the current mark.
      */
-    protected int marklimit;
+    private int marklimit;
 
     /**
      * The currently marked position. -1 indicates no mark has been set or the
      * mark has been invalidated.
      */
-    protected int markpos = -1;
+    private int markpos = -1;
 
     /**
      * The current position within the byte array {@code buf}.
      */
-    protected int pos;
+    private int pos;
 
     public RecyclableBufferedInputStream(InputStream in, byte[] buffer) {
         super(in);
@@ -90,18 +85,30 @@
      */
     @Override
     public synchronized int available() throws IOException {
-        InputStream localIn = in; // &#39;in&#39; could be invalidated by close()
+        // in could be invalidated by close().
+        InputStream localIn = in;
         if (buf == null || localIn == null) {
             throw streamClosed();
         }
         return count - pos + localIn.available();
     }
 
-    private IOException streamClosed() throws IOException {
+    private static IOException streamClosed() throws IOException {
         throw new IOException("BufferedInputStream is closed");
     }
 
     /**
+     * Reduces the mark limit to match the current buffer length to prevent the buffer from
+     * continuing to increase in size.
+     *
+     * <p>Subsequent calls to {@link #mark(int)} will be obeyed and may cause the buffer size
+     * to increase.
+     */
+    public synchronized void fixMarkLimit() {
+        marklimit = buf.length;
+    }
+
+    /**
      * Closes this stream. The source stream is closed and any resources
      * associated with it are released.
      *
@@ -120,27 +127,30 @@
 
     private int fillbuf(InputStream localIn, byte[] localBuf)
             throws IOException {
-        if (markpos == -1 || (pos - markpos >= marklimit)) {
-            /* Mark position not set or exceeded readlimit */
+        if (markpos == -1 || pos - markpos >= marklimit) {
+            // Mark position not set or exceeded readlimit
             int result = localIn.read(localBuf);
             if (result > 0) {
                 markpos = -1;
                 pos = 0;
-                count = result == -1 ? 0 : result;
+                count = result;
             }
             return result;
         }
-        //Added count == localBuf.length so that we do not immediately double the buffer size before reading any data
+        // Added count == localBuf.length so that we do not immediately double the buffer size before reading any data
         // when marklimit > localBuf.length. Instead, we will double the buffer size only after reading the initial
         // localBuf worth of data without finding what we're looking for in the stream. This allows us to set a
         // relatively small initial buffer size and a large marklimit for safety without causing an allocation each time
         // read is called.
         if (markpos == 0 && marklimit > localBuf.length && count == localBuf.length) {
-            /* Increase buffer size to accommodate the readlimit */
+            // Increase buffer size to accommodate the readlimit
             int newLength = localBuf.length * 2;
             if (newLength > marklimit) {
                 newLength = marklimit;
             }
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "allocate buffer of length: " + newLength);
+            }
             byte[] newbuf = new byte[newLength];
             System.arraycopy(localBuf, 0, newbuf, 0, localBuf.length);
             // Reassign buf, which will invalidate any local references
@@ -150,7 +160,7 @@
             System.arraycopy(localBuf, markpos, localBuf, 0, localBuf.length
                     - markpos);
         }
-        /* Set the new position and mark position */
+        // Set the new position and mark position
         pos -= markpos;
         count = markpos = 0;
         int bytesread = localIn.read(localBuf, pos, localBuf.length - pos);
@@ -161,7 +171,7 @@
     /**
      * Sets a mark position in this stream. The parameter {@code readlimit}
      * indicates how many bytes can be read before a mark is invalidated.
-     * Calling {@code reset()} will reposition the stream back to the marked
+     * Calling {@link #reset()} will reposition the stream back to the marked
      * position if {@code readlimit} has not been surpassed. The underlying
      * buffer may be increased in size to allow {@code readlimit} number of
      * bytes to be supported.
@@ -173,22 +183,17 @@
      */
     @Override
     public synchronized void mark(int readlimit) {
-        //This is stupid, but BitmapFactory.decodeStream calls mark(1024)
-        //which is too small for a substantial portion of images. This
-        //change (using Math.max) ensures that we don't overwrite readlimit
-        //with a smaller value
+        // This is stupid, but BitmapFactory.decodeStream calls mark(1024)
+        // which is too small for a substantial portion of images. This
+        // change (using Math.max) ensures that we don't overwrite readlimit
+        // with a smaller value
         marklimit = Math.max(marklimit, readlimit);
         markpos = pos;
     }
 
-    public synchronized void clearMark() {
-        markpos = -1;
-        marklimit = 0;
-    }
-
     /**
-     * Indicates whether {@code BufferedInputStream} supports the {@code mark()}
-     * and {@code reset()} methods.
+     * Indicates whether {@code BufferedInputStream} supports the {@link #mark(int)}
+     * and {@link #reset()} methods.
      *
      * @return {@code true} for BufferedInputStreams.
      * @see #mark(int)
@@ -220,9 +225,10 @@
             throw streamClosed();
         }
 
-        /* Are there buffered bytes available? */
+        // Are there buffered bytes available?
         if (pos >= count && fillbuf(localIn, localBuf) == -1) {
-            return -1; /* no, fill buffer */
+            // no, fill buffer
+            return -1;
         }
         // localBuf may have been invalidated by fillbuf
         if (localBuf != buf) {
@@ -232,7 +238,7 @@
             }
         }
 
-        /* Did filling the buffer fail with -1 (EOF)? */
+        // Did filling the buffer fail with -1 (EOF)?
         if (count - pos > 0) {
             return localBuf[pos++] & 0xFF;
         }
@@ -245,14 +251,14 @@
      * number of bytes actually read or -1 if no bytes were read and the end of
      * the stream was encountered. If all the buffered bytes have been used, a
      * mark has not been set and the requested number of bytes is larger than
-     * the receiver&#39;s buffer size, this implementation bypasses the buffer and
+     * the receiver's buffer size, this implementation bypasses the buffer and
      * simply places the results directly into {@code buffer}.
      *
      * @param buffer
      *            the byte array in which to store the bytes read.
      * @return the number of bytes actually read or -1 if end of stream.
      * @throws IndexOutOfBoundsException
-     *             if {@code offset &lt; 0} or {@code byteCount &lt; 0}, or if
+     *             if {@code offset < 0} or {@code byteCount < 0}, or if
      *             {@code offset + byteCount} is greater than the size of
      *             {@code buffer}.
      * @throws IOException
@@ -261,8 +267,7 @@
      */
     @Override
     public synchronized int read(byte[] buffer, int offset, int byteCount) throws IOException {
-        // Use local ref since buf may be invalidated by an unsynchronized
-        // close()
+        // Use local ref since buf may be invalidated by an unsynchronized close()
         byte[] localBuf = buf;
         if (localBuf == null) {
             throw streamClosed();
@@ -278,7 +283,7 @@
 
         int required;
         if (pos < count) {
-            /* There are bytes available in the buffer. */
+            // There are bytes available in the buffer.
             int copylength = count - pos >= byteCount ? byteCount : count - pos;
             System.arraycopy(localBuf, pos, buffer, offset, copylength);
             pos += copylength;
@@ -293,10 +298,8 @@
 
         while (true) {
             int read;
-            /*
-             * If we&#39;re not marked and the required size is greater than the
-             * buffer, simply read the bytes directly bypassing the buffer.
-             */
+            // If we're not marked and the required size is greater than the buffer,
+            // simply read the bytes directly bypassing the buffer.
             if (markpos == -1 && required >= localBuf.length) {
                 read = localIn.read(buffer, offset, required);
                 if (read == -1) {
@@ -351,11 +354,11 @@
 
     /**
      * Skips {@code byteCount} bytes in this stream. Subsequent calls to
-     * {@code read} will not return these bytes unless {@code reset} is
+     * {@link #read} will not return these bytes unless {@link #reset} is
      * used.
      *
      * @param byteCount
-     *            the number of bytes to skip. {@code skip} does nothing and
+     *            the number of bytes to skip. {@link #skip} does nothing and
      *            returns 0 if {@code byteCount} is less than zero.
      * @return the number of bytes actually skipped.
      * @throws IOException
@@ -363,8 +366,7 @@
      */
     @Override
     public synchronized long skip(long byteCount) throws IOException {
-        // Use local refs since buf and in may be invalidated by an
-        // unsynchronized close()
+        // Use local refs since buf and in may be invalidated by an unsynchronized close()
         byte[] localBuf = buf;
         InputStream localIn = in;
         if (localBuf == null) {
@@ -384,21 +386,31 @@
         long read = count - pos;
         pos = count;
 
-        if (markpos != -1) {
-            if (byteCount <= marklimit) {
-                if (fillbuf(localIn, localBuf) == -1) {
-                    return read;
-                }
-                if (count - pos >= byteCount - read) {
-                    pos += byteCount - read;
-                    return byteCount;
-                }
-                // Couldn&#39;t get all the bytes, skip what we read
-                read += (count - pos);
-                pos = count;
+        if (markpos != -1 && byteCount <= marklimit) {
+            if (fillbuf(localIn, localBuf) == -1) {
                 return read;
             }
+            if (count - pos >= byteCount - read) {
+                pos += byteCount - read;
+                return byteCount;
+            }
+            // Couldn't get all the bytes, skip what we read.
+            read = read + count - pos;
+            pos = count;
+            return read;
         }
         return read + localIn.skip(byteCount - read);
     }
+
+    /**
+     * An exception thrown when a mark can no longer be obeyed because the underlying buffer size is smaller than the
+     * amount of data read after the mark position.
+     */
+    public static class InvalidMarkException extends RuntimeException {
+        private static final long serialVersionUID = -4338378848813561757L;
+
+        public InvalidMarkException(String detailMessage) {
+            super(detailMessage);
+        }
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/StreamBitmapDataLoadProvider.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/StreamBitmapDataLoadProvider.java
index de3413e..e05e484 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/StreamBitmapDataLoadProvider.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/StreamBitmapDataLoadProvider.java
@@ -1,29 +1,39 @@
 package com.bumptech.glide.load.resource.bitmap;
 
 import android.graphics.Bitmap;
-import com.bumptech.glide.DataLoadProvider;
+
+import com.bumptech.glide.load.DecodeFormat;
 import com.bumptech.glide.load.Encoder;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.ResourceEncoder;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 import com.bumptech.glide.load.model.StreamEncoder;
+import com.bumptech.glide.load.resource.file.FileToStreamDecoder;
+import com.bumptech.glide.provider.DataLoadProvider;
 
+import java.io.File;
 import java.io.InputStream;
 
+/**
+ * An {@link com.bumptech.glide.provider.DataLoadProvider} that provides decoders and encoders for decoding and caching
+ * {@link android.graphics.Bitmap}s using {@link java.io.InputStream} data.
+ */
 public class StreamBitmapDataLoadProvider implements DataLoadProvider<InputStream, Bitmap> {
     private final StreamBitmapDecoder decoder;
     private final BitmapEncoder encoder;
     private final StreamEncoder sourceEncoder;
+    private final FileToStreamDecoder<Bitmap> cacheDecoder;
 
-    public StreamBitmapDataLoadProvider(BitmapPool bitmapPool) {
+    public StreamBitmapDataLoadProvider(BitmapPool bitmapPool, DecodeFormat decodeFormat) {
         sourceEncoder = new StreamEncoder();
-        decoder = new StreamBitmapDecoder(bitmapPool);
+        decoder = new StreamBitmapDecoder(bitmapPool, decodeFormat);
         encoder = new BitmapEncoder();
+        cacheDecoder = new FileToStreamDecoder<Bitmap>(decoder);
     }
 
     @Override
-    public ResourceDecoder<InputStream, Bitmap> getCacheDecoder() {
-        return decoder;
+    public ResourceDecoder<File, Bitmap> getCacheDecoder() {
+        return cacheDecoder;
     }
 
     @Override
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/StreamBitmapDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/StreamBitmapDecoder.java
index 0064e2f..407b8cb 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/StreamBitmapDecoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/StreamBitmapDecoder.java
@@ -2,15 +2,20 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
+
 import com.bumptech.glide.Glide;
-import com.bumptech.glide.load.engine.Resource;
 import com.bumptech.glide.load.DecodeFormat;
 import com.bumptech.glide.load.ResourceDecoder;
+import com.bumptech.glide.load.engine.Resource;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 
 import java.io.InputStream;
 
-//TODO(actually fill out this stub)
+/**
+ * An {@link com.bumptech.glide.load.ResourceDecoder} that uses an
+ * {@link com.bumptech.glide.load.resource.bitmap.Downsampler} to decode an {@link android.graphics.Bitmap} from an
+ * {@link java.io.InputStream}.
+ */
 public class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> {
     private static final String ID = "StreamBitmapDecoder.com.bumptech.glide.load.resource.bitmap";
     private final Downsampler downsampler;
@@ -23,7 +28,15 @@
     }
 
     public StreamBitmapDecoder(BitmapPool bitmapPool) {
-        this(Downsampler.AT_LEAST, bitmapPool, DecodeFormat.ALWAYS_ARGB_8888);
+        this(bitmapPool, DecodeFormat.DEFAULT);
+    }
+
+    public StreamBitmapDecoder(Context context, DecodeFormat decodeFormat) {
+        this(Glide.get(context).getBitmapPool(), decodeFormat);
+    }
+
+    public StreamBitmapDecoder(BitmapPool bitmapPool, DecodeFormat decodeFormat) {
+        this(Downsampler.AT_LEAST, bitmapPool, decodeFormat);
     }
 
     public StreamBitmapDecoder(Downsampler downsampler, BitmapPool bitmapPool, DecodeFormat decodeFormat) {
@@ -35,11 +48,7 @@
     @Override
     public Resource<Bitmap> decode(InputStream source, int width, int height) {
         Bitmap bitmap = downsampler.decode(source, bitmapPool, width, height, decodeFormat);
-        if (bitmap == null) {
-            return null;
-        } else {
-            return new BitmapResource(bitmap, bitmapPool);
-        }
+        return BitmapResource.obtain(bitmap, bitmapPool);
     }
 
     @Override
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java
index 9cffbd8..2a7d5f9 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java
@@ -1,35 +1,39 @@
-/*
- * Copyright (c) 2012 Bump Technologies Inc. All rights reserved.
- */
 package com.bumptech.glide.load.resource.bitmap;
 
+import android.annotation.TargetApi;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.RectF;
 import android.media.ExifInterface;
+import android.os.Build;
 import android.util.Log;
+
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 
 /**
- * A class for storing methods to resize Bitmaps
+ * A class with methods to efficiently resize Bitmaps.
  */
-public class TransformationUtils {
+public final class TransformationUtils {
     private static final String TAG = "TransformationUtils";
     public static final int PAINT_FLAGS = Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.FILTER_BITMAP_FLAG;
 
+    private TransformationUtils() {
+        // Utility class.
+    }
+
     /**
      * A potentially expensive operation to crop the given Bitmap so that it fills the given dimensions. This operation
      * is significantly less expensive in terms of memory if a mutable Bitmap with the given dimensions is passed in
      * as well.
      *
      * @param recycled A mutable Bitmap with dimensions width and height that we can load the cropped portion of toCrop
-     *                 into
-     * @param toCrop The Bitmap to resize
-     * @param width The width of the final Bitmap
-     * @param height The height of the final Bitmap
-     * @return The resized Bitmap (will be recycled if recycled is not null)
+     *                 into.
+     * @param toCrop The Bitmap to resize.
+     * @param width The width in pixels of the final Bitmap.
+     * @param height The height in pixels of the final Bitmap.
+     * @return The resized Bitmap (will be recycled if recycled is not null).
      */
     public static Bitmap centerCrop(Bitmap recycled, Bitmap toCrop, int width, int height) {
         if (toCrop == null) {
@@ -37,9 +41,7 @@
         } else if (toCrop.getWidth() == width && toCrop.getHeight() == height) {
             return toCrop;
         }
-        //from ImageView/Bitmap.createScaledBitmap
-        //https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/widget/ImageView.java
-        //https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/graphics/java/android/graphics/Bitmap.java
+        // From ImageView/Bitmap.createScaledBitmap.
         final float scale;
         float dx = 0, dy = 0;
         Matrix m = new Matrix();
@@ -52,14 +54,18 @@
         }
 
         m.setScale(scale, scale);
-        m.postTranslate((int) dx + 0.5f, (int) dy + 0.5f);
+        m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
         final Bitmap result;
         if (recycled != null) {
             result = recycled;
         } else {
-            result = Bitmap.createBitmap(width, height, toCrop.getConfig() == null ?
-                                                            Bitmap.Config.ARGB_8888 : toCrop.getConfig());
+            result = Bitmap.createBitmap(width, height, toCrop.getConfig() == null
+                        ? Bitmap.Config.ARGB_8888 : toCrop.getConfig());
         }
+
+        // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given.
+        TransformationUtils.setAlpha(toCrop, result);
+
         Canvas canvas = new Canvas(result);
         Paint paint = new Paint(PAINT_FLAGS);
         canvas.drawBitmap(toCrop, m, paint);
@@ -72,8 +78,8 @@
      *
      * @param toFit The Bitmap to shrink.
      * @param pool The BitmapPool to try to reuse a bitmap from.
-     * @param width The width the final image will fit within.
-     * @param height The height the final image will fit within.
+     * @param width The width in pixels the final image will fit within.
+     * @param height The height in pixels the final image will fit within.
      * @return A new Bitmap shrunk to fit within the given dimensions, or toFit if toFit's width or height matches the
      * given dimensions and toFit fits within the given dimensions
      */
@@ -106,6 +112,8 @@
         if (toReuse == null) {
             toReuse = Bitmap.createBitmap(targetWidth, targetHeight, config);
         }
+        // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given.
+        TransformationUtils.setAlpha(toFit, toReuse);
 
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "request: " + width + "x" + height);
@@ -124,25 +132,37 @@
     }
 
     /**
+     * Sets the alpha of the Bitmap we're going to re-use to the alpha of the Bitmap we're going to transform. This
+     * keeps {@link android.graphics.Bitmap#hasAlpha()}} consistent before and after the transformation for
+     * transformations that don't add or remove transparent pixels.
+     *
+     * @param toTransform The {@link android.graphics.Bitmap} that will be transformed.
+     * @param outBitmap The {@link android.graphics.Bitmap} that will be returned from the transformation.
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+    public static void setAlpha(Bitmap toTransform, Bitmap outBitmap) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && outBitmap != null) {
+            outBitmap.setHasAlpha(toTransform.hasAlpha());
+        }
+    }
+
+    /**
      * Returns a matrix with rotation set based on Exif orientation tag.
      * If the orientation is undefined or 0 null is returned.
      *
+     * @deprecated No longer used by Glide, scheduled to be removed in Glide 4.0
      * @param pathToOriginal Path to original image file that may have exif data.
      * @return  A rotation in degrees based on exif orientation
      */
+    @TargetApi(Build.VERSION_CODES.ECLAIR)
+    @Deprecated
     public static int getOrientation(String pathToOriginal) {
         int degreesToRotate = 0;
-        try{
+        try {
             ExifInterface exif = new ExifInterface(pathToOriginal);
             int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
-            if (orientation == ExifInterface.ORIENTATION_ROTATE_90){
-                degreesToRotate = 90;
-            } else if (orientation == ExifInterface.ORIENTATION_ROTATE_180){
-                degreesToRotate = 180;
-            } else if (orientation == ExifInterface.ORIENTATION_ROTATE_270){
-                degreesToRotate = 270;
-            }
-        } catch (Exception e){
+            return getExifOrientationDegrees(orientation);
+        } catch (Exception e) {
             if (Log.isLoggable(TAG, Log.ERROR)) {
                 Log.e(TAG, "Unable to get orientation for image with path=" + pathToOriginal, e);
             }
@@ -154,12 +174,13 @@
      * This is an expensive operation that copies the image in place with the pixels rotated.
      * If possible rather use getOrientationMatrix, and set that as the imageMatrix on an ImageView.
      *
+     * @deprecated No longer used by Glide, scheduled to be removed in Glide 4.0
      * @param pathToOriginal Path to original image file that may have exif data.
      * @param imageToOrient Image Bitmap to orient.
      * @return The oriented bitmap. May be the imageToOrient without modification, or a new Bitmap.
      */
-    @SuppressWarnings("unused")
-    public static Bitmap orientImage(String pathToOriginal, Bitmap imageToOrient){
+    @Deprecated
+    public static Bitmap orientImage(String pathToOriginal, Bitmap imageToOrient) {
         int degreesToRotate = getOrientation(pathToOriginal);
         return rotateImage(imageToOrient, degreesToRotate);
     }
@@ -169,15 +190,17 @@
      * If possible rather use getOrientationMatrix, and set that as the imageMatrix on an ImageView.
      *
      * @param imageToOrient Image Bitmap to orient.
-     * @param degreesToRotate number of degrees to rotate the image by. If zero the original image is returned unmodified.
+     * @param degreesToRotate number of degrees to rotate the image by. If zero the original image is returned
+     *                        unmodified.
      * @return The oriented bitmap. May be the imageToOrient without modification, or a new Bitmap.
      */
     public static Bitmap rotateImage(Bitmap imageToOrient, int degreesToRotate) {
-        try{
-            if(degreesToRotate != 0) {
+        Bitmap result = imageToOrient;
+        try {
+            if (degreesToRotate != 0) {
                 Matrix matrix = new Matrix();
                 matrix.setRotate(degreesToRotate);
-                imageToOrient = Bitmap.createBitmap(
+                result = Bitmap.createBitmap(
                         imageToOrient,
                         0,
                         0,
@@ -190,9 +213,8 @@
             if (Log.isLoggable(TAG, Log.ERROR)) {
                 Log.e(TAG, "Exception when trying to orient image", e);
             }
-            e.printStackTrace();
         }
-        return imageToOrient;
+        return result;
     }
 
     /**
@@ -224,15 +246,44 @@
     }
 
     /**
-     * Rotate and/or flip the image to match the given exif orientation
+     * Rotate and/or flip the image to match the given exif orientation.
      *
-     * @param toOrient The bitmap to rotate/flip
-     * @param pool A pool that may or may not contain an image of the necessary dimensions
-     * @param exifOrientation the exif orientation [1-8]
-     * @return The rotated and/or flipped image or toOrient if no rotation or flip was necessary
+     * @param toOrient The bitmap to rotate/flip.
+     * @param pool A pool that may or may not contain an image of the necessary dimensions.
+     * @param exifOrientation the exif orientation [1-8].
+     * @return The rotated and/or flipped image or toOrient if no rotation or flip was necessary.
      */
     public static Bitmap rotateImageExif(Bitmap toOrient, BitmapPool pool, int exifOrientation) {
+        if (exifOrientation == ExifInterface.ORIENTATION_NORMAL
+                || exifOrientation == ExifInterface.ORIENTATION_UNDEFINED) {
+            return toOrient;
+        }
         final Matrix matrix = new Matrix();
+        initializeMatrixForRotation(exifOrientation, matrix);
+
+        // From Bitmap.createBitmap.
+        final RectF newRect = new RectF(0, 0, toOrient.getWidth(), toOrient.getHeight());
+        matrix.mapRect(newRect);
+
+        final int newWidth = Math.round(newRect.width());
+        final int newHeight = Math.round(newRect.height());
+
+        Bitmap result = pool.get(newWidth, newHeight, toOrient.getConfig());
+        if (result == null) {
+            result = Bitmap.createBitmap(newWidth, newHeight, toOrient.getConfig());
+        }
+
+        matrix.postTranslate(-newRect.left, -newRect.top);
+
+        final Canvas canvas = new Canvas(result);
+        final Paint paint = new Paint(PAINT_FLAGS);
+        canvas.drawBitmap(toOrient, matrix, paint);
+
+        return result;
+    }
+
+    // Visible for testing.
+    static void initializeMatrixForRotation(int exifOrientation, Matrix matrix) {
         switch (exifOrientation) {
             case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
                 matrix.setScale(-1, 1);
@@ -258,28 +309,8 @@
             case ExifInterface.ORIENTATION_ROTATE_270:
                 matrix.setRotate(-90);
                 break;
-            default: //case ExifInterface.ORIENTATION_NORMAL
-                return toOrient;
+            default:
+                // Do nothing.
         }
-
-        // From Bitmap.createBitmap.
-        final RectF newRect = new RectF(0, 0, toOrient.getWidth(), toOrient.getHeight());
-        matrix.mapRect(newRect);
-
-        final int newWidth = Math.round(newRect.width());
-        final int newHeight = Math.round(newRect.height());
-
-        Bitmap result = pool.get(newWidth, newHeight, toOrient.getConfig());
-        if (result == null) {
-            result = Bitmap.createBitmap(newWidth, newHeight, toOrient.getConfig());
-        }
-
-        matrix.postTranslate(-newRect.left, -newRect.top);
-
-        final Canvas canvas = new Canvas(result);
-        final Paint paint = new Paint(PAINT_FLAGS);
-        canvas.drawBitmap(toOrient, matrix, paint);
-
-        return result;
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/VideoBitmapDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/VideoBitmapDecoder.java
index f2c74c1..537b185 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/VideoBitmapDecoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/VideoBitmapDecoder.java
@@ -3,24 +3,26 @@
 import android.graphics.Bitmap;
 import android.media.MediaMetadataRetriever;
 import android.os.ParcelFileDescriptor;
-import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+
 import com.bumptech.glide.load.DecodeFormat;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 
 import java.io.IOException;
 
+/**
+ * An {@link com.bumptech.glide.load.resource.bitmap.BitmapDecoder} that can decode a thumbnail frame
+ * {@link Bitmap} from a {@link android.os.ParcelFileDescriptor} containing a video.
+ *
+ * @see android.media.MediaMetadataRetriever
+ */
 public class VideoBitmapDecoder implements BitmapDecoder<ParcelFileDescriptor> {
-    private static final DefaultFactory DEFAULT_FACTORY = new DefaultFactory();
+    private static final MediaMetadataRetrieverFactory DEFAULT_FACTORY =  new MediaMetadataRetrieverFactory();
     private MediaMetadataRetrieverFactory factory;
 
-    interface MediaMetadataRetrieverFactory {
-        public MediaMetadataRetriever build();
-    }
-
     public VideoBitmapDecoder() {
         this(DEFAULT_FACTORY);
     }
 
-
     VideoBitmapDecoder(MediaMetadataRetrieverFactory factory) {
         this.factory = factory;
     }
@@ -42,8 +44,8 @@
         return "VideoBitmapDecoder.com.bumptech.glide.load.resource.bitmap";
     }
 
-    private static class DefaultFactory implements MediaMetadataRetrieverFactory {
-        @Override
+    // Visible for testing.
+    static class MediaMetadataRetrieverFactory {
         public MediaMetadataRetriever build() {
             return new MediaMetadataRetriever();
         }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bytes/BytesResource.java b/library/src/main/java/com/bumptech/glide/load/resource/bytes/BytesResource.java
index b6d369b..a6368d2 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bytes/BytesResource.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bytes/BytesResource.java
@@ -2,10 +2,16 @@
 
 import com.bumptech.glide.load.engine.Resource;
 
-public class BytesResource extends Resource<byte[]> {
-    private byte[] bytes;
+/**
+ * An {@link com.bumptech.glide.load.engine.Resource} wrapping a byte array.
+ */
+public class BytesResource implements Resource<byte[]> {
+    private final byte[] bytes;
 
     public BytesResource(byte[] bytes) {
+        if (bytes == null) {
+            throw new NullPointerException("Bytes must not be null");
+        }
         this.bytes = bytes;
     }
 
@@ -20,5 +26,7 @@
     }
 
     @Override
-    protected void recycleInternal() {  }
+    public void recycle() {
+        // Do nothing.
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/drawable/DrawableResource.java b/library/src/main/java/com/bumptech/glide/load/resource/drawable/DrawableResource.java
new file mode 100644
index 0000000..643f7b4
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/drawable/DrawableResource.java
@@ -0,0 +1,38 @@
+package com.bumptech.glide.load.resource.drawable;
+
+import android.graphics.drawable.Drawable;
+
+import com.bumptech.glide.load.engine.Resource;
+
+/**
+ * Simple wrapper for an Android {@link Drawable} which returns a
+ * {@link android.graphics.drawable.Drawable.ConstantState#newDrawable() new drawable}
+ * based on it's {@link android.graphics.drawable.Drawable.ConstantState state}.
+ *
+ * <b>Suggested usages only include {@code T}s where the new drawable is of the same or descendant class.</b>
+ *
+ * @param <T> type of the wrapped {@link Drawable}
+ */
+public abstract class DrawableResource<T extends Drawable> implements Resource<T> {
+    protected final T drawable;
+    private boolean returnedOriginalDrawable;
+
+    public DrawableResource(T drawable) {
+        if (drawable == null) {
+            throw new NullPointerException("Drawable must not be null!");
+        }
+        this.drawable = drawable;
+    }
+
+    @SuppressWarnings("unchecked")
+    // drawables should always return a copy of the same class
+    @Override
+    public final T get() {
+        if (!returnedOriginalDrawable) {
+            returnedOriginalDrawable = true;
+            return drawable;
+        } else {
+            return (T) drawable.getConstantState().newDrawable();
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/drawable/GlideDrawable.java b/library/src/main/java/com/bumptech/glide/load/resource/drawable/GlideDrawable.java
new file mode 100644
index 0000000..c38dcef
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/drawable/GlideDrawable.java
@@ -0,0 +1,29 @@
+package com.bumptech.glide.load.resource.drawable;
+
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+
+/**
+ * A base class for drawables that are either static equivalents of {@link android.graphics.drawable.BitmapDrawable} or
+ * that contain an animation.
+ */
+public abstract class GlideDrawable extends Drawable implements Animatable {
+    /** A constant indicating that an animated drawable should loop continuously. */
+    public static final int LOOP_FOREVER = -1;
+    /**
+     * A constant indicating that an animated drawable should loop for its default number of times. For animated GIFs,
+     * this constant indicates the GIF should use the netscape loop count if present.
+     */
+    public static final int LOOP_INTRINSIC = 0;
+
+    /**
+     * Returns {@code true} if this drawable is animated.
+     */
+    public abstract boolean isAnimated();
+
+    /**
+     * Sets the number of times the animation should loop. This method will only have an affect if
+     * {@link #isAnimated ()}}  returns {@code true}. A loop count of <=0 indicates loop forever.
+     */
+    public abstract void setLoopCount(int loopCount);
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/file/FileDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/file/FileDecoder.java
new file mode 100644
index 0000000..ebd1e11
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/file/FileDecoder.java
@@ -0,0 +1,22 @@
+package com.bumptech.glide.load.resource.file;
+
+import com.bumptech.glide.load.ResourceDecoder;
+import com.bumptech.glide.load.engine.Resource;
+
+import java.io.File;
+
+/**
+ * A simple {@link com.bumptech.glide.load.ResourceDecoder} that creates resource for a given {@link java.io.File}.
+ */
+public class FileDecoder implements ResourceDecoder<File, File> {
+
+    @Override
+    public Resource<File> decode(File source, int width, int height) {
+        return new FileResource(source);
+    }
+
+    @Override
+    public String getId() {
+        return "";
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/file/FileResource.java b/library/src/main/java/com/bumptech/glide/load/resource/file/FileResource.java
new file mode 100644
index 0000000..40830a8
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/file/FileResource.java
@@ -0,0 +1,14 @@
+package com.bumptech.glide.load.resource.file;
+
+import com.bumptech.glide.load.resource.SimpleResource;
+
+import java.io.File;
+
+/**
+ * A simple {@link com.bumptech.glide.load.engine.Resource} that wraps a {@link File}.
+ */
+public class FileResource extends SimpleResource<File> {
+    public FileResource(File file) {
+        super(file);
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/file/FileToStreamDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/file/FileToStreamDecoder.java
new file mode 100644
index 0000000..a060998
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/file/FileToStreamDecoder.java
@@ -0,0 +1,63 @@
+package com.bumptech.glide.load.resource.file;
+
+import com.bumptech.glide.load.ResourceDecoder;
+import com.bumptech.glide.load.engine.Resource;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A decoder that wraps an {@link InputStream} decoder to allow it to decode from a file.
+ *
+ * @param <T> The type of resource that the wrapped InputStream decoder decodes.
+ */
+public class FileToStreamDecoder<T> implements ResourceDecoder<File, T> {
+    private static final FileOpener DEFAULT_FILE_OPENER = new FileOpener();
+
+    private ResourceDecoder<InputStream, T> streamDecoder;
+    private final FileOpener fileOpener;
+
+    public FileToStreamDecoder(ResourceDecoder<InputStream, T> streamDecoder) {
+        this(streamDecoder, DEFAULT_FILE_OPENER);
+    }
+
+    // Exposed for testing.
+    FileToStreamDecoder(ResourceDecoder<InputStream, T> streamDecoder, FileOpener fileOpener) {
+        this.streamDecoder = streamDecoder;
+        this.fileOpener = fileOpener;
+    }
+
+    @Override
+    public Resource<T> decode(File source, int width, int height) throws IOException {
+        InputStream is = null;
+        Resource<T> result = null;
+        try {
+            is = fileOpener.open(source);
+            result = streamDecoder.decode(is, width, height);
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    // Do nothing.
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public String getId() {
+        return "";
+    }
+
+    // Visible for testing.
+    static class FileOpener {
+        public InputStream open(File file) throws FileNotFoundException {
+            return new FileInputStream(file);
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/file/StreamFileDataLoadProvider.java b/library/src/main/java/com/bumptech/glide/load/resource/file/StreamFileDataLoadProvider.java
new file mode 100644
index 0000000..13df0a2
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/file/StreamFileDataLoadProvider.java
@@ -0,0 +1,62 @@
+package com.bumptech.glide.load.resource.file;
+
+import com.bumptech.glide.load.Encoder;
+import com.bumptech.glide.load.ResourceDecoder;
+import com.bumptech.glide.load.ResourceEncoder;
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.model.StreamEncoder;
+import com.bumptech.glide.load.resource.NullResourceEncoder;
+import com.bumptech.glide.provider.DataLoadProvider;
+
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * An {@link com.bumptech.glide.provider.DataLoadProvider} that provides encoders and decoders for for obtaining a
+ * cache file from {@link java.io.InputStream} data.
+ */
+public class StreamFileDataLoadProvider implements DataLoadProvider<InputStream, File> {
+    private static final ErrorSourceDecoder ERROR_DECODER = new ErrorSourceDecoder();
+
+    private final ResourceDecoder<File, File> cacheDecoder;
+    private final Encoder<InputStream> encoder;
+
+    public StreamFileDataLoadProvider() {
+        cacheDecoder = new FileDecoder();
+        encoder = new StreamEncoder();
+    }
+
+    @Override
+    public ResourceDecoder<File, File> getCacheDecoder() {
+        return cacheDecoder;
+    }
+
+    @Override
+    public ResourceDecoder<InputStream, File> getSourceDecoder() {
+        return ERROR_DECODER;
+    }
+
+    @Override
+    public Encoder<InputStream> getSourceEncoder() {
+        return encoder;
+    }
+
+    @Override
+    public ResourceEncoder<File> getEncoder() {
+        return NullResourceEncoder.get();
+    }
+
+    private static class ErrorSourceDecoder implements ResourceDecoder<InputStream, File> {
+        @Override
+        public Resource<File> decode(InputStream source, int width, int height) {
+            throw new Error("You cannot decode a File from an InputStream by default,"
+                    + " try either #diskCacheStratey(DiskCacheStrategy.SOURCE) to avoid this call or"
+                    + " #decoder(ResourceDecoder) to replace this Decoder");
+        }
+
+        @Override
+        public String getId() {
+            return "";
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifBitmapProvider.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifBitmapProvider.java
new file mode 100644
index 0000000..de52039
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifBitmapProvider.java
@@ -0,0 +1,27 @@
+package com.bumptech.glide.load.resource.gif;
+
+
+import android.graphics.Bitmap;
+
+import com.bumptech.glide.gifdecoder.GifDecoder;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+
+class GifBitmapProvider implements GifDecoder.BitmapProvider {
+    private final BitmapPool bitmapPool;
+
+    public GifBitmapProvider(BitmapPool bitmapPool) {
+        this.bitmapPool = bitmapPool;
+    }
+
+    @Override
+    public Bitmap obtain(int width, int height, Bitmap.Config config) {
+        return bitmapPool.getDirty(width, height, config);
+    }
+
+    @Override
+    public void release(Bitmap bitmap) {
+        if (!bitmapPool.put(bitmap)) {
+            bitmap.recycle();
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifData.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifData.java
deleted file mode 100644
index 07e85c3..0000000
--- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifData.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.bumptech.glide.load.resource.gif;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import com.bumptech.glide.gifdecoder.GifDecoder;
-import com.bumptech.glide.gifdecoder.GifHeader;
-import com.bumptech.glide.load.Transformation;
-import com.bumptech.glide.load.UnitTransformation;
-import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class GifData {
-    private final Context context;
-    private final GifHeader header;
-    private final byte[] data;
-    private final GifDecoderBitmapProvider bitmapProvider;
-    private final String gifId;
-    private final int targetWidth;
-    private final int targetHeight;
-    private final List<GifDrawable> drawables = new ArrayList<GifDrawable>();
-    private Transformation<Bitmap> frameTransformation;
-
-    public GifData(Context context, BitmapPool bitmapPool, String gifId, GifHeader header, byte[] data,
-            int targetWidth, int targetHeight) {
-        this.context = context;
-        this.header = header;
-        this.data = data;
-        this.gifId = gifId;
-        this.targetWidth = targetWidth;
-        this.targetHeight = targetHeight;
-        bitmapProvider = new GifDecoderBitmapProvider(bitmapPool);
-    }
-
-    @SuppressWarnings("unchecked")
-    public Transformation<Bitmap> getFrameTransformation() {
-        if (frameTransformation != null) {
-            return frameTransformation;
-        } else {
-            return UnitTransformation.get();
-        }
-    }
-
-    public void setFrameTransformation(Transformation<Bitmap> transformation) {
-        this.frameTransformation = transformation;
-    }
-
-    public int getByteSize() {
-        return data.length;
-    }
-
-    public byte[] getData() {
-        return data;
-    }
-
-    public GifDrawable getDrawable() {
-        GifDecoder gifDecoder = new GifDecoder(bitmapProvider);
-        gifDecoder.setData(gifId, header, data);
-        GifFrameManager frameManager = new GifFrameManager(context, gifDecoder, getFrameTransformation(),
-                targetWidth, targetHeight);
-
-        GifDrawable result = new GifDrawable(gifDecoder, frameManager);
-        drawables.add(result);
-        return result;
-    }
-
-    public void recycle() {
-        for (GifDrawable drawable : drawables) {
-            drawable.stop();
-            drawable.recycle();
-        }
-    }
-
-    private static class GifDecoderBitmapProvider implements GifDecoder.BitmapProvider {
-        private BitmapPool bitmapPool;
-
-        public GifDecoderBitmapProvider(BitmapPool bitmapPool) {
-            this.bitmapPool = bitmapPool;
-        }
-
-        @Override
-        public Bitmap obtain(int width, int height, Bitmap.Config config) {
-            return bitmapPool.get(width, height, config);
-        }
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDataLoadProvider.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDataLoadProvider.java
deleted file mode 100644
index 7c76bad..0000000
--- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDataLoadProvider.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.bumptech.glide.load.resource.gif;
-
-import android.content.Context;
-import com.bumptech.glide.DataLoadProvider;
-import com.bumptech.glide.load.Encoder;
-import com.bumptech.glide.load.ResourceDecoder;
-import com.bumptech.glide.load.ResourceEncoder;
-import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
-import com.bumptech.glide.load.model.StreamEncoder;
-
-import java.io.InputStream;
-
-public class GifDataLoadProvider implements DataLoadProvider<InputStream, GifData> {
-    private final GifResourceDecoder decoder;
-    private final GifResourceEncoder encoder;
-    private final StreamEncoder sourceEncoder;
-
-    public GifDataLoadProvider(Context context, BitmapPool bitmapPool) {
-        decoder = new GifResourceDecoder(context, bitmapPool);
-        encoder = new GifResourceEncoder();
-        sourceEncoder = new StreamEncoder();
-    }
-
-    @Override
-    public ResourceDecoder<InputStream, GifData> getCacheDecoder() {
-        return decoder;
-    }
-
-    @Override
-    public ResourceDecoder<InputStream, GifData> getSourceDecoder() {
-        return decoder;
-    }
-
-    @Override
-    public Encoder<InputStream> getSourceEncoder() {
-        return sourceEncoder;
-    }
-
-    @Override
-    public ResourceEncoder<GifData> getEncoder() {
-        return encoder;
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDataResource.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDataResource.java
deleted file mode 100644
index b7cc468..0000000
--- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDataResource.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.bumptech.glide.load.resource.gif;
-
-import com.bumptech.glide.load.engine.Resource;
-
-public class GifDataResource extends Resource<GifData> {
-    private GifData gifData;
-
-    public GifDataResource(GifData gifData) {
-        this.gifData = gifData;
-    }
-
-    @Override
-    public GifData get() {
-        return gifData;
-    }
-
-    @Override
-    public int getSize() {
-        return gifData.getByteSize();
-    }
-
-    @Override
-    protected void recycleInternal() {
-        gifData.recycle();
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDataTransformation.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDataTransformation.java
deleted file mode 100644
index 22402e2..0000000
--- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDataTransformation.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.bumptech.glide.load.resource.gif;
-
-import android.graphics.Bitmap;
-import com.bumptech.glide.load.engine.Resource;
-import com.bumptech.glide.load.MultiTransformation;
-import com.bumptech.glide.load.Transformation;
-
-public class GifDataTransformation implements Transformation<GifData> {
-    private Transformation<Bitmap> wrapped;
-
-    public GifDataTransformation(Transformation<Bitmap> wrapped) {
-        this.wrapped = wrapped;
-    }
-
-    @Override
-    public Resource<GifData> transform(Resource<GifData> resource, int outWidth, int outHeight) {
-        GifData data = resource.get();
-        Transformation<Bitmap> newTransformation =
-                new MultiTransformation<Bitmap>(data.getFrameTransformation(), wrapped);
-        data.setFrameTransformation(newTransformation);
-        return resource;
-    }
-
-    @Override
-    public String getId() {
-        return wrapped.getId();
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawable.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawable.java
index b4bd2c8..47e06fb 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawable.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawable.java
@@ -1,70 +1,201 @@
 package com.bumptech.glide.load.resource.gif;
 
 import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
-import android.graphics.drawable.Animatable;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.view.Gravity;
 
 import com.bumptech.glide.gifdecoder.GifDecoder;
+import com.bumptech.glide.gifdecoder.GifHeader;
+import com.bumptech.glide.load.Transformation;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
 
-public class GifDrawable extends Drawable implements Animatable, GifFrameManager.FrameCallback {
-
-    private final Paint paint;
+/**
+ * An animated {@link android.graphics.drawable.Drawable} that plays the frames of an animated GIF.
+ */
+public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameCallback {
+    private final Paint paint = new Paint();
+    private final Rect destRect = new Rect();
     private final GifFrameManager frameManager;
-    private int width;
-    private int height;
-    private GifDecoder decoder;
-    private boolean isRunning;
-    private Bitmap currentFrame;
-    private boolean isRecycled;
+    private final GifState state;
+    private final GifDecoder decoder;
 
-    public GifDrawable(GifDecoder decoder, GifFrameManager frameManager) {
+    /** True if the drawable is currently animating. */
+    private boolean isRunning;
+    /** True if the drawable should animate while visible. */
+    private boolean isStarted;
+    /** True if the drawable's resources have been recycled. */
+    private boolean isRecycled;
+    /**
+     * True if the drawable is currently visible. Default to true because on certain platforms (at least 4.1.1),
+     * setVisible is not called on {@link android.graphics.drawable.Drawable Drawables} during
+     * {@link android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}. See issue #130.
+     */
+    private boolean isVisible = true;
+    /** The number of times we've looped over all the frames in the gif. */
+    private int loopCount;
+    /** The number of times to loop through the gif animation. */
+    private int maxLoopCount = LOOP_FOREVER;
+
+    private boolean applyGravity;
+
+    /**
+     * Constructor for GifDrawable.
+     *
+     * @see #setFrameTransformation(com.bumptech.glide.load.Transformation, android.graphics.Bitmap)
+     *
+     * @param context A context.
+     * @param bitmapProvider An {@link com.bumptech.glide.gifdecoder.GifDecoder.BitmapProvider} that can be used to
+     *                       retrieve re-usable {@link android.graphics.Bitmap}s.
+     * @param bitmapPool A {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} that can be used to return
+     *                   the first frame when this drawable is recycled.
+     * @param frameTransformation An {@link com.bumptech.glide.load.Transformation} that can be applied to each frame.
+     * @param targetFrameWidth The desired width of the frames displayed by this drawable (the width of the view or
+     *                         {@link com.bumptech.glide.request.target.Target} this drawable is being loaded into).
+     * @param targetFrameHeight The desired height of the frames displayed by this drawable (the height of the view or
+     *                          {@link com.bumptech.glide.request.target.Target} this drawable is being loaded into).
+     * @param gifHeader The header data for this gif.
+     * @param data The full bytes of the gif.
+     * @param firstFrame The decoded and transformed first frame of this gif.
+     */
+    public GifDrawable(Context context, GifDecoder.BitmapProvider bitmapProvider, BitmapPool bitmapPool,
+            Transformation<Bitmap> frameTransformation, int targetFrameWidth, int targetFrameHeight,
+            GifHeader gifHeader, byte[] data, Bitmap firstFrame) {
+        this(new GifState(gifHeader, data, context, frameTransformation, targetFrameWidth, targetFrameHeight,
+                bitmapProvider, bitmapPool, firstFrame));
+    }
+
+    GifDrawable(GifState state) {
+        if (state == null) {
+            throw new NullPointerException("GifState must not be null");
+        }
+
+        this.state = state;
+        this.decoder = new GifDecoder(state.bitmapProvider);
+        decoder.setData(state.gifHeader, state.data);
+        frameManager = new GifFrameManager(state.context, decoder, state.targetWidth, state.targetHeight);
+        frameManager.setFrameTransformation(state.frameTransformation);
+    }
+
+    // Visible for testing.
+    GifDrawable(GifDecoder decoder, GifFrameManager frameManager, Bitmap firstFrame, BitmapPool bitmapPool) {
         this.decoder = decoder;
         this.frameManager = frameManager;
-        width = -1;
-        height = -1;
+        this.state = new GifState(null);
+        state.bitmapPool = bitmapPool;
+        state.firstFrame = firstFrame;
+    }
 
-        paint = new Paint();
+    public Bitmap getFirstFrame() {
+        return state.firstFrame;
+    }
+
+    public void setFrameTransformation(Transformation<Bitmap> frameTransformation, Bitmap firstFrame) {
+        if (firstFrame == null) {
+            throw new NullPointerException("The first frame of the GIF must not be null");
+        }
+        if (frameTransformation == null) {
+            throw new NullPointerException("The frame transformation must not be null");
+        }
+        state.frameTransformation = frameTransformation;
+        state.firstFrame = firstFrame;
+        frameManager.setFrameTransformation(frameTransformation);
+    }
+
+    public GifDecoder getDecoder() {
+        return decoder;
+    }
+
+    public Transformation<Bitmap> getFrameTransformation() {
+        return state.frameTransformation;
+    }
+
+    public byte[] getData() {
+        return state.data;
+    }
+
+    public int getFrameCount() {
+        return decoder.getFrameCount();
+    }
+
+    private void resetLoopCount() {
+        loopCount = 0;
     }
 
     @Override
     public void start() {
-        if (!isRunning) {
+        isStarted = true;
+        resetLoopCount();
+        if (isVisible) {
+            startRunning();
+        }
+    }
+
+    @Override
+    public void stop() {
+        isStarted = false;
+        stopRunning();
+
+        // On APIs > honeycomb we know our drawable is not being displayed anymore when it's callback is cleared and so
+        // we can use the absence of a callback as an indication that it's ok to clear our temporary data. Prior to
+        // honeycomb we can't tell if our callback is null and instead eagerly reset to avoid holding on to resources we
+        // no longer need.
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+            reset();
+        }
+    }
+
+    /**
+     * Clears temporary data and resets the drawable back to the first frame.
+     */
+    private void reset() {
+        frameManager.clear();
+        invalidateSelf();
+    }
+
+    private void startRunning() {
+        // If we have only a single frame, we don't want to decode it endlessly.
+        if (decoder.getFrameCount() == 1) {
+            invalidateSelf();
+        }  else if (!isRunning) {
             isRunning = true;
             frameManager.getNextFrame(this);
             invalidateSelf();
         }
     }
 
+    private void stopRunning() {
+        isRunning = false;
+    }
+
     @Override
     public boolean setVisible(boolean visible, boolean restart) {
+        isVisible = visible;
         if (!visible) {
-            stop();
-        } else {
-            start();
+            stopRunning();
+        } else if (isStarted) {
+            startRunning();
         }
         return super.setVisible(visible, restart);
     }
 
-
     @Override
     public int getIntrinsicWidth() {
-        return width;
+        return state.firstFrame.getWidth();
     }
 
     @Override
     public int getIntrinsicHeight() {
-        return height;
-    }
-
-    @Override
-    public void stop() {
-        isRunning = false;
+        return state.firstFrame.getHeight();
     }
 
     @Override
@@ -78,10 +209,25 @@
     }
 
     @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        applyGravity = true;
+    }
+
+    @Override
     public void draw(Canvas canvas) {
-        if (currentFrame != null) {
-            canvas.drawBitmap(currentFrame, 0, 0, paint);
+        if (isRecycled) {
+            return;
         }
+
+        if (applyGravity) {
+            Gravity.apply(GifState.GRAVITY, getIntrinsicWidth(), getIntrinsicHeight(), getBounds(), destRect);
+            applyGravity = false;
+        }
+
+        Bitmap currentFrame = frameManager.getCurrentFrame();
+        Bitmap toDraw = currentFrame != null ? currentFrame : state.firstFrame;
+        canvas.drawBitmap(toDraw, null, destRect, paint);
     }
 
     @Override
@@ -96,40 +242,129 @@
 
     @Override
     public int getOpacity() {
-        return decoder.isTransparent() ? PixelFormat.TRANSPARENT : PixelFormat.OPAQUE;
+        // We can't tell, so default to transparent to be safe.
+        return PixelFormat.TRANSPARENT;
     }
 
-    @TargetApi(11)
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
     @Override
-    public void onFrameRead(Bitmap frame) {
-        if (Build.VERSION.SDK_INT >= 11 && getCallback() == null) {
+    public void onFrameRead(int frameIndex) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && getCallback() == null) {
             stop();
+            reset();
             return;
-        } if (!isRunning) {
+        }
+        if (!isRunning) {
             return;
         }
 
-        if (width == -1) {
-            width = frame.getWidth();
-        }
-        if (height == -1) {
-            height = frame.getHeight();
+        invalidateSelf();
+
+        if (frameIndex == decoder.getFrameCount() - 1) {
+            loopCount++;
         }
 
-        if (frame != null) {
-            currentFrame = frame;
-            invalidateSelf();
+        if (maxLoopCount != LOOP_FOREVER && loopCount >= maxLoopCount) {
+            stop();
+        } else {
+            frameManager.getNextFrame(this);
         }
-
-        frameManager.getNextFrame(this);
     }
 
+    @Override
+    public ConstantState getConstantState() {
+        return state;
+    }
+
+    /**
+     * Clears any resources for loading frames that are currently held on to by this object.
+     */
     public void recycle() {
         isRecycled = true;
+        state.bitmapPool.put(state.firstFrame);
         frameManager.clear();
     }
 
-    public boolean isRecycled() {
+    // For testing.
+    boolean isRecycled() {
         return isRecycled;
     }
+
+    @Override
+    public boolean isAnimated() {
+        return true;
+    }
+
+    @Override
+    public void setLoopCount(int loopCount) {
+        if (loopCount <= 0 && loopCount != LOOP_FOREVER && loopCount != LOOP_INTRINSIC) {
+            throw new IllegalArgumentException("Loop count must be greater than 0, or equal to "
+                    + "GlideDrawable.LOOP_FOREVER, or equal to GlideDrawable.LOOP_INTRINSIC");
+        }
+
+        if (loopCount == LOOP_INTRINSIC) {
+            maxLoopCount = decoder.getLoopCount();
+        } else {
+            maxLoopCount = loopCount;
+        }
+    }
+
+    static class GifState extends ConstantState {
+        private static final int GRAVITY = Gravity.FILL;
+        GifHeader gifHeader;
+        byte[] data;
+        Context context;
+        Transformation<Bitmap> frameTransformation;
+        int targetWidth;
+        int targetHeight;
+        GifDecoder.BitmapProvider bitmapProvider;
+        BitmapPool bitmapPool;
+        Bitmap firstFrame;
+
+        public GifState(GifHeader header, byte[] data, Context context,
+                Transformation<Bitmap> frameTransformation, int targetWidth, int targetHeight,
+                GifDecoder.BitmapProvider provider, BitmapPool bitmapPool, Bitmap firstFrame) {
+            if (firstFrame == null) {
+                throw new NullPointerException("The first frame of the GIF must not be null");
+            }
+            gifHeader = header;
+            this.data = data;
+            this.bitmapPool = bitmapPool;
+            this.firstFrame = firstFrame;
+            this.context = context.getApplicationContext();
+            this.frameTransformation = frameTransformation;
+            this.targetWidth = targetWidth;
+            this.targetHeight = targetHeight;
+            bitmapProvider = provider;
+        }
+
+        public GifState(GifState original) {
+            if (original != null) {
+                gifHeader = original.gifHeader;
+                data = original.data;
+                context = original.context;
+                frameTransformation = original.frameTransformation;
+                targetWidth = original.targetWidth;
+                targetHeight = original.targetHeight;
+                bitmapProvider = original.bitmapProvider;
+                bitmapPool = original.bitmapPool;
+                firstFrame = original.firstFrame;
+            }
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return newDrawable();
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new GifDrawable(this);
+        }
+
+        @Override
+        public int getChangingConfigurations() {
+            return 0;
+        }
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableLoadProvider.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableLoadProvider.java
new file mode 100644
index 0000000..971847d
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableLoadProvider.java
@@ -0,0 +1,52 @@
+package com.bumptech.glide.load.resource.gif;
+
+import android.content.Context;
+
+import com.bumptech.glide.load.Encoder;
+import com.bumptech.glide.load.ResourceDecoder;
+import com.bumptech.glide.load.ResourceEncoder;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.model.StreamEncoder;
+import com.bumptech.glide.load.resource.file.FileToStreamDecoder;
+import com.bumptech.glide.provider.DataLoadProvider;
+
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * An {@link com.bumptech.glide.provider.DataLoadProvider} that loads an {@link java.io.InputStream} into
+ * {@link com.bumptech.glide.load.resource.gif.GifDrawable} that can be used to display an animated GIF.
+ */
+public class GifDrawableLoadProvider implements DataLoadProvider<InputStream, GifDrawable> {
+    private final GifResourceDecoder decoder;
+    private final GifResourceEncoder encoder;
+    private final StreamEncoder sourceEncoder;
+    private final FileToStreamDecoder<GifDrawable> cacheDecoder;
+
+    public GifDrawableLoadProvider(Context context, BitmapPool bitmapPool) {
+        decoder = new GifResourceDecoder(context, bitmapPool);
+        cacheDecoder = new FileToStreamDecoder<GifDrawable>(decoder);
+        encoder = new GifResourceEncoder(bitmapPool);
+        sourceEncoder = new StreamEncoder();
+    }
+
+    @Override
+    public ResourceDecoder<File, GifDrawable> getCacheDecoder() {
+        return cacheDecoder;
+    }
+
+    @Override
+    public ResourceDecoder<InputStream, GifDrawable> getSourceDecoder() {
+        return decoder;
+    }
+
+    @Override
+    public Encoder<InputStream> getSourceEncoder() {
+        return sourceEncoder;
+    }
+
+    @Override
+    public ResourceEncoder<GifDrawable> getEncoder() {
+        return encoder;
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableResource.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableResource.java
index cfa984b..7d2b67f 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableResource.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableResource.java
@@ -1,26 +1,24 @@
 package com.bumptech.glide.load.resource.gif;
 
-import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.resource.drawable.DrawableResource;
+import com.bumptech.glide.util.Util;
 
-public class GifDrawableResource extends Resource<GifDrawable> {
-    private Resource<GifData> wrapped;
-
-    public GifDrawableResource(Resource<GifData> wrapped) {
-        this.wrapped = wrapped;
-    }
-
-    @Override
-    public GifDrawable get() {
-        return wrapped.get().getDrawable();
+/**
+ * A resource wrapping an {@link com.bumptech.glide.load.resource.gif.GifDrawable}.
+ */
+public class GifDrawableResource extends DrawableResource<GifDrawable> {
+    public GifDrawableResource(GifDrawable drawable) {
+        super(drawable);
     }
 
     @Override
     public int getSize() {
-        return wrapped.getSize();
+        return drawable.getData().length + Util.getBitmapByteSize(drawable.getFirstFrame());
     }
 
     @Override
-    protected void recycleInternal() {
-        wrapped.recycle();
+    public void recycle() {
+        drawable.stop();
+        drawable.recycle();
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableTransformation.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableTransformation.java
new file mode 100644
index 0000000..2e17d29
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableTransformation.java
@@ -0,0 +1,47 @@
+package com.bumptech.glide.load.resource.gif;
+
+import android.graphics.Bitmap;
+
+import com.bumptech.glide.load.Transformation;
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.bitmap.BitmapResource;
+
+/**
+ * An {@link com.bumptech.glide.load.Transformation} that wraps a transformation for a {@link Bitmap}
+ * and can apply it to every frame of any {@link com.bumptech.glide.load.resource.gif.GifDrawable}.
+ */
+public class GifDrawableTransformation implements Transformation<GifDrawable> {
+    private final Transformation<Bitmap> wrapped;
+    private final BitmapPool bitmapPool;
+
+    public GifDrawableTransformation(Transformation<Bitmap> wrapped, BitmapPool bitmapPool) {
+        this.wrapped = wrapped;
+        this.bitmapPool = bitmapPool;
+    }
+
+    @Override
+    public Resource<GifDrawable> transform(Resource<GifDrawable> resource, int outWidth, int outHeight) {
+        GifDrawable drawable = resource.get();
+
+        // The drawable needs to be initialized with the correct width and height in order for a view displaying it
+        // to end up with the right dimensions. Since our transformations may arbitrarily modify the dimensions of
+        // our gif, here we create a stand in for a frame and pass it to the transformation to see what the final
+        // transformed dimensions will be so that our drawable can report the correct intrinsic width and height.
+        Bitmap firstFrame = resource.get().getFirstFrame();
+        Resource<Bitmap> bitmapResource = new BitmapResource(firstFrame, bitmapPool);
+        Resource<Bitmap> transformed = wrapped.transform(bitmapResource, outWidth, outHeight);
+        if (!bitmapResource.equals(transformed)) {
+            bitmapResource.recycle();
+        }
+        Bitmap transformedFrame = transformed.get();
+
+        drawable.setFrameTransformation(wrapped, transformedFrame);
+        return resource;
+    }
+
+    @Override
+    public String getId() {
+        return wrapped.getId();
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameManager.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameManager.java
index 2c66f7b..ad2cdbc 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameManager.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameManager.java
@@ -2,132 +2,126 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
+
+import com.bumptech.glide.GenericRequestBuilder;
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.gifdecoder.GifDecoder;
 import com.bumptech.glide.load.Encoder;
-import com.bumptech.glide.load.ResourceDecoder;
-import com.bumptech.glide.load.ResourceEncoder;
-import com.bumptech.glide.load.SkipCache;
+import com.bumptech.glide.load.Key;
 import com.bumptech.glide.load.Transformation;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
-import com.bumptech.glide.load.engine.cache.MemorySizeCalculator;
-import com.bumptech.glide.load.model.NullEncoder;
-import com.bumptech.glide.load.resource.NullDecoder;
-import com.bumptech.glide.load.resource.bitmap.BitmapEncoder;
-import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder;
-import com.bumptech.glide.request.GlideAnimation;
+import com.bumptech.glide.load.resource.NullEncoder;
+import com.bumptech.glide.load.resource.UnitTransformation;
+import com.bumptech.glide.request.animation.GlideAnimation;
 import com.bumptech.glide.request.target.SimpleTarget;
 
-import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.util.UUID;
 
 class GifFrameManager {
-    // 16ms per frame = 60fps
-    static final long MIN_FRAME_DELAY = 16;
-    private final MemorySizeCalculator calculator;
-    private final GifFrameModelLoader frameLoader;
-    private final GifFrameResourceDecoder frameResourceDecoder;
-    private final ResourceDecoder<InputStream, Bitmap> cacheDecoder;
-    private final Encoder<GifDecoder> sourceEncoder;
     private final GifDecoder decoder;
     private final Handler mainHandler;
-    private final ResourceEncoder<Bitmap> encoder;
-    private final Context context;
-
-    private Transformation<Bitmap> transformation;
     private final int targetWidth;
     private final int targetHeight;
+    private final FrameSignature signature;
+    private final GenericRequestBuilder<GifDecoder, GifDecoder, Bitmap, Bitmap> requestBuilder;
+    private boolean isLoadInProgress;
     private DelayTarget current;
     private DelayTarget next;
-    private int frameSize = -1;
+    private Transformation<Bitmap> transformation = UnitTransformation.get();
 
     public interface FrameCallback {
-        public void onFrameRead(Bitmap frame);
+        void onFrameRead(int index);
     }
 
-    public GifFrameManager(Context context, GifDecoder decoder, Transformation<Bitmap> transformation, int targetWidth,
-            int targetHeight) {
-        this(context, Glide.get(context).getBitmapPool(), decoder, new Handler(Looper.getMainLooper()), transformation,
-                targetWidth, targetHeight);
+    public GifFrameManager(Context context, GifDecoder decoder, int targetWidth, int targetHeight) {
+        this(context, Glide.get(context).getBitmapPool(), decoder, new Handler(Looper.getMainLooper()), targetWidth,
+                targetHeight);
     }
 
     public GifFrameManager(Context context, BitmapPool bitmapPool, GifDecoder decoder, Handler mainHandler,
-            Transformation<Bitmap> transformation, int targetWidth, int targetHeight) {
-        this.context = context;
+                           int targetWidth, int targetHeight) {
+
         this.decoder = decoder;
         this.mainHandler = mainHandler;
-        this.transformation = transformation;
         this.targetWidth = targetWidth;
         this.targetHeight = targetHeight;
-        calculator = new MemorySizeCalculator(context);
-        frameLoader = new GifFrameModelLoader();
-        frameResourceDecoder = new GifFrameResourceDecoder(bitmapPool);
-        sourceEncoder = NullEncoder.get();
+        this.signature = new FrameSignature();
 
-        if (!decoder.isTransparent()) {
-            // For non transparent gifs, we can beat the performance of our gif decoder for each frame by decoding jpegs
-            // from disk.
-            cacheDecoder = new StreamBitmapDecoder(context);
-            encoder = new BitmapEncoder(Bitmap.CompressFormat.JPEG, 70);
-        } else {
-            // For transparent gifs, we would have to encode as pngs which is actually slower than our gif decoder so we
-            // avoid writing frames to the disk cache entirely.
-            cacheDecoder = NullDecoder.get();
-            encoder = SkipCache.get();
-        }
-    }
+        GifFrameResourceDecoder frameResourceDecoder = new GifFrameResourceDecoder(bitmapPool);
+        GifFrameModelLoader frameLoader = new GifFrameModelLoader();
+        Encoder<GifDecoder> sourceEncoder = NullEncoder.get();
 
-    Transformation<Bitmap> getTransformation() {
-        return transformation;
-    }
-
-    private int getEstimatedTotalFrameSize() {
-        if (frameSize == -1) {
-            return decoder.getDecodedFramesByteSizeSum();
-        } else {
-            return frameSize * decoder.getFrameCount();
-        }
-    }
-
-    public void getNextFrame(FrameCallback cb) {
-        decoder.advance();
-        // We don't want to blow out the entire memory cache with frames of gifs, so try to set some
-        // maximum size beyond which we will always just decode one frame at a time.
-        boolean skipCache = getEstimatedTotalFrameSize() > calculator.getMemoryCacheSize() / 2;
-
-        long targetTime = SystemClock.uptimeMillis() + (Math.max(MIN_FRAME_DELAY, decoder.getNextDelay()));
-        next = new DelayTarget(cb, targetTime);
-
-        Glide.with(context)
+        requestBuilder = Glide.with(context)
                 .using(frameLoader, GifDecoder.class)
-                .load(decoder)
+                .from(GifDecoder.class)
                 .as(Bitmap.class)
-                .decoder(frameResourceDecoder)
-                .cacheDecoder(cacheDecoder)
-                .transform(transformation)
-                .encoder(encoder)
+                .signature(signature)
                 .sourceEncoder(sourceEncoder)
-                .skipMemoryCache(skipCache)
+                .decoder(frameResourceDecoder)
+                .skipMemoryCache(true)
+                .diskCacheStrategy(DiskCacheStrategy.NONE);
+    }
+
+    public void setFrameTransformation(Transformation<Bitmap> transformation) {
+        if (transformation == null) {
+            throw new NullPointerException("Transformation must not be null");
+        }
+        this.transformation = transformation;
+    }
+
+    @SuppressWarnings("unchecked")
+    public void getNextFrame(FrameCallback cb) {
+        if (isLoadInProgress) {
+            return;
+        }
+        isLoadInProgress = true;
+
+        decoder.advance();
+
+        long targetTime = SystemClock.uptimeMillis() + decoder.getNextDelay();
+        next = new DelayTarget(cb, targetTime);
+        next.setFrameIndex(decoder.getCurrentFrameIndex());
+
+        // Use an incrementing signature to make sure we never hit an active resource that matches one of our frames.
+        signature.increment();
+        requestBuilder
+                .load(decoder)
+                .transform(transformation)
                 .into(next);
     }
 
+    public Bitmap getCurrentFrame() {
+        return current != null ? current.resource : null;
+    }
+
     public void clear() {
+        isLoadInProgress = false;
         if (current != null) {
             Glide.clear(current);
             mainHandler.removeCallbacks(current);
+            current = null;
         }
         if (next != null) {
             Glide.clear(next);
             mainHandler.removeCallbacks(next);
+            next = null;
         }
+
+        decoder.resetFrameIndex();
     }
 
     class DelayTarget extends SimpleTarget<Bitmap> implements Runnable {
-        private FrameCallback cb;
-        private long targetTime;
+        private final FrameCallback cb;
+        private final long targetTime;
         private Bitmap resource;
+        private int index;
 
         public DelayTarget(FrameCallback cb, long targetTime) {
             super(targetWidth, targetHeight);
@@ -135,22 +129,70 @@
             this.targetTime = targetTime;
         }
 
+        public void setFrameIndex(int index) {
+            this.index = index;
+        }
+
         @Override
-        public void onResourceReady(final Bitmap resource, GlideAnimation<Bitmap> glideAnimation) {
-            // Ignore allocationByteSize, we only want the minimum frame size.
-            frameSize = resource.getHeight() * resource.getRowBytes();
+        public void onResourceReady(final Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
             this.resource = resource;
             mainHandler.postAtTime(this, targetTime);
-            if (current != null) {
-                Glide.clear(current);
-            }
-            current = next;
-            next = null;
         }
 
         @Override
         public void run() {
-            cb.onFrameRead(resource);
+            isLoadInProgress = false;
+            cb.onFrameRead(index);
+            if (current != null) {
+                // TODO: figure out why this is necessary and fix it. See issue #219.
+                final DelayTarget recycleCurrent = current;
+                mainHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        Glide.clear(recycleCurrent);
+                    }
+                });
+            }
+            current = this;
+        }
+
+        @Override
+        public void onLoadCleared(Drawable placeholder) {
+            resource = null;
+        }
+    }
+
+    private static class FrameSignature implements Key {
+        private final UUID uuid;
+        private int id;
+
+        public FrameSignature() {
+            this.uuid = UUID.randomUUID();
+        }
+
+        public void increment() {
+            id++;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof FrameSignature) {
+                FrameSignature other = (FrameSignature) o;
+                return other.uuid.equals(uuid) && id == other.id;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = uuid.hashCode();
+            result = 31 * result + id;
+            return result;
+        }
+
+        @Override
+        public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException {
+            throw new UnsupportedOperationException("Not implemented");
         }
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameModelLoader.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameModelLoader.java
index c5fafb0..4db390f 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameModelLoader.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameModelLoader.java
@@ -5,7 +5,7 @@
 import com.bumptech.glide.load.data.DataFetcher;
 import com.bumptech.glide.load.model.ModelLoader;
 
-public class GifFrameModelLoader implements ModelLoader<GifDecoder, GifDecoder> {
+class GifFrameModelLoader implements ModelLoader<GifDecoder, GifDecoder> {
 
     @Override
     public DataFetcher<GifDecoder> getResourceFetcher(GifDecoder model, int width, int height) {
@@ -13,26 +13,30 @@
     }
 
     private static class GifFrameDataFetcher implements DataFetcher<GifDecoder> {
-        private GifDecoder decoder;
+        private final GifDecoder decoder;
 
         public GifFrameDataFetcher(GifDecoder decoder) {
             this.decoder = decoder;
         }
 
         @Override
-        public GifDecoder loadData(Priority priority) throws Exception {
+        public GifDecoder loadData(Priority priority) {
             return decoder;
         }
 
         @Override
-        public void cleanup() { }
-
-        @Override
-        public String getId() {
-            return decoder.getId() + decoder.getCurrentFrameIndex();
+        public void cleanup() {
+            // Do nothing. GifDecoder reads from an arbitrary InputStream, the caller will close that stream.
         }
 
         @Override
-        public void cancel() { }
+        public String getId() {
+            return String.valueOf(decoder.getCurrentFrameIndex());
+        }
+
+        @Override
+        public void cancel() {
+            // Do nothing.
+        }
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameResourceDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameResourceDecoder.java
index ee2fc82..1815c99 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameResourceDecoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameResourceDecoder.java
@@ -1,25 +1,24 @@
 package com.bumptech.glide.load.resource.gif;
 
 import android.graphics.Bitmap;
-import com.bumptech.glide.load.engine.Resource;
+
 import com.bumptech.glide.gifdecoder.GifDecoder;
 import com.bumptech.glide.load.ResourceDecoder;
+import com.bumptech.glide.load.engine.Resource;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 import com.bumptech.glide.load.resource.bitmap.BitmapResource;
 
-import java.io.IOException;
-
 class GifFrameResourceDecoder implements ResourceDecoder<GifDecoder, Bitmap> {
-    private BitmapPool bitmapPool;
+    private final BitmapPool bitmapPool;
 
     public GifFrameResourceDecoder(BitmapPool bitmapPool) {
         this.bitmapPool = bitmapPool;
     }
 
     @Override
-    public Resource<Bitmap> decode(GifDecoder source, int width, int height) throws IOException {
+    public Resource<Bitmap> decode(GifDecoder source, int width, int height) {
         Bitmap bitmap = source.getNextFrame();
-        return new BitmapResource(bitmap ,bitmapPool);
+        return BitmapResource.obtain(bitmap, bitmapPool);
     }
 
     @Override
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifResourceDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifResourceDecoder.java
index d6384d9..55a257f 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifResourceDecoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifResourceDecoder.java
@@ -1,74 +1,151 @@
 package com.bumptech.glide.load.resource.gif;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.util.Log;
+
 import com.bumptech.glide.Glide;
+import com.bumptech.glide.gifdecoder.GifDecoder;
 import com.bumptech.glide.gifdecoder.GifHeader;
 import com.bumptech.glide.gifdecoder.GifHeaderParser;
 import com.bumptech.glide.load.ResourceDecoder;
+import com.bumptech.glide.load.Transformation;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.UnitTransformation;
 import com.bumptech.glide.util.Util;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.UUID;
+import java.util.Queue;
 
-public class GifResourceDecoder implements ResourceDecoder<InputStream, GifData> {
+/**
+ * An {@link com.bumptech.glide.load.ResourceDecoder} that decodes
+ * {@link com.bumptech.glide.load.resource.gif.GifDrawable} from {@link java.io.InputStream} data.
+ */
+public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawable> {
     private static final String TAG = "GifResourceDecoder";
-    private Context context;
-    private BitmapPool bitmapPool;
+    private static final GifHeaderParserPool PARSER_POOL = new GifHeaderParserPool();
+    private static final GifDecoderPool DECODER_POOL = new GifDecoderPool();
+
+    private final Context context;
+    private final GifHeaderParserPool parserPool;
+    private final BitmapPool bitmapPool;
+    private final GifDecoderPool decoderPool;
+    private final GifBitmapProvider provider;
 
     public GifResourceDecoder(Context context) {
         this(context, Glide.get(context).getBitmapPool());
     }
 
     public GifResourceDecoder(Context context, BitmapPool bitmapPool) {
+        this(context, bitmapPool, PARSER_POOL, DECODER_POOL);
+    }
+
+    // Visible for testing.
+    GifResourceDecoder(Context context, BitmapPool bitmapPool, GifHeaderParserPool parserPool,
+            GifDecoderPool decoderPool) {
         this.context = context;
         this.bitmapPool = bitmapPool;
+        this.decoderPool = decoderPool;
+        this.provider = new GifBitmapProvider(bitmapPool);
+        this.parserPool = parserPool;
     }
 
     @Override
-    public GifDataResource decode(InputStream source, int width, int height) throws IOException {
+    public GifDrawableResource decode(InputStream source, int width, int height) {
         byte[] data = inputStreamToBytes(source);
-        GifHeader header = new GifHeaderParser(data).parseHeader();
-        String id = getGifId(data);
-        return new GifDataResource(new GifData(context, bitmapPool, id, header, data, width, height));
+        final GifHeaderParser parser = parserPool.obtain(data);
+        final GifDecoder decoder = decoderPool.obtain(provider);
+        try {
+            return decode(data, width, height, parser, decoder);
+        } finally {
+            parserPool.release(parser);
+            decoderPool.release(decoder);
+        }
+    }
+
+    private GifDrawableResource decode(byte[] data, int width, int height, GifHeaderParser parser, GifDecoder decoder) {
+        final GifHeader header = parser.parseHeader();
+        if (header.getNumFrames() <= 0 || header.getStatus() != GifDecoder.STATUS_OK) {
+            // If we couldn't decode the GIF, we will end up with a frame count of 0.
+            return null;
+        }
+
+        Bitmap firstFrame = decodeFirstFrame(decoder, header, data);
+        if (firstFrame == null) {
+            return null;
+        }
+
+        Transformation<Bitmap> unitTransformation = UnitTransformation.get();
+
+        GifDrawable gifDrawable = new GifDrawable(context, provider, bitmapPool, unitTransformation, width, height,
+                header, data, firstFrame);
+
+        return new GifDrawableResource(gifDrawable);
+    }
+
+    private Bitmap decodeFirstFrame(GifDecoder decoder, GifHeader header, byte[] data) {
+        decoder.setData(header, data);
+        decoder.advance();
+        return decoder.getNextFrame();
     }
 
     @Override
     public String getId() {
-        return "GifResourceDecoder.com.bumptech.glide.load.resource.gif";
+        return "";
     }
 
-    private String getGifId(byte[] data) {
-        try {
-            MessageDigest digest = MessageDigest.getInstance("SHA-1");
-            digest.update(data);
-            return Util.sha256BytesToHex(digest.digest());
-        } catch (NoSuchAlgorithmException e) {
-            if (Log.isLoggable(TAG, Log.WARN)) {
-                Log.w(TAG, "Missing sha1 algorithm?", e);
-            }
-        }
-        return UUID.randomUUID().toString();
-    }
-
-    private byte[] inputStreamToBytes(InputStream is) {
-        int capacity = 16384;
-        ByteArrayOutputStream buffer = new ByteArrayOutputStream(capacity);
+    private static byte[] inputStreamToBytes(InputStream is) {
+        final int bufferSize = 16384;
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream(bufferSize);
         try {
             int nRead;
-            byte[] data = new byte[16384];
-            while ((nRead = is.read(data, 0, data.length)) != -1) {
+            byte[] data = new byte[bufferSize];
+            while ((nRead = is.read(data)) != -1) {
                 buffer.write(data, 0, nRead);
             }
             buffer.flush();
         } catch (IOException e) {
             Log.w(TAG, "Error reading data from stream", e);
         }
+        //TODO the returned byte[] may be partial if an IOException was thrown from read
         return buffer.toByteArray();
     }
+
+    // Visible for testing.
+    static class GifDecoderPool {
+        private final Queue<GifDecoder> pool = Util.createQueue(0);
+
+        public synchronized GifDecoder obtain(GifDecoder.BitmapProvider bitmapProvider) {
+            GifDecoder result = pool.poll();
+            if (result == null) {
+                result = new GifDecoder(bitmapProvider);
+            }
+            return result;
+        }
+
+        public synchronized void release(GifDecoder decoder) {
+            decoder.clear();
+            pool.offer(decoder);
+        }
+    }
+
+    // Visible for testing.
+    static class GifHeaderParserPool {
+        private final Queue<GifHeaderParser> pool = Util.createQueue(0);
+
+        public synchronized GifHeaderParser obtain(byte[] data) {
+            GifHeaderParser result = pool.poll();
+            if (result == null) {
+                result = new GifHeaderParser();
+            }
+            return result.setData(data);
+        }
+
+        public synchronized void release(GifHeaderParser parser) {
+            parser.clear();
+            pool.offer(parser);
+        }
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifResourceEncoder.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifResourceEncoder.java
index 31893c9..df3b51f 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifResourceEncoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifResourceEncoder.java
@@ -1,31 +1,148 @@
 package com.bumptech.glide.load.resource.gif;
 
+import android.graphics.Bitmap;
 import android.util.Log;
-import com.bumptech.glide.load.engine.Resource;
+
+import com.bumptech.glide.gifdecoder.GifDecoder;
+import com.bumptech.glide.gifdecoder.GifHeader;
+import com.bumptech.glide.gifdecoder.GifHeaderParser;
+import com.bumptech.glide.gifencoder.AnimatedGifEncoder;
 import com.bumptech.glide.load.ResourceEncoder;
+import com.bumptech.glide.load.Transformation;
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.UnitTransformation;
+import com.bumptech.glide.load.resource.bitmap.BitmapResource;
+import com.bumptech.glide.util.LogTime;
 
 import java.io.IOException;
 import java.io.OutputStream;
 
-public class GifResourceEncoder implements ResourceEncoder<GifData> {
+/**
+ * An {@link com.bumptech.glide.load.ResourceEncoder} that can write
+ * {@link com.bumptech.glide.load.resource.gif.GifDrawable} to cache.
+ */
+public class GifResourceEncoder implements ResourceEncoder<GifDrawable> {
+    private static final Factory FACTORY = new Factory();
     private static final String TAG = "GifEncoder";
+    private final GifDecoder.BitmapProvider provider;
+    private final BitmapPool bitmapPool;
+    private final Factory factory;
+
+    public GifResourceEncoder(BitmapPool bitmapPool) {
+        this(bitmapPool, FACTORY);
+    }
+
+    // Visible for testing.
+    GifResourceEncoder(BitmapPool bitmapPool, Factory factory) {
+        this.bitmapPool = bitmapPool;
+        provider = new GifBitmapProvider(bitmapPool);
+        this.factory = factory;
+    }
 
     @Override
-    public boolean encode(Resource<GifData> resource, OutputStream os) {
-        boolean result = true;
+    public boolean encode(Resource<GifDrawable> resource, OutputStream os) {
+        long startTime = LogTime.getLogTime();
+
+        GifDrawable drawable = resource.get();
+        Transformation<Bitmap> transformation = drawable.getFrameTransformation();
+        if (transformation instanceof UnitTransformation) {
+            return writeDataDirect(drawable.getData(), os);
+        }
+
+        GifDecoder decoder = decodeHeaders(drawable.getData());
+
+        AnimatedGifEncoder encoder = factory.buildEncoder();
+        if (!encoder.start(os)) {
+            return false;
+        }
+
+        for (int i = 0; i < decoder.getFrameCount(); i++) {
+            Bitmap currentFrame = decoder.getNextFrame();
+            Resource<Bitmap> transformedResource = getTransformedFrame(currentFrame, transformation, drawable);
+            try {
+                if (!encoder.addFrame(transformedResource.get())) {
+                    return false;
+                }
+                int currentFrameIndex = decoder.getCurrentFrameIndex();
+                int delay = decoder.getDelay(currentFrameIndex);
+                encoder.setDelay(delay);
+
+                decoder.advance();
+            } finally {
+                transformedResource.recycle();
+            }
+        }
+
+        boolean result = encoder.finish();
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "Encoded gif with " + decoder.getFrameCount() + " frames and " + drawable.getData().length
+                    + " bytes in " + LogTime.getElapsedMillis(startTime) + " ms");
+        }
+
+        return result;
+    }
+
+    private boolean writeDataDirect(byte[] data, OutputStream os) {
+        boolean success = true;
         try {
-            os.write(resource.get().getData());
+            os.write(data);
         } catch (IOException e) {
             if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Failed to encode gif", e);
+                Log.d(TAG, "Failed to write data to output stream in GifResourceEncoder", e);
             }
-            result = false;
+            success = false;
         }
-        return result;
+        return success;
+    }
+
+    private GifDecoder decodeHeaders(byte[] data) {
+        GifHeaderParser parser = factory.buildParser();
+        parser.setData(data);
+        GifHeader header = parser.parseHeader();
+
+        GifDecoder decoder = factory.buildDecoder(provider);
+        decoder.setData(header, data);
+        decoder.advance();
+
+        return decoder;
+    }
+
+    private Resource<Bitmap> getTransformedFrame(Bitmap currentFrame, Transformation<Bitmap> transformation,
+            GifDrawable drawable) {
+        // TODO: what if current frame is null?
+        Resource<Bitmap> bitmapResource = factory.buildFrameResource(currentFrame, bitmapPool);
+        Resource<Bitmap> transformedResource = transformation.transform(bitmapResource,
+                drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+        if (!bitmapResource.equals(transformedResource)) {
+            bitmapResource.recycle();
+        }
+        return transformedResource;
     }
 
     @Override
     public String getId() {
-        return "GifResourceEncoder.com.bumptech.glide.load.resource.gif";
+        return "";
+    }
+
+    // Visible for testing.
+    static class Factory {
+
+        public GifDecoder buildDecoder(GifDecoder.BitmapProvider bitmapProvider) {
+            return new GifDecoder(bitmapProvider);
+        }
+
+        public GifHeaderParser buildParser() {
+            return new GifHeaderParser();
+        }
+
+        public AnimatedGifEncoder buildEncoder() {
+            return new AnimatedGifEncoder();
+        }
+
+        public Resource<Bitmap> buildFrameResource(Bitmap bitmap, BitmapPool bitmapPool) {
+            return new BitmapResource(bitmap, bitmapPool);
+        }
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapper.java b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapper.java
index 2540f59..0b3b62b 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapper.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapper.java
@@ -1,14 +1,19 @@
 package com.bumptech.glide.load.resource.gifbitmap;
 
 import android.graphics.Bitmap;
-import com.bumptech.glide.load.engine.Resource;
-import com.bumptech.glide.load.resource.gif.GifData;
 
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.resource.gif.GifDrawable;
+
+/**
+ * A wrapper that contains either an {@link android.graphics.Bitmap} resource or an
+ * {@link com.bumptech.glide.load.resource.gif.GifDrawable} resource.
+ */
 public class GifBitmapWrapper {
-    private final Resource<GifData> gifResource;
+    private final Resource<GifDrawable> gifResource;
     private final Resource<Bitmap> bitmapResource;
 
-    public GifBitmapWrapper(Resource<Bitmap> bitmapResource, Resource<GifData> gifResource) {
+    public GifBitmapWrapper(Resource<Bitmap> bitmapResource, Resource<GifDrawable> gifResource) {
         if (bitmapResource != null && gifResource != null) {
             throw new IllegalArgumentException("Can only contain either a bitmap resource or a gif resource, not both");
         }
@@ -19,6 +24,9 @@
         this.gifResource = gifResource;
     }
 
+    /**
+     * Returns the size of the wrapped resource.
+     */
     public int getSize() {
         if (bitmapResource != null) {
             return bitmapResource.getSize();
@@ -27,11 +35,17 @@
         }
     }
 
+    /**
+     * Returns the wrapped {@link android.graphics.Bitmap} resource if it exists, or null.
+     */
     public Resource<Bitmap> getBitmapResource() {
         return bitmapResource;
     }
 
-    public Resource<GifData> getGifResource() {
+    /**
+     * Returns the wrapped {@link com.bumptech.glide.load.resource.gif.GifDrawable} resource if it exists, or null.
+     */
+    public Resource<GifDrawable> getGifResource() {
         return gifResource;
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResource.java b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResource.java
index 22e6b96..0fcd220 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResource.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResource.java
@@ -1,13 +1,20 @@
 package com.bumptech.glide.load.resource.gifbitmap;
 
 import android.graphics.Bitmap;
-import com.bumptech.glide.load.engine.Resource;
-import com.bumptech.glide.load.resource.gif.GifData;
 
-public class GifBitmapWrapperResource extends Resource<GifBitmapWrapper> {
-    private GifBitmapWrapper data;
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.resource.gif.GifDrawable;
+
+/**
+ * A resource that wraps an {@link com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapper}.
+ */
+public class GifBitmapWrapperResource implements Resource<GifBitmapWrapper> {
+    private final GifBitmapWrapper data;
 
     public GifBitmapWrapperResource(GifBitmapWrapper data) {
+        if (data == null) {
+            throw new NullPointerException("Data must not be null");
+        }
         this.data = data;
     }
 
@@ -22,12 +29,12 @@
     }
 
     @Override
-    protected void recycleInternal() {
+    public void recycle() {
         Resource<Bitmap> bitmapResource = data.getBitmapResource();
         if (bitmapResource != null) {
             bitmapResource.recycle();
         }
-        Resource<GifData> gifDataResource = data.getGifResource();
+        Resource<GifDrawable> gifDataResource = data.getGifResource();
         if (gifDataResource != null) {
             gifDataResource.recycle();
         }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResourceDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResourceDecoder.java
index 28b02a4..18b198a 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResourceDecoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResourceDecoder.java
@@ -1,53 +1,129 @@
 package com.bumptech.glide.load.resource.gifbitmap;
 
 import android.graphics.Bitmap;
-import com.bumptech.glide.load.engine.Resource;
+
 import com.bumptech.glide.load.ResourceDecoder;
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 import com.bumptech.glide.load.model.ImageVideoWrapper;
+import com.bumptech.glide.load.resource.bitmap.BitmapResource;
 import com.bumptech.glide.load.resource.bitmap.ImageHeaderParser;
 import com.bumptech.glide.load.resource.bitmap.RecyclableBufferedInputStream;
-import com.bumptech.glide.load.resource.gif.GifData;
+import com.bumptech.glide.load.resource.gif.GifDrawable;
 import com.bumptech.glide.util.ByteArrayPool;
 
 import java.io.IOException;
 import java.io.InputStream;
 
+/**
+ * An {@link ResourceDecoder} that can decode either an {@link Bitmap} or an {@link GifDrawable}
+ * from an {@link InputStream} or a {@link android.os.ParcelFileDescriptor ParcelFileDescriptor}.
+ */
 public class GifBitmapWrapperResourceDecoder implements ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> {
+    private static final ImageTypeParser DEFAULT_PARSER = new ImageTypeParser();
+    private static final BufferedStreamFactory DEFAULT_STREAM_FACTORY = new BufferedStreamFactory();
+    // 2048 is rather arbitrary, for most well formatted image types we only need 32 bytes.
+    // Visible for testing.
+    static final int MARK_LIMIT_BYTES = 2048;
+
     private final ResourceDecoder<ImageVideoWrapper, Bitmap> bitmapDecoder;
-    private final ResourceDecoder<InputStream, GifData> gifDecoder;
+    private final ResourceDecoder<InputStream, GifDrawable> gifDecoder;
+    private final BitmapPool bitmapPool;
+    private final ImageTypeParser parser;
+    private final BufferedStreamFactory streamFactory;
     private String id;
 
     public GifBitmapWrapperResourceDecoder(ResourceDecoder<ImageVideoWrapper, Bitmap> bitmapDecoder,
-            ResourceDecoder<InputStream, GifData> gifDecoder) {
-        this.bitmapDecoder = bitmapDecoder;
-        this.gifDecoder = gifDecoder;
+            ResourceDecoder<InputStream, GifDrawable> gifDecoder, BitmapPool bitmapPool) {
+        this(bitmapDecoder, gifDecoder, bitmapPool, DEFAULT_PARSER, DEFAULT_STREAM_FACTORY);
     }
 
+    // Visible for testing.
+    GifBitmapWrapperResourceDecoder(ResourceDecoder<ImageVideoWrapper, Bitmap> bitmapDecoder,
+            ResourceDecoder<InputStream, GifDrawable> gifDecoder, BitmapPool bitmapPool, ImageTypeParser parser,
+            BufferedStreamFactory streamFactory) {
+        this.bitmapDecoder = bitmapDecoder;
+        this.gifDecoder = gifDecoder;
+        this.bitmapPool = bitmapPool;
+        this.parser = parser;
+        this.streamFactory = streamFactory;
+    }
+
+    @SuppressWarnings("resource")
+    // @see ResourceDecoder.decode
     @Override
     public Resource<GifBitmapWrapper> decode(ImageVideoWrapper source, int width, int height) throws IOException {
         ByteArrayPool pool = ByteArrayPool.get();
-        InputStream is = source.getStream();
         byte[] tempBytes = pool.getBytes();
-        RecyclableBufferedInputStream bis = new RecyclableBufferedInputStream(is, tempBytes);
-        GifBitmapWrapper result = null;
-        if (is != null) {
-            source = new ImageVideoWrapper(bis, source.getFileDescriptor());
-            bis.mark(2048);
-            ImageHeaderParser.ImageType type = new ImageHeaderParser(bis).getType();
-            bis.reset();
 
-            if (type == ImageHeaderParser.ImageType.GIF) {
-                Resource<GifData> gifResource = gifDecoder.decode(bis, width, height);
-                result = new GifBitmapWrapper(null, gifResource);
+        GifBitmapWrapper wrapper = null;
+        try {
+            wrapper = decode(source, width, height, tempBytes);
+        } finally {
+            pool.releaseBytes(tempBytes);
+        }
+        return wrapper != null ? new GifBitmapWrapperResource(wrapper) : null;
+    }
+
+    private GifBitmapWrapper decode(ImageVideoWrapper source, int width, int height, byte[] bytes) throws IOException {
+        final GifBitmapWrapper result;
+        if (source.getStream() != null) {
+            result = decodeStream(source, width, height, bytes);
+        } else {
+            result = decodeBitmapWrapper(source, width, height);
+        }
+        return result;
+    }
+
+    private GifBitmapWrapper decodeStream(ImageVideoWrapper source, int width, int height, byte[] bytes)
+            throws IOException {
+        InputStream bis = streamFactory.build(source.getStream(), bytes);
+        bis.mark(MARK_LIMIT_BYTES);
+        ImageHeaderParser.ImageType type = parser.parse(bis);
+        bis.reset();
+
+        GifBitmapWrapper result = null;
+        if (type == ImageHeaderParser.ImageType.GIF) {
+            result = decodeGifWrapper(bis, width, height);
+        }
+        // Decoding the gif may fail even if the type matches.
+        if (result == null) {
+            // We can only reset the buffered InputStream, so to start from the beginning of the stream, we need to
+            // pass in a new source containing the buffered stream rather than the original stream.
+            ImageVideoWrapper forBitmapDecoder = new ImageVideoWrapper(bis, source.getFileDescriptor());
+            result = decodeBitmapWrapper(forBitmapDecoder, width, height);
+        }
+        return result;
+    }
+
+    private GifBitmapWrapper decodeGifWrapper(InputStream bis, int width, int height) throws IOException {
+        GifBitmapWrapper result = null;
+        Resource<GifDrawable> gifResource = gifDecoder.decode(bis, width, height);
+        if (gifResource != null) {
+            GifDrawable drawable = gifResource.get();
+            // We can more efficiently hold Bitmaps in memory, so for static GIFs, try to return Bitmaps
+            // instead. Returning a Bitmap incurs the cost of allocating the GifDrawable as well as the normal
+            // Bitmap allocation, but since we can encode the Bitmap out as a JPEG, future decodes will be
+            // efficient.
+            if (drawable.getFrameCount() > 1) {
+                result = new GifBitmapWrapper(null /*bitmapResource*/, gifResource);
+            } else {
+                Resource<Bitmap> bitmapResource = new BitmapResource(drawable.getFirstFrame(), bitmapPool);
+                result = new GifBitmapWrapper(bitmapResource, null /*gifResource*/);
             }
         }
+        return result;
+    }
 
-        if (result == null) {
-            Resource<Bitmap> bitmapResource = bitmapDecoder.decode(source, width, height);
+    private GifBitmapWrapper decodeBitmapWrapper(ImageVideoWrapper toDecode, int width, int height) throws IOException {
+        GifBitmapWrapper result = null;
+
+        Resource<Bitmap> bitmapResource = bitmapDecoder.decode(toDecode, width, height);
+        if (bitmapResource != null) {
             result = new GifBitmapWrapper(bitmapResource, null);
         }
-        pool.releaseBytes(tempBytes);
-        return new GifBitmapWrapperResource(result);
+
+        return result;
     }
 
     @Override
@@ -57,4 +133,18 @@
         }
         return id;
     }
+
+    // Visible for testing.
+    static class BufferedStreamFactory {
+        public InputStream build(InputStream is, byte[] buffer) {
+            return new RecyclableBufferedInputStream(is, buffer);
+        }
+    }
+
+    // Visible for testing.
+    static class ImageTypeParser {
+        public ImageHeaderParser.ImageType parse(InputStream is) throws IOException {
+            return new ImageHeaderParser(is).getType();
+        }
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResourceEncoder.java b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResourceEncoder.java
index 5a10cec..ee18ab7 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResourceEncoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResourceEncoder.java
@@ -1,18 +1,24 @@
 package com.bumptech.glide.load.resource.gifbitmap;
 
 import android.graphics.Bitmap;
-import com.bumptech.glide.load.engine.Resource;
+
 import com.bumptech.glide.load.ResourceEncoder;
-import com.bumptech.glide.load.resource.gif.GifData;
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.resource.gif.GifDrawable;
 
 import java.io.OutputStream;
 
+/**
+ * A {@link com.bumptech.glide.load.ResourceEncoder} that can encode either an {@link Bitmap} or
+ * {@link com.bumptech.glide.load.resource.gif.GifDrawable}.
+ */
 public class GifBitmapWrapperResourceEncoder implements ResourceEncoder<GifBitmapWrapper> {
     private final ResourceEncoder<Bitmap> bitmapEncoder;
-    private final ResourceEncoder<GifData> gifEncoder;
+    private final ResourceEncoder<GifDrawable> gifEncoder;
     private String id;
 
-    public GifBitmapWrapperResourceEncoder(ResourceEncoder<Bitmap> bitmapEncoder, ResourceEncoder<GifData> gifEncoder) {
+    public GifBitmapWrapperResourceEncoder(ResourceEncoder<Bitmap> bitmapEncoder,
+            ResourceEncoder<GifDrawable> gifEncoder) {
         this.bitmapEncoder = bitmapEncoder;
         this.gifEncoder = gifEncoder;
     }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperStreamResourceDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperStreamResourceDecoder.java
index 51ccb2e..2e594e5 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperStreamResourceDecoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperStreamResourceDecoder.java
@@ -1,16 +1,21 @@
 package com.bumptech.glide.load.resource.gifbitmap;
 
-import com.bumptech.glide.load.engine.Resource;
 import com.bumptech.glide.load.ResourceDecoder;
+import com.bumptech.glide.load.engine.Resource;
 import com.bumptech.glide.load.model.ImageVideoWrapper;
 
 import java.io.IOException;
 import java.io.InputStream;
 
-public class GifBitmapWrapperStreamResourceDecoder implements ResourceDecoder<InputStream, GifBitmapWrapper>{
-    private ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> gifBitmapDecoder;
+/**
+ * A {@link com.bumptech.glide.load.ResourceDecoder} that can decode an
+ * {@link com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapper} from {@link java.io.InputStream} data.
+ */
+public class GifBitmapWrapperStreamResourceDecoder implements ResourceDecoder<InputStream, GifBitmapWrapper> {
+    private final ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> gifBitmapDecoder;
 
-    public GifBitmapWrapperStreamResourceDecoder(ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> gifBitmapDecoder) {
+    public GifBitmapWrapperStreamResourceDecoder(
+            ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> gifBitmapDecoder) {
         this.gifBitmapDecoder = gifBitmapDecoder;
     }
 
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperTransformation.java b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperTransformation.java
index f0f2404..791e81b 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperTransformation.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperTransformation.java
@@ -1,21 +1,27 @@
 package com.bumptech.glide.load.resource.gifbitmap;
 
 import android.graphics.Bitmap;
-import com.bumptech.glide.load.engine.Resource;
+
 import com.bumptech.glide.load.Transformation;
-import com.bumptech.glide.load.resource.gif.GifData;
-import com.bumptech.glide.load.resource.gif.GifDataTransformation;
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.gif.GifDrawable;
+import com.bumptech.glide.load.resource.gif.GifDrawableTransformation;
 
+/**
+ * A {@link com.bumptech.glide.load.Transformation} that can apply a wrapped {@link android.graphics.Bitmap}
+ * transformation to both {@link android.graphics.Bitmap}s and {@link com.bumptech.glide.load.resource.gif.GifDrawable}.
+ */
 public class GifBitmapWrapperTransformation implements Transformation<GifBitmapWrapper> {
-    private Transformation<Bitmap> bitmapTransformation;
-    private Transformation<GifData> gifDataTransformation;
+    private final Transformation<Bitmap> bitmapTransformation;
+    private final Transformation<GifDrawable> gifDataTransformation;
 
-    public GifBitmapWrapperTransformation(Transformation<Bitmap> bitmapTransformation) {
-        this(bitmapTransformation, new GifDataTransformation(bitmapTransformation));
+    public GifBitmapWrapperTransformation(BitmapPool bitmapPool, Transformation<Bitmap> bitmapTransformation) {
+        this(bitmapTransformation, new GifDrawableTransformation(bitmapTransformation, bitmapPool));
     }
 
     GifBitmapWrapperTransformation(Transformation<Bitmap> bitmapTransformation,
-            Transformation<GifData> gifDataTransformation) {
+            Transformation<GifDrawable> gifDataTransformation) {
         this.bitmapTransformation = bitmapTransformation;
         this.gifDataTransformation = gifDataTransformation;
     }
@@ -23,16 +29,16 @@
     @Override
     public Resource<GifBitmapWrapper> transform(Resource<GifBitmapWrapper> resource, int outWidth, int outHeight) {
         Resource<Bitmap> bitmapResource = resource.get().getBitmapResource();
-        Resource<GifData> gifResource = resource.get().getGifResource();
+        Resource<GifDrawable> gifResource = resource.get().getGifResource();
         if (bitmapResource != null && bitmapTransformation != null) {
             Resource<Bitmap> transformed = bitmapTransformation.transform(bitmapResource, outWidth, outHeight);
-            if (transformed != bitmapResource) {
+            if (!bitmapResource.equals(transformed)) {
                 GifBitmapWrapper gifBitmap = new GifBitmapWrapper(transformed, resource.get().getGifResource());
                 return new GifBitmapWrapperResource(gifBitmap);
             }
         } else if (gifResource != null && gifDataTransformation != null) {
-            Resource<GifData> transformed = gifDataTransformation.transform(gifResource, outWidth, outHeight);
-            if (transformed != gifResource) {
+            Resource<GifDrawable> transformed = gifDataTransformation.transform(gifResource, outWidth, outHeight);
+            if (!gifResource.equals(transformed)) {
                 GifBitmapWrapper gifBitmap = new GifBitmapWrapper(resource.get().getBitmapResource(), transformed);
                 return new GifBitmapWrapperResource(gifBitmap);
             }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/ImageVideoGifDataLoadProvider.java b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/ImageVideoGifDataLoadProvider.java
deleted file mode 100644
index 2299abb..0000000
--- a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/ImageVideoGifDataLoadProvider.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.bumptech.glide.load.resource.gifbitmap;
-
-import android.graphics.Bitmap;
-import android.os.ParcelFileDescriptor;
-import com.bumptech.glide.DataLoadProvider;
-import com.bumptech.glide.load.Encoder;
-import com.bumptech.glide.load.ResourceDecoder;
-import com.bumptech.glide.load.ResourceEncoder;
-import com.bumptech.glide.load.model.ImageVideoWrapper;
-import com.bumptech.glide.load.model.NullEncoder;
-import com.bumptech.glide.load.resource.gif.GifData;
-
-import java.io.InputStream;
-
-public class ImageVideoGifDataLoadProvider implements DataLoadProvider<ImageVideoWrapper, GifBitmapWrapper> {
-    private final GifBitmapWrapperStreamResourceDecoder cacheDecoder;
-    private final GifBitmapWrapperResourceDecoder sourceDecoder;
-    private final GifBitmapWrapperResourceEncoder encoder;
-    private final Encoder<ImageVideoWrapper> sourceEncoder;
-
-    public ImageVideoGifDataLoadProvider(DataLoadProvider<ImageVideoWrapper, Bitmap> bitmapProvider,
-            DataLoadProvider<InputStream, GifData> gifProvider) {
-        cacheDecoder = new GifBitmapWrapperStreamResourceDecoder(new GifBitmapWrapperResourceDecoder(
-                bitmapProvider.getSourceDecoder(),
-                gifProvider.getCacheDecoder()));
-        sourceDecoder = new GifBitmapWrapperResourceDecoder(
-                bitmapProvider.getSourceDecoder(),
-                gifProvider.getSourceDecoder());
-        encoder = new GifBitmapWrapperResourceEncoder(bitmapProvider.getEncoder(), gifProvider.getEncoder());
-
-        Encoder<ParcelFileDescriptor> fileDescriptorEncoder = NullEncoder.get();
-
-        //TODO: what about the gif provider?
-        sourceEncoder = bitmapProvider.getSourceEncoder();
-    }
-
-    @Override
-    public ResourceDecoder<InputStream, GifBitmapWrapper> getCacheDecoder() {
-        return cacheDecoder;
-    }
-
-    @Override
-    public ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> getSourceDecoder() {
-        return sourceDecoder;
-    }
-
-    @Override
-    public Encoder<ImageVideoWrapper> getSourceEncoder() {
-        return sourceEncoder;
-    }
-
-    @Override
-    public ResourceEncoder<GifBitmapWrapper> getEncoder() {
-        return encoder;
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/ImageVideoGifDrawableLoadProvider.java b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/ImageVideoGifDrawableLoadProvider.java
new file mode 100644
index 0000000..e084fb5
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/gifbitmap/ImageVideoGifDrawableLoadProvider.java
@@ -0,0 +1,63 @@
+package com.bumptech.glide.load.resource.gifbitmap;
+
+import android.graphics.Bitmap;
+
+import com.bumptech.glide.load.Encoder;
+import com.bumptech.glide.load.ResourceDecoder;
+import com.bumptech.glide.load.ResourceEncoder;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.model.ImageVideoWrapper;
+import com.bumptech.glide.load.resource.file.FileToStreamDecoder;
+import com.bumptech.glide.load.resource.gif.GifDrawable;
+import com.bumptech.glide.provider.DataLoadProvider;
+
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * An {@link com.bumptech.glide.provider.DataLoadProvider} that can load either an
+ * {@link com.bumptech.glide.load.resource.gif.GifDrawable} or an {@link Bitmap} from either an
+ * {@link java.io.InputStream} or an {@link android.os.ParcelFileDescriptor}.
+ */
+public class ImageVideoGifDrawableLoadProvider implements DataLoadProvider<ImageVideoWrapper, GifBitmapWrapper> {
+    private final ResourceDecoder<File, GifBitmapWrapper> cacheDecoder;
+    private final ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> sourceDecoder;
+    private final ResourceEncoder<GifBitmapWrapper> encoder;
+    private final Encoder<ImageVideoWrapper> sourceEncoder;
+
+    public ImageVideoGifDrawableLoadProvider(DataLoadProvider<ImageVideoWrapper, Bitmap> bitmapProvider,
+            DataLoadProvider<InputStream, GifDrawable> gifProvider, BitmapPool bitmapPool) {
+
+        final GifBitmapWrapperResourceDecoder decoder = new GifBitmapWrapperResourceDecoder(
+                bitmapProvider.getSourceDecoder(),
+                gifProvider.getSourceDecoder(),
+                bitmapPool
+        );
+        cacheDecoder = new FileToStreamDecoder<GifBitmapWrapper>(new GifBitmapWrapperStreamResourceDecoder(decoder));
+        sourceDecoder = decoder;
+        encoder = new GifBitmapWrapperResourceEncoder(bitmapProvider.getEncoder(), gifProvider.getEncoder());
+
+        //TODO: what about the gif provider?
+        sourceEncoder = bitmapProvider.getSourceEncoder();
+    }
+
+    @Override
+    public ResourceDecoder<File, GifBitmapWrapper> getCacheDecoder() {
+        return cacheDecoder;
+    }
+
+    @Override
+    public ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> getSourceDecoder() {
+        return sourceDecoder;
+    }
+
+    @Override
+    public Encoder<ImageVideoWrapper> getSourceEncoder() {
+        return sourceEncoder;
+    }
+
+    @Override
+    public ResourceEncoder<GifBitmapWrapper> getEncoder() {
+        return encoder;
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/transcode/BitmapBytesTranscoder.java b/library/src/main/java/com/bumptech/glide/load/resource/transcode/BitmapBytesTranscoder.java
index 96b9d66..01d9440 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/transcode/BitmapBytesTranscoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/transcode/BitmapBytesTranscoder.java
@@ -1,11 +1,17 @@
 package com.bumptech.glide.load.resource.transcode;
 
 import android.graphics.Bitmap;
+
 import com.bumptech.glide.load.engine.Resource;
 import com.bumptech.glide.load.resource.bytes.BytesResource;
 
 import java.io.ByteArrayOutputStream;
 
+/**
+ * An {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} that converts
+ * {@link android.graphics.Bitmap}s into byte arrays using
+ * {@link android.graphics.Bitmap#compress(android.graphics.Bitmap.CompressFormat, int, java.io.OutputStream)}.
+ */
 public class BitmapBytesTranscoder implements ResourceTranscoder<Bitmap, byte[]> {
     private final Bitmap.CompressFormat compressFormat;
     private final int quality;
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/transcode/BitmapDrawableTranscoder.java b/library/src/main/java/com/bumptech/glide/load/resource/transcode/BitmapDrawableTranscoder.java
deleted file mode 100644
index e511865..0000000
--- a/library/src/main/java/com/bumptech/glide/load/resource/transcode/BitmapDrawableTranscoder.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.bumptech.glide.load.resource.transcode;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import com.bumptech.glide.load.engine.Resource;
-import com.bumptech.glide.load.resource.bitmap.BitmapDrawableResource;
-import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
-
-public class BitmapDrawableTranscoder implements ResourceTranscoder<Bitmap, BitmapDrawable> {
-    private Resources resources;
-    private BitmapPool bitmapPool;
-
-    public BitmapDrawableTranscoder(Resources resources, BitmapPool bitmapPool) {
-        this.resources = resources;
-        this.bitmapPool = bitmapPool;
-    }
-
-    @Override
-    public Resource<BitmapDrawable> transcode(Resource<Bitmap> toTranscode) {
-        BitmapDrawable drawable = new BitmapDrawable(resources, toTranscode.get());
-        return new BitmapDrawableResource(drawable, bitmapPool);
-    }
-
-    @Override
-    public String getId() {
-        return "BitmapDrawableTranscoder.com.bumptech.glide.load.resource.transcode";
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/transcode/BitmapToGlideDrawableTranscoder.java b/library/src/main/java/com/bumptech/glide/load/resource/transcode/BitmapToGlideDrawableTranscoder.java
new file mode 100644
index 0000000..2934391
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/transcode/BitmapToGlideDrawableTranscoder.java
@@ -0,0 +1,39 @@
+package com.bumptech.glide.load.resource.transcode;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+
+/**
+ * A wrapper for {@link com.bumptech.glide.load.resource.transcode.GlideBitmapDrawableTranscoder} that transcodes
+ * to {@link com.bumptech.glide.load.resource.drawable.GlideDrawable} rather than
+ * {@link com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawable}.
+ *
+ * TODO: use ? extends GlideDrawable rather than GlideDrawable directly and remove this class.
+ */
+public class BitmapToGlideDrawableTranscoder implements ResourceTranscoder<Bitmap, GlideDrawable> {
+
+    private final GlideBitmapDrawableTranscoder glideBitmapDrawableTranscoder;
+
+    public BitmapToGlideDrawableTranscoder(Context context) {
+        this(new GlideBitmapDrawableTranscoder(context));
+    }
+
+    public BitmapToGlideDrawableTranscoder(GlideBitmapDrawableTranscoder glideBitmapDrawableTranscoder) {
+        this.glideBitmapDrawableTranscoder = glideBitmapDrawableTranscoder;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Resource<GlideDrawable> transcode(Resource<Bitmap> toTranscode) {
+        return (Resource<GlideDrawable>) (Resource<? extends GlideDrawable>)
+                glideBitmapDrawableTranscoder.transcode(toTranscode);
+    }
+
+    @Override
+    public String getId() {
+        return glideBitmapDrawableTranscoder.getId();
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/transcode/GifBitmapWrapperDrawableTranscoder.java b/library/src/main/java/com/bumptech/glide/load/resource/transcode/GifBitmapWrapperDrawableTranscoder.java
index b5336f0..d226d68 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/transcode/GifBitmapWrapperDrawableTranscoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/transcode/GifBitmapWrapperDrawableTranscoder.java
@@ -1,35 +1,39 @@
 package com.bumptech.glide.load.resource.transcode;
 
 import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
+
 import com.bumptech.glide.load.engine.Resource;
-import com.bumptech.glide.load.resource.gif.GifData;
+import com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawable;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
 import com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapper;
 
-public class GifBitmapWrapperDrawableTranscoder implements ResourceTranscoder<GifBitmapWrapper, Drawable> {
-    private final ResourceTranscoder<Bitmap, ? extends Drawable> bitmapDrawableResourceTranscoder;
-    private final ResourceTranscoder<GifData, ? extends Drawable> gifDrawableResourceTranscoder;
+/**
+ * An {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} that can transcode either an
+ * {@link Bitmap} or an {@link com.bumptech.glide.load.resource.gif.GifDrawable} into an
+ * {@link android.graphics.drawable.Drawable}.
+ */
+public class GifBitmapWrapperDrawableTranscoder implements ResourceTranscoder<GifBitmapWrapper, GlideDrawable> {
+    private final ResourceTranscoder<Bitmap, GlideBitmapDrawable> bitmapDrawableResourceTranscoder;
 
-    public GifBitmapWrapperDrawableTranscoder(ResourceTranscoder<Bitmap, ? extends Drawable> bitmapDrawableResourceTranscoder,
-            ResourceTranscoder<GifData, ? extends Drawable> gifDrawableResourceTranscoder) {
+    public GifBitmapWrapperDrawableTranscoder(
+            ResourceTranscoder<Bitmap, GlideBitmapDrawable> bitmapDrawableResourceTranscoder) {
         this.bitmapDrawableResourceTranscoder = bitmapDrawableResourceTranscoder;
-        this.gifDrawableResourceTranscoder = gifDrawableResourceTranscoder;
     }
 
     @SuppressWarnings("unchecked")
     @Override
-    public Resource<Drawable> transcode(Resource<GifBitmapWrapper> toTranscode) {
+    public Resource<GlideDrawable> transcode(Resource<GifBitmapWrapper> toTranscode) {
         GifBitmapWrapper gifBitmap = toTranscode.get();
         Resource<Bitmap> bitmapResource = gifBitmap.getBitmapResource();
 
-        final Resource<? extends Drawable> result;
+        final Resource<? extends GlideDrawable> result;
         if (bitmapResource != null) {
             result = bitmapDrawableResourceTranscoder.transcode(bitmapResource);
         } else {
-            result = gifDrawableResourceTranscoder.transcode(gifBitmap.getGifResource());
+            result = gifBitmap.getGifResource();
         }
         // This is unchecked but always safe, anything that extends a Drawable can be safely cast to a Drawable.
-        return (Resource<Drawable>) result;
+        return (Resource<GlideDrawable>) result;
     }
 
     @Override
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/transcode/GifDataBytesTranscoder.java b/library/src/main/java/com/bumptech/glide/load/resource/transcode/GifDataBytesTranscoder.java
deleted file mode 100644
index 40f9247..0000000
--- a/library/src/main/java/com/bumptech/glide/load/resource/transcode/GifDataBytesTranscoder.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.bumptech.glide.load.resource.transcode;
-
-import com.bumptech.glide.load.engine.Resource;
-import com.bumptech.glide.load.resource.bytes.BytesResource;
-import com.bumptech.glide.load.resource.gif.GifData;
-
-public class GifDataBytesTranscoder implements ResourceTranscoder<GifData, byte[]> {
-    @Override
-    public Resource<byte[]> transcode(Resource<GifData> toTranscode) {
-        GifData gifData = toTranscode.get();
-        return new BytesResource(gifData.getData());
-    }
-
-    @Override
-    public String getId() {
-        return "GifDataBytesTranscoder.com.bumptech.glide.load.resource.transcode";
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/transcode/GifDataDrawableTranscoder.java b/library/src/main/java/com/bumptech/glide/load/resource/transcode/GifDataDrawableTranscoder.java
deleted file mode 100644
index c863464..0000000
--- a/library/src/main/java/com/bumptech/glide/load/resource/transcode/GifDataDrawableTranscoder.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.bumptech.glide.load.resource.transcode;
-
-import com.bumptech.glide.load.engine.Resource;
-import com.bumptech.glide.load.resource.gif.GifData;
-import com.bumptech.glide.load.resource.gif.GifDrawable;
-import com.bumptech.glide.load.resource.gif.GifDrawableResource;
-
-public class GifDataDrawableTranscoder implements ResourceTranscoder<GifData, GifDrawable> {
-    @Override
-    public Resource<GifDrawable> transcode(Resource<GifData> toTranscode) {
-        return new GifDrawableResource(toTranscode);
-    }
-
-    @Override
-    public String getId() {
-        return "GifDataDrawableTranscoder.com.bumptech.glide.load.resource.transcode";
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/transcode/GifDrawableBytesTranscoder.java b/library/src/main/java/com/bumptech/glide/load/resource/transcode/GifDrawableBytesTranscoder.java
new file mode 100644
index 0000000..586744c
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/transcode/GifDrawableBytesTranscoder.java
@@ -0,0 +1,23 @@
+package com.bumptech.glide.load.resource.transcode;
+
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.resource.bytes.BytesResource;
+import com.bumptech.glide.load.resource.gif.GifDrawable;
+
+/**
+ * An {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} that converts
+ * {@link com.bumptech.glide.load.resource.gif.GifDrawable} into bytes by obtaining the original bytes of the GIF from
+ * the {@link com.bumptech.glide.load.resource.gif.GifDrawable}.
+ */
+public class GifDrawableBytesTranscoder implements ResourceTranscoder<GifDrawable, byte[]> {
+    @Override
+    public Resource<byte[]> transcode(Resource<GifDrawable> toTranscode) {
+        GifDrawable gifData = toTranscode.get();
+        return new BytesResource(gifData.getData());
+    }
+
+    @Override
+    public String getId() {
+        return "GifDrawableBytesTranscoder.com.bumptech.glide.load.resource.transcode";
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/transcode/GlideBitmapDrawableTranscoder.java b/library/src/main/java/com/bumptech/glide/load/resource/transcode/GlideBitmapDrawableTranscoder.java
new file mode 100644
index 0000000..bdc03df
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/transcode/GlideBitmapDrawableTranscoder.java
@@ -0,0 +1,40 @@
+package com.bumptech.glide.load.resource.transcode;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawable;
+import com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawableResource;
+
+/**
+ * An {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} that converts
+ * {@link android.graphics.Bitmap}s into {@link android.graphics.drawable.BitmapDrawable}s.
+ */
+public class GlideBitmapDrawableTranscoder implements ResourceTranscoder<Bitmap, GlideBitmapDrawable> {
+    private final Resources resources;
+    private final BitmapPool bitmapPool;
+
+    public GlideBitmapDrawableTranscoder(Context context) {
+        this(context.getResources(), Glide.get(context).getBitmapPool());
+    }
+
+    public GlideBitmapDrawableTranscoder(Resources resources, BitmapPool bitmapPool) {
+        this.resources = resources;
+        this.bitmapPool = bitmapPool;
+    }
+
+    @Override
+    public Resource<GlideBitmapDrawable> transcode(Resource<Bitmap> toTranscode) {
+        GlideBitmapDrawable drawable = new GlideBitmapDrawable(resources, toTranscode.get());
+        return new GlideBitmapDrawableResource(drawable, bitmapPool);
+    }
+
+    @Override
+    public String getId() {
+        return "GlideBitmapDrawableTranscoder.com.bumptech.glide.load.resource.transcode";
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/transcode/ResourceTranscoder.java b/library/src/main/java/com/bumptech/glide/load/resource/transcode/ResourceTranscoder.java
index d82dad9..0e70dcb 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/transcode/ResourceTranscoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/transcode/ResourceTranscoder.java
@@ -9,12 +9,13 @@
  * @param <R> The type of the resource that will be transcoded to.
  */
 public interface ResourceTranscoder<Z, R> {
+
     /**
      * Transcodes the given resource to the new resource type and returns the wew resource.
      *
      * @param toTranscode The resource to transcode.
      */
-    public Resource<R> transcode(Resource<Z> toTranscode);
+    Resource<R> transcode(Resource<Z> toTranscode);
 
-    public String getId();
+    String getId();
 }
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/transcode/TranscoderFactory.java b/library/src/main/java/com/bumptech/glide/load/resource/transcode/TranscoderFactory.java
deleted file mode 100644
index 83acb5c..0000000
--- a/library/src/main/java/com/bumptech/glide/load/resource/transcode/TranscoderFactory.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package com.bumptech.glide.load.resource.transcode;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class TranscoderFactory {
-    private static final MultiClassKey GET_KEY = new MultiClassKey();
-
-    private static class MultiClassKey {
-        private Class decoded;
-        private Class transcoded;
-
-        public MultiClassKey() {}
-
-        public MultiClassKey(Class decoded, Class transcoded) {
-            this.decoded = decoded;
-            this.transcoded = transcoded;
-        }
-
-        public void set(Class decoded, Class transcoded) {
-            this.decoded = decoded;
-            this.transcoded = transcoded;
-        }
-
-        @Override
-        public String toString() {
-            return "MultiClassKey{" +
-                    "decoded=" + decoded +
-                    ", transcoded=" + transcoded +
-                    '}';
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-
-            MultiClassKey that = (MultiClassKey) o;
-
-            if (!decoded.equals(that.decoded)) {
-                return false;
-            }
-            if (!transcoded.equals(that.transcoded)) {
-                return false;
-            }
-
-            return true;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = decoded.hashCode();
-            result = 31 * result + transcoded.hashCode();
-            return result;
-        }
-    }
-
-    private Map<MultiClassKey, ResourceTranscoder> factories = new HashMap<MultiClassKey, ResourceTranscoder>();
-
-    public <Z, R> void register(Class<Z> decodedClass, Class<R> transcodedClass, ResourceTranscoder<Z, R> factory) {
-        factories.put(new MultiClassKey(decodedClass, transcodedClass), factory);
-    }
-
-    @SuppressWarnings("unchecked")
-    public <Z, R> ResourceTranscoder<Z, R> get(Class<Z> decodedClass, Class<R> transcodedClass) {
-        if (decodedClass.equals(transcodedClass)) {
-            return UnitTranscoder.get();
-        }
-        ResourceTranscoder<Z, R> result;
-        synchronized (GET_KEY) {
-            GET_KEY.set(decodedClass, transcodedClass);
-            result = factories.get(GET_KEY);
-        }
-        if (result == null) {
-            throw new IllegalArgumentException("No transcoder registered for " + decodedClass + " and "
-                    + transcodedClass);
-        }
-        return result;
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/transcode/TranscoderRegistry.java b/library/src/main/java/com/bumptech/glide/load/resource/transcode/TranscoderRegistry.java
new file mode 100644
index 0000000..adc854c
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/resource/transcode/TranscoderRegistry.java
@@ -0,0 +1,58 @@
+package com.bumptech.glide.load.resource.transcode;
+
+import com.bumptech.glide.util.MultiClassKey;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class that allows {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder}s to be registered and
+ * retrieved by the classes they convert between.
+ */
+public class TranscoderRegistry {
+    private static final MultiClassKey GET_KEY = new MultiClassKey();
+
+    private final Map<MultiClassKey, ResourceTranscoder<?, ?>> factories =
+            new HashMap<MultiClassKey, ResourceTranscoder<?, ?>>();
+
+    /**
+     * Registers the given {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} using the given
+     * classes so it can later be retrieved using the given classes.
+     *
+     * @param decodedClass The class of the resource that the transcoder transcodes from.
+     * @param transcodedClass The class of the resource that the transcoder transcodes to.
+     * @param transcoder The transcoder.
+     * @param <Z> The type of the resource that the transcoder transcodes from.
+     * @param <R> The type of the resource that the transcoder transcodes to.
+     */
+    public <Z, R> void register(Class<Z> decodedClass, Class<R> transcodedClass, ResourceTranscoder<Z, R> transcoder) {
+        factories.put(new MultiClassKey(decodedClass, transcodedClass), transcoder);
+    }
+
+    /**
+     * Returns the currently registered {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} for the
+     * given classes.
+     *
+     * @param decodedClass The class of the resource that the transcoder transcodes from.
+     * @param transcodedClass The class of the resource that the transcoder transcodes to.
+     * @param <Z> The type of the resource that the transcoder transcodes from.
+     * @param <R> The type of the resource that the transcoder transcodes to.
+     */
+    @SuppressWarnings("unchecked")
+    public <Z, R> ResourceTranscoder<Z, R> get(Class<Z> decodedClass, Class<R> transcodedClass) {
+        if (decodedClass.equals(transcodedClass)) {
+            // we know they're the same type (Z and R)
+            return (ResourceTranscoder<Z, R>) UnitTranscoder.get();
+        }
+        final ResourceTranscoder<?, ?> result;
+        synchronized (GET_KEY) {
+            GET_KEY.set(decodedClass, transcodedClass);
+            result = factories.get(GET_KEY);
+        }
+        if (result == null) {
+            throw new IllegalArgumentException("No transcoder registered for " + decodedClass + " and "
+                    + transcodedClass);
+        }
+        return (ResourceTranscoder<Z, R>) result;
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/transcode/UnitTranscoder.java b/library/src/main/java/com/bumptech/glide/load/resource/transcode/UnitTranscoder.java
index 6d223c9..6bf8ed8 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/transcode/UnitTranscoder.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/transcode/UnitTranscoder.java
@@ -2,16 +2,21 @@
 
 import com.bumptech.glide.load.engine.Resource;
 
-public class UnitTranscoder implements ResourceTranscoder {
-    private static final UnitTranscoder UNIT_TRANSCODER = new UnitTranscoder();
+/**
+ * A simple {@link ResourceTranscoder} that simply returns the given resource.
+ *
+ * @param <Z> The type of the resource that will be transcoded from and to.
+ */
+public class UnitTranscoder<Z> implements ResourceTranscoder<Z, Z> {
+    private static final UnitTranscoder<?> UNIT_TRANSCODER = new UnitTranscoder<Object>();
 
     @SuppressWarnings("unchecked")
-    public static <Z, R> ResourceTranscoder<Z, R> get() {
-        return UNIT_TRANSCODER;
+    public static <Z> ResourceTranscoder<Z, Z> get() {
+        return (ResourceTranscoder<Z, Z>) UNIT_TRANSCODER;
     }
 
     @Override
-    public Resource transcode(Resource toTranscode) {
+    public Resource<Z> transcode(Resource<Z> toTranscode) {
         return toTranscode;
     }
 
diff --git a/library/src/main/java/com/bumptech/glide/manager/ActivityFragmentLifecycle.java b/library/src/main/java/com/bumptech/glide/manager/ActivityFragmentLifecycle.java
new file mode 100644
index 0000000..f91270c
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/manager/ActivityFragmentLifecycle.java
@@ -0,0 +1,65 @@
+package com.bumptech.glide.manager;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * A {@link com.bumptech.glide.manager.Lifecycle} implementation for tracking and notifying listeners of
+ * {@link android.app.Fragment} and {@link android.app.Activity} lifecycle events.
+ */
+class ActivityFragmentLifecycle implements Lifecycle {
+    private final Set<LifecycleListener> lifecycleListeners =
+            Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<LifecycleListener, Boolean>()));
+    private boolean isStarted;
+    private boolean isDestroyed;
+
+    /**
+     * Adds the given listener to the list of listeners to be notified on each lifecycle event.
+     *
+     * <p>
+     *     The latest lifecycle event will be called on the given listener synchronously in this method. If the
+     *     activity or fragment is stopped, {@link LifecycleListener#onStop()}} will be called, and same for onStart and
+     *     onDestroy.
+     * </p>
+     *
+     * <p>
+     *     Note - {@link com.bumptech.glide.manager.LifecycleListener}s that are added more than once will have their
+     *     lifecycle methods called more than once. It is the caller's responsibility to avoid adding listeners
+     *     multiple times.
+     * </p>
+     */
+    @Override
+    public void addListener(LifecycleListener listener) {
+        lifecycleListeners.add(listener);
+
+        if (isDestroyed) {
+            listener.onDestroy();
+        } else if (isStarted) {
+            listener.onStart();
+        } else {
+            listener.onStop();
+        }
+    }
+
+    void onStart() {
+        isStarted = true;
+        for (LifecycleListener lifecycleListener : lifecycleListeners) {
+            lifecycleListener.onStart();
+        }
+    }
+
+    void onStop() {
+        isStarted = false;
+        for (LifecycleListener lifecycleListener : lifecycleListeners) {
+            lifecycleListener.onStop();
+        }
+    }
+
+    void onDestroy() {
+        isDestroyed = true;
+        for (LifecycleListener lifecycleListener : lifecycleListeners) {
+            lifecycleListener.onDestroy();
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/manager/ApplicationLifecycle.java b/library/src/main/java/com/bumptech/glide/manager/ApplicationLifecycle.java
new file mode 100644
index 0000000..de9a77d
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/manager/ApplicationLifecycle.java
@@ -0,0 +1,17 @@
+package com.bumptech.glide.manager;
+
+/**
+ * A {@link com.bumptech.glide.manager.Lifecycle} implementation for tracking and notifying listeners of
+ * {@link android.app.Application} lifecycle events.
+ *
+ * <p>
+ *     Since there are essentially no {@link android.app.Application} lifecycle events, this class simply defaults to
+ *     notifying new listeners that they are started.
+ * </p>
+ */
+class ApplicationLifecycle implements Lifecycle {
+    @Override
+    public void addListener(LifecycleListener listener) {
+        listener.onStart();
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/manager/ConnectivityMonitor.java b/library/src/main/java/com/bumptech/glide/manager/ConnectivityMonitor.java
index 5086988..fbd671b 100644
--- a/library/src/main/java/com/bumptech/glide/manager/ConnectivityMonitor.java
+++ b/library/src/main/java/com/bumptech/glide/manager/ConnectivityMonitor.java
@@ -1,12 +1,19 @@
 package com.bumptech.glide.manager;
 
-public interface ConnectivityMonitor {
+/**
+ * An interface for monitoring network connectivity events.
+ */
+public interface ConnectivityMonitor extends LifecycleListener {
 
-    public interface ConnectivityListener {
-        public void onConnectivityChanged(boolean isConnected);
+    /**
+     * An interface for listening to network connectivity events picked up by the monitor.
+     */
+    interface ConnectivityListener {
+        /**
+         * Called when the connectivity state changes.
+         *
+         * @param isConnected True if we're currently connected to a network, false otherwise.
+         */
+        void onConnectivityChanged(boolean isConnected);
     }
-
-    public void register();
-
-    public void unregister();
 }
diff --git a/library/src/main/java/com/bumptech/glide/manager/ConnectivityMonitorFactory.java b/library/src/main/java/com/bumptech/glide/manager/ConnectivityMonitorFactory.java
index 5862f60..77d1c50 100644
--- a/library/src/main/java/com/bumptech/glide/manager/ConnectivityMonitorFactory.java
+++ b/library/src/main/java/com/bumptech/glide/manager/ConnectivityMonitorFactory.java
@@ -3,10 +3,15 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 
+/**
+ * A factory class that produces a functional {@link com.bumptech.glide.manager.ConnectivityMonitor} if the application
+ * has the {@code android.permission.ACCESS_NETWORK_STATE} permission and a no-op non functional
+ * {@link com.bumptech.glide.manager.ConnectivityMonitor} if the app does not have the required permission.
+ */
 public class ConnectivityMonitorFactory {
     public ConnectivityMonitor build(Context context, ConnectivityMonitor.ConnectivityListener listener) {
-        int res = context.checkCallingOrSelfPermission("android.permission.ACCESS_NETWORK_STATE");
-        boolean hasPermission = res == PackageManager.PERMISSION_GRANTED;
+        final int res = context.checkCallingOrSelfPermission("android.permission.ACCESS_NETWORK_STATE");
+        final boolean hasPermission = res == PackageManager.PERMISSION_GRANTED;
         if (hasPermission) {
             return new DefaultConnectivityMonitor(context, listener);
         } else {
diff --git a/library/src/main/java/com/bumptech/glide/manager/DefaultConnectivityMonitor.java b/library/src/main/java/com/bumptech/glide/manager/DefaultConnectivityMonitor.java
index 5ff498b..aa194c4 100644
--- a/library/src/main/java/com/bumptech/glide/manager/DefaultConnectivityMonitor.java
+++ b/library/src/main/java/com/bumptech/glide/manager/DefaultConnectivityMonitor.java
@@ -30,8 +30,7 @@
         this.listener = listener;
     }
 
-    @Override
-    public void register() {
+    private void register() {
         if (isRegistered) {
             return;
         }
@@ -41,8 +40,7 @@
         isRegistered = true;
     }
 
-    @Override
-    public void unregister() {
+    private void unregister() {
         if (!isRegistered) {
             return;
         }
@@ -52,8 +50,24 @@
     }
 
     private boolean isConnected(Context context) {
-        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        ConnectivityManager connectivityManager =
+                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
         NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
         return networkInfo != null && networkInfo.isConnected();
     }
+
+    @Override
+    public void onStart() {
+        register();
+    }
+
+    @Override
+    public void onStop() {
+        unregister();
+    }
+
+    @Override
+    public void onDestroy() {
+        // Do nothing.
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/manager/Lifecycle.java b/library/src/main/java/com/bumptech/glide/manager/Lifecycle.java
new file mode 100644
index 0000000..a4a9be9
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/manager/Lifecycle.java
@@ -0,0 +1,11 @@
+package com.bumptech.glide.manager;
+
+/**
+ * An interface for listening to Activity/Fragment lifecycle events.
+ */
+public interface Lifecycle {
+    /**
+     * Adds the given listener to the set of listeners managed by this Lifecycle implementation.
+     */
+    void addListener(LifecycleListener listener);
+}
diff --git a/library/src/main/java/com/bumptech/glide/manager/LifecycleListener.java b/library/src/main/java/com/bumptech/glide/manager/LifecycleListener.java
new file mode 100644
index 0000000..e9deebe
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/manager/LifecycleListener.java
@@ -0,0 +1,23 @@
+package com.bumptech.glide.manager;
+
+/**
+ * An interface for listener to {@link android.app.Fragment} and {@link android.app.Activity} lifecycle events.
+ */
+public interface LifecycleListener {
+
+    /**
+     * Callback for when {@link android.app.Fragment#onStart()}} or {@link android.app.Activity#onStart()} is called.
+     */
+    void onStart();
+
+    /**
+     * Callback for when {@link android.app.Fragment#onStop()}} or {@link android.app.Activity#onStop()}} is called.
+     */
+    void onStop();
+
+    /**
+     * Callback for when {@link android.app.Fragment#onDestroy()}} or {@link android.app.Activity#onDestroy()} is
+     * called.
+     */
+    void onDestroy();
+}
diff --git a/library/src/main/java/com/bumptech/glide/manager/NullConnectivityMonitor.java b/library/src/main/java/com/bumptech/glide/manager/NullConnectivityMonitor.java
index ca73204..1cbb0b8 100644
--- a/library/src/main/java/com/bumptech/glide/manager/NullConnectivityMonitor.java
+++ b/library/src/main/java/com/bumptech/glide/manager/NullConnectivityMonitor.java
@@ -1,14 +1,22 @@
 package com.bumptech.glide.manager;
 
+/**
+ * A no-op {@link com.bumptech.glide.manager.ConnectivityMonitor}.
+ */
 class NullConnectivityMonitor implements ConnectivityMonitor {
 
     @Override
-    public void register() {
-
+    public void onStart() {
+        // Do nothing.
     }
 
     @Override
-    public void unregister() {
+    public void onStop() {
+        // Do nothing.
+    }
 
+    @Override
+    public void onDestroy() {
+        // Do nothing.
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/manager/RequestManagerFragment.java b/library/src/main/java/com/bumptech/glide/manager/RequestManagerFragment.java
index 21bd4b4..47d93af 100644
--- a/library/src/main/java/com/bumptech/glide/manager/RequestManagerFragment.java
+++ b/library/src/main/java/com/bumptech/glide/manager/RequestManagerFragment.java
@@ -1,20 +1,52 @@
 package com.bumptech.glide.manager;
 
+import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.Fragment;
-import android.util.Log;
+import android.os.Build;
 
 import com.bumptech.glide.RequestManager;
 
-@TargetApi(11)
+/**
+ * A view-less {@link android.app.Fragment} used to safely store an {@link com.bumptech.glide.RequestManager} that
+ * can be used to start, stop and manage Glide requests started for targets the fragment or activity this fragment is a
+ * child of.
+ *
+ * @see com.bumptech.glide.manager.SupportRequestManagerFragment
+ * @see com.bumptech.glide.manager.RequestManagerRetriever
+ * @see com.bumptech.glide.RequestManager
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB)
 public class RequestManagerFragment extends Fragment {
+    private final ActivityFragmentLifecycle lifecycle;
     private RequestManager requestManager;
-    private static String TAG = "RequestManagerFragment";
 
+    public RequestManagerFragment() {
+        this(new ActivityFragmentLifecycle());
+    }
+
+    // For testing only.
+    @SuppressLint("ValidFragment")
+    RequestManagerFragment(ActivityFragmentLifecycle lifecycle) {
+        this.lifecycle = lifecycle;
+    }
+
+    /**
+     * Sets the current {@link com.bumptech.glide.RequestManager}.
+     *
+     * @param requestManager The request manager to use.
+     */
     public void setRequestManager(RequestManager requestManager) {
         this.requestManager = requestManager;
     }
 
+    ActivityFragmentLifecycle getLifecycle() {
+        return lifecycle;
+    }
+
+    /**
+     * Returns the current {@link com.bumptech.glide.RequestManager} or null if none exists.
+     */
     public RequestManager getRequestManager() {
         return requestManager;
     }
@@ -22,32 +54,18 @@
     @Override
     public void onStart() {
         super.onStart();
-        if (requestManager != null) {
-            requestManager.onStart();
-        }
+        lifecycle.onStart();
     }
 
     @Override
     public void onStop() {
         super.onStop();
-        if (requestManager != null) {
-            try {
-                requestManager.onStop();
-            } catch (RuntimeException e) {
-                Log.e(TAG, "exception during onStop", e);
-            }
-        }
+        lifecycle.onStop();
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
-        if (requestManager != null) {
-            try {
-                requestManager.onDestroy();
-            } catch (RuntimeException e) {
-                Log.e(TAG, "exception during onDestroy", e);
-            }
-        }
+        lifecycle.onDestroy();
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/manager/RequestManagerLifecycleFragment.java b/library/src/main/java/com/bumptech/glide/manager/RequestManagerLifecycleFragment.java
deleted file mode 100644
index c7b9f18..0000000
--- a/library/src/main/java/com/bumptech/glide/manager/RequestManagerLifecycleFragment.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.bumptech.glide.manager;
-
-import com.bumptech.glide.RequestManager;
-
-public interface RequestManagerLifecycleFragment {
-
-    public void setRequestManager(RequestManager requestManager);
-
-    public RequestManager getRequestManager();
-
-    public void onStart();
-
-    public void onStop();
-
-    public void onDestroy();
-}
diff --git a/library/src/main/java/com/bumptech/glide/manager/RequestManagerRetriever.java b/library/src/main/java/com/bumptech/glide/manager/RequestManagerRetriever.java
index 55ba600..b0b80fa 100644
--- a/library/src/main/java/com/bumptech/glide/manager/RequestManagerRetriever.java
+++ b/library/src/main/java/com/bumptech/glide/manager/RequestManagerRetriever.java
@@ -2,114 +2,224 @@
 
 import android.annotation.TargetApi;
 import android.app.Activity;
+import android.app.Application;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.FragmentManager;
+import android.util.Log;
+
 import com.bumptech.glide.RequestManager;
+import com.bumptech.glide.util.Util;
 
-public class RequestManagerRetriever {
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A collection of static methods for creating new {@link com.bumptech.glide.RequestManager}s or retrieving existing
+ * ones from activities and fragment.
+ */
+public class RequestManagerRetriever implements Handler.Callback {
     static final String TAG = "com.bumptech.glide.manager";
-    private static RequestManager applicationManager;
 
-    public static RequestManager get(Context context) {
+    /** The singleton instance of RequestManagerRetriever. */
+    private static final RequestManagerRetriever INSTANCE = new RequestManagerRetriever();
+
+    private static final int ID_REMOVE_FRAGMENT_MANAGER = 1;
+    private static final int ID_REMOVE_SUPPORT_FRAGMENT_MANAGER = 2;
+
+    /** The top application level RequestManager. */
+    private volatile RequestManager applicationManager;
+
+    // Visible for testing.
+    /** Pending adds for RequestManagerFragments. */
+    final Map<android.app.FragmentManager, RequestManagerFragment> pendingRequestManagerFragments =
+            new HashMap<android.app.FragmentManager, RequestManagerFragment>();
+
+    // Visible for testing.
+    /** Pending adds for SupportRequestManagerFragments. */
+    final Map<FragmentManager, SupportRequestManagerFragment> pendingSupportRequestManagerFragments =
+            new HashMap<FragmentManager, SupportRequestManagerFragment>();
+
+    /** Main thread handler to handle cleaning up pending fragment maps. */
+    private final Handler handler;
+
+    /**
+     * Retrieves and returns the RequestManagerRetriever singleton.
+     */
+    public static RequestManagerRetriever get() {
+        return INSTANCE;
+    }
+
+    // Visible for testing.
+    RequestManagerRetriever() {
+        handler = new Handler(Looper.getMainLooper(), this /* Callback */);
+    }
+
+    private RequestManager getApplicationManager(Context context) {
+        // Either an application context or we're on a background thread.
+        if (applicationManager == null) {
+            synchronized (this) {
+                if (applicationManager == null) {
+                    // Normally pause/resume is taken care of by the fragment we add to the fragment or activity.
+                    // However, in this case since the manager attached to the application will not receive lifecycle
+                    // events, we must force the manager to start resumed using ApplicationLifecycle.
+                    applicationManager = new RequestManager(context.getApplicationContext(),
+                            new ApplicationLifecycle());
+                }
+            }
+        }
+
+        return applicationManager;
+    }
+
+    public RequestManager get(Context context) {
         if (context == null) {
             throw new IllegalArgumentException("You cannot start a load on a null Context");
-        } else if (context instanceof FragmentActivity) {
-            return get((FragmentActivity) context);
-        } else if (context instanceof Activity) {
-            return get((Activity) context);
-        } else {
-            if (applicationManager == null) {
-                applicationManager = new RequestManager(context.getApplicationContext());
+        } else if (Util.isOnMainThread() && !(context instanceof Application)) {
+            if (context instanceof FragmentActivity) {
+                return get((FragmentActivity) context);
+            } else if (context instanceof Activity) {
+                return get((Activity) context);
+            } else if (context instanceof ContextWrapper) {
+                return get(((ContextWrapper) context).getBaseContext());
             }
-            return applicationManager;
+        }
+
+        return getApplicationManager(context);
+    }
+
+    public RequestManager get(FragmentActivity activity) {
+        if (Util.isOnBackgroundThread()) {
+            return get(activity.getApplicationContext());
+        } else {
+            assertNotDestroyed(activity);
+            FragmentManager fm = activity.getSupportFragmentManager();
+            return supportFragmentGet(activity, fm);
         }
     }
 
-    @TargetApi(17)
-    public static RequestManager get(FragmentActivity activity) {
-        if (Build.VERSION.SDK_INT >= 11 && activity.isDestroyed()) {
-            throw new IllegalArgumentException("You cannot start a load for a destroyed activity");
-        }
-        FragmentManager fm = activity.getSupportFragmentManager();
-        return supportFragmentGet(activity, fm);
-    }
-
-    public static RequestManager get(Fragment fragment) {
+    public RequestManager get(Fragment fragment) {
         if (fragment.getActivity() == null) {
             throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
         }
-        if (fragment.isDetached()) {
-            throw new IllegalArgumentException("You cannot start a load on a detached fragment");
+        if (Util.isOnBackgroundThread()) {
+            return get(fragment.getActivity().getApplicationContext());
+        } else {
+            if (fragment.isDetached()) {
+                throw new IllegalArgumentException("You cannot start a load on a detached fragment");
+            }
+            FragmentManager fm = fragment.getChildFragmentManager();
+            return supportFragmentGet(fragment.getActivity(), fm);
         }
-        FragmentManager fm = fragment.getChildFragmentManager();
-        return supportFragmentGet(fragment.getActivity(), fm);
     }
 
-    @TargetApi(17)
-    public static RequestManager get(Activity activity) {
-        if (Build.VERSION.SDK_INT >= 17 && activity.isDestroyed()) {
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public RequestManager get(Activity activity) {
+        if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+            return get(activity.getApplicationContext());
+        } else {
+            assertNotDestroyed(activity);
+            android.app.FragmentManager fm = activity.getFragmentManager();
+            return fragmentGet(activity, fm);
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    private static void assertNotDestroyed(Activity activity) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) {
             throw new IllegalArgumentException("You cannot start a load for a destroyed activity");
         }
-        android.app.FragmentManager fm = activity.getFragmentManager();
-        return fragmentGet(activity, fm);
     }
 
-    @TargetApi(17)
-    public static RequestManager get(android.app.Fragment fragment) {
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    public RequestManager get(android.app.Fragment fragment) {
         if (fragment.getActivity() == null) {
             throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
         }
-        if (Build.VERSION.SDK_INT >= 13 && fragment.isDetached()) {
-            throw new IllegalArgumentException("You cannot start a load on a detached fragment");
-        }
-        if (Build.VERSION.SDK_INT >= 17) {
+        if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            return get(fragment.getActivity().getApplicationContext());
+        } else {
+            assertNotDetached(fragment);
             android.app.FragmentManager fm = fragment.getChildFragmentManager();
             return fragmentGet(fragment.getActivity(), fm);
-        } else {
-            return get(fragment.getActivity().getApplicationContext());
         }
     }
 
-    @TargetApi(11)
-    static RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
+    private static void assertNotDetached(android.app.Fragment fragment) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2 && fragment.isDetached()) {
+            throw new IllegalArgumentException("You cannot start a load on a detached fragment");
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    RequestManager fragmentGet(Context context, final android.app.FragmentManager fm) {
         RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(TAG);
         if (current == null) {
-            current = new RequestManagerFragment();
-            fm.beginTransaction().add(current, TAG).commitAllowingStateLoss();
-            // Normally fragment transactions are posted to the main thread. Since we may start multiple requests within
-            // a single synchronous call, we need to make sure that we only add a single fragment for the first call. To
-            // do so, we use executePendingTransactions to skip the post and synchronously add the new fragment so that
-            // the next synchronous request will retrieve it rather than creating a new one.
-            fm.executePendingTransactions();
+            current = pendingRequestManagerFragments.get(fm);
+            if (current == null) {
+                current = new RequestManagerFragment();
+                pendingRequestManagerFragments.put(fm, current);
+                fm.beginTransaction().add(current, TAG).commitAllowingStateLoss();
+                handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
+            }
         }
         RequestManager requestManager = current.getRequestManager();
         if (requestManager == null) {
-            requestManager = new RequestManager(context);
+            requestManager = new RequestManager(context, current.getLifecycle());
             current.setRequestManager(requestManager);
         }
         return requestManager;
 
     }
 
-    static RequestManager supportFragmentGet(Context context, FragmentManager fm) {
+    RequestManager supportFragmentGet(Context context, final FragmentManager fm) {
         SupportRequestManagerFragment current = (SupportRequestManagerFragment) fm.findFragmentByTag(TAG);
         if (current == null) {
-            current = new SupportRequestManagerFragment();
-            fm.beginTransaction().add(current, TAG).commitAllowingStateLoss();
-            // Normally fragment transactions are posted to the main thread. Since we may start multiple requests within
-            // a single synchronous call, we need to make sure that we only add a single fragment for the first call. To
-            // do so, we use executePendingTransactions to skip the post and synchronously add the new fragment so that
-            // the next synchronous request will retrieve it rather than creating a new one.
-            fm.executePendingTransactions();
+            current = pendingSupportRequestManagerFragments.get(fm);
+            if (current == null) {
+                current = new SupportRequestManagerFragment();
+                pendingSupportRequestManagerFragments.put(fm, current);
+                fm.beginTransaction().add(current, TAG).commitAllowingStateLoss();
+                handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
+            }
         }
         RequestManager requestManager = current.getRequestManager();
         if (requestManager == null) {
-            requestManager = new RequestManager(context);
+            requestManager = new RequestManager(context, current.getLifecycle());
             current.setRequestManager(requestManager);
         }
         return requestManager;
     }
+
+    @Override
+    public boolean handleMessage(Message message) {
+        boolean handled = true;
+        Object removed = null;
+        Object key = null;
+        switch (message.what) {
+            case ID_REMOVE_FRAGMENT_MANAGER:
+                android.app.FragmentManager fm = (android.app.FragmentManager) message.obj;
+                key = fm;
+                removed = pendingRequestManagerFragments.remove(fm);
+                break;
+            case ID_REMOVE_SUPPORT_FRAGMENT_MANAGER:
+                FragmentManager supportFm = (FragmentManager) message.obj;
+                key = supportFm;
+                removed = pendingSupportRequestManagerFragments.remove(supportFm);
+                break;
+            default:
+                handled = false;
+        }
+        if (handled && removed == null && Log.isLoggable(TAG, Log.WARN)) {
+            Log.w(TAG, "Failed to remove expected request manager fragment, manager: " + key);
+        }
+        return handled;
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/manager/RequestTracker.java b/library/src/main/java/com/bumptech/glide/manager/RequestTracker.java
index 9fd9d55..1fed826 100644
--- a/library/src/main/java/com/bumptech/glide/manager/RequestTracker.java
+++ b/library/src/main/java/com/bumptech/glide/manager/RequestTracker.java
@@ -6,6 +6,9 @@
 import java.util.Set;
 import java.util.WeakHashMap;
 
+/**
+ * A class for tracking, canceling, and restarting in progress, completed, and failed requests.
+ */
 public class RequestTracker {
     // Most requests will be for views and will therefore be held strongly (and safely) by the view via the tag.
     // However, a user can always pass in a different type of target which may end up not being strongly referenced even
@@ -14,22 +17,45 @@
     // can always make repeated requests into targets other than views, or use an activity manager in a fragment pager
     // where holding strong references would steadily leak bitmaps and/or views.
     private final Set<Request> requests = Collections.newSetFromMap(new WeakHashMap<Request, Boolean>());
+    private boolean isPaused;
 
-    public void addRequest(Request request) {
+    /**
+     * Starts tracking the given request.
+     */
+    public void runRequest(Request request) {
+        requests.add(request);
+        if (!isPaused) {
+            request.begin();
+        }
+    }
+
+    // Exposed for testing.
+    void addRequest(Request request) {
         requests.add(request);
     }
 
+    /**
+     * Stops tracking the given request.
+     */
     public void removeRequest(Request request) {
         requests.remove(request);
     }
 
     /**
+     * Returns {@code true} if requests are currently paused, and {@code false} otherwise.
+     */
+    public boolean isPaused() {
+        return isPaused;
+    }
+
+    /**
      * Stops any in progress requests.
      */
     public void pauseRequests() {
+        isPaused = true;
         for (Request request : requests) {
-            if (!request.isComplete() && !request.isFailed()) {
-                request.clear();
+            if (request.isRunning()) {
+                request.pause();
             }
         }
     }
@@ -38,9 +64,10 @@
      * Starts any not yet completed or failed requests.
      */
     public void resumeRequests() {
+        isPaused = false;
         for (Request request : requests) {
-            if (!request.isComplete() && !request.isRunning()) {
-                request.run();
+            if (!request.isComplete() && !request.isCancelled() && !request.isRunning()) {
+                request.begin();
             }
         }
     }
@@ -59,11 +86,12 @@
      */
     public void restartRequests() {
         for (Request request : requests) {
-            if (request.isFailed()) {
-                request.run();
-            } else if (!request.isComplete()) {
-                request.clear();
-                request.run();
+            if (!request.isComplete() && !request.isCancelled()) {
+                // Ensure the request will be restarted in onResume.
+                request.pause();
+                if (!isPaused) {
+                    request.begin();
+                }
             }
         }
     }
diff --git a/library/src/main/java/com/bumptech/glide/manager/SupportRequestManagerFragment.java b/library/src/main/java/com/bumptech/glide/manager/SupportRequestManagerFragment.java
index 935ebff..b5e73b6 100644
--- a/library/src/main/java/com/bumptech/glide/manager/SupportRequestManagerFragment.java
+++ b/library/src/main/java/com/bumptech/glide/manager/SupportRequestManagerFragment.java
@@ -1,15 +1,49 @@
 package com.bumptech.glide.manager;
 
+import android.annotation.SuppressLint;
 import android.support.v4.app.Fragment;
+
 import com.bumptech.glide.RequestManager;
 
+/**
+ * A view-less {@link android.support.v4.app.Fragment} used to safely store an
+ * {@link com.bumptech.glide.RequestManager} that can be used to start, stop and manage Glide requests started for
+ * targets within the fragment or activity this fragment is a child of.
+ *
+ * @see com.bumptech.glide.manager.RequestManagerFragment
+ * @see com.bumptech.glide.manager.RequestManagerRetriever
+ * @see com.bumptech.glide.RequestManager
+ */
 public class SupportRequestManagerFragment extends Fragment {
     private RequestManager requestManager;
+    private final ActivityFragmentLifecycle lifecycle;
 
+    public SupportRequestManagerFragment() {
+        this(new ActivityFragmentLifecycle());
+    }
+
+    // For testing only.
+    @SuppressLint("ValidFragment")
+    public SupportRequestManagerFragment(ActivityFragmentLifecycle lifecycle) {
+        this.lifecycle = lifecycle;
+    }
+
+    /**
+     * Sets the current {@link com.bumptech.glide.RequestManager}.
+     *
+     * @param requestManager The manager to set.
+     */
     public void setRequestManager(RequestManager requestManager) {
         this.requestManager = requestManager;
     }
 
+    ActivityFragmentLifecycle getLifecycle() {
+        return lifecycle;
+    }
+
+    /**
+     * Returns the current {@link com.bumptech.glide.RequestManager} or null if none is set.
+     */
     public RequestManager getRequestManager() {
         return requestManager;
     }
@@ -17,24 +51,18 @@
     @Override
     public void onStart() {
         super.onStart();
-        if (requestManager != null) {
-            requestManager.onStart();
-        }
+        lifecycle.onStart();
     }
 
     @Override
     public void onStop() {
         super.onStop();
-        if (requestManager != null) {
-            requestManager.onStop();
-        }
+        lifecycle.onStop();
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
-        if (requestManager != null) {
-            requestManager.onDestroy();
-        }
+        lifecycle.onDestroy();
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/provider/ChildLoadProvider.java b/library/src/main/java/com/bumptech/glide/provider/ChildLoadProvider.java
index 68384d1..5478eab 100644
--- a/library/src/main/java/com/bumptech/glide/provider/ChildLoadProvider.java
+++ b/library/src/main/java/com/bumptech/glide/provider/ChildLoadProvider.java
@@ -3,14 +3,24 @@
 import com.bumptech.glide.load.Encoder;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.ResourceEncoder;
-import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
 import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
 
-import java.io.InputStream;
+import java.io.File;
 
-public class ChildLoadProvider<A, T, Z, R> implements LoadProvider<A, T, Z, R> {
-    private LoadProvider<A, T, Z, R> parent;
-    private ResourceDecoder<InputStream, Z> cacheDecoder;
+/**
+ * A {@link com.bumptech.glide.provider.LoadProvider} that returns classes preferentially from those set on it but
+ * that also defaults to a wrapped {@link com.bumptech.glide.provider.LoadProvider} when a particular class is not set.
+ *
+ * @param <A> The type of the model the resource will be loaded from.
+ * @param <T> The type of the data that will be retrieved for the model.
+ * @param <Z> The type of the resource that will be decoded from the data.
+ * @param <R> The type of the resource that will be transcoded from the decoded resource.
+ */
+public class ChildLoadProvider<A, T, Z, R> implements LoadProvider<A, T, Z, R>, Cloneable {
+    private final LoadProvider<A, T, Z, R> parent;
+
+    private ResourceDecoder<File, Z> cacheDecoder;
     private ResourceDecoder<T, Z> sourceDecoder;
     private ResourceEncoder<Z> encoder;
     private ResourceTranscoder<Z, R> transcoder;
@@ -25,28 +35,58 @@
         return parent.getModelLoader();
     }
 
-    public void setCacheDecoder(ResourceDecoder<InputStream, Z> cacheDecoder) {
+    /**
+     * Sets the {@link com.bumptech.glide.load.ResourceDecoder} to use for decoding the resource from the disk cache.
+     *
+     * @param cacheDecoder The decoder to use.
+     */
+    public void setCacheDecoder(ResourceDecoder<File, Z> cacheDecoder) {
         this.cacheDecoder = cacheDecoder;
     }
 
+    /**
+     * Sets the {@link com.bumptech.glide.load.ResourceDecoder} to use to decoding the resource from the original data.
+     *
+     * @param sourceDecoder The decoder to use.
+     */
     public void setSourceDecoder(ResourceDecoder<T, Z> sourceDecoder) {
         this.sourceDecoder = sourceDecoder;
     }
 
+    /**
+     * Sets the {@link com.bumptech.glide.load.ResourceEncoder} to use to write the decoded and transformed resource to
+     * the disk cache.
+     *
+     * @param encoder The encoder to use.
+     */
     public void setEncoder(ResourceEncoder<Z> encoder) {
         this.encoder = encoder;
     }
 
+    /**
+     * Sets the {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} to use to transcode the decoded
+     * resource.
+     *
+     * @param transcoder The transcoder to use.
+     */
     public void setTranscoder(ResourceTranscoder<Z, R> transcoder) {
         this.transcoder = transcoder;
     }
 
+    /**
+     * Sets the {@link com.bumptech.glide.load.Encoder} to use to write the original data to the disk cache.
+     *
+     * @param sourceEncoder The encoder to use.
+     */
     public void setSourceEncoder(Encoder<T> sourceEncoder) {
         this.sourceEncoder = sourceEncoder;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public ResourceDecoder<InputStream, Z> getCacheDecoder() {
+    public ResourceDecoder<File, Z> getCacheDecoder() {
         if (cacheDecoder != null) {
             return cacheDecoder;
         } else {
@@ -54,6 +94,9 @@
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public ResourceDecoder<T, Z> getSourceDecoder() {
         if (sourceDecoder != null) {
@@ -63,6 +106,9 @@
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public Encoder<T> getSourceEncoder() {
         if (sourceEncoder != null) {
@@ -72,6 +118,9 @@
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public ResourceEncoder<Z> getEncoder() {
         if (encoder != null) {
@@ -81,6 +130,9 @@
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public ResourceTranscoder<Z, R> getTranscoder() {
         if (transcoder != null) {
@@ -90,4 +142,13 @@
         }
     }
 
+    @SuppressWarnings("unchecked")
+    @Override
+    public ChildLoadProvider<A, T, Z, R> clone() {
+        try {
+            return (ChildLoadProvider<A, T, Z, R>) super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/provider/DataLoadProvider.java b/library/src/main/java/com/bumptech/glide/provider/DataLoadProvider.java
new file mode 100644
index 0000000..9a504c3
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/provider/DataLoadProvider.java
@@ -0,0 +1,38 @@
+package com.bumptech.glide.provider;
+
+import com.bumptech.glide.load.Encoder;
+import com.bumptech.glide.load.ResourceDecoder;
+import com.bumptech.glide.load.ResourceEncoder;
+
+import java.io.File;
+
+/**
+ * A load provider that provides the necessary encoders and decoders to decode a specific type of resource from a
+ * specific type of data.
+ *
+ * @param <T> The type of data the resource will be decoded from.
+ * @param <Z> The type of resource that will be decoded.
+ */
+public interface DataLoadProvider<T, Z> {
+
+    /**
+     * Returns the {@link com.bumptech.glide.load.ResourceDecoder} to use to decode the resource from the disk cache.
+     */
+    ResourceDecoder<File, Z> getCacheDecoder();
+
+    /**
+     * Returns the {@link com.bumptech.glide.load.ResourceDecoder} to use to decode the resource from the original data.
+     */
+    ResourceDecoder<T, Z> getSourceDecoder();
+
+    /**
+     * Returns the {@link com.bumptech.glide.load.Encoder} to use to write the original data to the disk cache.
+     */
+    Encoder<T> getSourceEncoder();
+
+    /**
+     * Returns the {@link com.bumptech.glide.load.ResourceEncoder} to use to write the decoded and transformed resource
+     * to the disk cache.
+     */
+    ResourceEncoder<Z> getEncoder();
+}
diff --git a/library/src/main/java/com/bumptech/glide/provider/DataLoadProviderFactory.java b/library/src/main/java/com/bumptech/glide/provider/DataLoadProviderFactory.java
deleted file mode 100644
index 63d22a3..0000000
--- a/library/src/main/java/com/bumptech/glide/provider/DataLoadProviderFactory.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package com.bumptech.glide.provider;
-
-import com.bumptech.glide.DataLoadProvider;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class DataLoadProviderFactory {
-    private static final MultiClassKey GET_KEY = new MultiClassKey();
-
-    private static class MultiClassKey {
-        private Class dataClass;
-        private Class resourceClass;
-
-        public MultiClassKey() { }
-
-        public MultiClassKey(Class dataClass, Class resourceClass) {
-            this.dataClass = dataClass;
-            this.resourceClass = resourceClass;
-        }
-
-        @Override
-        public String toString() {
-            return "MultiClassKey{" +
-                    "dataClass=" + dataClass +
-                    ", resourceClass=" + resourceClass +
-                    '}';
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-
-            MultiClassKey that = (MultiClassKey) o;
-
-            if (!dataClass.equals(that.dataClass)) {
-                return false;
-            }
-            if (!resourceClass.equals(that.resourceClass)) {
-                return false;
-            }
-
-            return true;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = dataClass.hashCode();
-            result = 31 * result + resourceClass.hashCode();
-            return result;
-        }
-
-        public void set(Class dataClass, Class resourceClass) {
-            this.dataClass = dataClass;
-            this.resourceClass = resourceClass;
-        }
-    }
-
-    private final Map<MultiClassKey, DataLoadProvider> providers = new HashMap<MultiClassKey, DataLoadProvider>();
-
-    public <T, Z> void register(Class<T> dataClass, Class<Z> resourceClass, DataLoadProvider provider) {
-        providers.put(new MultiClassKey(dataClass, resourceClass), provider);
-    }
-
-    @SuppressWarnings("unchecked")
-    public <T, Z> DataLoadProvider<T, Z> get(Class<T> dataClass, Class<Z> resourceClass) {
-        DataLoadProvider<T, Z> result;
-        synchronized (GET_KEY) {
-            GET_KEY.set(dataClass, resourceClass);
-            result = providers.get(GET_KEY);
-        }
-        if (result == null) {
-            result = EmptyDataLoadProvider.get();
-        }
-        return result;
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/provider/DataLoadProviderRegistry.java b/library/src/main/java/com/bumptech/glide/provider/DataLoadProviderRegistry.java
new file mode 100644
index 0000000..7c50577
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/provider/DataLoadProviderRegistry.java
@@ -0,0 +1,53 @@
+package com.bumptech.glide.provider;
+
+import com.bumptech.glide.util.MultiClassKey;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class that allows {@link com.bumptech.glide.provider.DataLoadProvider}s to be registered and retrieved by the
+ * data and resource classes they provide encoders and decoders for.
+ */
+public class DataLoadProviderRegistry {
+    private static final MultiClassKey GET_KEY = new MultiClassKey();
+
+    private final Map<MultiClassKey, DataLoadProvider<?, ?>> providers =
+            new HashMap<MultiClassKey, DataLoadProvider<?, ?>>();
+
+    /**
+     * Registers the given {@link com.bumptech.glide.provider.DataLoadProvider} using the given classes so it can later
+     * be retrieved using the given classes.
+     *
+     * @param dataClass The class of the data that the provider provides encoders and decoders for.
+     * @param resourceClass The class of the resource that the provider provides encoders and decoders for.
+     * @param provider The provider.
+     * @param <T> The type of the data that the provider provides encoders and decoders for.
+     * @param <Z> The type of the resource that the provider provides encoders and decoders for.
+     */
+    public <T, Z> void register(Class<T> dataClass, Class<Z> resourceClass, DataLoadProvider<T, Z> provider) {
+        //TODO: maybe something like DataLoadProvider<? super T, ? extends Z> may work here
+        providers.put(new MultiClassKey(dataClass, resourceClass), provider);
+    }
+
+    /**
+     * Returns the currently registered {@link com.bumptech.glide.provider.DataLoadProvider} for the given classes.
+     *
+     * @param dataClass The class of the data that the provider provides encoders and decoders for.
+     * @param resourceClass The class of the resource that the provider provides encoders and decoders for.
+     * @param <T> The type of the data that the provider provides encoders and decoders for.
+     * @param <Z> The type of the resource that the provider provides encoders and decoders for.
+     */
+    @SuppressWarnings("unchecked")
+    public <T, Z> DataLoadProvider<T, Z> get(Class<T> dataClass, Class<Z> resourceClass) {
+        DataLoadProvider<?, ?> result;
+        synchronized (GET_KEY) {
+            GET_KEY.set(dataClass, resourceClass);
+            result = providers.get(GET_KEY);
+        }
+        if (result == null) {
+            result = EmptyDataLoadProvider.get();
+        }
+        return (DataLoadProvider<T, Z>) result;
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/provider/EmptyDataLoadProvider.java b/library/src/main/java/com/bumptech/glide/provider/EmptyDataLoadProvider.java
index 3c30ca7..7e40d9d 100644
--- a/library/src/main/java/com/bumptech/glide/provider/EmptyDataLoadProvider.java
+++ b/library/src/main/java/com/bumptech/glide/provider/EmptyDataLoadProvider.java
@@ -1,35 +1,42 @@
 package com.bumptech.glide.provider;
 
-import com.bumptech.glide.DataLoadProvider;
 import com.bumptech.glide.load.Encoder;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.ResourceEncoder;
 
-public class EmptyDataLoadProvider implements DataLoadProvider {
-    private static final EmptyDataLoadProvider EMPTY_DATA_LOAD_PROVIDER = new EmptyDataLoadProvider();
+import java.io.File;
+
+/**
+ * A {@link com.bumptech.glide.provider.DataLoadProvider} that returns {@code null} for every class.
+ *
+ * @param <T> unused data type.
+ * @param <Z> unused resource type.
+ */
+public class EmptyDataLoadProvider<T, Z> implements DataLoadProvider<T, Z> {
+    private static final DataLoadProvider<?, ?> EMPTY_DATA_LOAD_PROVIDER = new EmptyDataLoadProvider<Object, Object>();
 
     @SuppressWarnings("unchecked")
     public static <T, Z> DataLoadProvider<T, Z> get() {
-        return EMPTY_DATA_LOAD_PROVIDER;
+        return (DataLoadProvider<T, Z>) EMPTY_DATA_LOAD_PROVIDER;
     }
 
     @Override
-    public ResourceDecoder getCacheDecoder() {
+    public ResourceDecoder<File, Z> getCacheDecoder() {
         return null;
     }
 
     @Override
-    public ResourceDecoder getSourceDecoder() {
+    public ResourceDecoder<T, Z> getSourceDecoder() {
         return null;
     }
 
     @Override
-    public Encoder getSourceEncoder() {
+    public Encoder<T> getSourceEncoder() {
         return null;
     }
 
     @Override
-    public ResourceEncoder getEncoder() {
+    public ResourceEncoder<Z> getEncoder() {
         return null;
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/provider/FixedLoadProvider.java b/library/src/main/java/com/bumptech/glide/provider/FixedLoadProvider.java
index 4c5474e..7f50c9e 100644
--- a/library/src/main/java/com/bumptech/glide/provider/FixedLoadProvider.java
+++ b/library/src/main/java/com/bumptech/glide/provider/FixedLoadProvider.java
@@ -1,17 +1,25 @@
 package com.bumptech.glide.provider;
 
-import com.bumptech.glide.DataLoadProvider;
 import com.bumptech.glide.load.Encoder;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.ResourceEncoder;
-import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
 import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
 
-import java.io.InputStream;
+import java.io.File;
 
+/**
+ * A {@link com.bumptech.glide.provider.LoadProvider} that sets the classes it provides using non null arguments in its
+ * constructor.
+ *
+ * @param <A> The type of the model the resource will be loaded from.
+ * @param <T> The type of the data that will be retrieved for the model.
+ * @param <Z> The type of the resource that will be decoded from the data.
+ * @param <R> The type of the resource that will be transcoded from the decoded resource.
+ */
 public class FixedLoadProvider<A, T, Z, R> implements LoadProvider<A, T, Z, R>  {
     private final ModelLoader<A, T> modelLoader;
-    private ResourceTranscoder<Z, R> transcoder;
+    private final ResourceTranscoder<Z, R> transcoder;
     private final DataLoadProvider<T, Z> dataLoadProvider;
 
     public FixedLoadProvider(ModelLoader<A, T> modelLoader, ResourceTranscoder<Z, R> transcoder,
@@ -20,41 +28,61 @@
             throw new NullPointerException("ModelLoader must not be null");
         }
         this.modelLoader = modelLoader;
+
         if (transcoder == null) {
             throw new NullPointerException("Transcoder must not be null");
         }
         this.transcoder = transcoder;
+
         if (dataLoadProvider == null) {
             throw new NullPointerException("DataLoadProvider must not be null");
         }
         this.dataLoadProvider = dataLoadProvider;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public ModelLoader<A, T> getModelLoader() {
         return modelLoader;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public ResourceTranscoder<Z, R> getTranscoder() {
         return transcoder;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public ResourceDecoder<InputStream, Z> getCacheDecoder() {
+    public ResourceDecoder<File, Z> getCacheDecoder() {
         return dataLoadProvider.getCacheDecoder();
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public ResourceDecoder<T, Z> getSourceDecoder() {
         return dataLoadProvider.getSourceDecoder();
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public Encoder<T> getSourceEncoder() {
         return dataLoadProvider.getSourceEncoder();
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public ResourceEncoder<Z> getEncoder() {
         return dataLoadProvider.getEncoder();
diff --git a/library/src/main/java/com/bumptech/glide/provider/LoadProvider.java b/library/src/main/java/com/bumptech/glide/provider/LoadProvider.java
index dd901cc..694ab54 100644
--- a/library/src/main/java/com/bumptech/glide/provider/LoadProvider.java
+++ b/library/src/main/java/com/bumptech/glide/provider/LoadProvider.java
@@ -1,10 +1,13 @@
 package com.bumptech.glide.provider;
 
-import com.bumptech.glide.DataLoadProvider;
-import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
 import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
 
 /**
+ * An extension of {@link com.bumptech.glide.provider.DataLoadProvider} that also allows a
+ * {@link com.bumptech.glide.load.model.ModelLoader} and a
+ * {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} to be retrieved.
+ *
  * @param <A> The type of model.
  * @param <T> The type of data that will be decoded from.
  * @param <Z> The type of resource that will be decoded.
@@ -12,7 +15,14 @@
  */
 public interface LoadProvider<A, T, Z, R> extends DataLoadProvider<T, Z> {
 
-    public ModelLoader<A, T> getModelLoader();
+    /**
+     * Returns the {@link com.bumptech.glide.load.model.ModelLoader} to convert from the given model to a data type.
+     */
+    ModelLoader<A, T> getModelLoader();
 
-    public ResourceTranscoder<Z, R> getTranscoder();
+    /**
+     * Returns the {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} to convert from the decoded
+     * and transformed resource into the transcoded resource.
+     */
+    ResourceTranscoder<Z, R> getTranscoder();
 }
diff --git a/library/src/main/java/com/bumptech/glide/request/DrawableCrossFadeViewAnimation.java b/library/src/main/java/com/bumptech/glide/request/DrawableCrossFadeViewAnimation.java
deleted file mode 100644
index 6d39482..0000000
--- a/library/src/main/java/com/bumptech/glide/request/DrawableCrossFadeViewAnimation.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.bumptech.glide.request;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.TransitionDrawable;
-import android.view.View;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import com.bumptech.glide.request.target.Target;
-
-public class DrawableCrossFadeViewAnimation implements GlideAnimation<Drawable> {
-    // 150 ms.
-    public static final int DEFAULT_DURATION = 300;
-    private Animation defaultAnimation;
-    private int duration;
-
-    private static Animation getDefaultAnimation() {
-        AlphaAnimation animation = new AlphaAnimation(0f, 1f);
-        animation.setDuration(DEFAULT_DURATION / 2);
-        return animation;
-    }
-
-    public static class DrawableCrossFadeFactory implements GlideAnimationFactory<Drawable> {
-        private Context context;
-        private int defaultAnimationId;
-        private Animation defaultAnimation;
-        private int duration;
-        private DrawableCrossFadeViewAnimation animation;
-
-        public DrawableCrossFadeFactory() {
-            this(getDefaultAnimation(), DEFAULT_DURATION);
-        }
-
-        public DrawableCrossFadeFactory(int duration) {
-            this(getDefaultAnimation(), duration);
-        }
-
-        public DrawableCrossFadeFactory(Context context, int defaultAnimationId, int duration) {
-            this.context = context;
-            this.defaultAnimationId = defaultAnimationId;
-            this.duration = duration;
-        }
-
-        public DrawableCrossFadeFactory(Animation defaultAnimation, int duration) {
-            this.defaultAnimation = defaultAnimation;
-            this.duration = duration;
-        }
-
-        @Override
-        public GlideAnimation<Drawable> build(boolean isFromMemoryCache, boolean isFirstImage) {
-            if (isFromMemoryCache) {
-                return NoAnimation.get();
-            }
-
-            if (animation == null) {
-                if (defaultAnimation == null) {
-                    defaultAnimation = AnimationUtils.loadAnimation(context, defaultAnimationId);
-                }
-                animation = new DrawableCrossFadeViewAnimation(defaultAnimation, duration);
-            }
-
-            return animation;
-        }
-    }
-
-    public DrawableCrossFadeViewAnimation(Animation defaultAnimation, int duration) {
-        this.defaultAnimation = defaultAnimation;
-        this.duration = duration;
-    }
-
-    @Override
-    public boolean animate(Drawable previous, Drawable current, View view, Target<Drawable> target) {
-        if (previous != null) {
-            TransitionDrawable transitionDrawable = new TransitionDrawable(new Drawable[] { previous, current });
-            transitionDrawable.setCrossFadeEnabled(true);
-            transitionDrawable.startTransition(duration);
-            GlideAnimation<Drawable> none = NoAnimation.get();
-            target.onResourceReady(transitionDrawable, none);
-            return true;
-        } else {
-            view.startAnimation(defaultAnimation);
-            return false;
-        }
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/request/FutureTarget.java b/library/src/main/java/com/bumptech/glide/request/FutureTarget.java
new file mode 100644
index 0000000..882a8c9
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/FutureTarget.java
@@ -0,0 +1,35 @@
+package com.bumptech.glide.request;
+
+import com.bumptech.glide.request.target.Target;
+
+import java.util.concurrent.Future;
+
+/**
+ * An interface for an object that is both a {@link com.bumptech.glide.request.target.Target} and a
+ * {@link java.util.concurrent.Future}. For example:
+ * <pre>
+ * {@code
+ * FutureTarget<Bitmap> futureTarget = Glide.with(fragment)
+ *                                       .load("http://goo.gl/1asf12")
+ *                                       .asBitmap()
+ *                                       .into(250, 250);
+ * Bitmap myBitmap = futureTarget.get();
+ * ... // do things with bitmap and then release when finished:
+ * Glide.clear(futureTarget);
+ * }
+ * </pre>
+ *
+ * <p>
+ *     Note - {@link #get()} and {@link #get(long, java.util.concurrent.TimeUnit)} must be called
+ *     off of the main thread or they will block forever.
+ * </p>
+ *
+ * @param <R> The type of resource this FutureTarget will retrieve.
+ */
+public interface FutureTarget<R> extends Future<R>, Target<R>  {
+
+    /**
+     * Safely clears the target from a background thread to release its resources.
+     */
+    void clear();
+}
diff --git a/library/src/main/java/com/bumptech/glide/request/GenericRequest.java b/library/src/main/java/com/bumptech/glide/request/GenericRequest.java
index ed96fcd..7472a02 100644
--- a/library/src/main/java/com/bumptech/glide/request/GenericRequest.java
+++ b/library/src/main/java/com/bumptech/glide/request/GenericRequest.java
@@ -3,34 +3,60 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.util.Log;
+
 import com.bumptech.glide.Priority;
-import com.bumptech.glide.load.Encoder;
-import com.bumptech.glide.load.ResourceDecoder;
-import com.bumptech.glide.load.ResourceEncoder;
+import com.bumptech.glide.load.Key;
 import com.bumptech.glide.load.Transformation;
 import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
 import com.bumptech.glide.load.engine.Engine;
 import com.bumptech.glide.load.engine.Resource;
 import com.bumptech.glide.load.model.ModelLoader;
 import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
 import com.bumptech.glide.provider.LoadProvider;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.animation.GlideAnimationFactory;
+import com.bumptech.glide.request.target.SizeReadyCallback;
 import com.bumptech.glide.request.target.Target;
 import com.bumptech.glide.util.LogTime;
+import com.bumptech.glide.util.Util;
 
-import java.io.InputStream;
-import java.util.ArrayDeque;
 import java.util.Queue;
 
 /**
- * A {@link Request} that loads a {@link Resource} into a given {@link Target}.
+ * A {@link Request} that loads a {@link com.bumptech.glide.load.engine.Resource} into a given {@link Target}.
  *
  * @param <A> The type of the model that the resource will be loaded from.
  * @param <T> The type of the data that the resource will be loaded from.
  * @param <Z> The type of the resource that will be loaded.
+ * @param <R> The type of the resource that will be transcoded from the loaded resource.
  */
-public class GenericRequest<A, T, Z, R> implements Request, Target.SizeReadyCallback, ResourceCallback {
+public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback,
+        ResourceCallback {
     private static final String TAG = "GenericRequest";
+    private static final Queue<GenericRequest<?, ?, ?, ?>> REQUEST_POOL = Util.createQueue(0);
+    private static final double TO_MEGABYTE = 1d / (1024d * 1024d);
 
+    private enum Status {
+        /** Created but not yet running. */
+        PENDING,
+        /** In the process of fetching media. */
+        RUNNING,
+        /** Waiting for a callback given to the Target to be called to determine target dimensions. */
+        WAITING_FOR_SIZE,
+        /** Finished loading media successfully. */
+        COMPLETE,
+        /** Failed to load media. */
+        FAILED,
+        /** Cancelled by the user, may not be restarted. */
+        CANCELLED,
+        /** Temporarily paused by the system, may be restarted. */
+        PAUSED,
+    }
+
+    private final String tag = String.valueOf(hashCode());
+
+    private Key signature;
     private int placeholderResourceId;
     private int errorResourceId;
     private Context context;
@@ -42,31 +68,27 @@
     private boolean isMemoryCacheable;
     private Priority priority;
     private Target<R> target;
-    private RequestListener<A, R> requestListener;
+    private RequestListener<? super A, R> requestListener;
     private float sizeMultiplier;
     private Engine engine;
     private GlideAnimationFactory<R> animationFactory;
     private int overrideWidth;
     private int overrideHeight;
-    private String tag = String.valueOf(hashCode());
-    private boolean cacheSource;
+    private DiskCacheStrategy diskCacheStrategy;
 
     private Drawable placeholderDrawable;
     private Drawable errorDrawable;
-    private boolean isCancelled;
-    private boolean isError;
     private boolean loadedFromMemoryCache;
-    private Resource resource;
+    // doing our own type check
+    private Resource<?> resource;
     private Engine.LoadStatus loadStatus;
-    private boolean isRunning;
     private long startTime;
+    private Status status;
 
-    private static final Queue<GenericRequest> queue = new ArrayDeque<GenericRequest>();
-
-    @SuppressWarnings("unchecked")
     public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain(
             LoadProvider<A, T, Z, R> loadProvider,
             A model,
+            Key signature,
             Context context,
             Priority priority,
             Target<R> target,
@@ -75,7 +97,7 @@
             int placeholderResourceId,
             Drawable errorDrawable,
             int errorResourceId,
-            RequestListener<A, R> requestListener,
+            RequestListener<? super A, R> requestListener,
             RequestCoordinator requestCoordinator,
             Engine engine,
             Transformation<Z> transformation,
@@ -84,13 +106,15 @@
             GlideAnimationFactory<R> animationFactory,
             int overrideWidth,
             int overrideHeight,
-            boolean cacheSource) {
-        GenericRequest request = queue.poll();
+            DiskCacheStrategy diskCacheStrategy) {
+        @SuppressWarnings("unchecked")
+        GenericRequest<A, T, Z, R> request = (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();
         if (request == null) {
-            request = new GenericRequest();
+            request = new GenericRequest<A, T, Z, R>();
         }
         request.init(loadProvider,
                 model,
+                signature,
                 context,
                 priority,
                 target,
@@ -108,12 +132,12 @@
                 animationFactory,
                 overrideWidth,
                 overrideHeight,
-                cacheSource);
+                diskCacheStrategy);
         return request;
     }
 
     private GenericRequest() {
-
+        // just create, instances are reused with recycle/init
     }
 
     @Override
@@ -126,22 +150,17 @@
         errorDrawable = null;
         requestListener = null;
         requestCoordinator = null;
-        engine = null;
         transformation = null;
         animationFactory = null;
-        isCancelled = false;
-        isError = false;
         loadedFromMemoryCache = false;
         loadStatus = null;
-        isRunning = false;
-        cacheSource = false;
-        queue.offer(this);
+        REQUEST_POOL.offer(this);
     }
 
-
     private void init(
             LoadProvider<A, T, Z, R> loadProvider,
             A model,
+            Key signature,
             Context context,
             Priority priority,
             Target<R> target,
@@ -150,7 +169,7 @@
             int placeholderResourceId,
             Drawable errorDrawable,
             int errorResourceId,
-            RequestListener<A, R> requestListener,
+            RequestListener<? super A, R> requestListener,
             RequestCoordinator requestCoordinator,
             Engine engine,
             Transformation<Z> transformation,
@@ -159,10 +178,11 @@
             GlideAnimationFactory<R> animationFactory,
             int overrideWidth,
             int overrideHeight,
-            boolean cacheSource) {
+            DiskCacheStrategy diskCacheStrategy) {
         this.loadProvider = loadProvider;
         this.model = model;
-        this.context = context;
+        this.signature = signature;
+        this.context = context.getApplicationContext();
         this.priority = priority;
         this.target = target;
         this.sizeMultiplier = sizeMultiplier;
@@ -179,105 +199,181 @@
         this.animationFactory = animationFactory;
         this.overrideWidth = overrideWidth;
         this.overrideHeight = overrideHeight;
-        this.cacheSource = cacheSource;
+        this.diskCacheStrategy = diskCacheStrategy;
+        status = Status.PENDING;
 
         // We allow null models by just setting an error drawable. Null models will always have empty providers, we
         // simply skip our sanity checks in that unusual case.
         if (model != null) {
-            if (loadProvider.getCacheDecoder() == null) {
-                throw new NullPointerException("CacheDecoder must not be null, try .cacheDecoder(ResouceDecoder)");
+            check("ModelLoader", loadProvider.getModelLoader(), "try .using(ModelLoader)");
+            check("Transcoder", loadProvider.getTranscoder(), "try .as*(Class).transcode(ResourceTranscoder)");
+            check("Transformation", transformation, "try .transform(UnitTransformation.get())");
+            if (diskCacheStrategy.cacheSource()) {
+                check("SourceEncoder", loadProvider.getSourceEncoder(),
+                        "try .sourceEncoder(Encoder) or .diskCacheStrategy(NONE/RESULT)");
+            } else {
+                check("SourceDecoder", loadProvider.getSourceDecoder(),
+                        "try .decoder/.imageDecoder/.videoDecoder(ResourceDecoder) or .diskCacheStrategy(ALL/SOURCE)");
             }
-            if (loadProvider.getSourceDecoder() == null) {
-                throw new NullPointerException("SourceDecoder must not be null, try .imageDecoder(ResourceDecoder) " +
-                        "and/or .videoDecoder()");
+            if (diskCacheStrategy.cacheSource() || diskCacheStrategy.cacheResult()) {
+                // TODO if(resourceClass.isAssignableFrom(InputStream.class) it is possible to wrap sourceDecoder
+                // and use it instead of cacheDecoder: new FileToStreamDecoder<Z>(sourceDecoder)
+                // in that case this shouldn't throw
+                check("CacheDecoder", loadProvider.getCacheDecoder(),
+                        "try .cacheDecoder(ResouceDecoder) or .diskCacheStrategy(NONE)");
             }
-            if (loadProvider.getEncoder() == null) {
-                throw new NullPointerException("Encoder must not be null, try .encode(ResourceEncoder)");
-            }
-            if (loadProvider.getTranscoder() == null) {
-                throw new NullPointerException("Transcoder must not be null, try .as(Class, ResourceTranscoder)");
-            }
-            if (loadProvider.getModelLoader() == null) {
-                throw new NullPointerException("ModelLoader must not be null, try .using(ModelLoader)");
-            }
-            if (loadProvider.getSourceEncoder() == null) {
-                throw new NullPointerException("SourceEncoder must not be null, try .sourceEncoder(Encoder)");
+            if (diskCacheStrategy.cacheResult()) {
+                check("Encoder", loadProvider.getEncoder(),
+                        "try .encode(ResourceEncoder) or .diskCacheStrategy(NONE/SOURCE)");
             }
         }
     }
 
+    private static void check(String name, Object object, String suggestion) {
+        if (object == null) {
+            StringBuilder message = new StringBuilder(name);
+            message.append(" must not be null");
+            if (suggestion != null) {
+                message.append(", ");
+                message.append(suggestion);
+            }
+            throw new NullPointerException(message.toString());
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public void run() {
+    public void begin() {
         startTime = LogTime.getLogTime();
         if (model == null) {
             onException(null);
             return;
         }
 
+        status = Status.WAITING_FOR_SIZE;
         if (overrideWidth > 0 && overrideHeight > 0) {
             onSizeReady(overrideWidth, overrideHeight);
         } else {
             target.getSize(this);
         }
 
-        if (!isComplete() && !isFailed()) {
-            setPlaceHolder();
-            isRunning = true;
+        if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
+            target.onLoadStarted(getPlaceholderDrawable());
         }
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             logV("finished run method in " + LogTime.getElapsedMillis(startTime));
         }
     }
 
-    public void cancel() {
-        isRunning = false;
-        isCancelled = true;
+    /**
+     * Cancels the current load but does not release any resources held by the request and continues to display
+     * the loaded resource if the load completed before the call to cancel.
+     *
+     * <p>
+     *     Cancelled requests can be restarted with a subsequent call to {@link #begin()}.
+     * </p>
+     *
+     * @see #clear()
+     */
+    void cancel() {
+        status = Status.CANCELLED;
         if (loadStatus != null) {
             loadStatus.cancel();
             loadStatus = null;
         }
     }
 
+    /**
+     * Cancels the current load if it is in progress, clears any resources held onto by the request and replaces
+     * the loaded resource if the load completed with the placeholder.
+     *
+     * <p>
+     *     Cleared requests can be restarted with a subsequent call to {@link #begin()}
+     * </p>
+     *
+     * @see #cancel()
+     */
     @Override
     public void clear() {
+        Util.assertMainThread();
         cancel();
-        setPlaceHolder();
+        // Resource must be released before canNotifyStatusChanged is called.
         if (resource != null) {
-            resource.release();
-            resource = null;
+            releaseResource(resource);
+        }
+        if (canNotifyStatusChanged()) {
+            target.onLoadCleared(getPlaceholderDrawable());
         }
     }
 
     @Override
+    public boolean isPaused() {
+        return status == Status.PAUSED;
+    }
+
+    @Override
+    public void pause() {
+        clear();
+        status = Status.PAUSED;
+    }
+
+    private void releaseResource(Resource resource) {
+        engine.release(resource);
+        this.resource = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public boolean isRunning() {
-        return isRunning;
+        return status == Status.RUNNING || status == Status.WAITING_FOR_SIZE;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public boolean isComplete() {
-        return resource != null;
+        return status == Status.COMPLETE;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isResourceSet() {
+        return isComplete();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isCancelled() {
+        return status == Status.CANCELLED;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public boolean isFailed() {
-        return isError;
+        return status == Status.FAILED;
     }
 
-    private void setPlaceHolder() {
-        if (!canSetPlaceholder()) return;
-
-        target.setPlaceholder(getPlaceholderDrawable());
-    }
-
-    private void setErrorPlaceholder() {
-        if (!canSetPlaceholder()) return;
+    private void setErrorPlaceholder(Exception e) {
+        if (!canNotifyStatusChanged()) {
+            return;
+        }
 
         Drawable error = getErrorDrawable();
-        if (error != null) {
-            target.setPlaceholder(error);
-        } else {
-            setPlaceHolder();
+        if (error == null) {
+            error = getPlaceholderDrawable();
         }
+        target.onLoadFailed(e, error);
     }
 
     private Drawable getErrorDrawable() {
@@ -294,93 +390,124 @@
         return placeholderDrawable;
     }
 
+    /**
+     * A callback method that should never be invoked directly.
+     */
     @Override
     public void onSizeReady(int width, int height) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
         }
-        if (isCancelled) {
+        if (status != Status.WAITING_FOR_SIZE) {
             return;
         }
+        status = Status.RUNNING;
 
         width = Math.round(sizeMultiplier * width);
         height = Math.round(sizeMultiplier * height);
-        ResourceDecoder<InputStream, Z> cacheDecoder = loadProvider.getCacheDecoder();
-        Encoder<T> sourceEncoder = loadProvider.getSourceEncoder();
-        ResourceDecoder<T, Z> decoder = loadProvider.getSourceDecoder();
-        ResourceEncoder <Z> encoder = loadProvider.getEncoder();
-        ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
-        ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
 
+        ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
         final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
 
+        if (dataFetcher == null) {
+            onException(new Exception("Got null fetcher from model loader"));
+            return;
+        }
+        ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
         }
         loadedFromMemoryCache = true;
-        loadStatus = engine.load(width, height, cacheDecoder, dataFetcher, cacheSource, sourceEncoder, decoder,
-                transformation, encoder, transcoder, priority, isMemoryCacheable, this);
+        loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
+                priority, isMemoryCacheable, diskCacheStrategy, this);
         loadedFromMemoryCache = resource != null;
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
         }
     }
 
-    private boolean canSetImage() {
+    private boolean canSetResource() {
         return requestCoordinator == null || requestCoordinator.canSetImage(this);
     }
 
-    private boolean canSetPlaceholder() {
-        return requestCoordinator == null || requestCoordinator.canSetPlaceholder(this);
+    private boolean canNotifyStatusChanged() {
+        return requestCoordinator == null || requestCoordinator.canNotifyStatusChanged(this);
     }
 
-    private boolean isFirstImage() {
-        return requestCoordinator == null || !requestCoordinator.isAnyRequestComplete();
+    private boolean isFirstReadyResource() {
+        return requestCoordinator == null || !requestCoordinator.isAnyResourceSet();
     }
 
+    /**
+     * A callback method that should never be invoked directly.
+     */
     @SuppressWarnings("unchecked")
     @Override
-    public void onResourceReady(Resource resource) {
-        isRunning = false;
-        if (!canSetImage()) {
-            resource.release();
+    public void onResourceReady(Resource<?> resource) {
+        if (resource == null) {
+            onException(new Exception("Expected to receive a Resource<R> with an object of " + transcodeClass
+                    + " inside, but instead got null."));
             return;
         }
-        if (resource == null || !transcodeClass.isAssignableFrom(resource.get().getClass())) {
-            if (resource != null) {
-                resource.release();
-            }
-            onException(new Exception("Expected to receive an object of " + transcodeClass + " but instead got " +
-                    (resource != null ? resource.get() : null)));
-            return;
-        }
-        R result = (R) resource.get();
 
+        Object received = resource.get();
+        if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
+            releaseResource(resource);
+            onException(new Exception("Expected to receive an object of " + transcodeClass
+                    + " but instead got " + (received != null ? received.getClass() : "") + "{" + received + "}"
+                    + " inside Resource{" + resource + "}."
+                    + (received != null ? "" : " "
+                        + "To indicate failure return a null Resource object, "
+                        + "rather than a Resource object containing null data.")
+            ));
+            return;
+        }
+
+        if (!canSetResource()) {
+            releaseResource(resource);
+            // We can't set the status to complete before asking canSetResource().
+            status = Status.COMPLETE;
+            return;
+        }
+
+        onResourceReady(resource, (R) received);
+    }
+
+    /**
+     * Internal {@link #onResourceReady(Resource)} where arguments are known to be safe.
+     *
+     * @param resource original {@link Resource}, never <code>null</code>
+     * @param result object returned by {@link Resource#get()}, checked for type and never <code>null</code>
+     */
+    private void onResourceReady(Resource<?> resource, R result) {
         if (requestListener == null || !requestListener.onResourceReady(result, model, target, loadedFromMemoryCache,
-                isFirstImage())) {
-            GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstImage());
+                isFirstReadyResource())) {
+            GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstReadyResource());
             target.onResourceReady(result, animation);
         }
 
+        status = Status.COMPLETE;
         this.resource = resource;
 
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             logV("Resource ready in " + LogTime.getElapsedMillis(startTime) + " size: "
-                    + (resource.getSize() / (1024d * 1024d)) + " fromCache: " + loadedFromMemoryCache);
+                    + (resource.getSize() * TO_MEGABYTE) + " fromCache: " + loadedFromMemoryCache);
         }
     }
 
+    /**
+     * A callback method that should never be invoked directly.
+     */
     @Override
     public void onException(Exception e) {
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "load failed", e);
         }
 
-        isRunning = false;
-        isError = true;
+        status = Status.FAILED;
         //TODO: what if this is a thumbnail request?
-        if (requestListener == null || !requestListener.onException(e, model, target, isFirstImage())) {
-            setErrorPlaceholder();
+        if (requestListener == null || !requestListener.onException(e, model, target, isFirstReadyResource())) {
+            setErrorPlaceholder(e);
         }
     }
 
diff --git a/library/src/main/java/com/bumptech/glide/request/GlideAnimation.java b/library/src/main/java/com/bumptech/glide/request/GlideAnimation.java
deleted file mode 100644
index 415172a..0000000
--- a/library/src/main/java/com/bumptech/glide/request/GlideAnimation.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.bumptech.glide.request;
-
-import android.graphics.drawable.Drawable;
-import android.view.View;
-import com.bumptech.glide.request.target.Target;
-
-public interface GlideAnimation<R> {
-    public boolean animate(Drawable previous, R current, View view, Target<R> target);
-}
diff --git a/library/src/main/java/com/bumptech/glide/request/GlideAnimationFactory.java b/library/src/main/java/com/bumptech/glide/request/GlideAnimationFactory.java
deleted file mode 100644
index aeb2d48..0000000
--- a/library/src/main/java/com/bumptech/glide/request/GlideAnimationFactory.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.bumptech.glide.request;
-
-public interface GlideAnimationFactory<R> {
-
-    public GlideAnimation<R> build(boolean isFromMemoryCache, boolean isFirstImage);
-
-}
diff --git a/library/src/main/java/com/bumptech/glide/request/NoAnimation.java b/library/src/main/java/com/bumptech/glide/request/NoAnimation.java
deleted file mode 100644
index 090e35e..0000000
--- a/library/src/main/java/com/bumptech/glide/request/NoAnimation.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.bumptech.glide.request;
-
-import android.graphics.drawable.Drawable;
-import android.view.View;
-import com.bumptech.glide.request.target.Target;
-
-public class NoAnimation implements GlideAnimation {
-    private static final NoAnimation NO_ANIMATION = new NoAnimation();
-    private static final GlideAnimationFactory NO_ANIMATION_FACTORY = new NoAnimationFactory();
-
-    public static class NoAnimationFactory implements GlideAnimationFactory {
-        @Override
-        public GlideAnimation build(boolean isFromMemoryCache, boolean isFirstImage) {
-            return NO_ANIMATION;
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    public static <R> GlideAnimationFactory<R> getFactory() {
-        return NO_ANIMATION_FACTORY;
-    }
-
-    @SuppressWarnings("unchecked")
-    public static <R> GlideAnimation<R> get() {
-        return NO_ANIMATION;
-    }
-
-    @Override
-    public boolean animate(Drawable previous, Object current, View view, Target target) {
-        return false;
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/request/Request.java b/library/src/main/java/com/bumptech/glide/request/Request.java
index 25e71d3..44b5482 100644
--- a/library/src/main/java/com/bumptech/glide/request/Request.java
+++ b/library/src/main/java/com/bumptech/glide/request/Request.java
@@ -1,37 +1,59 @@
 package com.bumptech.glide.request;
 
-import com.bumptech.glide.request.target.Target;
-
 /**
- * A request that loads an asset for an {@link Target}.
+ * A request that loads a resource for an {@link com.bumptech.glide.request.target.Target}.
  */
 public interface Request {
 
     /**
      * Starts an asynchronous load.
      */
-    public void run();
+    void begin();
 
     /**
-     * Prevents any bitmaps being loaded from previous requests, releases any resources held by this request and
-     * displays the current placeholder if one was provided.
+     * Identical to {@link #clear()} except that the request may later be restarted.
      */
-    public void clear();
+    void pause();
+
+    /**
+     * Prevents any bitmaps being loaded from previous requests, releases any resources held by this request,
+     * displays the current placeholder if one was provided, and marks the request as having been cancelled.
+     */
+    void clear();
+
+    /**
+     * Returns true if this request is paused and may be restarted.
+     */
+    boolean isPaused();
 
     /**
      * Returns true if this request is running and has not completed or failed.
      */
-    public boolean isRunning();
+    boolean isRunning();
 
     /**
-     * Returns true if the request has successfully completed.
+     * Returns true if the request has completed successfully.
      */
-    public boolean isComplete();
+    boolean isComplete();
+
+    /**
+     * Returns true if a non-placeholder resource is set. For Requests that load more than one resource, isResourceSet
+     * may return true even if {@link #isComplete()}} returns false.
+     */
+    boolean isResourceSet();
+
+    /**
+     * Returns true if the request has been cancelled.
+     */
+    boolean isCancelled();
 
     /**
      * Returns true if the request has failed.
      */
-    public boolean isFailed();
+    boolean isFailed();
 
-    public void recycle();
+    /**
+     * Recycles the request object and releases its resources.
+     */
+    void recycle();
 }
diff --git a/library/src/main/java/com/bumptech/glide/request/RequestCoordinator.java b/library/src/main/java/com/bumptech/glide/request/RequestCoordinator.java
index 8560cf1..29a3f3a 100644
--- a/library/src/main/java/com/bumptech/glide/request/RequestCoordinator.java
+++ b/library/src/main/java/com/bumptech/glide/request/RequestCoordinator.java
@@ -1,9 +1,7 @@
 package com.bumptech.glide.request;
 
-import com.bumptech.glide.request.target.Target;
-
 /**
- * An interface for coordinating multiple requests with the same {@link Target}.
+ * An interface for coordinating multiple requests with the same {@link com.bumptech.glide.request.target.Target}.
  */
 public interface RequestCoordinator {
 
@@ -12,19 +10,19 @@
      *
      * @param request The {@link Request} requesting permission to display a bitmap.
      */
-    public boolean canSetImage(Request request);
+    boolean canSetImage(Request request);
 
     /**
      * Returns true if the {@link Request} can display a placeholder.
      *
      * @param request The {@link Request} requesting permission to display a placeholder.
      */
-    public boolean canSetPlaceholder(Request request);
+    boolean canNotifyStatusChanged(Request request);
 
     /**
      * Returns true if any coordinated {@link Request} has successfully completed.
      *
      * @see Request#isComplete()
      */
-    public boolean isAnyRequestComplete();
+    boolean isAnyResourceSet();
 }
diff --git a/library/src/main/java/com/bumptech/glide/request/RequestFutureTarget.java b/library/src/main/java/com/bumptech/glide/request/RequestFutureTarget.java
new file mode 100644
index 0000000..e7b58a8
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/RequestFutureTarget.java
@@ -0,0 +1,270 @@
+package com.bumptech.glide.request;
+
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SizeReadyCallback;
+import com.bumptech.glide.util.Util;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A {@link java.util.concurrent.Future} implementation for Glide that can be used to load resources in a blocking
+ * manner on background threads.
+ *
+ * <p>
+ *     Note - Unlike most targets, RequestFutureTargets can be used once and only once. Attempting to reuse a
+ *     RequestFutureTarget will probably result in undesirable behavior or exceptions. Instead of reusing
+ *     objects of this class, the pattern should be:
+ *
+ *     <pre>
+ *     {@code
+ *      RequestFutureTarget target = Glide.load("")...
+ *     Object resource = target.get();
+ *     // Do something with resource, and when finished:
+ *     Glide.clear(target);
+ *     }
+ *     </pre>
+ *     The {@link com.bumptech.glide.Glide#clear(FutureTarget)} call will make sure any resources used are recycled.
+ * </p>
+ *
+ * @param <T> The type of the data to load.
+ * @param <R> The type of the resource that will be loaded.
+ */
+public class RequestFutureTarget<T, R> implements FutureTarget<R>, Runnable {
+    private static final Waiter DEFAULT_WAITER = new Waiter();
+
+    private final Handler mainHandler;
+    private final int width;
+    private final int height;
+    // Exists for testing only.
+    private final boolean assertBackgroundThread;
+    private final Waiter waiter;
+
+    private R resource;
+    private Request request;
+    private boolean isCancelled;
+    private Exception exception;
+    private boolean resultReceived;
+    private boolean exceptionReceived;
+
+    /**
+     * Constructor for a RequestFutureTarget. Should not be used directly.
+     */
+    public RequestFutureTarget(Handler mainHandler, int width, int height) {
+        this(mainHandler, width, height, true, DEFAULT_WAITER);
+    }
+
+    RequestFutureTarget(Handler mainHandler, int width, int height, boolean assertBackgroundThread, Waiter waiter) {
+        this.mainHandler = mainHandler;
+        this.width = width;
+        this.height = height;
+        this.assertBackgroundThread = assertBackgroundThread;
+        this.waiter = waiter;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized boolean cancel(boolean b) {
+        if (isCancelled) {
+            return true;
+        }
+
+        final boolean result = !isDone();
+        if (result) {
+            isCancelled = true;
+            clear();
+            waiter.notifyAll(this);
+        }
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized boolean isCancelled() {
+        return isCancelled;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized boolean isDone() {
+        return isCancelled || resultReceived;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public R get() throws InterruptedException, ExecutionException {
+        try {
+            return doGet(null);
+        } catch (TimeoutException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public R get(long time, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException {
+        return doGet(timeUnit.toMillis(time));
+    }
+
+    /**
+     * A callback that should never be invoked directly.
+     */
+    @Override
+    public void getSize(SizeReadyCallback cb) {
+        cb.onSizeReady(width, height);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setRequest(Request request) {
+        this.request = request;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Request getRequest() {
+        return request;
+    }
+
+    /**
+     * A callback that should never be invoked directly.
+     */
+    @Override
+    public void onLoadCleared(Drawable placeholder) {
+        // Do nothing.
+    }
+
+    /**
+     * A callback that should never be invoked directly.
+     */
+    @Override
+    public void onLoadStarted(Drawable placeholder) {
+        // Do nothing.
+    }
+
+    /**
+     * A callback that should never be invoked directly.
+     */
+    @Override
+    public synchronized void onLoadFailed(Exception e, Drawable errorDrawable) {
+         // We might get a null exception.
+        exceptionReceived = true;
+        this.exception = e;
+        waiter.notifyAll(this);
+    }
+
+    /**
+     * A callback that should never be invoked directly.
+     */
+    @Override
+    public synchronized void onResourceReady(R resource, GlideAnimation<? super R> glideAnimation) {
+        // We might get a null result.
+        resultReceived = true;
+        this.resource = resource;
+        waiter.notifyAll(this);
+    }
+
+    private synchronized R doGet(Long timeoutMillis) throws ExecutionException, InterruptedException, TimeoutException {
+        if (assertBackgroundThread) {
+            Util.assertBackgroundThread();
+        }
+
+        if (isCancelled) {
+            throw new CancellationException();
+        } else if (exceptionReceived) {
+            throw new ExecutionException(exception);
+        } else if (resultReceived) {
+            return resource;
+        }
+
+        if (timeoutMillis == null) {
+            waiter.waitForTimeout(this, 0);
+        } else if (timeoutMillis > 0) {
+            waiter.waitForTimeout(this, timeoutMillis);
+        }
+
+        if (Thread.interrupted()) {
+            throw new InterruptedException();
+        } else if (exceptionReceived) {
+            throw new ExecutionException(exception);
+        } else if (isCancelled) {
+            throw new CancellationException();
+        } else if (!resultReceived) {
+            throw new TimeoutException();
+        }
+
+        return resource;
+    }
+
+    /**
+     * A callback that should never be invoked directly.
+     */
+    @Override
+    public void run() {
+        request.clear();
+    }
+
+    /**
+     * Can be safely called from either the main thread or a background thread to cleanup the resources used by this
+     * target.
+     */
+    @Override
+    public void clear() {
+        mainHandler.post(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onStart() {
+        // Do nothing.
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onStop() {
+        // Do nothing.
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onDestroy() {
+        // Do nothing.
+    }
+
+    // Visible for testing.
+    static class Waiter {
+
+        public void waitForTimeout(Object toWaitOn, long timeoutMillis) throws InterruptedException {
+            toWaitOn.wait(timeoutMillis);
+        }
+
+        public void notifyAll(Object toNotify) {
+            toNotify.notifyAll();
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/request/RequestListener.java b/library/src/main/java/com/bumptech/glide/request/RequestListener.java
index e3ceda8..4719a8a 100644
--- a/library/src/main/java/com/bumptech/glide/request/RequestListener.java
+++ b/library/src/main/java/com/bumptech/glide/request/RequestListener.java
@@ -19,12 +19,12 @@
      *     It is safe to reload this or a different model or change what is displayed in the target at this point.
      *     For example:
      * <pre>
-     * <code>
-     *     public void onException(Exception e, ModelType model, Target target) {
-     *         target.setPlaceholder(R.drawable.a_specific_error_for_my_exception);
-     *         Glide.load(model).into(target);
-     *     }
-     * </code>
+     * {@code
+     * public void onException(Exception e, T model, Target target, boolean isFirstResource) {
+     *     target.setPlaceholder(R.drawable.a_specific_error_for_my_exception);
+     *     Glide.load(model).into(target);
+     * }
+     * }
      * </pre>
      * </p>
      *
@@ -33,16 +33,18 @@
      *     relevant builder calls (like centerCrop, placeholder etc).
      * </p>
      *
-     * @param e The exception, or null
-     * @param model The model we were trying to load when the exception occurred
-     * @param target The {@link Target} we were trying to load the image into
+     * @param e The exception, or null.
+     * @param model The model we were trying to load when the exception occurred.
+     * @param target The {@link Target} we were trying to load the image into.
+     * @param isFirstResource True if this exception is for the first resource to load.
      * @return True if the listener has handled updating the target for the given exception, false to allow
      *         Glide's request to update the target.
      */
-    public abstract boolean onException(Exception e, T model, Target target, boolean isFirstImage);
+    boolean onException(Exception e, T model, Target<R> target, boolean isFirstResource);
 
     /**
-     * Called when a load completes successfully, immediately after {@link Target#onResourceReady(Object)}.
+     * Called when a load completes successfully, immediately after
+     * {@link Target#onResourceReady(Object, com.bumptech.glide.request.animation.GlideAnimation)}.
      *
      * @param resource The resource that was loaded for the target.
      * @param model The specific model that was used to load the image.
@@ -55,6 +57,5 @@
      * @return True if the listener has handled setting the resource on the target (including any animations), false to
      *         allow Glide's request to update the target (again including animations).
      */
-    public abstract boolean onResourceReady(R resource, T model, Target target, boolean isFromMemoryCache,
-            boolean isFirstResource);
+    boolean onResourceReady(R resource, T model, Target<R> target, boolean isFromMemoryCache, boolean isFirstResource);
 }
diff --git a/library/src/main/java/com/bumptech/glide/request/ResourceCallback.java b/library/src/main/java/com/bumptech/glide/request/ResourceCallback.java
index e1332f8..5704774 100644
--- a/library/src/main/java/com/bumptech/glide/request/ResourceCallback.java
+++ b/library/src/main/java/com/bumptech/glide/request/ResourceCallback.java
@@ -2,9 +2,23 @@
 
 import com.bumptech.glide.load.engine.Resource;
 
+/**
+ * A callback that listens for when a resource load completes successfully or fails due to an exception.
+ */
 public interface ResourceCallback {
 
-    public void onResourceReady(Resource resource);
+    /**
+     * Called when a resource is successfully loaded.
+     *
+     * @param resource The loaded resource.
+     */
+    void onResourceReady(Resource<?> resource);
 
-    public void onException(Exception e);
+    /**
+     * Called when a resource fails to load successfully.
+     *
+     * @param e The exception that caused the failure, or null it the load failed for some reason other than an
+     *          exception.
+     */
+    void onException(Exception e);
 }
diff --git a/library/src/main/java/com/bumptech/glide/request/ThumbnailRequestCoordinator.java b/library/src/main/java/com/bumptech/glide/request/ThumbnailRequestCoordinator.java
index d5ec86c..eb293eb 100644
--- a/library/src/main/java/com/bumptech/glide/request/ThumbnailRequestCoordinator.java
+++ b/library/src/main/java/com/bumptech/glide/request/ThumbnailRequestCoordinator.java
@@ -22,66 +22,116 @@
         this.thumb = thumb;
     }
 
+    /**
+     *
+     * Returns true if the request is either the request loading the fullsize image or if the request loading the
+     * full size image has not yet completed.
+     *
+     * @param request {@inheritDoc}
+     */
     @Override
     public boolean canSetImage(Request request) {
-        return parentCanSetImage() && (request == full || !full.isComplete());
+        return parentCanSetImage() && (request.equals(full) || !full.isResourceSet());
     }
 
     private boolean parentCanSetImage() {
         return coordinator == null || coordinator.canSetImage(this);
     }
 
+    /**
+     * Returns true if the request is the request loading the fullsize image and if neither the full nor the thumbnail
+     * image have completed sucessfully.
+     *
+     * @param request {@inheritDoc}.
+     */
     @Override
-    public boolean canSetPlaceholder(Request request) {
-        return parentCanSetPlaceholder() && (request == full && !isAnyRequestComplete());
+    public boolean canNotifyStatusChanged(Request request) {
+        return parentCanNotifyStatusChanged() && request.equals(full) && !isAnyResourceSet();
     }
 
-    private boolean parentCanSetPlaceholder() {
-        return coordinator == null || coordinator.canSetPlaceholder(this);
+    private boolean parentCanNotifyStatusChanged() {
+        return coordinator == null || coordinator.canNotifyStatusChanged(this);
     }
 
     @Override
-    public boolean isAnyRequestComplete() {
-        return parentIsAnyRequestComplete() || full.isComplete() || thumb.isComplete();
+    public boolean isAnyResourceSet() {
+        return parentIsAnyResourceSet() || isResourceSet();
     }
 
-    private boolean parentIsAnyRequestComplete() {
-        return coordinator != null && coordinator.isAnyRequestComplete();
+    private boolean parentIsAnyResourceSet() {
+        return coordinator != null && coordinator.isAnyResourceSet();
     }
 
+    /**
+     * Starts first the thumb request and then the full request.
+     */
     @Override
-    public void run() {
+    public void begin() {
         if (!thumb.isRunning()) {
-            thumb.run();
+            thumb.begin();
         }
         if (!full.isRunning()) {
-            full.run();
+            full.begin();
         }
     }
 
     @Override
-    public void clear() {
-        full.clear();
-        thumb.clear();
+    public void pause() {
+        full.pause();
+        thumb.pause();
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        thumb.clear();
+        full.clear();
+    }
+
+    @Override
+    public boolean isPaused() {
+        return full.isPaused();
+    }
+
+    /**
+     * Returns true if the full request is still running.
+     */
     @Override
     public boolean isRunning() {
         return full.isRunning();
     }
 
+    /**
+     * Returns true if the full request is complete.
+     */
     @Override
     public boolean isComplete() {
-        // TODO: this is a little strange, but we often want to avoid restarting the request or
-        // setting placeholders even if only the thumb is complete.
         return full.isComplete() || thumb.isComplete();
     }
 
     @Override
+    public boolean isResourceSet() {
+        return full.isResourceSet() || thumb.isResourceSet();
+    }
+
+    @Override
+    public boolean isCancelled() {
+        return full.isCancelled();
+    }
+
+    /**
+     * Returns true if the full request has failed.
+     */
+    @Override
     public boolean isFailed() {
         return full.isFailed();
     }
 
+    /**
+     * {@inheritDoc}.
+     */
     @Override
     public void recycle() {
         full.recycle();
diff --git a/library/src/main/java/com/bumptech/glide/request/ViewAnimation.java b/library/src/main/java/com/bumptech/glide/request/ViewAnimation.java
deleted file mode 100644
index 3c30f54..0000000
--- a/library/src/main/java/com/bumptech/glide/request/ViewAnimation.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.bumptech.glide.request;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import com.bumptech.glide.request.target.Target;
-
-public class ViewAnimation implements GlideAnimation {
-
-    public static class ViewAnimationFactory implements GlideAnimationFactory {
-        private Animation animation;
-        private Context context;
-        private int animationId;
-        private GlideAnimation glideAnimation;
-
-        public ViewAnimationFactory(Animation animation) {
-            this.animation = animation;
-        }
-
-        public ViewAnimationFactory(Context context, int animationId) {
-            this.context = context;
-            this.animationId = animationId;
-        }
-
-        @Override
-        public GlideAnimation build(boolean isFromMemoryCache, boolean isFirstImage) {
-            if (isFromMemoryCache || !isFirstImage) {
-                return NoAnimation.get();
-            }
-
-            if (glideAnimation == null) {
-                if (animation == null) {
-                    animation = AnimationUtils.loadAnimation(context, animationId);
-                }
-                glideAnimation = new ViewAnimation(animation);
-            }
-
-            return glideAnimation;
-        }
-    }
-
-    private Animation animation;
-
-    public ViewAnimation(Animation animation) {
-        this.animation = animation;
-    }
-
-    @Override
-    public boolean animate(Drawable previous, Object current, View view, Target target) {
-        view.clearAnimation();
-
-        view.startAnimation(animation);
-
-        return false;
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/request/ViewPropertyAnimation.java b/library/src/main/java/com/bumptech/glide/request/ViewPropertyAnimation.java
deleted file mode 100644
index a897004..0000000
--- a/library/src/main/java/com/bumptech/glide/request/ViewPropertyAnimation.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.bumptech.glide.request;
-
-import android.graphics.drawable.Drawable;
-import android.view.View;
-import com.bumptech.glide.request.target.Target;
-
-public class ViewPropertyAnimation implements GlideAnimation {
-
-    public interface Animator {
-        public void animate(View view);
-    }
-
-    public static class ViewPropertyAnimationFactory implements GlideAnimationFactory {
-        private Animator animator;
-        private ViewPropertyAnimation animation;
-
-        public ViewPropertyAnimationFactory(Animator animator) {
-            this.animator = animator;
-        }
-
-        @Override
-        public GlideAnimation build(boolean isFromMemoryCache, boolean isFirstImage) {
-            if (isFromMemoryCache || !isFirstImage) {
-                return NoAnimation.get();
-            }
-            if (animation == null) {
-                animation = new ViewPropertyAnimation(animator);
-            }
-
-            return animation;
-        }
-    }
-
-    private Animator animator;
-
-    public ViewPropertyAnimation(Animator animator) {
-        this.animator = animator;
-    }
-
-    @Override
-    public boolean animate(Drawable previous, Object current, View view, Target target) {
-        animator.animate(view);
-        return false;
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/request/animation/DrawableCrossFadeFactory.java b/library/src/main/java/com/bumptech/glide/request/animation/DrawableCrossFadeFactory.java
new file mode 100644
index 0000000..5bd3305
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/animation/DrawableCrossFadeFactory.java
@@ -0,0 +1,72 @@
+package com.bumptech.glide.request.animation;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+
+/**
+ * A factory class that produces a new {@link com.bumptech.glide.request.animation.GlideAnimation} that varies depending
+ * on whether or not the drawable was loaded from the memory cache and whether or not the drawable is the first
+ * image to be set on the target.
+ *
+ * <p>
+ *     Resources are usually loaded from the memory cache just before the user can see the view,
+ *     for example when the user changes screens or scrolls back and forth in a list. In those cases the user
+ *     typically does not expect to see an animation. As a result, when the resource is loaded from the memory
+ *     cache this factory produces an {@link com.bumptech.glide.request.animation.NoAnimation}.
+ * </p>
+ *
+ * @param <T> The type of the {@link android.graphics.drawable.Drawable} that will be animated.
+ */
+public class DrawableCrossFadeFactory<T extends Drawable> implements GlideAnimationFactory<T> {
+    private static final int DEFAULT_DURATION_MS = 300;
+    private final ViewAnimationFactory<T> animationFactory;
+    private final int duration;
+    private DrawableCrossFadeViewAnimation<T> animation;
+
+    public DrawableCrossFadeFactory() {
+        this(DEFAULT_DURATION_MS);
+    }
+
+    public DrawableCrossFadeFactory(int duration) {
+        this(new ViewAnimationFactory<T>(new DefaultAnimationFactory()), duration);
+    }
+
+    public DrawableCrossFadeFactory(Context context, int defaultAnimationId, int duration) {
+        this(new ViewAnimationFactory<T>(context, defaultAnimationId), duration);
+    }
+
+    public DrawableCrossFadeFactory(Animation defaultAnimation, int duration) {
+        this(new ViewAnimationFactory<T>(defaultAnimation), duration);
+    }
+
+    DrawableCrossFadeFactory(ViewAnimationFactory<T> animationFactory, int duration) {
+        this.animationFactory = animationFactory;
+        this.duration = duration;
+    }
+
+    @Override
+    public GlideAnimation<T> build(boolean isFromMemoryCache, boolean isFirstResource) {
+        if (isFromMemoryCache) {
+            return NoAnimation.get();
+        }
+
+        if (animation == null) {
+            GlideAnimation<T> defaultAnimation = animationFactory.build(false, isFirstResource);
+            animation = new DrawableCrossFadeViewAnimation<T>(defaultAnimation, duration);
+        }
+
+        return animation;
+    }
+
+    private static class DefaultAnimationFactory implements ViewAnimation.AnimationFactory {
+
+        @Override
+        public Animation build() {
+            AlphaAnimation animation = new AlphaAnimation(0f, 1f);
+            animation.setDuration(DEFAULT_DURATION_MS / 2);
+            return animation;
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/request/animation/DrawableCrossFadeViewAnimation.java b/library/src/main/java/com/bumptech/glide/request/animation/DrawableCrossFadeViewAnimation.java
new file mode 100644
index 0000000..a721aa1
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/animation/DrawableCrossFadeViewAnimation.java
@@ -0,0 +1,56 @@
+package com.bumptech.glide.request.animation;
+
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.TransitionDrawable;
+
+/**
+ * A cross fade {@link GlideAnimation} for {@link android.graphics.drawable.Drawable}s
+ * that uses an {@link android.graphics.drawable.TransitionDrawable} to transition from an existing drawable
+ * already visible on the target to a new drawable. If no existing drawable exists, this class can instead fall back
+ * to a default animation that doesn't rely on {@link android.graphics.drawable.TransitionDrawable}.
+ *
+ * @param <T> The type of the {@link android.graphics.drawable.Drawable} that will be animated.
+ */
+public class DrawableCrossFadeViewAnimation<T extends Drawable> implements GlideAnimation<T> {
+    private final GlideAnimation<T> defaultAnimation;
+    private final int duration;
+
+    /**
+     * Constructor that takes a default animation and a duration in milliseconds that the cross fade animation should
+     * last.
+     * @param duration The duration that the cross fade animation should run if there is something to cross fade from
+     *                 when a new {@link android.graphics.drawable.Drawable} is set.
+     */
+    public DrawableCrossFadeViewAnimation(GlideAnimation<T> defaultAnimation, int duration) {
+        this.defaultAnimation = defaultAnimation;
+        this.duration = duration;
+    }
+
+    /**
+     * Animates from the previous drawable to the current drawable in one of two ways.
+     *
+     * <ol>
+     *     <li>Using the default animation provided in the constructor if the previous drawable is null</li>
+     *     <li>Using the cross fade animation with the duration provided in the constructor if the previous
+     *     drawable is non null</li>
+     * </ol>
+     *
+     * @param current {@inheritDoc}
+     * @param adapter  {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public boolean animate(T current, ViewAdapter adapter) {
+        Drawable previous = adapter.getCurrentDrawable();
+        if (previous != null) {
+            TransitionDrawable transitionDrawable = new TransitionDrawable(new Drawable[] { previous, current });
+            transitionDrawable.setCrossFadeEnabled(true);
+            transitionDrawable.startTransition(duration);
+            adapter.setDrawable(transitionDrawable);
+            return true;
+        } else {
+            defaultAnimation.animate(current, adapter);
+            return false;
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/request/animation/GlideAnimation.java b/library/src/main/java/com/bumptech/glide/request/animation/GlideAnimation.java
new file mode 100644
index 0000000..9cf865e
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/animation/GlideAnimation.java
@@ -0,0 +1,55 @@
+package com.bumptech.glide.request.animation;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+/**
+ * An interface that allows a transformation to be applied to {@link android.view.View}s in
+ * {@link com.bumptech.glide.request.target.Target}s in across resource types. Targets that wrap views will be able to
+ * provide all of the necessary arguments and start the animation. Those that do not will be unable to provide the
+ * necessary arguments and will therefore be forced to ignore the animation. This interface is a compromise that
+ * allows view animations in Glide's complex world of arbitrary resource types and arbitrary target types.
+ *
+ * @param <R> The type of the resource that should be animated to.
+ */
+public interface GlideAnimation<R> {
+
+    /**
+     * An interface wrapping a view that exposes the necessary methods to run the various types of android animations
+     * ({@link com.bumptech.glide.request.animation.ViewAnimation},
+     * {@link com.bumptech.glide.request.animation.ViewPropertyAnimation} and animated
+     * {@link android.graphics.drawable.Drawable}s).
+     */
+    interface ViewAdapter {
+        /**
+         * Returns the wrapped {@link android.view.View}.
+         */
+        View getView();
+
+        /**
+         * Returns the current drawable being displayed in the view, or null if no such drawable exists (or one cannot
+         * be retrieved).
+         */
+        Drawable getCurrentDrawable();
+
+        /**
+         * Sets the current drawable (usually an animated drawable) to display in the wrapped view.
+         *
+         * @param drawable The drawable to display in the wrapped view.
+         */
+        void setDrawable(Drawable drawable);
+    }
+
+    /**
+     * Animates from the previous {@link android.graphics.drawable.Drawable} that is currently being displayed in the
+     * given view, if not null, to the new resource that should be displayed in the view.
+     *
+     * @param current The new resource that will be displayed in the view.
+     * @param adapter The {@link com.bumptech.glide.request.animation.GlideAnimation.ViewAdapter} wrapping a view that
+     *                can at least return an {@link android.view.View} from
+     *                {@link com.bumptech.glide.request.animation.GlideAnimation.ViewAdapter#getView()}.
+     * @return True if int he process of running the animation the new resource was set on the view, false if the caller
+     * needs to manually set the current resource on the view.
+     */
+    boolean animate(R current, ViewAdapter adapter);
+}
diff --git a/library/src/main/java/com/bumptech/glide/request/animation/GlideAnimationFactory.java b/library/src/main/java/com/bumptech/glide/request/animation/GlideAnimationFactory.java
new file mode 100644
index 0000000..83ae613
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/animation/GlideAnimationFactory.java
@@ -0,0 +1,17 @@
+package com.bumptech.glide.request.animation;
+
+/**
+ * A factory class that can produce different {@link com.bumptech.glide.request.animation.GlideAnimation}s based on the
+ * state of the request.
+ * @param <R> The type of resource that needs to be animated into the target.
+ */
+public interface GlideAnimationFactory<R> {
+
+    /**
+     * Returns a new {@link com.bumptech.glide.request.animation.GlideAnimation}.
+     *
+     * @param isFromMemoryCache True if this will be an animation for a resource that was loaded from the memory cache.
+     * @param isFirstResource True if this is the first resource to be loaded into the target.
+     */
+    GlideAnimation<R> build(boolean isFromMemoryCache, boolean isFirstResource);
+}
diff --git a/library/src/main/java/com/bumptech/glide/request/animation/NoAnimation.java b/library/src/main/java/com/bumptech/glide/request/animation/NoAnimation.java
new file mode 100644
index 0000000..aeec01d
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/animation/NoAnimation.java
@@ -0,0 +1,47 @@
+package com.bumptech.glide.request.animation;
+
+/**
+ * A simple {@link com.bumptech.glide.request.animation.GlideAnimation} that performs no actions.
+ *
+ * @param <R> animated resource type
+ */
+public class NoAnimation<R> implements GlideAnimation<R> {
+    private static final NoAnimation<?> NO_ANIMATION = new NoAnimation<Object>();
+    @SuppressWarnings("rawtypes")
+    private static final GlideAnimationFactory<?> NO_ANIMATION_FACTORY = new NoAnimationFactory();
+
+    /**
+     * A factory that always returns the same {@link com.bumptech.glide.request.animation.NoAnimation}.
+     */
+    public static class NoAnimationFactory<R> implements GlideAnimationFactory<R> {
+        @SuppressWarnings("unchecked")
+        @Override
+        public GlideAnimation<R> build(boolean isFromMemoryCache, boolean isFirstResource) {
+            return (GlideAnimation<R>) NO_ANIMATION;
+        }
+    }
+
+    /**
+     * Returns an instance of a factory that produces {@link com.bumptech.glide.request.animation.NoAnimation}s.
+     */
+    @SuppressWarnings("unchecked")
+    public static <R> GlideAnimationFactory<R> getFactory() {
+        return (GlideAnimationFactory<R>) NO_ANIMATION_FACTORY;
+    }
+
+    /**
+     * Returns an instance of {@link com.bumptech.glide.request.animation.NoAnimation}.
+     */
+    @SuppressWarnings("unchecked")
+    public static <R> GlideAnimation<R> get() {
+        return (GlideAnimation<R>) NO_ANIMATION;
+    }
+
+    /**
+     * Performs no animation and always returns {@code false}.
+     */
+    @Override
+    public boolean animate(Object current, ViewAdapter adapter) {
+        return false;
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/request/animation/ViewAnimation.java b/library/src/main/java/com/bumptech/glide/request/animation/ViewAnimation.java
new file mode 100644
index 0000000..7f62719
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/animation/ViewAnimation.java
@@ -0,0 +1,49 @@
+package com.bumptech.glide.request.animation;
+
+import android.view.View;
+import android.view.animation.Animation;
+
+/**
+ * A {@link com.bumptech.glide.request.animation.GlideAnimation GlideAnimation} that can apply a
+ * {@link android.view.animation.Animation Animation} to a {@link android.view.View View} using
+ * {@link android.view.View#startAnimation(android.view.animation.Animation) View.startAnimation}.
+ *
+ * @param <R> The type of the resource displayed in the view that is animated
+ */
+public class ViewAnimation<R> implements GlideAnimation<R> {
+
+    private final AnimationFactory animationFactory;
+
+    /**
+     * Constructs a new ViewAnimation that will start the given {@link android.view.animation.Animation}.
+     */
+    ViewAnimation(AnimationFactory animationFactory) {
+        this.animationFactory = animationFactory;
+    }
+
+    /**
+     * Always clears the current animation on the view using {@link android.view.View#clearAnimation()}, then
+     * starts the {@link android.view.animation.Animation} given in the constructor using
+     * {@link android.view.View#startAnimation(android.view.animation.Animation)} and then returns {@code false} because
+     * the animation does not actually set the current resource on the view.
+     *
+     * @param current {@inheritDoc}
+     * @param adapter {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public boolean animate(R current, ViewAdapter adapter) {
+        View view = adapter.getView();
+        if (view != null) {
+            view.clearAnimation();
+            Animation animation = animationFactory.build();
+            view.startAnimation(animation);
+        }
+
+        return false;
+    }
+
+    interface AnimationFactory {
+        Animation build();
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/request/animation/ViewAnimationFactory.java b/library/src/main/java/com/bumptech/glide/request/animation/ViewAnimationFactory.java
new file mode 100644
index 0000000..df2761a
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/animation/ViewAnimationFactory.java
@@ -0,0 +1,78 @@
+package com.bumptech.glide.request.animation;
+
+import android.content.Context;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+/**
+ * A {@link com.bumptech.glide.request.animation.GlideAnimationFactory} that produces
+ * {@link com.bumptech.glide.request.animation.ViewAnimation}s.
+ *
+ * @param <R> The type of the resource displayed in the view that is animated
+ */
+public class ViewAnimationFactory<R> implements GlideAnimationFactory<R> {
+    private final ViewAnimation.AnimationFactory animationFactory;
+    private GlideAnimation<R> glideAnimation;
+
+    public ViewAnimationFactory(Animation animation) {
+        this(new ConcreteAnimationFactory(animation));
+    }
+
+    public ViewAnimationFactory(Context context, int animationId) {
+        this(new ResourceAnimationFactory(context, animationId));
+    }
+
+    ViewAnimationFactory(ViewAnimation.AnimationFactory animationFactory) {
+        this.animationFactory = animationFactory;
+    }
+
+    /**
+     * Returns a new {@link com.bumptech.glide.request.animation.GlideAnimation} for the given arguments. If
+     * isFromMemoryCache is {@code true} or isFirstImage is {@code false}, returns a
+     * {@link com.bumptech.glide.request.animation.NoAnimation} and otherwise returns a new
+     * {@link com.bumptech.glide.request.animation.ViewAnimation}.
+     *
+     * @param isFromMemoryCache {@inheritDoc}
+     * @param isFirstResource   {@inheritDoc}
+     */
+    @Override
+    public GlideAnimation<R> build(boolean isFromMemoryCache, boolean isFirstResource) {
+        if (isFromMemoryCache || !isFirstResource) {
+            return NoAnimation.get();
+        }
+
+        if (glideAnimation == null) {
+            glideAnimation = new ViewAnimation<R>(animationFactory);
+        }
+
+        return glideAnimation;
+    }
+
+    private static class ConcreteAnimationFactory implements ViewAnimation.AnimationFactory {
+        private final Animation animation;
+
+        public ConcreteAnimationFactory(Animation animation) {
+            this.animation = animation;
+        }
+
+        @Override
+        public Animation build() {
+            return animation;
+        }
+    }
+
+    private static class ResourceAnimationFactory implements ViewAnimation.AnimationFactory {
+        private final Context context;
+        private final int animationId;
+
+        public ResourceAnimationFactory(Context context, int animationId) {
+            this.context = context.getApplicationContext();
+            this.animationId = animationId;
+        }
+
+        @Override
+        public Animation build() {
+            return AnimationUtils.loadAnimation(context, animationId);
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/request/animation/ViewPropertyAnimation.java b/library/src/main/java/com/bumptech/glide/request/animation/ViewPropertyAnimation.java
new file mode 100644
index 0000000..6b42e81
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/animation/ViewPropertyAnimation.java
@@ -0,0 +1,57 @@
+package com.bumptech.glide.request.animation;
+
+import android.view.View;
+
+/**
+ * A {@link com.bumptech.glide.request.animation.GlideAnimation GlideAnimation} that accepts an interface
+ * that can apply an animation like a {@link android.view.ViewPropertyAnimator}
+ * or a {@link android.animation.ObjectAnimator} to an {@link View}.
+ *
+ * @param <R> The type of the resource displayed in the view that is animated
+ */
+public class ViewPropertyAnimation<R> implements GlideAnimation<R> {
+
+    private final Animator animator;
+
+    /**
+     * Constructor for a view property animation that takes an
+     * {@link com.bumptech.glide.request.animation.ViewPropertyAnimation.Animator} interface that can apply an animation
+     * to a view.
+     *
+     * @param animator The animator to use.
+     */
+    public ViewPropertyAnimation(Animator animator) {
+        this.animator = animator;
+    }
+
+    /**
+     * Always applies the {@link com.bumptech.glide.request.animation.ViewPropertyAnimation.Animator} given in the
+     * constructor to the given view and returns {@code false} because the animator cannot set the new resource on
+     * the view.
+     *
+     * @param current {@inheritDoc}
+     * @param adapter {@inheritDoc}
+     * @return {@inheritDoc}
+     */
+    @Override
+    public boolean animate(R current, ViewAdapter adapter) {
+        final View view = adapter.getView();
+        if (view != null) {
+            animator.animate(adapter.getView());
+        }
+        return false;
+    }
+
+    /**
+     * An interface that allows an animation to be applied on or started from an {@link android.view.View}.
+     */
+    public interface Animator {
+        /**
+         * Starts an animation on the given {@link android.view.View}.
+         *
+         * @param view The view to animate.
+         */
+        void animate(View view);
+    }
+
+}
diff --git a/library/src/main/java/com/bumptech/glide/request/animation/ViewPropertyAnimationFactory.java b/library/src/main/java/com/bumptech/glide/request/animation/ViewPropertyAnimationFactory.java
new file mode 100644
index 0000000..f8c5002
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/animation/ViewPropertyAnimationFactory.java
@@ -0,0 +1,34 @@
+package com.bumptech.glide.request.animation;
+
+/**
+ * A {@link GlideAnimationFactory} that produces ViewPropertyAnimations.
+ *
+ * @param <R> The type of the resource displayed in the view that is animated
+ */
+public class ViewPropertyAnimationFactory<R> implements GlideAnimationFactory<R> {
+    private final ViewPropertyAnimation.Animator animator;
+    private ViewPropertyAnimation<R> animation;
+
+    public ViewPropertyAnimationFactory(ViewPropertyAnimation.Animator animator) {
+        this.animator = animator;
+    }
+
+    /**
+     * Returns a new {@link GlideAnimation} for the given arguments. If
+     * isMemoryCache is {@code true} or isFirstImage is {@code false}, returns a
+     * {@link NoAnimation} and otherwise returns a new
+     * {@link ViewPropertyAnimation} for the
+     * {@link com.bumptech.glide.request.animation.ViewPropertyAnimation.Animator} provided in the constructor.
+     */
+    @Override
+    public GlideAnimation<R> build(boolean isFromMemoryCache, boolean isFirstResource) {
+        if (isFromMemoryCache || !isFirstResource) {
+            return NoAnimation.get();
+        }
+        if (animation == null) {
+            animation = new ViewPropertyAnimation<R>(animator);
+        }
+
+        return animation;
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/request/target/BaseTarget.java b/library/src/main/java/com/bumptech/glide/request/target/BaseTarget.java
index 3aac94e..37169ec 100644
--- a/library/src/main/java/com/bumptech/glide/request/target/BaseTarget.java
+++ b/library/src/main/java/com/bumptech/glide/request/target/BaseTarget.java
@@ -1,37 +1,92 @@
 package com.bumptech.glide.request.target;
 
-import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
-import android.view.View;
-import com.bumptech.glide.Glide;
+
 import com.bumptech.glide.request.Request;
 
 /**
- * A base {@link Target} for loading {@link Bitmap}s that provides basic or empty implementations for most methods.
+ * A base {@link Target} for loading {@link com.bumptech.glide.load.engine.Resource}s that provides basic or empty
+ * implementations for most methods.
  *
  * <p>
- *     For maximum efficiency, clear this target when you have finished using or displaying the {@link Bitmap} loaded
- *     into it using {@link Glide#clear(Target)}.
+ *     For maximum efficiency, clear this target when you have finished using or displaying the
+ *     {@link com.bumptech.glide.load.engine.Resource} loaded into it using
+ *     {@link com.bumptech.glide.Glide#clear(Target)}.
  * </p>
  *
  * <p>
- *     For loading {@link Bitmap}s into {@link View}s, {@link ViewTarget} is preferable to this class.
+ *     For loading {@link com.bumptech.glide.load.engine.Resource}s into {@link android.view.View}s,
+ *     {@link com.bumptech.glide.request.target.ViewTarget} or {@link com.bumptech.glide.request.target.ImageViewTarget}
+ *     are preferable.
  * </p>
+ *
+ * @param <Z> The type of resource that will be received by this target.
  */
 public abstract class BaseTarget<Z> implements Target<Z> {
 
     private Request request;
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public void setRequest(Request request) {
         this.request = request;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public Request getRequest() {
         return request;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public void setPlaceholder(Drawable placeholder) { }
+    public void onLoadCleared(Drawable placeholder) {
+        // Do nothing.
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onLoadStarted(Drawable placeholder) {
+        // Do nothing.
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onLoadFailed(Exception e, Drawable errorDrawable) {
+        // Do nothing.
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onStart() {
+        // Do nothing.
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onStop() {
+        // Do nothing.
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onDestroy() {
+        // Do nothing.
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/request/target/BitmapImageViewTarget.java b/library/src/main/java/com/bumptech/glide/request/target/BitmapImageViewTarget.java
index 6eb5ad7..4d24b09 100644
--- a/library/src/main/java/com/bumptech/glide/request/target/BitmapImageViewTarget.java
+++ b/library/src/main/java/com/bumptech/glide/request/target/BitmapImageViewTarget.java
@@ -1,30 +1,27 @@
 package com.bumptech.glide.request.target;
 
 import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
 import android.widget.ImageView;
-import com.bumptech.glide.request.GlideAnimation;
 
 /**
- * A target wrapping an ImageView. Obtains the runtime dimensions of the ImageView.
+ * A {@link com.bumptech.glide.request.target.Target} that can display an {@link android.graphics.Bitmap} in an
+ * {@link android.widget.ImageView}.
+ *
+ * @see GlideDrawableImageViewTarget
  */
-public class BitmapImageViewTarget extends ViewTarget<ImageView, Bitmap> {
-    private final ImageView view;
-
+public class BitmapImageViewTarget extends ImageViewTarget<Bitmap> {
     public BitmapImageViewTarget(ImageView view) {
         super(view);
-        this.view = view;
     }
 
+    /**
+     * Sets the {@link android.graphics.Bitmap} on the view using
+     * {@link android.widget.ImageView#setImageBitmap(android.graphics.Bitmap)}.
+     *
+     * @param resource The bitmap to display.
+     */
     @Override
-    public void onResourceReady(Bitmap resource, GlideAnimation<Bitmap> glideAnimation) {
-        if (glideAnimation == null || !glideAnimation.animate(view.getDrawable(), resource, view, this)) {
-            view.setImageBitmap(resource);
-        }
-    }
-
-    @Override
-    public void setPlaceholder(Drawable placeholder) {
-        view.setImageDrawable(placeholder);
+    protected void setResource(Bitmap resource) {
+        view.setImageBitmap(resource);
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/request/target/DrawableImageViewTarget.java b/library/src/main/java/com/bumptech/glide/request/target/DrawableImageViewTarget.java
index 8fbd0e4..7298303 100644
--- a/library/src/main/java/com/bumptech/glide/request/target/DrawableImageViewTarget.java
+++ b/library/src/main/java/com/bumptech/glide/request/target/DrawableImageViewTarget.java
@@ -2,39 +2,17 @@
 
 import android.graphics.drawable.Drawable;
 import android.widget.ImageView;
-import com.bumptech.glide.request.GlideAnimation;
 
-public class DrawableImageViewTarget extends ViewTarget<ImageView, Drawable> {
-    private static final float SQUARE_RATIO_MARGIN = 0.05f;
-    private final ImageView view;
-
+/**
+ * A target for display {@link Drawable} objects in {@link ImageView}s.
+ */
+public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {
     public DrawableImageViewTarget(ImageView view) {
         super(view);
-        this.view = view;
     }
 
     @Override
-    public void onResourceReady(Drawable resource, GlideAnimation<Drawable> animation) {
-
-        //TODO: Try to generalize this to other sizes/shapes.
-        // This is a dirty hack that tries to make loading square thumbnails and then square full images less costly by
-        // forcing both the smaller thumb and the larger version to have exactly the same intrinsic dimensions. If a
-        // drawable is replaced in an ImageView by another drawable with different intrinsic dimensions, the ImageView
-        // requests a layout. Scrolling rapidly while replacing thumbs with larger images triggers lots of these calls
-        // and causes significant amounts of jank.
-        float viewRatio = view.getWidth() / (float) view.getHeight();
-        float drawableRatio = resource.getIntrinsicWidth() / (float) resource.getIntrinsicHeight();
-        if (Math.abs(viewRatio - 1f) <= SQUARE_RATIO_MARGIN && Math.abs(drawableRatio - 1f) <= SQUARE_RATIO_MARGIN) {
-            resource = new SquaringDrawable(resource, view.getWidth());
-        }
-
-        if (animation == null || !animation.animate(view.getDrawable(), resource, view, this)) {
-            view.setImageDrawable(resource);
-        }
-    }
-
-    @Override
-    public void setPlaceholder(Drawable placeholder) {
-        view.setImageDrawable(placeholder);
+    protected void setResource(Drawable resource) {
+       view.setImageDrawable(resource);
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/request/target/GlideDrawableImageViewTarget.java b/library/src/main/java/com/bumptech/glide/request/target/GlideDrawableImageViewTarget.java
new file mode 100644
index 0000000..93a0bcf
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/target/GlideDrawableImageViewTarget.java
@@ -0,0 +1,96 @@
+package com.bumptech.glide.request.target;
+
+import android.widget.ImageView;
+
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+import com.bumptech.glide.request.animation.GlideAnimation;
+
+/**
+ * A {@link com.bumptech.glide.request.target.Target} that can display an {@link android.graphics.drawable.Drawable} in
+ * an {@link android.widget.ImageView}.
+ */
+public class GlideDrawableImageViewTarget extends ImageViewTarget<GlideDrawable> {
+    private static final float SQUARE_RATIO_MARGIN = 0.05f;
+    private int maxLoopCount;
+    private GlideDrawable resource;
+
+    /**
+     * Constructor for an {@link com.bumptech.glide.request.target.Target} that can display an
+     * {@link com.bumptech.glide.load.resource.drawable.GlideDrawable} in an {@link android.widget.ImageView}.
+     *
+     * @param view The view to display the drawable in.
+     */
+    public GlideDrawableImageViewTarget(ImageView view) {
+        this(view, GlideDrawable.LOOP_FOREVER);
+    }
+
+    /**
+     * Constructor for an {@link com.bumptech.glide.request.target.Target} that can display an
+     * {@link com.bumptech.glide.load.resource.drawable.GlideDrawable} in an {@link android.widget.ImageView}.
+     *
+     * @param view The view to display the drawable in.
+     * @param maxLoopCount A value to pass to to {@link com.bumptech.glide.load.resource.drawable.GlideDrawable}s
+     *                     indicating how many times they should repeat their animation (if they have one). See
+     *                     {@link com.bumptech.glide.load.resource.drawable.GlideDrawable#setLoopCount(int)}.
+     */
+    public GlideDrawableImageViewTarget(ImageView view, int maxLoopCount) {
+        super(view);
+        this.maxLoopCount = maxLoopCount;
+    }
+
+    /**
+     * {@inheritDoc}
+     * If no {@link com.bumptech.glide.request.animation.GlideAnimation} is given or if the animation does not set the
+     * {@link android.graphics.drawable.Drawable} on the view, the drawable is set using
+     * {@link android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
+     *
+     * @param resource {@inheritDoc}
+     * @param animation {@inheritDoc}
+     */
+    @Override
+    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
+        if (!resource.isAnimated()) {
+            //TODO: Try to generalize this to other sizes/shapes.
+            // This is a dirty hack that tries to make loading square thumbnails and then square full images less costly
+            // by forcing both the smaller thumb and the larger version to have exactly the same intrinsic dimensions.
+            // If a drawable is replaced in an ImageView by another drawable with different intrinsic dimensions,
+            // the ImageView requests a layout. Scrolling rapidly while replacing thumbs with larger images triggers
+            // lots of these calls and causes significant amounts of jank.
+            float viewRatio = view.getWidth() / (float) view.getHeight();
+            float drawableRatio = resource.getIntrinsicWidth() / (float) resource.getIntrinsicHeight();
+            if (Math.abs(viewRatio - 1f) <= SQUARE_RATIO_MARGIN
+                    && Math.abs(drawableRatio - 1f) <= SQUARE_RATIO_MARGIN) {
+                resource = new SquaringDrawable(resource, view.getWidth());
+            }
+        }
+        super.onResourceReady(resource, animation);
+        this.resource = resource;
+        resource.setLoopCount(maxLoopCount);
+        resource.start();
+    }
+
+    /**
+     * Sets the drawable on the view using
+     * {@link android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
+     *
+     * @param resource The {@link android.graphics.drawable.Drawable} to display in the view.
+     */
+    @Override
+    protected void setResource(GlideDrawable resource) {
+        view.setImageDrawable(resource);
+    }
+
+    @Override
+    public void onStart() {
+        if (resource != null) {
+            resource.start();
+        }
+    }
+
+    @Override
+    public void onStop() {
+        if (resource != null) {
+            resource.stop();
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/request/target/ImageViewTarget.java b/library/src/main/java/com/bumptech/glide/request/target/ImageViewTarget.java
new file mode 100644
index 0000000..dbc0db2
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/target/ImageViewTarget.java
@@ -0,0 +1,83 @@
+package com.bumptech.glide.request.target;
+
+import android.graphics.drawable.Drawable;
+import android.widget.ImageView;
+
+import com.bumptech.glide.request.animation.GlideAnimation;
+
+/**
+ * A base {@link com.bumptech.glide.request.target.Target} for displaying resources in
+ * {@link android.widget.ImageView}s.
+ *
+ * @param <Z> The type of resource that this target will display in the wrapped {@link android.widget.ImageView}.
+ */
+public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z> implements GlideAnimation.ViewAdapter {
+
+    public ImageViewTarget(ImageView view) {
+        super(view);
+    }
+
+    /**
+     * Returns the current {@link android.graphics.drawable.Drawable} being displayed in the view using
+     * {@link android.widget.ImageView#getDrawable()}.
+     */
+    @Override
+    public Drawable getCurrentDrawable() {
+        return view.getDrawable();
+    }
+
+    /**
+     * Sets the given {@link android.graphics.drawable.Drawable} on the view using
+     * {@link android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
+     *
+     * @param drawable {@inheritDoc}
+     */
+    @Override
+    public void setDrawable(Drawable drawable) {
+        view.setImageDrawable(drawable);
+    }
+
+    /**
+     * Sets the given {@link android.graphics.drawable.Drawable} on the view using
+     * {@link android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
+     *
+     * @param placeholder {@inheritDoc}
+     */
+    @Override
+    public void onLoadStarted(Drawable placeholder) {
+        view.setImageDrawable(placeholder);
+    }
+
+    /**
+     * Sets the given {@link android.graphics.drawable.Drawable} on the view using
+     * {@link android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
+     *
+     * @param errorDrawable {@inheritDoc}
+     */
+    @Override
+    public void onLoadFailed(Exception e, Drawable errorDrawable) {
+        view.setImageDrawable(errorDrawable);
+    }
+
+    /**
+     * Sets the given {@link android.graphics.drawable.Drawable} on the view using
+     * {@link android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
+     *
+     * @param placeholder {@inheritDoc}
+     */
+    @Override
+    public void onLoadCleared(Drawable placeholder) {
+        view.setImageDrawable(placeholder);
+    }
+
+    @Override
+    public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) {
+        if (glideAnimation == null || !glideAnimation.animate(resource, this)) {
+            setResource(resource);
+        }
+    }
+
+    protected abstract void setResource(Z resource);
+
+}
+
diff --git a/library/src/main/java/com/bumptech/glide/request/target/ImageViewTargetFactory.java b/library/src/main/java/com/bumptech/glide/request/target/ImageViewTargetFactory.java
index cb185df..97e84f3 100644
--- a/library/src/main/java/com/bumptech/glide/request/target/ImageViewTargetFactory.java
+++ b/library/src/main/java/com/bumptech/glide/request/target/ImageViewTargetFactory.java
@@ -4,16 +4,25 @@
 import android.graphics.drawable.Drawable;
 import android.widget.ImageView;
 
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+
+/**
+ * A factory responsible for producing the correct type of {@link com.bumptech.glide.request.target.Target} for a given
+ * {@link android.view.View} subclass.
+ */
 public class ImageViewTargetFactory {
 
     @SuppressWarnings("unchecked")
     public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
-        if (Bitmap.class.equals(clazz)) {
+        if (GlideDrawable.class.isAssignableFrom(clazz)) {
+            return (Target<Z>) new GlideDrawableImageViewTarget(view);
+        } else if (Bitmap.class.equals(clazz)) {
             return (Target<Z>) new BitmapImageViewTarget(view);
         } else if (Drawable.class.isAssignableFrom(clazz)) {
             return (Target<Z>) new DrawableImageViewTarget(view);
         } else {
-            throw new IllegalArgumentException("Unhandled class: " + clazz);
+            throw new IllegalArgumentException("Unhandled class: " + clazz
+                    + ", try .as*(Class).transcode(ResourceTranscoder)");
         }
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/request/target/PreloadTarget.java b/library/src/main/java/com/bumptech/glide/request/target/PreloadTarget.java
index 66917dd..a8a9b5a 100644
--- a/library/src/main/java/com/bumptech/glide/request/target/PreloadTarget.java
+++ b/library/src/main/java/com/bumptech/glide/request/target/PreloadTarget.java
@@ -1,42 +1,33 @@
 package com.bumptech.glide.request.target;
 
-import android.graphics.drawable.Drawable;
 import com.bumptech.glide.Glide;
-import com.bumptech.glide.request.GlideAnimation;
-import com.bumptech.glide.request.Request;
+import com.bumptech.glide.request.animation.GlideAnimation;
 
-public class PreloadTarget implements Target {
-    private Request request;
-    private int width;
-    private int height;
+/**
+ * A one time use {@link com.bumptech.glide.request.target.Target} class that loads a resource into memory and then
+ * clears itself.
+ *
+ * @param <Z> The type of resource that will be loaded into memory.
+ */
+public final class PreloadTarget<Z> extends SimpleTarget<Z> {
 
-    public PreloadTarget(int width, int height) {
-        this.width = width;
-        this.height = height;
+    /**
+     * Returns a PreloadTarget.
+     *
+     * @param width The width in pixels of the desired resource.
+     * @param height The height in pixels of the desired resource.
+     * @param <Z> The type of the desired resource.
+     */
+    public static <Z> PreloadTarget<Z> obtain(int width, int height) {
+        return new PreloadTarget<Z>(width, height);
+    }
+
+    private PreloadTarget(int width, int height) {
+        super(width, height);
     }
 
     @Override
-    public void onResourceReady(Object resource, GlideAnimation glideAnimation) {
+    public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) {
         Glide.clear(this);
     }
-
-    @Override
-    public void setPlaceholder(Drawable placeholder) {
-
-    }
-
-    @Override
-    public void getSize(SizeReadyCallback cb) {
-        cb.onSizeReady(width, height);
-    }
-
-    @Override
-    public void setRequest(Request request) {
-        this.request = request;
-    }
-
-    @Override
-    public Request getRequest() {
-        return request;
-    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/request/target/SimpleTarget.java b/library/src/main/java/com/bumptech/glide/request/target/SimpleTarget.java
index b3e2e8b..cdbc9a4 100644
--- a/library/src/main/java/com/bumptech/glide/request/target/SimpleTarget.java
+++ b/library/src/main/java/com/bumptech/glide/request/target/SimpleTarget.java
@@ -1,21 +1,68 @@
 package com.bumptech.glide.request.target;
 
 /**
- * A simpler interface for targets with default (usually noop) implementations of non essential methods that allows the
- * caller to specify an exact width/height.
+ * A simple {@link com.bumptech.glide.request.target.Target} base class with default (usually noop) implementations
+ * of non essential methods that allows the caller to specify an exact width/height. Typicaly use cases look something
+ * like this:
+ * <pre>
+ * <code>
+ * Glide.load("http://somefakeurl.com/fakeImage.jpeg")
+ *      .asBitmap()
+ *      .fitCenter()
+ *      .into(new SimpleTarget<Bitmap>(250, 250) {
+ *
+ *          {@literal @Override}
+ *          public void onResourceReady(Bitmap resource, GlideAnimation<Bitmap> glideAnimation) {
+ *              // Do something with bitmap here.
+ *          }
+ *
+ *      });
+ * }
+ * </code>
+ * </pre>
+ *
+ * @param <Z> The type of resource that this target will receive.
  */
-@SuppressWarnings("unused")
 public abstract class SimpleTarget<Z> extends BaseTarget<Z> {
     private final int width;
     private final int height;
 
+    /**
+     * Constructor for the target that assumes you will have called
+     * {@link com.bumptech.glide.GenericRequestBuilder#override(int, int)} on the request builder this target is given
+     * to.
+     *
+     * <p>
+     *     Requests that load into this target will throw an {@link java.lang.IllegalArgumentException} if
+     *     {@link com.bumptech.glide.GenericRequestBuilder#override(int, int)} was not called on the request builder.
+     * </p>
+     */
+    public SimpleTarget() {
+        this(-1, -1);
+    }
+
+    /**
+     * Constructor for the target that takes the desired dimensions of the decoded and/or transformed resource.
+     *
+     * @param width The width in pixels of the desired resource.
+     * @param height The height in pixels of the desired resource.
+     */
     public SimpleTarget(int width, int height) {
         this.width = width;
         this.height = height;
     }
 
+    /**
+     * Immediately calls the given callback with the sizes given in the constructor.
+     *
+     * @param cb {@inheritDoc}
+     */
     @Override
     public final void getSize(SizeReadyCallback cb) {
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException("Width and height must both be > 0, but given width: " + width + " and"
+                    + " height: " + height + ", either provide dimensions in the constructor or call override()");
+        }
         cb.onSizeReady(width, height);
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/request/target/SizeReadyCallback.java b/library/src/main/java/com/bumptech/glide/request/target/SizeReadyCallback.java
new file mode 100644
index 0000000..22b4118
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/request/target/SizeReadyCallback.java
@@ -0,0 +1,15 @@
+package com.bumptech.glide.request.target;
+
+/**
+ * A callback that must be called when the target has determined its size. For fixed size targets it can
+ * be called synchronously.
+ */
+public interface SizeReadyCallback {
+    /**
+     * A callback called on the main thread.
+     *
+     * @param width The width in pixels of the target.
+     * @param height The height in pixels of the target.
+     */
+    void onSizeReady(int width, int height);
+}
diff --git a/library/src/main/java/com/bumptech/glide/request/target/SquaringDrawable.java b/library/src/main/java/com/bumptech/glide/request/target/SquaringDrawable.java
index b0c8b60..d19bba0 100644
--- a/library/src/main/java/com/bumptech/glide/request/target/SquaringDrawable.java
+++ b/library/src/main/java/com/bumptech/glide/request/target/SquaringDrawable.java
@@ -6,6 +6,9 @@
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
+
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
 
 /**
  * A wrapper drawable to square the wrapped drawable so that it expands to fill a square with exactly the given side
@@ -13,11 +16,11 @@
  * they will be displayed in to avoid a costly requestLayout call. This class should not be used with views or drawables
  * that are not square.
  */
-public class SquaringDrawable extends Drawable {
-    private final Drawable wrapped;
-    private int side;
+public class SquaringDrawable extends GlideDrawable {
+    private final GlideDrawable wrapped;
+    private final int side;
 
-    public SquaringDrawable(Drawable wrapped, int side) {
+    public SquaringDrawable(GlideDrawable wrapped, int side) {
         this.wrapped = wrapped;
         this.side = side;
     }
@@ -33,6 +36,8 @@
         super.setBounds(bounds);
         wrapped.setBounds(bounds);
     }
+
+    @Override
     public void setChangingConfigurations(int configs) {
         wrapped.setChangingConfigurations(configs);
     }
@@ -52,13 +57,13 @@
         wrapped.setFilterBitmap(filter);
     }
 
-    @TargetApi(11)
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
     @Override
     public Callback getCallback() {
         return wrapped.getCallback();
     }
 
-    @TargetApi(19)
+    @TargetApi(Build.VERSION_CODES.KITKAT)
     @Override
     public int getAlpha() {
         return wrapped.getAlpha();
@@ -111,19 +116,19 @@
 
     @Override
     public void invalidateSelf() {
-        super.invalidateSelf();    //To change body of overridden methods use File | Settings | File Templates.
+        super.invalidateSelf();
         wrapped.invalidateSelf();
     }
 
     @Override
     public void unscheduleSelf(Runnable what) {
-        super.unscheduleSelf(what);    //To change body of overridden methods use File | Settings | File Templates.
+        super.unscheduleSelf(what);
         wrapped.unscheduleSelf(what);
     }
 
     @Override
     public void scheduleSelf(Runnable what, long when) {
-        super.scheduleSelf(what, when);    //To change body of overridden methods use File | Settings | File Templates.
+        super.scheduleSelf(what, when);
         wrapped.scheduleSelf(what, when);
     }
 
@@ -146,4 +151,29 @@
     public int getOpacity() {
         return wrapped.getOpacity();
     }
+
+    @Override
+    public boolean isAnimated() {
+        return wrapped.isAnimated();
+    }
+
+    @Override
+    public void setLoopCount(int loopCount) {
+        wrapped.setLoopCount(loopCount);
+    }
+
+    @Override
+    public void start() {
+        wrapped.start();
+    }
+
+    @Override
+    public void stop() {
+        wrapped.stop();
+    }
+
+    @Override
+    public boolean isRunning() {
+        return wrapped.isRunning();
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/request/target/Target.java b/library/src/main/java/com/bumptech/glide/request/target/Target.java
index 5ea9d82..4613206 100644
--- a/library/src/main/java/com/bumptech/glide/request/target/Target.java
+++ b/library/src/main/java/com/bumptech/glide/request/target/Target.java
@@ -1,45 +1,92 @@
 package com.bumptech.glide.request.target;
 
 import android.graphics.drawable.Drawable;
-import com.bumptech.glide.request.GlideAnimation;
+
+import com.bumptech.glide.manager.LifecycleListener;
 import com.bumptech.glide.request.Request;
+import com.bumptech.glide.request.animation.GlideAnimation;
 
 /**
- * An interface that Glide can load an image into
+ * An interface that Glide can load a resource into and notify of relevant lifecycle events during a load.
+ *
+ * <p>
+ *     The lifecycle events in this class are as follows:
+ *     <ul>
+ *         <li>onLoadStarted</li>
+ *         <li>onResourceReady</li>
+ *         <li>onLoadCleared</li>
+ *         <li>onLoadFailed</li>
+ *     </ul>
+ *
+ *     The typical lifecycle is onLoadStarted -> onResourceReady or onLoadFailed -> onLoadCleared. However, there are no
+ *     guarantees. onLoadStarted may not be called if the resource is in memory or if the load will fail because of a
+ *     null model object. onLoadCleared similarly may never be called if the target is never cleared. See the docs for
+ *     the individual methods for details.
+ * </p>
  *
  * @param <R> The type of resource the target can display.
  */
-public interface Target<R> {
+public interface Target<R> extends LifecycleListener {
 
     /**
-     * A callback that must be called when the target has determined its size. For fixed size targets it can
-     * be called synchronously.
+     * A lifecycle callback that is called when a load is started.
+     *
+     * <p>
+     *     Note - This may not be called for every load, it is possible for example for loads to fail before the load
+     *     starts (when the model object is null).
+     * </p>
+     *
+     * <p>
+     *     Note - This method may be called multiple times before any other lifecycle method is called. Loads can be
+     *     paused and restarted due to lifecycle or connectivity events and each restart may cause a call here.
+     * </p>
+     *
+     * @param placeholder The placeholder drawable to optionally show, or null.
      */
-    public interface SizeReadyCallback {
-        public void onSizeReady(int width, int height);
-    }
+    void onLoadStarted(Drawable placeholder);
 
     /**
-     * The method that will be called when the image load has finished
+     * A lifecycle callback that is called when a load fails.
+     *
+     * <p>
+     *     Note - This may be called before {@link #onLoadStarted(android.graphics.drawable.Drawable)} if the model
+     *     object is null.
+     * </p>
+     *
+     * @param e The exception causing the load to fail, or null if no exception occurred (usually because a decoder
+     *          simply returned null).
+     * @param errorDrawable The error drawable to optionally show, or null.
+     */
+    void onLoadFailed(Exception e, Drawable errorDrawable);
+
+    /**
+     * The method that will be called when the resource load has finished.
+     *
      * @param resource the loaded resource.
      */
-    public void onResourceReady(R resource, GlideAnimation<R> glideAnimation);
+    void onResourceReady(R resource, GlideAnimation<? super R> glideAnimation);
 
     /**
-     * A method that can optionally be implemented to set any placeholder that might have been passed to Glide to
-     * display either while an image is loading or after the load has failed.
+     * A lifecycle callback that is called when a load is cancelled and its resources are freed.
      *
-     * @param placeholder The drawable to display
+     * @param placeholder The placeholder drawable to optionally show, or null.
      */
-    public void setPlaceholder(Drawable placeholder);
+    void onLoadCleared(Drawable placeholder);
 
     /**
-     * A method to retrieve the size of this target
+     * A method to retrieve the size of this target.
+     *
      * @param cb The callback that must be called when the size of the target has been determined
      */
-    public void getSize(SizeReadyCallback cb);
+    void getSize(SizeReadyCallback cb);
 
-    public void setRequest(Request request);
+    /**
+     * Sets the current request for this target to retain, should not be called outside of Glide.
+     */
+    void setRequest(Request request);
 
-    public Request getRequest();
+    /**
+     * Retrieves the current request for this target, should not be called outside of Glide.
+     */
+    Request getRequest();
 }
diff --git a/library/src/main/java/com/bumptech/glide/request/target/ViewTarget.java b/library/src/main/java/com/bumptech/glide/request/target/ViewTarget.java
index f06e884..dcf7f5d 100644
--- a/library/src/main/java/com/bumptech/glide/request/target/ViewTarget.java
+++ b/library/src/main/java/com/bumptech/glide/request/target/ViewTarget.java
@@ -1,28 +1,28 @@
 package com.bumptech.glide.request.target;
 
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.util.Log;
 import android.view.Display;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
-import android.widget.ListView;
+
 import com.bumptech.glide.request.Request;
 
 import java.lang.ref.WeakReference;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
- * A base {@link Target} for loading {@link Bitmap}s into {@link View}s that provides default implementations for most
- * most methods and can determine the size of views using a {@link ViewTreeObserver.OnGlobalLayoutListener}.
+ * A base {@link Target} for loading {@link android.graphics.Bitmap}s into {@link View}s that provides default
+ * implementations for most most methods and can determine the size of views using a
+ * {@link android.view.ViewTreeObserver.OnDrawListener}.
  *
  * <p>
- *     To detect {@link View} reuse in {@link ListView} or any {@link ViewGroup} that reuses views, this class uses the
- *     {@link View#setTag(Object)} method to store some metadata so that if a view is reused, any previous loads or
- *     resources from previous loads can be cancelled or reused.
+ *     To detect {@link View} reuse in {@link android.widget.ListView} or any {@link ViewGroup} that reuses views, this
+ *     class uses the {@link View#setTag(Object)} method to store some metadata so that if a view is reused, any
+ *     previous loads or resources from previous loads can be cancelled or reused.
  * </p>
  *
  * <p>
@@ -32,31 +32,65 @@
  * </p>
  *
  * @param <T> The specific subclass of view wrapped by this target.
+ * @param <Z> The resource type this target will receive.
  */
-public abstract class ViewTarget<T extends View, Z> implements Target<Z> {
+public abstract class ViewTarget<T extends View, Z> extends BaseTarget<Z> {
     private static final String TAG = "ViewTarget";
-    private final T view;
+
+    protected final T view;
     private final SizeDeterminer sizeDeterminer;
 
     public ViewTarget(T view) {
+        if (view == null) {
+            throw new NullPointerException("View must not be null!");
+        }
         this.view = view;
         sizeDeterminer = new SizeDeterminer(view);
     }
 
+    /**
+     * Returns the wrapped {@link android.view.View}.
+     */
     public T getView() {
         return view;
     }
 
+    /**
+     * Determines the size of the view by first checking {@link android.view.View#getWidth()} and
+     * {@link android.view.View#getHeight()}. If one or both are zero, it then checks the view's
+     * {@link android.view.ViewGroup.LayoutParams}. If one or both of the params width and height are less than or
+     * equal to zero, it then adds an {@link android.view.ViewTreeObserver.OnPreDrawListener} which waits until the view
+     * has been measured before calling the callback with the view's drawn width and height.
+     *
+     * @param cb {@inheritDoc}
+     */
     @Override
     public void getSize(SizeReadyCallback cb) {
         sizeDeterminer.getSize(cb);
     }
 
+    /**
+     * Stores the request using {@link View#setTag(Object)}.
+     *
+     * @param request {@inheritDoc}
+     */
     @Override
     public void setRequest(Request request) {
         view.setTag(request);
     }
 
+    /**
+     * Returns any stored request using {@link android.view.View#getTag()}.
+     *
+     * <p>
+     *     For Glide to function correctly, Glide must be the only thing that calls {@link View#setTag(Object)}. If the
+     *     tag is cleared or set to another object type, Glide will not be able to retrieve and cancel previous loads
+     *     which will not only prevent Glide from reusing resource, but will also result in incorrect images being
+     *     loaded and lots of flashing of images in lists. As a result, this will throw an
+     *     {@link java.lang.IllegalArgumentException} if {@link android.view.View#getTag()}} returns a non null object
+     *     that is not an {@link com.bumptech.glide.request.Request}.
+     * </p>
+     */
     @Override
     public Request getRequest() {
         Object tag = view.getTag();
@@ -78,7 +112,7 @@
 
     private static class SizeDeterminer {
         private final View view;
-        private Set<SizeReadyCallback> cbs = new HashSet<SizeReadyCallback>();
+        private final List<SizeReadyCallback> cbs = new ArrayList<SizeReadyCallback>();
         private SizeDeterminerLayoutListener layoutListener;
 
         public SizeDeterminer(View view) {
@@ -118,6 +152,7 @@
                 if (observer.isAlive()) {
                     observer.removeOnPreDrawListener(layoutListener);
                 }
+                layoutListener = null;
             }
         }
 
@@ -131,19 +166,24 @@
                 WindowManager windowManager =
                         (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE);
                 Display display = windowManager.getDefaultDisplay();
-                final int width = display.getWidth();
-                final int height = display.getHeight();
+                @SuppressWarnings("deprecation") final int width = display.getWidth(), height = display.getHeight();
                 if (Log.isLoggable(TAG, Log.WARN)) {
-                    Log.w(TAG, "Trying to load image into ImageView using WRAP_CONTENT, defaulting to screen" +
-                            " dimensions: [" + width + "x" + height + "]. Give the view an actual width and height " +
-                            " for better performance.");
+                    Log.w(TAG, "Trying to load image into ImageView using WRAP_CONTENT, defaulting to screen"
+                            + " dimensions: [" + width + "x" + height + "]. Give the view an actual width and height "
+                            + " for better performance.");
                 }
-                cb.onSizeReady(display.getWidth(), display.getHeight());
+                cb.onSizeReady(width, height);
             } else {
-                cbs.add(cb);
-                final ViewTreeObserver observer = view.getViewTreeObserver();
-                layoutListener = new SizeDeterminerLayoutListener(this);
-                observer.addOnPreDrawListener(layoutListener);
+                // We want to notify callbacks in the order they were added and we only expect one or two callbacks to
+                // be added a time, so a List is a reasonable choice.
+                if (!cbs.contains(cb)) {
+                    cbs.add(cb);
+                }
+                if (layoutListener == null) {
+                    final ViewTreeObserver observer = view.getViewTreeObserver();
+                    layoutListener = new SizeDeterminerLayoutListener(this);
+                    observer.addOnPreDrawListener(layoutListener);
+                }
             }
         }
 
@@ -159,7 +199,7 @@
 
         private boolean isLayoutParamsSizeValid() {
             final ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
-            return layoutParams != null && (layoutParams.width > 0 && layoutParams.height > 0);
+            return layoutParams != null && layoutParams.width > 0 && layoutParams.height > 0;
         }
 
         private static class SizeDeterminerLayoutListener implements ViewTreeObserver.OnPreDrawListener {
diff --git a/library/src/main/java/com/bumptech/glide/signature/ApplicationVersionSignature.java b/library/src/main/java/com/bumptech/glide/signature/ApplicationVersionSignature.java
new file mode 100644
index 0000000..1ea2ca1
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/signature/ApplicationVersionSignature.java
@@ -0,0 +1,63 @@
+package com.bumptech.glide.signature;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+import com.bumptech.glide.load.Key;
+
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A utility class for obtaining a {@link com.bumptech.glide.load.Key} signature containing the application version
+ * name using {@link android.content.pm.PackageInfo#versionCode}.
+ */
+public final class ApplicationVersionSignature {
+    private static final ConcurrentHashMap<String, Key> PACKAGE_NAME_TO_KEY = new ConcurrentHashMap<String, Key>();
+
+    /**
+     * Returns the signature {@link com.bumptech.glide.load.Key} for version code of the Application of the given
+     * Context.
+     */
+    public static Key obtain(Context context) {
+        String packageName = context.getPackageName();
+        Key result = PACKAGE_NAME_TO_KEY.get(packageName);
+        if (result == null) {
+            Key toAdd = obtainVersionSignature(context);
+            result = PACKAGE_NAME_TO_KEY.putIfAbsent(packageName, toAdd);
+            // There wasn't a previous mapping, so toAdd is now the Key.
+            if (result == null) {
+                result = toAdd;
+            }
+        }
+
+        return result;
+    }
+
+    // Visible for testing.
+    static void reset() {
+        PACKAGE_NAME_TO_KEY.clear();
+    }
+
+    private static Key obtainVersionSignature(Context context) {
+        PackageInfo pInfo = null;
+        try {
+            pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            // Should never happen.
+            e.printStackTrace();
+        }
+        final String versionCode;
+        if (pInfo != null) {
+            versionCode = String.valueOf(pInfo.versionCode);
+        } else {
+            versionCode = UUID.randomUUID().toString();
+        }
+        return new StringSignature(versionCode);
+    }
+
+    private ApplicationVersionSignature() {
+        // Empty for visibility.
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/signature/EmptySignature.java b/library/src/main/java/com/bumptech/glide/signature/EmptySignature.java
new file mode 100644
index 0000000..c46c914
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/signature/EmptySignature.java
@@ -0,0 +1,26 @@
+package com.bumptech.glide.signature;
+
+import com.bumptech.glide.load.Key;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+
+/**
+ * An empty key that is always equal to all other empty keys.
+ */
+public final class EmptySignature implements Key {
+    private static final EmptySignature EMPTY_KEY = new EmptySignature();
+
+    public static EmptySignature obtain() {
+        return EMPTY_KEY;
+    }
+
+    private EmptySignature() {
+        // Empty.
+    }
+
+    @Override
+    public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException {
+        // Do nothing.
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/signature/MediaStoreSignature.java b/library/src/main/java/com/bumptech/glide/signature/MediaStoreSignature.java
new file mode 100644
index 0000000..dbeb876
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/signature/MediaStoreSignature.java
@@ -0,0 +1,77 @@
+package com.bumptech.glide.signature;
+
+import com.bumptech.glide.load.Key;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+
+/**
+ * A unique signature based on metadata data from the media store that detects common changes to media store files like
+ * edits, rotations, and temporary file replacement.
+ */
+public class MediaStoreSignature implements Key {
+    private final String mimeType;
+    private final long dateModified;
+    private final int orientation;
+
+    /**
+     * Constructor for {@link com.bumptech.glide.signature.MediaStoreSignature}.
+     *
+     * @param mimeType The mime type of the media store media. Ok to default to empty string "". See
+     *      {@link android.provider.MediaStore.Images.ImageColumns#MIME_TYPE} or
+     *      {@link android.provider.MediaStore.Video.VideoColumns#MIME_TYPE}.
+     * @param dateModified The date modified time of the media store media. Ok to default to 0. See
+     *      {@link android.provider.MediaStore.Images.ImageColumns#DATE_MODIFIED} or
+     *      {@link android.provider.MediaStore.Video.VideoColumns#DATE_MODIFIED}.
+     * @param orientation The orientation of the media store media. Ok to default to 0. See
+     *      {@link android.provider.MediaStore.Images.ImageColumns#ORIENTATION}.
+     */
+    public MediaStoreSignature(String mimeType, long dateModified, int orientation) {
+        this.mimeType = mimeType;
+        this.dateModified = dateModified;
+        this.orientation = orientation;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        MediaStoreSignature that = (MediaStoreSignature) o;
+
+        if (dateModified != that.dateModified) {
+            return false;
+        }
+        if (orientation != that.orientation) {
+            return false;
+        }
+        if (mimeType != null ? !mimeType.equals(that.mimeType) : that.mimeType != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mimeType != null ? mimeType.hashCode() : 0;
+        result = 31 * result + (int) (dateModified ^ (dateModified >>> 32));
+        result = 31 * result + orientation;
+        return result;
+    }
+
+    @Override
+    public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException {
+        byte[] data = ByteBuffer.allocate(12)
+                .putLong(dateModified)
+                .putInt(orientation)
+                .array();
+        messageDigest.update(data);
+        messageDigest.update(mimeType.getBytes(STRING_CHARSET_NAME));
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/signature/StringSignature.java b/library/src/main/java/com/bumptech/glide/signature/StringSignature.java
new file mode 100644
index 0000000..a5fa2c9
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/signature/StringSignature.java
@@ -0,0 +1,44 @@
+package com.bumptech.glide.signature;
+
+import com.bumptech.glide.load.Key;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+
+/**
+ * A unique Signature that wraps a String.
+ */
+public class StringSignature implements Key {
+    private final String signature;
+
+    public StringSignature(String signature) {
+        if (signature == null) {
+            throw new NullPointerException("Signature cannot be null!");
+        }
+        this.signature = signature;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        StringSignature that = (StringSignature) o;
+
+        return signature.equals(that.signature);
+    }
+
+    @Override
+    public int hashCode() {
+        return signature.hashCode();
+    }
+
+    @Override
+    public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException {
+        messageDigest.update(signature.getBytes(STRING_CHARSET_NAME));
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/util/ByteArrayPool.java b/library/src/main/java/com/bumptech/glide/util/ByteArrayPool.java
index 54a5742..f89d5ff 100644
--- a/library/src/main/java/com/bumptech/glide/util/ByteArrayPool.java
+++ b/library/src/main/java/com/bumptech/glide/util/ByteArrayPool.java
@@ -2,32 +2,44 @@
 
 import android.util.Log;
 
-import java.util.LinkedList;
 import java.util.Queue;
 
-public class ByteArrayPool {
+/**
+ * A pool for reusing byte arrays that produces and contains byte arrays of a fixed size.
+ */
+public final class ByteArrayPool {
     private static final String TAG = "ByteArrayPool";
     // 64 KB.
     private static final int TEMP_BYTES_SIZE = 64 * 1024;
     // 512 KB.
-    private static final int MAX_SIZE = 512 * 1024;
+    private static final int MAX_SIZE = 2 * 1048 * 1024;
     private static final int MAX_BYTE_ARRAY_COUNT = MAX_SIZE / TEMP_BYTES_SIZE;
 
-    private final Queue<byte[]> tempQueue = new LinkedList<byte[]>();
+    private final Queue<byte[]> tempQueue = Util.createQueue(0);
     private static final ByteArrayPool BYTE_ARRAY_POOL = new ByteArrayPool();
 
+    /**
+     * Returns a constant singleton byte array pool.
+     */
     public static ByteArrayPool get() {
         return BYTE_ARRAY_POOL;
     }
 
     private ByteArrayPool() {  }
 
+    /**
+     * Removes all byte arrays from the pool.
+     */
     public void clear() {
         synchronized (tempQueue) {
             tempQueue.clear();
         }
     }
 
+    /**
+     * Returns a byte array by retrieving one from the pool if the pool is non empty or otherwise by creating a new
+     * byte array.
+     */
     public byte[] getBytes() {
         byte[] result;
         synchronized (tempQueue) {
@@ -42,6 +54,12 @@
         return result;
     }
 
+    /**
+     * Adds the given byte array to the pool if it is the correct size and the pool is not full and returns true if
+     * the byte array was added and false otherwise.
+     *
+     * @param bytes The bytes to try to add to the pool.
+     */
     public boolean releaseBytes(byte[] bytes) {
         if (bytes.length != TEMP_BYTES_SIZE) {
             return false;
diff --git a/library/src/main/java/com/bumptech/glide/util/ExceptionCatchingInputStream.java b/library/src/main/java/com/bumptech/glide/util/ExceptionCatchingInputStream.java
new file mode 100644
index 0000000..344a289
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/util/ExceptionCatchingInputStream.java
@@ -0,0 +1,138 @@
+package com.bumptech.glide.util;
+
+import com.bumptech.glide.load.resource.bitmap.RecyclableBufferedInputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Queue;
+
+/**
+ * An {@link java.io.InputStream} that catches {@link java.io.IOException}s during read and skip calls and stores them
+ * so they can later be handled or thrown. This class is a workaround for a framework issue where exceptions during
+ * reads while decoding bitmaps in {@link android.graphics.BitmapFactory} can return partially decoded bitmaps.
+ *
+ * See https://github.com/bumptech/glide/issues/126.
+ */
+public class ExceptionCatchingInputStream extends InputStream {
+
+    private static final Queue<ExceptionCatchingInputStream> QUEUE = Util.createQueue(0);
+
+    private RecyclableBufferedInputStream wrapped;
+    private IOException exception;
+
+    public static ExceptionCatchingInputStream obtain(RecyclableBufferedInputStream toWrap) {
+        ExceptionCatchingInputStream result;
+        synchronized (QUEUE) {
+            result = QUEUE.poll();
+        }
+        if (result == null) {
+            result = new ExceptionCatchingInputStream();
+        }
+        result.setInputStream(toWrap);
+        return result;
+    }
+
+    // Exposed for testing.
+    static void clearQueue() {
+        while (!QUEUE.isEmpty()) {
+            QUEUE.remove();
+        }
+    }
+
+    ExceptionCatchingInputStream() {
+        // Do nothing.
+    }
+
+    void setInputStream(RecyclableBufferedInputStream toWrap) {
+        wrapped = toWrap;
+    }
+
+    @Override
+    public int available() throws IOException {
+        return wrapped.available();
+    }
+
+    @Override
+    public void close() throws IOException {
+        wrapped.close();
+    }
+
+    @Override
+    public void mark(int readlimit) {
+        wrapped.mark(readlimit);
+    }
+
+    @Override
+    public boolean markSupported() {
+        return wrapped.markSupported();
+    }
+
+    @Override
+    public int read(byte[] buffer) throws IOException {
+        int read;
+        try {
+            read = wrapped.read(buffer);
+        } catch (IOException e) {
+            exception = e;
+            read = -1;
+        }
+        return read;
+    }
+
+    @Override
+    public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+        int read;
+        try {
+            read = wrapped.read(buffer, byteOffset, byteCount);
+        } catch (IOException e) {
+            exception = e;
+            read = -1;
+        }
+        return read;
+    }
+
+    @Override
+    public synchronized void reset() throws IOException {
+        wrapped.reset();
+    }
+
+    @Override
+    public long skip(long byteCount) throws IOException {
+        long skipped;
+        try {
+            skipped = wrapped.skip(byteCount);
+        } catch (IOException e) {
+            exception = e;
+            skipped = 0;
+        }
+        return skipped;
+    }
+
+    @Override
+    public int read() throws IOException {
+        int result;
+        try {
+            result = wrapped.read();
+        } catch (IOException e) {
+            exception = e;
+            result = -1;
+        }
+        return result;
+    }
+
+    public void fixMarkLimit() {
+        wrapped.fixMarkLimit();
+    }
+
+    public IOException getException() {
+        return exception;
+    }
+
+    public void release() {
+        exception = null;
+        wrapped = null;
+        synchronized (QUEUE) {
+            QUEUE.offer(this);
+        }
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/util/LogTime.java b/library/src/main/java/com/bumptech/glide/util/LogTime.java
index b24835c..c5eeb20 100644
--- a/library/src/main/java/com/bumptech/glide/util/LogTime.java
+++ b/library/src/main/java/com/bumptech/glide/util/LogTime.java
@@ -4,18 +4,35 @@
 import android.os.Build;
 import android.os.SystemClock;
 
-public class LogTime {
-    private static final double MILLIS_MULTIPLIER = Build.VERSION.SDK_INT >= 17 ? (1d / Math.pow(10, 6)) : 1d;
+/**
+ * A class for logging elapsed real time in millis.
+ */
+public final class LogTime {
+    private static final double MILLIS_MULTIPLIER =
+            Build.VERSION_CODES.JELLY_BEAN_MR1 <= Build.VERSION.SDK_INT ? 1d / Math.pow(10, 6) : 1d;
 
-    @TargetApi(17)
+    private LogTime() {
+        // Utility class.
+    }
+
+    /**
+     * Returns the current time in either millis or nanos depending on the api level to be used with
+     * {@link #getElapsedMillis(long)}.
+     */
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public static long getLogTime() {
-        if (Build.VERSION.SDK_INT >= 17) {
+        if (Build.VERSION_CODES.JELLY_BEAN_MR1 <= Build.VERSION.SDK_INT) {
             return SystemClock.elapsedRealtimeNanos();
         } else {
-            return SystemClock.currentThreadTimeMillis();
+            return System.currentTimeMillis();
         }
     }
 
+    /**
+     * Returns the time elapsed since the given logTime in millis.
+     *
+     * @param logTime The start time of the event.
+     */
     public static double getElapsedMillis(long logTime) {
         return (getLogTime() - logTime) * MILLIS_MULTIPLIER;
     }
diff --git a/library/src/main/java/com/bumptech/glide/util/LruCache.java b/library/src/main/java/com/bumptech/glide/util/LruCache.java
index 0fa2d19..fdc25d7 100644
--- a/library/src/main/java/com/bumptech/glide/util/LruCache.java
+++ b/library/src/main/java/com/bumptech/glide/util/LruCache.java
@@ -16,11 +16,23 @@
     private final int initialMaxSize;
     private int currentSize = 0;
 
+    /**
+     * Constructor for LruCache.
+     *
+     * @param size The maximum size of the cache, the units must match the units used in {@link #getSize(Object)}.
+     */
     public LruCache(int size) {
         this.initialMaxSize = size;
         this.maxSize = size;
     }
 
+    /**
+     * Sets a size multiplier that will be applied to the size provided in the constructor to set the new size of the
+     * cache. If the new size is less than the current size, entries will be evicted until the current size is less
+     * than or equal to the new size.
+     *
+     * @param multiplier The multiplier to apply.
+     */
     public void setSizeMultiplier(float multiplier) {
         if (multiplier < 0) {
             throw new IllegalArgumentException("Multiplier must be >= 0");
@@ -29,39 +41,96 @@
         evict();
     }
 
+    /**
+     * Returns the size of a given item, defaulting to one. The units must match those used in the size passed in to the
+     * constructor. Subclasses can override this method to return sizes in various units, usually bytes.
+     *
+     * @param item The item to get the size of.
+     */
     protected int getSize(Y item) {
         return 1;
     }
 
-    protected void onItemRemoved(T key, Y item) {  }
+    /**
+     * A callback called whenever an item is evicted from the cache. Subclasses can override.
+     *
+     * @param key The key of the evicted item.
+     * @param item The evicted item.
+     */
+    protected void onItemEvicted(T key, Y item) {
+        // optional override
+    }
 
+    /**
+     * Returns the current maximum size of the cache in bytes.
+     */
+    public int getMaxSize() {
+        return maxSize;
+    }
+
+    /**
+     * Returns the sum of the sizes of all items in the cache.
+     */
     public int getCurrentSize() {
         return currentSize;
     }
 
+    /**
+     * Returns true if there is a value for the given key in the cache.
+     *
+     * @param key The key to check.
+     */
+
     public boolean contains(T key) {
         return cache.containsKey(key);
     }
 
+    /**
+     * Returns the item in the cache for the given key or null if no such item exists.
+     *
+     * @param key The key to check.
+     */
     public Y get(T key) {
         return cache.get(key);
     }
 
+    /**
+     * Adds the given item to the cache with the given key and returns any previous entry for the given key that may
+     * have already been in the cache.
+     *
+     * <p>
+     *     If the size of the item is larger than the total cache size, the item will not be added to the cache and
+     *     instead {@link #onItemEvicted(Object, Object)} will be called synchronously with the given key and item.
+     * </p>
+     *
+     * @param key The key to add the item at.
+     * @param item The item to add.
+     */
     public Y put(T key, Y item) {
         final int itemSize = getSize(item);
         if (itemSize >= maxSize) {
-            onItemRemoved(key, item);
+            onItemEvicted(key, item);
             return null;
         }
 
         final Y result = cache.put(key, item);
-        if (result != item) {
+        if (item != null) {
             currentSize += getSize(item);
-            evict();
         }
+        if (result != null) {
+            // TODO: should we call onItemEvicted here?
+            currentSize -= getSize(result);
+        }
+        evict();
+
         return result;
     }
 
+    /**
+     * Removes the item at the given key and returns the removed item if present, and null otherwise.
+     *
+     * @param key The key to remove the item at.
+     */
     public Y remove(T key) {
         final Y value = cache.remove(key);
         if (value != null) {
@@ -70,10 +139,18 @@
         return value;
     }
 
+    /**
+     * Clears all items in the cache.
+     */
     public void clearMemory() {
         trimToSize(0);
     }
 
+    /**
+     * Removes the least recently used items from the cache until the current size is less than the given size.
+     *
+     * @param size The size the cache should be less than.
+     */
     protected void trimToSize(int size) {
         Map.Entry<T, Y> last;
         while (currentSize > size) {
@@ -82,7 +159,7 @@
             currentSize -= getSize(toRemove);
             final T key = last.getKey();
             cache.remove(key);
-            onItemRemoved(key, toRemove);
+            onItemEvicted(key, toRemove);
         }
     }
 
diff --git a/library/src/main/java/com/bumptech/glide/util/MultiClassKey.java b/library/src/main/java/com/bumptech/glide/util/MultiClassKey.java
new file mode 100644
index 0000000..42c5eb7
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/util/MultiClassKey.java
@@ -0,0 +1,58 @@
+package com.bumptech.glide.util;
+
+/**
+ * A key of two {@link Class}es to be used in hashed collections.
+ */
+public class MultiClassKey {
+    private Class<?> first;
+    private Class<?> second;
+
+    public MultiClassKey() {
+        // leave them null
+    }
+
+    public MultiClassKey(Class<?> first, Class<?> second) {
+        set(first, second);
+    }
+
+    public void set(Class<?> first, Class<?> second) {
+        this.first = first;
+        this.second = second;
+    }
+
+    @Override
+    public String toString() {
+        return "MultiClassKey{"
+                + "first=" + first
+                + ", second=" + second
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        MultiClassKey that = (MultiClassKey) o;
+
+        if (!first.equals(that.first)) {
+            return false;
+        }
+        if (!second.equals(that.second)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = first.hashCode();
+        result = 31 * result + second.hashCode();
+        return result;
+    }
+}
diff --git a/library/src/main/java/com/bumptech/glide/util/Util.java b/library/src/main/java/com/bumptech/glide/util/Util.java
index c83b3d6..cc0743f 100644
--- a/library/src/main/java/com/bumptech/glide/util/Util.java
+++ b/library/src/main/java/com/bumptech/glide/util/Util.java
@@ -3,37 +3,146 @@
 import android.annotation.TargetApi;
 import android.graphics.Bitmap;
 import android.os.Build;
+import android.os.Looper;
 
-public class Util {
-    private static final char[] hexArray = "0123456789abcdef".toCharArray();
-    private static final char[] sha256Chars = new char[64]; //32 bytes from sha-256 -> 64 hex chars
+import java.util.ArrayDeque;
+import java.util.Queue;
 
+/**
+ * A collection of assorted utility classes.
+ */
+public final class Util {
+    private static final char[] HEX_CHAR_ARRAY = "0123456789abcdef".toCharArray();
+    // 32 bytes from sha-256 -> 64 hex chars.
+    private static final char[] SHA_256_CHARS = new char[64];
+    // 20 bytes from sha-1 -> 40 chars.
+    private static final char[] SHA_1_CHARS = new char[40];
+
+    private Util() {
+        // Utility class.
+    }
+
+    /**
+     * Returns the hex string of the given byte array representing a SHA256 hash.
+     */
     public static String sha256BytesToHex(byte[] bytes) {
-        return bytesToHex(bytes, sha256Chars);
+        return bytesToHex(bytes, SHA_256_CHARS);
+    }
+
+    /**
+     * Returns the hex string of the given byte array representing a SHA1 hash.
+     */
+    public static String sha1BytesToHex(byte[] bytes) {
+        return bytesToHex(bytes, SHA_1_CHARS);
     }
 
     // Taken from:
     // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java/9655275#9655275
     private static String bytesToHex(byte[] bytes, char[] hexChars) {
         int v;
-        for ( int j = 0; j < bytes.length; j++ ) {
+        for (int j = 0; j < bytes.length; j++) {
             v = bytes[j] & 0xFF;
-            hexChars[j * 2] = hexArray[v >>> 4];
-            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+            hexChars[j * 2] = HEX_CHAR_ARRAY[v >>> 4];
+            hexChars[j * 2 + 1] = HEX_CHAR_ARRAY[v & 0x0F];
         }
         return new String(hexChars);
     }
 
     /**
-     * Returns the in memory size of the given {@link Bitmap}.
+     * Returns the allocated byte size of the given bitmap.
+     *
+     * @see #getBitmapByteSize(android.graphics.Bitmap)
+     *
+     * @deprecated Use {@link #getBitmapByteSize(android.graphics.Bitmap)} instead. Scheduled to be removed in Glide
+     * 4.0.
      */
-    @TargetApi(19)
+    @Deprecated
     public static int getSize(Bitmap bitmap) {
-        if (Build.VERSION.SDK_INT >= 19) {
-            return bitmap.getAllocationByteCount();
-        } else {
-            return bitmap.getHeight() * bitmap.getRowBytes();
+        return getBitmapByteSize(bitmap);
+    }
+
+    /**
+     * Returns the in memory size of the given {@link Bitmap} in bytes.
+     */
+    @TargetApi(Build.VERSION_CODES.KITKAT)
+    public static int getBitmapByteSize(Bitmap bitmap) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            // Workaround for KitKat initial release NPE in Bitmap, fixed in MR1. See issue #148.
+            try {
+                return bitmap.getAllocationByteCount();
+            } catch (NullPointerException e) {
+                // Do nothing.
+            }
+        }
+        return bitmap.getHeight() * bitmap.getRowBytes();
+    }
+
+    /**
+     * Returns the in memory size of {@link android.graphics.Bitmap} with the given width, height, and
+     * {@link android.graphics.Bitmap.Config}.
+     */
+    public static int getBitmapByteSize(int width, int height, Bitmap.Config config) {
+        return width * height * getBytesPerPixel(config);
+    }
+
+    private static int getBytesPerPixel(Bitmap.Config config) {
+        // A bitmap by decoding a gif has null "config" in certain environments.
+        if (config == null) {
+            config = Bitmap.Config.ARGB_8888;
+        }
+
+        int bytesPerPixel;
+        switch (config) {
+            case ALPHA_8:
+                bytesPerPixel = 1;
+                break;
+            case RGB_565:
+            case ARGB_4444:
+                bytesPerPixel = 2;
+                break;
+            case ARGB_8888:
+            default:
+                bytesPerPixel = 4;
+        }
+        return bytesPerPixel;
+    }
+
+    /**
+     * Throws an {@link java.lang.IllegalArgumentException} if called on a thread other than the main thread.
+     */
+    public static void assertMainThread() {
+        if (!isOnMainThread()) {
+            throw new IllegalArgumentException("You must call this method on the main thread");
         }
     }
 
+    /**
+     * Throws an {@link java.lang.IllegalArgumentException} if called on the main thread.
+     */
+    public static void assertBackgroundThread() {
+        if (!isOnBackgroundThread()) {
+            throw new IllegalArgumentException("YOu must call this method on a background thread");
+        }
+    }
+
+    /**
+     * Returns {@code true} if called on the main thread, {@code false} otherwise.
+     */
+    public static boolean isOnMainThread() {
+        return Looper.myLooper() == Looper.getMainLooper();
+    }
+
+    /**
+     * Returns {@code true} if called on the main thread, {@code false} otherwise.
+     */
+    public static boolean isOnBackgroundThread() {
+        return !isOnMainThread();
+    }
+
+    /**
+     * Creates a {@link java.util.Queue} of the given size using Glide's preferred implementation.
+     */
+    public static <T> Queue<T> createQueue(int size) {
+        return new ArrayDeque<T>(size);
+    }
 }
diff --git a/library/src/main/java/com/bumptech/glide/volley/RequestQueueWrapper.java b/library/src/main/java/com/bumptech/glide/volley/RequestQueueWrapper.java
deleted file mode 100644
index 10333b9..0000000
--- a/library/src/main/java/com/bumptech/glide/volley/RequestQueueWrapper.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.bumptech.glide.volley;
-
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.net.http.AndroidHttpClient;
-import android.os.Build;
-import com.android.volley.Cache;
-import com.android.volley.Network;
-import com.android.volley.RequestQueue;
-import com.android.volley.toolbox.BasicNetwork;
-import com.android.volley.toolbox.HttpClientStack;
-import com.android.volley.toolbox.HttpStack;
-import com.android.volley.toolbox.HurlStack;
-import com.android.volley.toolbox.NoCache;
-import com.bumptech.glide.load.engine.cache.DiskCache;
-
-import static android.content.pm.PackageManager.NameNotFoundException;
-
-public class RequestQueueWrapper {
-
-    public static RequestQueue getRequestQueue(Context context) {
-        return getRequestQueue(context, new NoCache());
-    }
-
-    public static RequestQueue getRequestQueue(Context context, DiskCache diskCache) {
-        return getRequestQueue(context, new VolleyDiskCacheWrapper(diskCache));
-    }
-
-    public static RequestQueue getRequestQueue(Context context, Cache diskCache) {
-        String userAgent = "volley/0";
-        try {
-            String packageName = context.getPackageName();
-            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
-            userAgent = packageName + "/" + info.versionCode;
-        } catch (NameNotFoundException e) {
-        }
-
-        final HttpStack stack;
-        if (Build.VERSION.SDK_INT >= 9) {
-            stack = new HurlStack();
-        } else {
-            // Prior to Gingerbread, HttpUrlConnection was unreliable.
-            // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
-            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
-        }
-
-        Network network = new BasicNetwork(stack);
-
-
-        RequestQueue queue = new RequestQueue(diskCache, network);
-        queue.start();
-        return queue;
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/volley/VolleyDiskCacheWrapper.java b/library/src/main/java/com/bumptech/glide/volley/VolleyDiskCacheWrapper.java
deleted file mode 100644
index 6bc3dac..0000000
--- a/library/src/main/java/com/bumptech/glide/volley/VolleyDiskCacheWrapper.java
+++ /dev/null
@@ -1,337 +0,0 @@
-package com.bumptech.glide.volley;
-
-import android.util.Log;
-import com.android.volley.Cache;
-import com.android.volley.toolbox.ByteArrayPool;
-import com.android.volley.toolbox.DiskBasedCache;
-import com.android.volley.toolbox.PoolingByteArrayOutputStream;
-import com.bumptech.glide.load.engine.cache.StringKey;
-import com.bumptech.glide.load.engine.cache.DiskCache;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Closely based on {@link DiskBasedCache}.
- */
-public class VolleyDiskCacheWrapper implements Cache {
-    private static final String TAG = "VolleyDiskCacheWrapper";
-    /** Magic number for current version of cache file format. */
-    private static final int CACHE_MAGIC = 0x20120504;
-    // 2 mb.
-    private static final int BYTE_POOL_SIZE = 2 * 1024 * 1024;
-    // 8 kb.
-    private static final int DEFAULT_BYTE_ARRAY_SIZE = 8 * 1024;
-
-    private final DiskCache diskCache;
-    private final ByteArrayPool byteArrayPool;
-
-    public VolleyDiskCacheWrapper(DiskCache diskCache) {
-        this.diskCache = diskCache;
-        this.byteArrayPool = new ByteArrayPool(BYTE_POOL_SIZE);
-    }
-
-    @Override
-    public Entry get(String key) {
-        InputStream result = diskCache.get(new StringKey(key));
-        if (result == null) {
-            return null;
-        }
-        try {
-            CacheHeader header = readHeader(result);
-            byte[] data = streamToBytes(result);
-            return header.toCacheEntry(data);
-        } catch (IOException e) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                e.printStackTrace();
-            }
-            diskCache.delete(new StringKey(key));
-        } finally {
-            try {
-                result.close();
-            } catch (IOException e) { }
-        }
-        return null;
-    }
-
-    @Override
-    public void put(final String key, final Entry entry) {
-        diskCache.put(new StringKey(key), new DiskCache.Writer() {
-            @Override
-            public boolean write(OutputStream os) {
-                CacheHeader header = new CacheHeader(key, entry);
-                boolean success = header.writeHeader(os);
-                if (success) {
-                    try {
-                        os.write(entry.data);
-                    } catch (IOException e) {
-                        success = false;
-                        if (Log.isLoggable(TAG, Log.DEBUG)) {
-                            Log.d(TAG, "Unable to write data", e);
-                        }
-                    }
-                }
-                return success;
-            }
-        });
-    }
-
-    @Override
-    public void initialize() { }
-
-    @Override
-    public void invalidate(String key, boolean fullExpire) {
-        Entry entry = get(key);
-        if (entry != null) {
-            entry.softTtl = 0;
-            if (fullExpire) {
-                entry.ttl = 0;
-            }
-            put(key, entry);
-        }
-    }
-
-    @Override
-    public void remove(String key) {
-        diskCache.delete(new StringKey(key));
-    }
-
-    @Override
-    public void clear() { }
-
-    /**
-     * Reads the header off of an InputStream and returns a CacheHeader object.
-     * @param is The InputStream to read from.
-     * @throws IOException
-     */
-    public CacheHeader readHeader(InputStream is) throws IOException {
-        CacheHeader entry = new CacheHeader();
-        int magic = readInt(is);
-        if (magic != CACHE_MAGIC) {
-            // don't bother deleting, it'll get pruned eventually
-            throw new IOException();
-        }
-        entry.key = readString(is);
-        entry.etag = readString(is);
-        if (entry.etag.equals("")) {
-            entry.etag = null;
-        }
-        entry.serverDate = readLong(is);
-        entry.ttl = readLong(is);
-        entry.softTtl = readLong(is);
-        entry.responseHeaders = readStringStringMap(is);
-        return entry;
-    }
-
-        /**
-     * Handles holding onto the cache headers for an entry.
-     */
-    // Visible for testing.
-    class CacheHeader {
-        /** The size of the data identified by this CacheHeader. (This is not
-         * serialized to disk. */
-        public long size;
-
-        /** The key that identifies the cache entry. */
-        public String key;
-
-        /** ETag for cache coherence. */
-        public String etag;
-
-        /** Date of this response as reported by the server. */
-        public long serverDate;
-
-        /** TTL for this record. */
-        public long ttl;
-
-        /** Soft TTL for this record. */
-        public long softTtl;
-
-        /** Headers from the response resulting in this cache entry. */
-        public Map<String, String> responseHeaders;
-
-        private CacheHeader() { }
-
-        /**
-         * Instantiates a new CacheHeader object
-         * @param key The key that identifies the cache entry
-         * @param entry The cache entry.
-         */
-        public CacheHeader(String key, Entry entry) {
-            this.key = key;
-            this.size = entry.data.length;
-            this.etag = entry.etag;
-            this.serverDate = entry.serverDate;
-            this.ttl = entry.ttl;
-            this.softTtl = entry.softTtl;
-            this.responseHeaders = entry.responseHeaders;
-        }
-
-        /**
-         * Creates a cache entry for the specified data.
-         */
-        public Entry toCacheEntry(byte[] data) {
-            Entry e = new Entry();
-            e.data = data;
-            e.etag = etag;
-            e.serverDate = serverDate;
-            e.ttl = ttl;
-            e.softTtl = softTtl;
-            e.responseHeaders = responseHeaders;
-            return e;
-        }
-
-        /**
-         * Writes the contents of this CacheHeader to the specified OutputStream.
-         */
-        public boolean writeHeader(OutputStream os) {
-            try {
-                writeInt(os, CACHE_MAGIC);
-                writeString(os, key);
-                writeString(os, etag == null ? "" : etag);
-                writeLong(os, serverDate);
-                writeLong(os, ttl);
-                writeLong(os, softTtl);
-                writeStringStringMap(responseHeaders, os);
-                os.flush();
-                return true;
-            } catch (IOException e) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d("%s", e.toString());
-                }
-                return false;
-            }
-        }
-    }
-
-    /*
-     * Homebrewed simple serialization system used for reading and writing cache
-     * headers on disk. Once upon a time, this used the standard Java
-     * Object{Input,Output}Stream, but the default implementation relies heavily
-     * on reflection (even for standard types) and generates a ton of garbage.
-     */
-
-    /**
-     * Simple wrapper around {@link InputStream#read()} that throws EOFException
-     * instead of returning -1.
-     */
-    private static int read(InputStream is) throws IOException {
-        int b = is.read();
-        if (b == -1) {
-            throw new EOFException();
-        }
-        return b;
-    }
-
-    static void writeInt(OutputStream os, int n) throws IOException {
-        os.write((n >> 0) & 0xff);
-        os.write((n >> 8) & 0xff);
-        os.write((n >> 16) & 0xff);
-        os.write((n >> 24) & 0xff);
-    }
-
-    static int readInt(InputStream is) throws IOException {
-        int n = 0;
-        n |= (read(is) << 0);
-        n |= (read(is) << 8);
-        n |= (read(is) << 16);
-        n |= (read(is) << 24);
-        return n;
-    }
-
-    static void writeLong(OutputStream os, long n) throws IOException {
-        os.write((byte)(n >>> 0));
-        os.write((byte)(n >>> 8));
-        os.write((byte)(n >>> 16));
-        os.write((byte)(n >>> 24));
-        os.write((byte)(n >>> 32));
-        os.write((byte)(n >>> 40));
-        os.write((byte)(n >>> 48));
-        os.write((byte)(n >>> 56));
-    }
-
-    static long readLong(InputStream is) throws IOException {
-        long n = 0;
-        n |= ((read(is) & 0xFFL) << 0);
-        n |= ((read(is) & 0xFFL) << 8);
-        n |= ((read(is) & 0xFFL) << 16);
-        n |= ((read(is) & 0xFFL) << 24);
-        n |= ((read(is) & 0xFFL) << 32);
-        n |= ((read(is) & 0xFFL) << 40);
-        n |= ((read(is) & 0xFFL) << 48);
-        n |= ((read(is) & 0xFFL) << 56);
-        return n;
-    }
-
-    static void writeString(OutputStream os, String s) throws IOException {
-        byte[] b = s.getBytes("UTF-8");
-        writeLong(os, b.length);
-        os.write(b, 0, b.length);
-    }
-
-    String readString(InputStream is) throws IOException {
-        int n = (int) readLong(is);
-        byte[] b = streamToBytes(is, n, byteArrayPool.getBuf(n));
-        String result = new String(b, "UTF-8");
-        byteArrayPool.returnBuf(b);
-        return result;
-    }
-
-    static void writeStringStringMap(Map<String, String> map, OutputStream os) throws IOException {
-        if (map != null) {
-            writeInt(os, map.size());
-            for (Map.Entry<String, String> entry : map.entrySet()) {
-                writeString(os, entry.getKey());
-                writeString(os, entry.getValue());
-            }
-        } else {
-            writeInt(os, 0);
-        }
-    }
-
-    Map<String, String> readStringStringMap(InputStream is) throws IOException {
-        int size = readInt(is);
-        Map<String, String> result = (size == 0)
-                ? Collections.<String, String>emptyMap()
-                : new HashMap<String, String>(size);
-        for (int i = 0; i < size; i++) {
-            String key = readString(is).intern();
-            String value = readString(is).intern();
-            result.put(key, value);
-        }
-        return result;
-    }
-
-    /**
-     * Reads the contents of an InputStream into a byte[].
-     */
-    private static byte[] streamToBytes(InputStream in, int length, byte[] bytes) throws IOException {
-        int count;
-        int pos = 0;
-        while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) {
-            pos += count;
-        }
-        if (pos != length) {
-            throw new IOException("Expected " + length + " bytes, read " + pos + " bytes");
-        }
-        return bytes;
-    }
-
-    private byte[] streamToBytes(InputStream in) throws IOException {
-        PoolingByteArrayOutputStream outputStream = new PoolingByteArrayOutputStream(byteArrayPool);
-        byte[] bytes = byteArrayPool.getBuf(DEFAULT_BYTE_ARRAY_SIZE);
-        int pos = 0;
-        while ((in.read(bytes, pos, bytes.length - pos)) != -1) {
-            outputStream.write(bytes);
-        }
-        byteArrayPool.returnBuf(bytes);
-        byte[] result = outputStream.toByteArray();
-        outputStream.close();
-        return result;
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/volley/VolleyRequestFuture.java b/library/src/main/java/com/bumptech/glide/volley/VolleyRequestFuture.java
deleted file mode 100644
index 460364b..0000000
--- a/library/src/main/java/com/bumptech/glide/volley/VolleyRequestFuture.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.bumptech.glide.volley;
-
-import com.android.volley.Request;
-import com.android.volley.Response;
-import com.android.volley.VolleyError;
-
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * TODO: contribute cancel modifications to volley and remove this class.
- *
- * A Future that represents a Volley request.
- *
- * Used by providing as your response and error listeners. For example:
- * <pre>
- * RequestFuture&lt;JSONObject&gt; future = RequestFuture.newFuture();
- * MyRequest request = new MyRequest(URL, future, future);
- *
- * // If you want to be able to cancel the request:
- * future.setRequest(requestQueue.add(request));
- *
- * // Otherwise:
- * requestQueue.add(request);
- *
- * try {
- *   JSONObject response = future.get();
- *   // do something with response
- * } catch (InterruptedException e) {
- *   // handle the error
- * } catch (ExecutionException e) {
- *   // handle the error
- * }
- * </pre>
- *
- * @param <T> The type of parsed response this future expects.
- */
-public class VolleyRequestFuture<T> implements Future<T>, Response.Listener<T>,
-       Response.ErrorListener {
-    private Request<?> mRequest;
-    private boolean mResultReceived = false;
-    private T mResult;
-    private VolleyError mException;
-    private boolean mIsCancelled = false;
-
-    public static <E> VolleyRequestFuture<E> newFuture() {
-        return new VolleyRequestFuture<E>();
-    }
-
-    public VolleyRequestFuture() {}
-
-    public synchronized void setRequest(Request<?> request) {
-        mRequest = request;
-        if (mIsCancelled && mRequest != null) {
-            mRequest.cancel();
-        }
-    }
-
-    @Override
-    public synchronized boolean cancel(boolean mayInterruptIfRunning) {
-        if (isDone()) {
-            return false;
-        }
-        mIsCancelled = true;
-        if (mRequest != null) {
-            mRequest.cancel();
-        }
-        notifyAll();
-
-        return true;
-    }
-
-    @Override
-    public T get() throws InterruptedException, ExecutionException {
-        try {
-            return doGet(null);
-        } catch (TimeoutException e) {
-            throw new AssertionError(e);
-        }
-    }
-
-    @Override
-    public T get(long timeout, TimeUnit unit)
-            throws InterruptedException, ExecutionException, TimeoutException {
-        return doGet(TimeUnit.MILLISECONDS.convert(timeout, unit));
-    }
-
-    private synchronized T doGet(Long timeoutMs)
-            throws InterruptedException, ExecutionException, TimeoutException {
-        if (mException != null) {
-            throw new ExecutionException(mException);
-        }
-
-        if (mResultReceived) {
-            return mResult;
-        }
-
-        if (isCancelled()) {
-            throw new CancellationException();
-        }
-
-        if (timeoutMs == null) {
-            wait(0);
-        } else if (timeoutMs > 0) {
-            wait(timeoutMs);
-        }
-
-        if (mException != null) {
-            throw new ExecutionException(mException);
-        }
-
-        if (isCancelled()) {
-            throw new CancellationException();
-        }
-
-        if (!mResultReceived) {
-            throw new TimeoutException();
-        }
-
-        return mResult;
-    }
-
-    @Override
-    public boolean isCancelled() {
-        return mIsCancelled;
-    }
-
-    @Override
-    public synchronized boolean isDone() {
-        return mResultReceived || mException != null || isCancelled();
-    }
-
-    @Override
-    public synchronized void onResponse(T response) {
-        mResultReceived = true;
-        mResult = response;
-        notifyAll();
-    }
-
-    @Override
-    public synchronized void onErrorResponse(VolleyError error) {
-        mException = error;
-        notifyAll();
-    }
-}
-
diff --git a/library/src/main/java/com/bumptech/glide/volley/VolleyStreamFetcher.java b/library/src/main/java/com/bumptech/glide/volley/VolleyStreamFetcher.java
deleted file mode 100644
index 76e2bbc..0000000
--- a/library/src/main/java/com/bumptech/glide/volley/VolleyStreamFetcher.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.bumptech.glide.volley;
-
-import com.android.volley.NetworkResponse;
-import com.android.volley.Request;
-import com.android.volley.RequestQueue;
-import com.android.volley.Response;
-import com.android.volley.toolbox.HttpHeaderParser;
-import com.bumptech.glide.Priority;
-import com.bumptech.glide.load.data.DataFetcher;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-
-/**
- * A DataFetcher backed by volley for fetching images via http.
- */
-public class VolleyStreamFetcher implements DataFetcher<InputStream> {
-    private final RequestQueue requestQueue;
-    private final String url;
-    private VolleyRequestFuture<InputStream> requestFuture;
-
-    @SuppressWarnings("unused")
-    public VolleyStreamFetcher(RequestQueue requestQueue, String url) {
-        this(requestQueue, url,  null);
-    }
-
-    public VolleyStreamFetcher(RequestQueue requestQueue, String url, VolleyRequestFuture<InputStream> requestFuture) {
-        this.requestQueue = requestQueue;
-        this.url = url;
-        this.requestFuture = requestFuture;
-        if (requestFuture == null) {
-            this.requestFuture = VolleyRequestFuture.newFuture();
-        }
-    }
-
-    @Override
-    public InputStream loadData(Priority priority) throws Exception {
-        GlideRequest request = new GlideRequest(url, requestFuture, glideToVolleyPriority(priority));
-
-        requestFuture.setRequest(requestQueue.add(request));
-
-        return requestFuture.get();
-    }
-
-    @Override
-    public void cleanup() {
-        // Do nothing.
-    }
-
-    @Override
-    public String getId() {
-        return url;
-    }
-
-    @Override
-    public void cancel() {
-        VolleyRequestFuture<InputStream> localFuture = requestFuture;
-        if (localFuture != null) {
-            localFuture.cancel(true);
-        }
-    }
-
-    private static Request.Priority glideToVolleyPriority(Priority priority) {
-        switch (priority) {
-            case LOW:
-                return Request.Priority.LOW;
-            case HIGH:
-                return Request.Priority.HIGH;
-            case IMMEDIATE:
-                return Request.Priority.IMMEDIATE;
-            default:
-                return Request.Priority.NORMAL;
-
-        }
-    }
-
-    private static class GlideRequest extends Request<byte[]> {
-        private final VolleyRequestFuture<InputStream> future;
-        private Priority priority;
-
-        public GlideRequest(String url, VolleyRequestFuture<InputStream> future, Priority priority) {
-            super(Method.GET, url, future);
-            this.future = future;
-            this.priority = priority;
-        }
-
-        @Override
-        public Priority getPriority() {
-            return priority;
-        }
-
-        @Override
-        protected Response<byte[]> parseNetworkResponse(NetworkResponse response) {
-            return Response.success(response.data, HttpHeaderParser.parseCacheHeaders(response));
-        }
-
-        @Override
-        protected void deliverResponse(byte[] response) {
-            future.onResponse(new ByteArrayInputStream(response));
-        }
-    }
-}
diff --git a/library/src/main/java/com/bumptech/glide/volley/VolleyUrlLoader.java b/library/src/main/java/com/bumptech/glide/volley/VolleyUrlLoader.java
deleted file mode 100644
index 43705a6..0000000
--- a/library/src/main/java/com/bumptech/glide/volley/VolleyUrlLoader.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.bumptech.glide.volley;
-
-import android.content.Context;
-import com.android.volley.RequestQueue;
-import com.bumptech.glide.load.model.GenericLoaderFactory;
-import com.bumptech.glide.load.model.GlideUrl;
-import com.bumptech.glide.load.model.ModelLoader;
-import com.bumptech.glide.load.model.ModelLoaderFactory;
-import com.bumptech.glide.load.data.DataFetcher;
-
-import java.io.InputStream;
-
-/**
- *  A simple model loader for fetching images for a given url
- */
-public class VolleyUrlLoader implements ModelLoader<GlideUrl, InputStream> {
-
-    public interface FutureFactory {
-        public VolleyRequestFuture<InputStream> build();
-    }
-
-    public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
-        private final FutureFactory futureFactory;
-        private RequestQueue requestQueue;
-
-        public Factory(RequestQueue requestQueue) {
-            this(requestQueue, new DefaultFutureFactory());
-        }
-
-        public Factory(RequestQueue requestQueue, FutureFactory futureFactory) {
-            this.requestQueue = requestQueue;
-            this.futureFactory = futureFactory;
-        }
-
-        @Override
-        public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
-            return new VolleyUrlLoader(requestQueue, futureFactory);
-        }
-
-        @Override
-        public void teardown() { }
-    }
-
-    private final RequestQueue requestQueue;
-    private final FutureFactory futureFactory;
-
-    public VolleyUrlLoader(RequestQueue requestQueue, FutureFactory futureFactory) {
-        this.requestQueue = requestQueue;
-        this.futureFactory = futureFactory;
-    }
-
-    @Override
-    public DataFetcher<InputStream> getResourceFetcher(GlideUrl url, int width, int height) {
-        return new VolleyStreamFetcher(requestQueue, url.toString(), futureFactory.build());
-    }
-
-    private static class DefaultFutureFactory implements FutureFactory {
-        @Override
-        public VolleyRequestFuture<InputStream> build() {
-            return VolleyRequestFuture.newFuture();
-        }
-    }
-
-}
diff --git a/library/tests/AndroidManifest.xml b/library/tests/AndroidManifest.xml
deleted file mode 100644
index a7c7317..0000000
--- a/library/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- package name must be unique so suffix with "tests" so package loader doesn't ignore us -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.bumptech.glide.tests"
-          android:versionCode="1"
-          android:versionName="1.0">
-  <uses-sdk android:minSdkVersion="8" />
-  <!-- We add an application tag here just so that we can indicate that
-       this package needs to link against the android.test library,
-       which is needed when building test cases. -->
-  <application>
-    <uses-library android:name="android.test.runner"/>
-  </application>
-  <!--
-  This declares that this application uses the instrumentation test runner targeting
-  the package of com.bumptech.glide.  To run the tests use the command:
-  "adb shell am instrument -w com.bumptech.glide.tests/android.test.InstrumentationTestRunner"
-  -->
-  <instrumentation android:name="android.test.InstrumentationTestRunner"
-                   android:targetPackage="com.bumptech.glide"
-                   android:label="Tests for com.bumptech.glide"/>
-</manifest>
diff --git a/library/tests/build.xml b/library/tests/build.xml
deleted file mode 100644
index 49f85a4..0000000
--- a/library/tests/build.xml
+++ /dev/null
@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project name="tests" default="help">
-
-  <!-- The local.properties file is created and updated by the 'android' tool.
-       It contains the path to the SDK. It should *NOT* be checked into
-       Version Control Systems. -->
-  <property file="local.properties"/>
-
-  <!-- The ant.properties file can be created by you. It is only edited by the
-       'android' tool to add properties to it.
-       This is the place to change some Ant specific build properties.
-       Here are some properties you may want to change/update:
-
-       source.dir
-           The name of the source directory. Default is 'src'.
-       out.dir
-           The name of the output directory. Default is 'bin'.
-
-       For other overridable properties, look at the beginning of the rules
-       files in the SDK, at tools/ant/build.xml
-
-       Properties related to the SDK location or the project target should
-       be updated using the 'android' tool with the 'update' action.
-
-       This file is an integral part of the build system for your
-       application and should be checked into Version Control Systems.
-
-       -->
-  <property file="ant.properties"/>
-
-  <!-- if sdk.dir was not set from one of the property file, then
-       get it from the ANDROID_HOME env var.
-       This must be done before we load project.properties since
-       the proguard config can use sdk.dir -->
-  <property environment="env"/>
-  <condition property="sdk.dir" value="${env.ANDROID_HOME}">
-    <isset property="env.ANDROID_HOME"/>
-  </condition>
-
-  <!-- The project.properties file is created and updated by the 'android'
-       tool, as well as ADT.
-
-       This contains project specific properties such as project target, and library
-       dependencies. Lower level build properties are stored in ant.properties
-       (or in .classpath for Eclipse projects).
-
-       This file is an integral part of the build system for your
-       application and should be checked into Version Control Systems. -->
-  <loadproperties srcFile="project.properties"/>
-
-  <!-- quick check on sdk.dir -->
-  <fail
-    message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
-    unless="sdk.dir"
-    />
-
-  <!--
-      Import per project custom build rules if present at the root of the project.
-      This is the place to put custom intermediary targets such as:
-          -pre-build
-          -pre-compile
-          -post-compile (This is typically used for code obfuscation.
-                         Compiled code location: ${out.classes.absolute.dir}
-                         If this is not done in place, override ${out.dex.input.absolute.dir})
-          -post-package
-          -post-build
-          -pre-clean
-  -->
-  <import file="custom_rules.xml" optional="true"/>
-
-  <!-- Import the actual build file.
-
-       To customize existing targets, there are two options:
-       - Customize only one target:
-           - copy/paste the target into this file, *before* the
-             <import> task.
-           - customize it to your needs.
-       - Customize the whole content of build.xml
-           - copy/paste the content of the rules files (minus the top node)
-             into this file, replacing the <import> task.
-           - customize to your needs.
-
-       ***********************
-       ****** IMPORTANT ******
-       ***********************
-       In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
-       in order to avoid having your file be overridden by tools such as "android update project"
-  -->
-  <!-- version-tag: 1 -->
-  <import file="${sdk.dir}/tools/ant/build.xml"/>
-
-</project>
diff --git a/library/tests/project.properties b/library/tests/project.properties
deleted file mode 100644
index ed6ad53..0000000
--- a/library/tests/project.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Project target.
-target=android-19
-
diff --git a/library/tests/src/com/bumptech/glide/load/engine/bitmap_recycle/SizeStrategyTest.java b/library/tests/src/com/bumptech/glide/load/engine/bitmap_recycle/SizeStrategyTest.java
deleted file mode 100644
index f695f64..0000000
--- a/library/tests/src/com/bumptech/glide/load/engine/bitmap_recycle/SizeStrategyTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.bumptech.glide.load.engine.bitmap_recycle;
-
-import android.graphics.Bitmap;
-import android.test.AndroidTestCase;
-
-public class SizeStrategyTest extends AndroidTestCase {
-    private SizeStrategy strategy;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        strategy = new SizeStrategy();
-    }
-
-    public void testIGetNullIfNoMatchingBitmapExists() {
-        assertNull(strategy.get(100, 100, Bitmap.Config.ARGB_8888));
-    }
-
-    public void testICanAddAndGetABitmapOfTheSameSizeAndDimensions() {
-        Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-        strategy.put(bitmap);
-        assertEquals(bitmap, strategy.get(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888));
-    }
-
-    public void testICanAddAndGetABitmapOfDifferentConfigsButSameSize() {
-        Bitmap original = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888);
-        strategy.put(original);
-        assertEquals(original, strategy.get(800, 400, Bitmap.Config.RGB_565));
-    }
-
-    public void testICanAddAndGetABitmapOfDifferentDimensionsButSameSize() {
-        Bitmap original = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888);
-        strategy.put(original);
-        assertEquals(original, strategy.get(200, 800, Bitmap.Config.ARGB_8888));
-    }
-
-    public void testICanGetABitmapUpToFourTimesLarger() {
-        Bitmap original = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888);
-        strategy.put(original);
-        assertEquals(original, strategy.get(200, 200, Bitmap.Config.ARGB_8888));
-    }
-
-    public void testICantGetABitmapMoreThanFourTimesLarger() {
-        Bitmap original = Bitmap.createBitmap(401, 401, Bitmap.Config.ARGB_8888);
-        strategy.put(original);
-        assertNull(strategy.get(200, 200, Bitmap.Config.ARGB_8888));
-    }
-
-    public void testICantGetASmallerBitmap() {
-        Bitmap original = Bitmap.createBitmap(99, 99, Bitmap.Config.ARGB_8888);
-        strategy.put(original);
-        assertNull(strategy.get(100, 100, Bitmap.Config.ARGB_8888));
-    }
-
-    public void testReturnedDimensionsMatchIfSizeDoesNotMatch() {
-        Bitmap original = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-        strategy.put(original);
-        Bitmap result = strategy.get(99, 99, Bitmap.Config.ARGB_8888);
-        assertEquals(99, result.getWidth());
-        assertEquals(99, result.getHeight());
-    }
-
-    public void testReturnedConfigMatchesIfSizeDoesNotMatch() {
-        Bitmap original = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-        strategy.put(original);
-        Bitmap result = strategy.get(100, 100, Bitmap.Config.RGB_565);
-        assertEquals(Bitmap.Config.RGB_565, result.getConfig());
-    }
-
-    public void testSmallestMatchingSizeIsReturned() {
-        Bitmap smallest = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-        Bitmap medium = Bitmap.createBitmap(120, 120, Bitmap.Config.ARGB_8888);
-        Bitmap large = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);
-
-        strategy.put(large);
-        strategy.put(smallest);
-        strategy.put(medium);
-
-        assertEquals(smallest, strategy.get(99, 99, Bitmap.Config.ARGB_8888));
-    }
-
-    // This ensures that our sizes are incremented and decremented appropriately so we don't think we have more bitmaps
-    // of a size than we actually do.
-    public void testAMatchingBitmapIsReturnedIfAvailable() {
-        strategy.put(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
-        strategy.get(99, 99, Bitmap.Config.ARGB_8888);
-        strategy.put(Bitmap.createBitmap(101, 101, Bitmap.Config.ARGB_8888));
-        assertNotNull(strategy.get(99, 99, Bitmap.Config.ARGB_8888));
-    }
-
-    public void testLeastRecentlyObtainedSizeIsRemovedFirst() {
-        Bitmap mostRecentlyUsed = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-        Bitmap other = Bitmap.createBitmap(1000, 1000, Bitmap.Config.RGB_565);
-        Bitmap leastRecentlyUsed = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);
-        strategy.get(500, 500, Bitmap.Config.ARGB_8888);
-        strategy.get(1000, 1000, Bitmap.Config.RGB_565);
-        strategy.get(100, 100, Bitmap.Config.ARGB_8888);
-
-        strategy.put(other);
-        strategy.put(leastRecentlyUsed);
-        strategy.put(mostRecentlyUsed);
-
-        assertEquals(leastRecentlyUsed, strategy.removeLast());
-    }
-}
diff --git a/pom.xml b/pom.xml
deleted file mode 100644
index 5e3e347..0000000
--- a/pom.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>org.sonatype.oss</groupId>
-    <artifactId>oss-parent</artifactId>
-    <version>7</version>
-  </parent>
-
-  <groupId>com.bumptech.glide</groupId>
-  <artifactId>glide-parent</artifactId>
-  <version>3.3.0-SNAPSHOT</version>
-  <packaging>pom</packaging>
-
-  <name>Glide (Parent)</name>
-  
-  <modules>
-    <module>library</module>
-    <module>third_party</module>
-    <module>samples</module>
-  </modules>
-
-  <dependencies>
-    <dependency>
-      <groupId>android.support</groupId>
-      <artifactId>compatibility-v4</artifactId>
-      <version>19.1.0</version>
-    </dependency>
-    <dependency>
-      <groupId>android</groupId>
-      <artifactId>android</artifactId>
-      <version>4.4.2_r3</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.mcxiaoke.volley</groupId>
-      <artifactId>library</artifactId>
-      <version>1.0.4</version>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>com.jayway.maven.plugins.android.generation2</groupId>
-        <artifactId>android-maven-plugin</artifactId>
-        <version>3.9.0-rc.2</version>
-        <configuration>
-          <sdk>
-            <platform>19</platform>
-          </sdk>
-          <undeployBeforeDeploy>true</undeployBeforeDeploy>
-        </configuration>
-        <extensions>true</extensions>
-      </plugin>
-      <plugin>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <version>3.1</version>
-        <configuration>
-            <source>1.6</source>
-            <target>1.6</target>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-</project>
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index e926c91..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1,3 +0,0 @@
-include ':library'
-include ':third_party:gif_decoder'
-include ':samples:flickr'
\ No newline at end of file
diff --git a/testutil/src/main/java/com/bumptech/glide/testutil/TestResourceUtil.java b/testutil/src/main/java/com/bumptech/glide/testutil/TestResourceUtil.java
new file mode 100644
index 0000000..e523b67
--- /dev/null
+++ b/testutil/src/main/java/com/bumptech/glide/testutil/TestResourceUtil.java
@@ -0,0 +1,23 @@
+package com.bumptech.glide.testutil;
+
+import java.io.InputStream;
+
+/**
+ * Test only utility for opening resources in androidTest/resources.
+ */
+public final class TestResourceUtil {
+    private TestResourceUtil() {
+        // Utility class
+    }
+
+    /**
+     * Returns an InputStream for the given test class and sub-path.
+     *
+     * @param testClass A Junit test class.
+     * @param subPath The sub-path under androidTest/resources where the desired resource is located.
+     *                Should not be prefixed with a '/'
+     */
+    public static InputStream openResource(Class testClass, String subPath) {
+        return testClass.getResourceAsStream("/" + subPath);
+    }
+}
diff --git a/testutil/src/main/java/com/bumptech/glide/testutil/TestUtil.java b/testutil/src/main/java/com/bumptech/glide/testutil/TestUtil.java
new file mode 100644
index 0000000..0ad40e0
--- /dev/null
+++ b/testutil/src/main/java/com/bumptech/glide/testutil/TestUtil.java
@@ -0,0 +1,37 @@
+package com.bumptech.glide.testutil;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Shared utility classes for tests.
+ */
+public final class TestUtil {
+    private TestUtil() {
+        // Utility class.
+    }
+
+    public static byte[] resourceToBytes(Class testClass, String resourceName) throws IOException {
+        return isToBytes(TestResourceUtil.openResource(testClass, resourceName));
+    }
+
+    public static byte[] isToBytes(InputStream is) throws IOException {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int read;
+        try {
+            while ((read = is.read(buffer)) != -1) {
+                os.write(buffer, 0, read);
+            }
+        } finally {
+            is.close();
+        }
+        return os.toByteArray();
+    }
+
+    public static String isToString(InputStream is) throws IOException {
+        return new String(isToBytes(is));
+    }
+
+}
diff --git a/third_party/disklrucache/.gitignore b/third_party/disklrucache/.gitignore
new file mode 100644
index 0000000..5bbf7d5
--- /dev/null
+++ b/third_party/disklrucache/.gitignore
@@ -0,0 +1,23 @@
+#Eclipse
+.project
+.classpath
+.settings
+.checkstyle
+
+#IntelliJ IDEA
+.idea
+*.iml
+*.ipr
+*.iws
+
+#Maven
+target
+release.properties
+pom.xml.*
+
+#OSX
+.DS_Store
+
+#gradle
+build/**
+.gradle/**
diff --git a/third_party/disklrucache/.travis.yml b/third_party/disklrucache/.travis.yml
new file mode 100644
index 0000000..dff5f3a
--- /dev/null
+++ b/third_party/disklrucache/.travis.yml
@@ -0,0 +1 @@
+language: java
diff --git a/third_party/disklrucache/CHANGELOG.md b/third_party/disklrucache/CHANGELOG.md
new file mode 100644
index 0000000..50a43e8
--- /dev/null
+++ b/third_party/disklrucache/CHANGELOG.md
@@ -0,0 +1,67 @@
+Change Log
+==========
+
+Version 2.0.2 *(2013-06-18)*
+----------------------------
+
+ * Fix: Prevent exception trying to delete a non-existent file.
+
+
+Version 2.0.1 *(2013-04-27)*
+----------------------------
+
+ * Fix: Do not throw runtime exceptions for racy file I/O.
+ * Fix: Synchronize calls to `isClosed`.
+
+
+Version 2.0.0 *(2013-04-13)*
+----------------------------
+
+The package name is now `com.jakewharton.disklrucache`.
+
+ * New: Automatically flush the cache when an edit is completed.
+ * Fix: Ensure file handles are not held when a file is not found.
+ * Fix: Correct journal rebuilds on Windows.
+ * Fix: Ensure file writer uses the appropriate encoding.
+
+
+Version 1.3.1 *(2013-01-02)*
+----------------------------
+
+ * Fix: Correct logic around detecting whether a journal rebuild is required.
+   *(Thanks Jonathan Gerbaud)*
+
+
+Version 1.3.0 *(2012-12-24)*
+----------------------------
+
+ * Re-allow dash in cache key (now `[a-z0-9_-]{1,64}`).
+ * New: `getLength` method on `Snapshot`. *(Thanks Edward Dale)*
+ * Performance improvements reading journal lines.
+
+
+Version 1.2.1 *(2012-10-08)*
+----------------------------
+
+ * Fix: Ensure library references Java 5-compatible version of
+   `Arrays.copyOfRange`. *(Thanks Edward Dale)*
+
+
+Version 1.2.0 *(2012-09-30)*
+----------------------------
+
+ * New API for cache size adjustment.
+ * Keys are now enforced to match `[a-z0-9_]{1,64}` *(Thanks Brian Langel)*
+ * Fix: Cache will gracefully recover if directory is deleted at runtime.
+
+
+Version 1.1.0 *(2012-01-07)*
+----------------------------
+
+ * New API for editing an existing snapshot. *(Thanks Jesse Wilson)*
+
+
+Version 1.0.0 *(2012-01-04)*
+----------------------------
+
+Initial version.
diff --git a/third_party/disklrucache/LICENSE.txt b/third_party/disklrucache/LICENSE.txt
new file mode 100644
index 0000000..53b48b6
--- /dev/null
+++ b/third_party/disklrucache/LICENSE.txt
@@ -0,0 +1,203 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2012 Jake Wharton
+   Copyright 2011 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/third_party/disklrucache/README.md b/third_party/disklrucache/README.md
new file mode 100644
index 0000000..5f27ca2
--- /dev/null
+++ b/third_party/disklrucache/README.md
@@ -0,0 +1,63 @@
+Disk LRU Cache
+==============
+
+A cache that uses a bounded amount of space on a filesystem. Each cache entry
+has a string key and a fixed number of values. Each key must match the regex
+`[a-z0-9_-]{1,64}`.  Values are byte sequences, accessible as streams or files.
+Each value must be between `0` and `Integer.MAX_VALUE` bytes in length.
+
+The cache stores its data in a directory on the filesystem. This directory must
+be exclusive to the cache; the cache may delete or overwrite files from its
+directory. It is an error for multiple processes to use the same cache
+directory at the same time.
+
+This cache limits the number of bytes that it will store on the filesystem.
+When the number of stored bytes exceeds the limit, the cache will remove
+entries in the background until the limit is satisfied. The limit is not
+strict: the cache may temporarily exceed it while waiting for files to be
+deleted. The limit does not include filesystem overhead or the cache journal so
+space-sensitive applications should set a conservative limit.
+
+Clients call `edit` to create or update the values of an entry. An entry may
+have only one editor at one time; if a value is not available to be edited then
+`edit` will return null.
+
+ *  When an entry is being **created** it is necessary to supply a full set of
+    values; the empty value should be used as a placeholder if necessary.
+ *  When an entry is being **edited**, it is not necessary to supply data for
+    every value; values default to their previous value.
+
+Every `edit` call must be matched by a call to `Editor.commit` or
+`Editor.abort`. Committing is atomic: a read observes the full set of values as
+they were before or after the commit, but never a mix of values.
+
+Clients call `get` to read a snapshot of an entry. The read will observe the
+value at the time that `get` was called. Updates and removals after the call do
+not impact ongoing reads.
+
+This class is tolerant of some I/O errors. If files are missing from the
+filesystem, the corresponding entries will be dropped from the cache. If an
+error occurs while writing a cache value, the edit will fail silently. Callers
+should handle other problems by catching `IOException` and responding
+appropriately.
+
+*Note: This implementation specifically targets Android compatibility.*
+
+License
+=======
+
+    Copyright 2012 Jake Wharton
+    Copyright 2011 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
diff --git a/third_party/disklrucache/README.third_party b/third_party/disklrucache/README.third_party
index 24b6acb..88189ae 100644
--- a/third_party/disklrucache/README.third_party
+++ b/third_party/disklrucache/README.third_party
@@ -1,5 +1,5 @@
-URL: https://github.com/JakeWharton/DiskLruCache/archive/disklrucache-2.0.2.tar.gz
-Version:c25de9c0a01974c41c75a41ba1bc4be8b3a1513a
+URL: https://github.com/JakeWharton/DiskLruCache/tarball/7a1ecbd38d2ad0873fb843e911d60235b7434acb
+Version: 7a1ecbd38d2ad0873fb843e911d60235b7434acb
 License: Apache 2.0
 License File: LICENSE
 
@@ -7,4 +7,4 @@
 Java implementation of a Disk-based LRU cache which specifically targets Android compatibility.
 
 Local Modifications:
-No local modifications.
+Exposed File objects directly to gets, removed key validation, removed test sources.
diff --git a/third_party/disklrucache/disklrucache-2.0.2.jar b/third_party/disklrucache/disklrucache-2.0.2.jar
deleted file mode 100644
index ca7907d..0000000
--- a/third_party/disklrucache/disklrucache-2.0.2.jar
+++ /dev/null
Binary files differ
diff --git a/third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/DiskLruCache.java b/third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/DiskLruCache.java
new file mode 100644
index 0000000..7fb4479
--- /dev/null
+++ b/third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/DiskLruCache.java
@@ -0,0 +1,875 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.bumptech.glide.disklrucache;
+
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A cache that uses a bounded amount of space on a filesystem. Each cache
+ * entry has a string key and a fixed number of values. Each key must match
+ * the regex <strong>[a-z0-9_-]{1,120}</strong>. Values are byte sequences,
+ * accessible as streams or files. Each value must be between {@code 0} and
+ * {@code Integer.MAX_VALUE} bytes in length.
+ *
+ * <p>The cache stores its data in a directory on the filesystem. This
+ * directory must be exclusive to the cache; the cache may delete or overwrite
+ * files from its directory. It is an error for multiple processes to use the
+ * same cache directory at the same time.
+ *
+ * <p>This cache limits the number of bytes that it will store on the
+ * filesystem. When the number of stored bytes exceeds the limit, the cache will
+ * remove entries in the background until the limit is satisfied. The limit is
+ * not strict: the cache may temporarily exceed it while waiting for files to be
+ * deleted. The limit does not include filesystem overhead or the cache
+ * journal so space-sensitive applications should set a conservative limit.
+ *
+ * <p>Clients call {@link #edit} to create or update the values of an entry. An
+ * entry may have only one editor at one time; if a value is not available to be
+ * edited then {@link #edit} will return null.
+ * <ul>
+ * <li>When an entry is being <strong>created</strong> it is necessary to
+ * supply a full set of values; the empty value should be used as a
+ * placeholder if necessary.
+ * <li>When an entry is being <strong>edited</strong>, it is not necessary
+ * to supply data for every value; values default to their previous
+ * value.
+ * </ul>
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
+ * or {@link Editor#abort}. Committing is atomic: a read observes the full set
+ * of values as they were before or after the commit, but never a mix of values.
+ *
+ * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
+ * observe the value at the time that {@link #get} was called. Updates and
+ * removals after the call do not impact ongoing reads.
+ *
+ * <p>This class is tolerant of some I/O errors. If files are missing from the
+ * filesystem, the corresponding entries will be dropped from the cache. If
+ * an error occurs while writing a cache value, the edit will fail silently.
+ * Callers should handle other problems by catching {@code IOException} and
+ * responding appropriately.
+ */
+public final class DiskLruCache implements Closeable {
+  static final String JOURNAL_FILE = "journal";
+  static final String JOURNAL_FILE_TEMP = "journal.tmp";
+  static final String JOURNAL_FILE_BACKUP = "journal.bkp";
+  static final String MAGIC = "libcore.io.DiskLruCache";
+  static final String VERSION_1 = "1";
+  static final long ANY_SEQUENCE_NUMBER = -1;
+  private static final String CLEAN = "CLEAN";
+  private static final String DIRTY = "DIRTY";
+  private static final String REMOVE = "REMOVE";
+  private static final String READ = "READ";
+
+    /*
+     * This cache uses a journal file named "journal". A typical journal file
+     * looks like this:
+     *     libcore.io.DiskLruCache
+     *     1
+     *     100
+     *     2
+     *
+     *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
+     *     DIRTY 335c4c6028171cfddfbaae1a9c313c52
+     *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
+     *     REMOVE 335c4c6028171cfddfbaae1a9c313c52
+     *     DIRTY 1ab96a171faeeee38496d8b330771a7a
+     *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
+     *     READ 335c4c6028171cfddfbaae1a9c313c52
+     *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
+     *
+     * The first five lines of the journal form its header. They are the
+     * constant string "libcore.io.DiskLruCache", the disk cache's version,
+     * the application's version, the value count, and a blank line.
+     *
+     * Each of the subsequent lines in the file is a record of the state of a
+     * cache entry. Each line contains space-separated values: a state, a key,
+     * and optional state-specific values.
+     *   o DIRTY lines track that an entry is actively being created or updated.
+     *     Every successful DIRTY action should be followed by a CLEAN or REMOVE
+     *     action. DIRTY lines without a matching CLEAN or REMOVE indicate that
+     *     temporary files may need to be deleted.
+     *   o CLEAN lines track a cache entry that has been successfully published
+     *     and may be read. A publish line is followed by the lengths of each of
+     *     its values.
+     *   o READ lines track accesses for LRU.
+     *   o REMOVE lines track entries that have been deleted.
+     *
+     * The journal file is appended to as cache operations occur. The journal may
+     * occasionally be compacted by dropping redundant lines. A temporary file named
+     * "journal.tmp" will be used during compaction; that file should be deleted if
+     * it exists when the cache is opened.
+     */
+
+  private final File directory;
+  private final File journalFile;
+  private final File journalFileTmp;
+  private final File journalFileBackup;
+  private final int appVersion;
+  private long maxSize;
+  private final int valueCount;
+  private long size = 0;
+  private Writer journalWriter;
+  private final LinkedHashMap<String, Entry> lruEntries =
+      new LinkedHashMap<String, Entry>(0, 0.75f, true);
+  private int redundantOpCount;
+
+  /**
+   * To differentiate between old and current snapshots, each entry is given
+   * a sequence number each time an edit is committed. A snapshot is stale if
+   * its sequence number is not equal to its entry's sequence number.
+   */
+  private long nextSequenceNumber = 0;
+
+  /** This cache uses a single background thread to evict entries. */
+  final ThreadPoolExecutor executorService =
+      new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+  private final Callable<Void> cleanupCallable = new Callable<Void>() {
+    public Void call() throws Exception {
+      synchronized (DiskLruCache.this) {
+        if (journalWriter == null) {
+          return null; // Closed.
+        }
+        trimToSize();
+        if (journalRebuildRequired()) {
+          rebuildJournal();
+          redundantOpCount = 0;
+        }
+      }
+      return null;
+    }
+  };
+
+  private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
+    this.directory = directory;
+    this.appVersion = appVersion;
+    this.journalFile = new File(directory, JOURNAL_FILE);
+    this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
+    this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
+    this.valueCount = valueCount;
+    this.maxSize = maxSize;
+  }
+
+  /**
+   * Opens the cache in {@code directory}, creating a cache if none exists
+   * there.
+   *
+   * @param directory a writable directory
+   * @param valueCount the number of values per cache entry. Must be positive.
+   * @param maxSize the maximum number of bytes this cache should use to store
+   * @throws IOException if reading or writing the cache directory fails
+   */
+  public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
+      throws IOException {
+    if (maxSize <= 0) {
+      throw new IllegalArgumentException("maxSize <= 0");
+    }
+    if (valueCount <= 0) {
+      throw new IllegalArgumentException("valueCount <= 0");
+    }
+
+    // If a bkp file exists, use it instead.
+    File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
+    if (backupFile.exists()) {
+      File journalFile = new File(directory, JOURNAL_FILE);
+      // If journal file also exists just delete backup file.
+      if (journalFile.exists()) {
+        backupFile.delete();
+      } else {
+        renameTo(backupFile, journalFile, false);
+      }
+    }
+
+    // Prefer to pick up where we left off.
+    DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+    if (cache.journalFile.exists()) {
+      try {
+        cache.readJournal();
+        cache.processJournal();
+        return cache;
+      } catch (IOException journalIsCorrupt) {
+        System.out
+            .println("DiskLruCache "
+                + directory
+                + " is corrupt: "
+                + journalIsCorrupt.getMessage()
+                + ", removing");
+        cache.delete();
+      }
+    }
+
+    // Create a new empty cache.
+    directory.mkdirs();
+    cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+    cache.rebuildJournal();
+    return cache;
+  }
+
+  private void readJournal() throws IOException {
+    StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
+    try {
+      String magic = reader.readLine();
+      String version = reader.readLine();
+      String appVersionString = reader.readLine();
+      String valueCountString = reader.readLine();
+      String blank = reader.readLine();
+      if (!MAGIC.equals(magic)
+          || !VERSION_1.equals(version)
+          || !Integer.toString(appVersion).equals(appVersionString)
+          || !Integer.toString(valueCount).equals(valueCountString)
+          || !"".equals(blank)) {
+        throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
+            + valueCountString + ", " + blank + "]");
+      }
+
+      int lineCount = 0;
+      while (true) {
+        try {
+          readJournalLine(reader.readLine());
+          lineCount++;
+        } catch (EOFException endOfJournal) {
+          break;
+        }
+      }
+      redundantOpCount = lineCount - lruEntries.size();
+
+      // If we ended on a truncated line, rebuild the journal before appending to it.
+      if (reader.hasUnterminatedLine()) {
+        rebuildJournal();
+      } else {
+        journalWriter = new BufferedWriter(new OutputStreamWriter(
+            new FileOutputStream(journalFile, true), Util.US_ASCII));
+      }
+    } finally {
+      Util.closeQuietly(reader);
+    }
+  }
+
+  private void readJournalLine(String line) throws IOException {
+    int firstSpace = line.indexOf(' ');
+    if (firstSpace == -1) {
+      throw new IOException("unexpected journal line: " + line);
+    }
+
+    int keyBegin = firstSpace + 1;
+    int secondSpace = line.indexOf(' ', keyBegin);
+    final String key;
+    if (secondSpace == -1) {
+      key = line.substring(keyBegin);
+      if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
+        lruEntries.remove(key);
+        return;
+      }
+    } else {
+      key = line.substring(keyBegin, secondSpace);
+    }
+
+    Entry entry = lruEntries.get(key);
+    if (entry == null) {
+      entry = new Entry(key);
+      lruEntries.put(key, entry);
+    }
+
+    if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
+      String[] parts = line.substring(secondSpace + 1).split(" ");
+      entry.readable = true;
+      entry.currentEditor = null;
+      entry.setLengths(parts);
+    } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
+      entry.currentEditor = new Editor(entry);
+    } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
+      // This work was already done by calling lruEntries.get().
+    } else {
+      throw new IOException("unexpected journal line: " + line);
+    }
+  }
+
+  /**
+   * Computes the initial size and collects garbage as a part of opening the
+   * cache. Dirty entries are assumed to be inconsistent and will be deleted.
+   */
+  private void processJournal() throws IOException {
+    deleteIfExists(journalFileTmp);
+    for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
+      Entry entry = i.next();
+      if (entry.currentEditor == null) {
+        for (int t = 0; t < valueCount; t++) {
+          size += entry.lengths[t];
+        }
+      } else {
+        entry.currentEditor = null;
+        for (int t = 0; t < valueCount; t++) {
+          deleteIfExists(entry.getCleanFile(t));
+          deleteIfExists(entry.getDirtyFile(t));
+        }
+        i.remove();
+      }
+    }
+  }
+
+  /**
+   * Creates a new journal that omits redundant information. This replaces the
+   * current journal if it exists.
+   */
+  private synchronized void rebuildJournal() throws IOException {
+    if (journalWriter != null) {
+      journalWriter.close();
+    }
+
+    Writer writer = new BufferedWriter(
+        new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));
+    try {
+      writer.write(MAGIC);
+      writer.write("\n");
+      writer.write(VERSION_1);
+      writer.write("\n");
+      writer.write(Integer.toString(appVersion));
+      writer.write("\n");
+      writer.write(Integer.toString(valueCount));
+      writer.write("\n");
+      writer.write("\n");
+
+      for (Entry entry : lruEntries.values()) {
+        if (entry.currentEditor != null) {
+          writer.write(DIRTY + ' ' + entry.key + '\n');
+        } else {
+          writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
+        }
+      }
+    } finally {
+      writer.close();
+    }
+
+    if (journalFile.exists()) {
+      renameTo(journalFile, journalFileBackup, true);
+    }
+    renameTo(journalFileTmp, journalFile, false);
+    journalFileBackup.delete();
+
+    journalWriter = new BufferedWriter(
+        new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
+  }
+
+  private static void deleteIfExists(File file) throws IOException {
+    if (file.exists() && !file.delete()) {
+      throw new IOException();
+    }
+  }
+
+  private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
+    if (deleteDestination) {
+      deleteIfExists(to);
+    }
+    if (!from.renameTo(to)) {
+      throw new IOException();
+    }
+  }
+
+  /**
+   * Returns a snapshot of the entry named {@code key}, or null if it doesn't
+   * exist is not currently readable. If a value is returned, it is moved to
+   * the head of the LRU queue.
+   */
+  public synchronized Value get(String key) throws IOException {
+    checkNotClosed();
+    Entry entry = lruEntries.get(key);
+    if (entry == null) {
+      return null;
+    }
+
+    if (!entry.readable) {
+      return null;
+    }
+
+    for (File file : entry.cleanFiles) {
+        // A file must have been deleted manually!
+        if (!file.exists()) {
+            return null;
+        }
+    }
+
+    redundantOpCount++;
+    journalWriter.append(READ);
+    journalWriter.append(' ');
+    journalWriter.append(key);
+    journalWriter.append('\n');
+    if (journalRebuildRequired()) {
+      executorService.submit(cleanupCallable);
+    }
+
+    return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);
+  }
+
+  /**
+   * Returns an editor for the entry named {@code key}, or null if another
+   * edit is in progress.
+   */
+  public Editor edit(String key) throws IOException {
+    return edit(key, ANY_SEQUENCE_NUMBER);
+  }
+
+  private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
+    checkNotClosed();
+    Entry entry = lruEntries.get(key);
+    if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
+        || entry.sequenceNumber != expectedSequenceNumber)) {
+      return null; // Value is stale.
+    }
+    if (entry == null) {
+      entry = new Entry(key);
+      lruEntries.put(key, entry);
+    } else if (entry.currentEditor != null) {
+      return null; // Another edit is in progress.
+    }
+
+    Editor editor = new Editor(entry);
+    entry.currentEditor = editor;
+
+    // Flush the journal before creating files to prevent file leaks.
+    journalWriter.append(DIRTY);
+    journalWriter.append(' ');
+    journalWriter.append(key);
+    journalWriter.append('\n');
+    journalWriter.flush();
+    return editor;
+  }
+
+  /** Returns the directory where this cache stores its data. */
+  public File getDirectory() {
+    return directory;
+  }
+
+  /**
+   * Returns the maximum number of bytes that this cache should use to store
+   * its data.
+   */
+  public synchronized long getMaxSize() {
+    return maxSize;
+  }
+
+  /**
+   * Changes the maximum number of bytes the cache can store and queues a job
+   * to trim the existing store, if necessary.
+   */
+  public synchronized void setMaxSize(long maxSize) {
+    this.maxSize = maxSize;
+    executorService.submit(cleanupCallable);
+  }
+
+  /**
+   * Returns the number of bytes currently being used to store the values in
+   * this cache. This may be greater than the max size if a background
+   * deletion is pending.
+   */
+  public synchronized long size() {
+    return size;
+  }
+
+  private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
+    Entry entry = editor.entry;
+    if (entry.currentEditor != editor) {
+      throw new IllegalStateException();
+    }
+
+    // If this edit is creating the entry for the first time, every index must have a value.
+    if (success && !entry.readable) {
+      for (int i = 0; i < valueCount; i++) {
+        if (!editor.written[i]) {
+          editor.abort();
+          throw new IllegalStateException("Newly created entry didn't create value for index " + i);
+        }
+        if (!entry.getDirtyFile(i).exists()) {
+          editor.abort();
+          return;
+        }
+      }
+    }
+
+    for (int i = 0; i < valueCount; i++) {
+      File dirty = entry.getDirtyFile(i);
+      if (success) {
+        if (dirty.exists()) {
+          File clean = entry.getCleanFile(i);
+          dirty.renameTo(clean);
+          long oldLength = entry.lengths[i];
+          long newLength = clean.length();
+          entry.lengths[i] = newLength;
+          size = size - oldLength + newLength;
+        }
+      } else {
+        deleteIfExists(dirty);
+      }
+    }
+
+    redundantOpCount++;
+    entry.currentEditor = null;
+    if (entry.readable | success) {
+      entry.readable = true;
+      journalWriter.append(CLEAN);
+      journalWriter.append(' ');
+      journalWriter.append(entry.key);
+      journalWriter.append(entry.getLengths());
+      journalWriter.append('\n');
+
+      if (success) {
+        entry.sequenceNumber = nextSequenceNumber++;
+      }
+    } else {
+      lruEntries.remove(entry.key);
+      journalWriter.append(REMOVE);
+      journalWriter.append(' ');
+      journalWriter.append(entry.key);
+      journalWriter.append('\n');
+    }
+    journalWriter.flush();
+
+    if (size > maxSize || journalRebuildRequired()) {
+      executorService.submit(cleanupCallable);
+    }
+  }
+
+  /**
+   * We only rebuild the journal when it will halve the size of the journal
+   * and eliminate at least 2000 ops.
+   */
+  private boolean journalRebuildRequired() {
+    final int redundantOpCompactThreshold = 2000;
+    return redundantOpCount >= redundantOpCompactThreshold //
+        && redundantOpCount >= lruEntries.size();
+  }
+
+  /**
+   * Drops the entry for {@code key} if it exists and can be removed. Entries
+   * actively being edited cannot be removed.
+   *
+   * @return true if an entry was removed.
+   */
+  public synchronized boolean remove(String key) throws IOException {
+    checkNotClosed();
+    Entry entry = lruEntries.get(key);
+    if (entry == null || entry.currentEditor != null) {
+      return false;
+    }
+
+    for (int i = 0; i < valueCount; i++) {
+      File file = entry.getCleanFile(i);
+      if (file.exists() && !file.delete()) {
+        throw new IOException("failed to delete " + file);
+      }
+      size -= entry.lengths[i];
+      entry.lengths[i] = 0;
+    }
+
+    redundantOpCount++;
+    journalWriter.append(REMOVE);
+    journalWriter.append(' ');
+    journalWriter.append(key);
+    journalWriter.append('\n');
+
+    lruEntries.remove(key);
+
+    if (journalRebuildRequired()) {
+      executorService.submit(cleanupCallable);
+    }
+
+    return true;
+  }
+
+  /** Returns true if this cache has been closed. */
+  public synchronized boolean isClosed() {
+    return journalWriter == null;
+  }
+
+  private void checkNotClosed() {
+    if (journalWriter == null) {
+      throw new IllegalStateException("cache is closed");
+    }
+  }
+
+  /** Force buffered operations to the filesystem. */
+  public synchronized void flush() throws IOException {
+    checkNotClosed();
+    trimToSize();
+    journalWriter.flush();
+  }
+
+  /** Closes this cache. Stored values will remain on the filesystem. */
+  public synchronized void close() throws IOException {
+    if (journalWriter == null) {
+      return; // Already closed.
+    }
+    for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
+      if (entry.currentEditor != null) {
+        entry.currentEditor.abort();
+      }
+    }
+    trimToSize();
+    journalWriter.close();
+    journalWriter = null;
+  }
+
+  private void trimToSize() throws IOException {
+    while (size > maxSize) {
+      Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
+      remove(toEvict.getKey());
+    }
+  }
+
+  /**
+   * Closes the cache and deletes all of its stored values. This will delete
+   * all files in the cache directory including files that weren't created by
+   * the cache.
+   */
+  public void delete() throws IOException {
+    close();
+    Util.deleteContents(directory);
+  }
+
+  private static String inputStreamToString(InputStream in) throws IOException {
+    return Util.readFully(new InputStreamReader(in, Util.UTF_8));
+  }
+
+  /** A snapshot of the values for an entry. */
+  public final class Value {
+    private final String key;
+    private final long sequenceNumber;
+    private final long[] lengths;
+    private final File[] files;
+
+      private Value(String key, long sequenceNumber, File[] files, long[] lengths) {
+      this.key = key;
+      this.sequenceNumber = sequenceNumber;
+      this.files = files;
+      this.lengths = lengths;
+    }
+
+    /**
+     * Returns an editor for this snapshot's entry, or null if either the
+     * entry has changed since this snapshot was created or if another edit
+     * is in progress.
+     */
+    public Editor edit() throws IOException {
+      return DiskLruCache.this.edit(key, sequenceNumber);
+    }
+
+    public File getFile(int index) {
+        return files[index];
+    }
+
+    /** Returns the string value for {@code index}. */
+    public String getString(int index) throws IOException {
+      InputStream is = new FileInputStream(files[index]);
+      return inputStreamToString(is);
+    }
+
+    /** Returns the byte length of the value for {@code index}. */
+    public long getLength(int index) {
+      return lengths[index];
+    }
+  }
+
+  /** Edits the values for an entry. */
+  public final class Editor {
+    private final Entry entry;
+    private final boolean[] written;
+    private boolean committed;
+
+    private Editor(Entry entry) {
+      this.entry = entry;
+      this.written = (entry.readable) ? null : new boolean[valueCount];
+    }
+
+    /**
+     * Returns an unbuffered input stream to read the last committed value,
+     * or null if no value has been committed.
+     */
+    private InputStream newInputStream(int index) throws IOException {
+      synchronized (DiskLruCache.this) {
+        if (entry.currentEditor != this) {
+          throw new IllegalStateException();
+        }
+        if (!entry.readable) {
+          return null;
+        }
+        try {
+          return new FileInputStream(entry.getCleanFile(index));
+        } catch (FileNotFoundException e) {
+          return null;
+        }
+      }
+    }
+
+    /**
+     * Returns the last committed value as a string, or null if no value
+     * has been committed.
+     */
+    public String getString(int index) throws IOException {
+      InputStream in = newInputStream(index);
+      return in != null ? inputStreamToString(in) : null;
+    }
+
+    public File getFile(int index) throws IOException {
+      synchronized (DiskLruCache.this) {
+        if (entry.currentEditor != this) {
+            throw new IllegalStateException();
+        }
+        if (!entry.readable) {
+            written[index] = true;
+        }
+        File dirtyFile = entry.getDirtyFile(index);
+        if (!directory.exists()) {
+            directory.mkdirs();
+        }
+        return dirtyFile;
+      }
+    }
+
+    /** Sets the value at {@code index} to {@code value}. */
+    public void set(int index, String value) throws IOException {
+      Writer writer = null;
+      try {
+        OutputStream os = new FileOutputStream(getFile(index));
+        writer = new OutputStreamWriter(os, Util.UTF_8);
+        writer.write(value);
+      } finally {
+        Util.closeQuietly(writer);
+      }
+    }
+
+    /**
+     * Commits this edit so it is visible to readers.  This releases the
+     * edit lock so another edit may be started on the same key.
+     */
+    public void commit() throws IOException {
+      // The object using this Editor must catch and handle any errors
+      // during the write. If there is an error and they call commit
+      // anyway, we will assume whatever they managed to write was valid.
+      // Normally they should call abort.
+      completeEdit(this, true);
+      committed = true;
+    }
+
+    /**
+     * Aborts this edit. This releases the edit lock so another edit may be
+     * started on the same key.
+     */
+    public void abort() throws IOException {
+      completeEdit(this, false);
+    }
+
+    public void abortUnlessCommitted() {
+      if (!committed) {
+        try {
+          abort();
+        } catch (IOException ignored) {
+        }
+      }
+    }
+  }
+
+  private final class Entry {
+    private final String key;
+
+    /** Lengths of this entry's files. */
+    private final long[] lengths;
+
+    /** Memoized File objects for this entry to avoid char[] allocations. */
+    File[] cleanFiles;
+    File[] dirtyFiles;
+
+    /** True if this entry has ever been published. */
+    private boolean readable;
+
+    /** The ongoing edit or null if this entry is not being edited. */
+    private Editor currentEditor;
+
+    /** The sequence number of the most recently committed edit to this entry. */
+    private long sequenceNumber;
+
+    private Entry(String key) {
+      this.key = key;
+      this.lengths = new long[valueCount];
+      cleanFiles = new File[valueCount];
+      dirtyFiles = new File[valueCount];
+
+      // The names are repetitive so re-use the same builder to avoid allocations.
+      StringBuilder fileBuilder = new StringBuilder(key).append('.');
+      int truncateTo = fileBuilder.length();
+      for (int i = 0; i < valueCount; i++) {
+          fileBuilder.append(i);
+          cleanFiles[i] = new File(directory, fileBuilder.toString());
+          fileBuilder.append(".tmp");
+          dirtyFiles[i] = new File(directory, fileBuilder.toString());
+          fileBuilder.setLength(truncateTo);
+      }
+    }
+
+    public String getLengths() throws IOException {
+      StringBuilder result = new StringBuilder();
+      for (long size : lengths) {
+        result.append(' ').append(size);
+      }
+      return result.toString();
+    }
+
+    /** Set lengths using decimal numbers like "10123". */
+    private void setLengths(String[] strings) throws IOException {
+      if (strings.length != valueCount) {
+        throw invalidLengths(strings);
+      }
+
+      try {
+        for (int i = 0; i < strings.length; i++) {
+          lengths[i] = Long.parseLong(strings[i]);
+        }
+      } catch (NumberFormatException e) {
+        throw invalidLengths(strings);
+      }
+    }
+
+    private IOException invalidLengths(String[] strings) throws IOException {
+      throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
+    }
+
+    public File getCleanFile(int i) {
+      return cleanFiles[i];
+    }
+
+    public File getDirtyFile(int i) {
+      return dirtyFiles[i];
+    }
+  }
+}
diff --git a/third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/StrictLineReader.java b/third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/StrictLineReader.java
new file mode 100644
index 0000000..11135db
--- /dev/null
+++ b/third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/StrictLineReader.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.bumptech.glide.disklrucache;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
+/**
+ * Buffers input from an {@link InputStream} for reading lines.
+ *
+ * <p>This class is used for buffered reading of lines. For purposes of this class, a line ends
+ * with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
+ * line at end of input is invalid and will be ignored, the caller may use {@code
+ * hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
+ *
+ * <p>This class is intended for reading input that strictly consists of lines, such as line-based
+ * cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
+ * with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
+ * end-of-input reporting and a more restrictive definition of a line.
+ *
+ * <p>This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
+ * and 10, respectively, and the representation of no other character contains these values.
+ * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
+ * The default charset is US_ASCII.
+ */
+class StrictLineReader implements Closeable {
+  private static final byte CR = (byte) '\r';
+  private static final byte LF = (byte) '\n';
+
+  private final InputStream in;
+  private final Charset charset;
+
+  /*
+   * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
+   * and the data in the range [pos, end) is buffered for reading. At end of input, if there is
+   * an unterminated line, we set end == -1, otherwise end == pos. If the underlying
+   * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
+   */
+  private byte[] buf;
+  private int pos;
+  private int end;
+
+  /**
+   * Constructs a new {@code LineReader} with the specified charset and the default capacity.
+   *
+   * @param in the {@code InputStream} to read data from.
+   * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
+   * supported.
+   * @throws NullPointerException if {@code in} or {@code charset} is null.
+   * @throws IllegalArgumentException if the specified charset is not supported.
+   */
+  public StrictLineReader(InputStream in, Charset charset) {
+    this(in, 8192, charset);
+  }
+
+  /**
+   * Constructs a new {@code LineReader} with the specified capacity and charset.
+   *
+   * @param in the {@code InputStream} to read data from.
+   * @param capacity the capacity of the buffer.
+   * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
+   * supported.
+   * @throws NullPointerException if {@code in} or {@code charset} is null.
+   * @throws IllegalArgumentException if {@code capacity} is negative or zero
+   * or the specified charset is not supported.
+   */
+  public StrictLineReader(InputStream in, int capacity, Charset charset) {
+    if (in == null || charset == null) {
+      throw new NullPointerException();
+    }
+    if (capacity < 0) {
+      throw new IllegalArgumentException("capacity <= 0");
+    }
+    if (!(charset.equals(Util.US_ASCII))) {
+      throw new IllegalArgumentException("Unsupported encoding");
+    }
+
+    this.in = in;
+    this.charset = charset;
+    buf = new byte[capacity];
+  }
+
+  /**
+   * Closes the reader by closing the underlying {@code InputStream} and
+   * marking this reader as closed.
+   *
+   * @throws IOException for errors when closing the underlying {@code InputStream}.
+   */
+  public void close() throws IOException {
+    synchronized (in) {
+      if (buf != null) {
+        buf = null;
+        in.close();
+      }
+    }
+  }
+
+  /**
+   * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
+   * this end of line marker is not included in the result.
+   *
+   * @return the next line from the input.
+   * @throws IOException for underlying {@code InputStream} errors.
+   * @throws EOFException for the end of source stream.
+   */
+  public String readLine() throws IOException {
+    synchronized (in) {
+      if (buf == null) {
+        throw new IOException("LineReader is closed");
+      }
+
+      // Read more data if we are at the end of the buffered data.
+      // Though it's an error to read after an exception, we will let {@code fillBuf()}
+      // throw again if that happens; thus we need to handle end == -1 as well as end == pos.
+      if (pos >= end) {
+        fillBuf();
+      }
+      // Try to find LF in the buffered data and return the line if successful.
+      for (int i = pos; i != end; ++i) {
+        if (buf[i] == LF) {
+          int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
+          String res = new String(buf, pos, lineEnd - pos, charset.name());
+          pos = i + 1;
+          return res;
+        }
+      }
+
+      // Let's anticipate up to 80 characters on top of those already read.
+      ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
+        @Override
+        public String toString() {
+          int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
+          try {
+            return new String(buf, 0, length, charset.name());
+          } catch (UnsupportedEncodingException e) {
+            throw new AssertionError(e); // Since we control the charset this will never happen.
+          }
+        }
+      };
+
+      while (true) {
+        out.write(buf, pos, end - pos);
+        // Mark unterminated line in case fillBuf throws EOFException or IOException.
+        end = -1;
+        fillBuf();
+        // Try to find LF in the buffered data and return the line if successful.
+        for (int i = pos; i != end; ++i) {
+          if (buf[i] == LF) {
+            if (i != pos) {
+              out.write(buf, pos, i - pos);
+            }
+            pos = i + 1;
+            return out.toString();
+          }
+        }
+      }
+    }
+  }
+
+  public boolean hasUnterminatedLine() {
+    return end == -1;
+  }
+
+  /**
+   * Reads new input data into the buffer. Call only with pos == end or end == -1,
+   * depending on the desired outcome if the function throws.
+   */
+  private void fillBuf() throws IOException {
+    int result = in.read(buf, 0, buf.length);
+    if (result == -1) {
+      throw new EOFException();
+    }
+    pos = 0;
+    end = result;
+  }
+}
+
diff --git a/third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/Util.java b/third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/Util.java
new file mode 100644
index 0000000..0260c63
--- /dev/null
+++ b/third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/Util.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.bumptech.glide.disklrucache;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+
+/** Junk drawer of utility methods. */
+final class Util {
+  static final Charset US_ASCII = Charset.forName("US-ASCII");
+  static final Charset UTF_8 = Charset.forName("UTF-8");
+
+  private Util() {
+  }
+
+  static String readFully(Reader reader) throws IOException {
+    try {
+      StringWriter writer = new StringWriter();
+      char[] buffer = new char[1024];
+      int count;
+      while ((count = reader.read(buffer)) != -1) {
+        writer.write(buffer, 0, count);
+      }
+      return writer.toString();
+    } finally {
+      reader.close();
+    }
+  }
+
+  /**
+   * Deletes the contents of {@code dir}. Throws an IOException if any file
+   * could not be deleted, or if {@code dir} is not a readable directory.
+   */
+  static void deleteContents(File dir) throws IOException {
+    File[] files = dir.listFiles();
+    if (files == null) {
+      throw new IOException("not a readable directory: " + dir);
+    }
+    for (File file : files) {
+      if (file.isDirectory()) {
+        deleteContents(file);
+      }
+      if (!file.delete()) {
+        throw new IOException("failed to delete file: " + file);
+      }
+    }
+  }
+
+  static void closeQuietly(/*Auto*/Closeable closeable) {
+    if (closeable != null) {
+      try {
+        closeable.close();
+      } catch (RuntimeException rethrown) {
+        throw rethrown;
+      } catch (Exception ignored) {
+      }
+    }
+  }
+}
diff --git a/third_party/gif_decoder/.gitignore b/third_party/gif_decoder/.gitignore
deleted file mode 100644
index 8c5b344..0000000
--- a/third_party/gif_decoder/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-local.properties
-bin/**/*
-gen/**/*
-target/**/*
diff --git a/third_party/gif_decoder/AndroidManifest.xml b/third_party/gif_decoder/AndroidManifest.xml
deleted file mode 100644
index 03f0e11..0000000
--- a/third_party/gif_decoder/AndroidManifest.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.bumptech.glide.gifdecoder"
-          android:versionCode="1"
-          android:versionName="1.0.0" >
-  <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="19" />
-  <application />
-</manifest>
diff --git a/third_party/gif_decoder/LICENSE b/third_party/gif_decoder/LICENSE
index d6add0e..79da9d5 100644
--- a/third_party/gif_decoder/LICENSE
+++ b/third_party/gif_decoder/LICENSE
@@ -1,22 +1,21 @@
-/**
- * Copyright (c) 2013 Xcellent Creations, Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
+Copyright (c) 2013 Xcellent Creations, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/third_party/gif_decoder/README.third_party b/third_party/gif_decoder/README.third_party
index 9e771c5..e872bcd 100644
--- a/third_party/gif_decoder/README.third_party
+++ b/third_party/gif_decoder/README.third_party
@@ -15,6 +15,6 @@
 http://show.docjava.com/book/cgij/exportToHTML/ip/gif/stills/GifDecoder.java.html
 
 Local Modifications:
-Broke headers and frames out into separate files and added ability to share 
-headers between multiple decoders. Added interface for reusing bitmaps each 
-frame.  
+Broke headers and frames out into separate files and added ability to share
+headers between multiple decoders. Added interface for reusing bitmaps each
+frame. Bugfixes.
diff --git a/third_party/gif_decoder/build.gradle b/third_party/gif_decoder/build.gradle
deleted file mode 100644
index 07bb766..0000000
--- a/third_party/gif_decoder/build.gradle
+++ /dev/null
@@ -1,21 +0,0 @@
-buildscript {
-    repositories {
-        mavenCentral()
-    }
-    dependencies {
-        classpath 'com.android.tools.build:gradle:0.11.2'
-    }
-}
-
-apply plugin: 'android-library'
-
-android {
-    compileSdkVersion 19
-    buildToolsVersion = '19.1.0'
-    sourceSets {
-        main {
-            java.srcDirs         = ['src/main/java']
-            manifest.srcFile 'AndroidManifest.xml'
-        }
-    }
-}
diff --git a/third_party/gif_decoder/build.xml b/third_party/gif_decoder/build.xml
deleted file mode 100644
index a6e9e00..0000000
--- a/third_party/gif_decoder/build.xml
+++ /dev/null
@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project name="glide" default="help">
-
-  <!-- The local.properties file is created and updated by the 'android' tool.
-       It contains the path to the SDK. It should *NOT* be checked into
-       Version Control Systems. -->
-  <property file="local.properties"/>
-
-  <!-- The ant.properties file can be created by you. It is only edited by the
-       'android' tool to add properties to it.
-       This is the place to change some Ant specific build properties.
-       Here are some properties you may want to change/update:
-
-       source.dir
-           The name of the source directory. Default is 'src'.
-       out.dir
-           The name of the output directory. Default is 'bin'.
-
-       For other overridable properties, look at the beginning of the rules
-       files in the SDK, at tools/ant/build.xml
-
-       Properties related to the SDK location or the project target should
-       be updated using the 'android' tool with the 'update' action.
-
-       This file is an integral part of the build system for your
-       application and should be checked into Version Control Systems.
-
-       -->
-  <property file="ant.properties"/>
-
-  <!-- if sdk.dir was not set from one of the property file, then
-       get it from the ANDROID_HOME env var.
-       This must be done before we load project.properties since
-       the proguard config can use sdk.dir -->
-  <property environment="env"/>
-  <condition property="sdk.dir" value="${env.ANDROID_HOME}">
-    <isset property="env.ANDROID_HOME"/>
-  </condition>
-
-  <!-- The project.properties file is created and updated by the 'android'
-       tool, as well as ADT.
-
-       This contains project specific properties such as project target, and library
-       dependencies. Lower level build properties are stored in ant.properties
-       (or in .classpath for Eclipse projects).
-
-       This file is an integral part of the build system for your
-       application and should be checked into Version Control Systems. -->
-  <loadproperties srcFile="project.properties"/>
-
-  <!-- quick check on sdk.dir -->
-  <fail
-    message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
-    unless="sdk.dir"
-    />
-
-  <!--
-      Import per project custom build rules if present at the root of the project.
-      This is the place to put custom intermediary targets such as:
-          -pre-build
-          -pre-compile
-          -post-compile (This is typically used for code obfuscation.
-                         Compiled code location: ${out.classes.absolute.dir}
-                         If this is not done in place, override ${out.dex.input.absolute.dir})
-          -post-package
-          -post-build
-          -pre-clean
-  -->
-  <import file="custom_rules.xml" optional="true"/>
-
-  <!-- Import the actual build file.
-
-       To customize existing targets, there are two options:
-       - Customize only one target:
-           - copy/paste the target into this file, *before* the
-             <import> task.
-           - customize it to your needs.
-       - Customize the whole content of build.xml
-           - copy/paste the content of the rules files (minus the top node)
-             into this file, replacing the <import> task.
-           - customize to your needs.
-
-       ***********************
-       ****** IMPORTANT ******
-       ***********************
-       In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
-       in order to avoid having your file be overridden by tools such as "android update project"
-  -->
-  <!-- version-tag: 1 -->
-  <import file="${sdk.dir}/tools/ant/build.xml"/>
-
-</project>
diff --git a/third_party/gif_decoder/custom_rules.xml b/third_party/gif_decoder/custom_rules.xml
deleted file mode 100644
index dd56892..0000000
--- a/third_party/gif_decoder/custom_rules.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project name="glide-rules" default="help">
-    <xmlproperty file="AndroidManifest.xml" prefix="mymanifest" collapseAttributes="true"/>
-
-    <target name="jar" depends="-compile">
-        <jar destfile="bin/gifdecoder-${mymanifest.manifest.android:versionName}.jar"
-            basedir="bin/classes" >
-        </jar>
-    </target>
-</project>
diff --git a/third_party/gif_decoder/pom.xml b/third_party/gif_decoder/pom.xml
deleted file mode 100644
index 5315cc3..0000000
--- a/third_party/gif_decoder/pom.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.bumptech.glide</groupId>
-    <artifactId>glide-third-party</artifactId>
-    <version>3.3.0-SNAPSHOT</version>
-    <relativePath>../pom.xml</relativePath>
-  </parent>
-
-  <artifactId>glide-gif-decoder</artifactId>
-  <packaging>aar</packaging>
-  <name>Glide Gif Decoder</name>
-
-</project>
diff --git a/third_party/gif_decoder/project.properties b/third_party/gif_decoder/project.properties
deleted file mode 100644
index d226f51..0000000
--- a/third_party/gif_decoder/project.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-target=android-19
-
-# https://code.google.com/p/android/issues/detail?id=40487
-renderscript.opt.level=O0
-
-android.library=true
diff --git a/third_party/gif_decoder/src/main/AndroidManifest.xml b/third_party/gif_decoder/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2f91b04
--- /dev/null
+++ b/third_party/gif_decoder/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.bumptech.glide.gifdecoder">
+  <application />
+</manifest>
diff --git a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java
index 2381a7a..c4b4281 100644
--- a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java
+++ b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java
@@ -24,8 +24,9 @@
  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  */
 
+import android.annotation.TargetApi;
 import android.graphics.Bitmap;
-import android.graphics.Color;
+import android.os.Build;
 import android.util.Log;
 
 import java.io.ByteArrayOutputStream;
@@ -33,6 +34,7 @@
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.Arrays;
 
 /**
  * Reads frame data from a GIF image source and decodes it into individual frames
@@ -59,7 +61,7 @@
      */
     public static final int STATUS_OK = 0;
     /**
-     * File read status: Error decoding file (may be partially decoded)
+     * File read status: Error decoding file (may be partially decoded).
      */
     public static final int STATUS_FORMAT_ERROR = 1;
     /**
@@ -67,50 +69,86 @@
      */
     public static final int STATUS_OPEN_ERROR = 2;
     /**
-     * max decoder pixel stack size
+     * Unable to fully decode the current frame.
+     */
+    public static final int STATUS_PARTIAL_DECODE = 3;
+    /**
+     * max decoder pixel stack size.
      */
     private static final int MAX_STACK_SIZE = 4096;
 
     /**
-     * GIF Disposal Method meaning take no action
+     * GIF Disposal Method meaning take no action.
      */
     private static final int DISPOSAL_UNSPECIFIED = 0;
     /**
-     * GIF Disposal Method meaning leave canvas from previous frame
+     * GIF Disposal Method meaning leave canvas from previous frame.
      */
     private static final int DISPOSAL_NONE = 1;
     /**
-     * GIF Disposal Method meaning clear canvas to background color
+     * GIF Disposal Method meaning clear canvas to background color.
      */
     private static final int DISPOSAL_BACKGROUND = 2;
     /**
-     * GIF Disposal Method meaning clear canvas to frame before last
+     * GIF Disposal Method meaning clear canvas to frame before last.
      */
     private static final int DISPOSAL_PREVIOUS = 3;
 
-    //Global File Header values and parsing flags
-    private int[] act; // active color table
+    private static final int NULL_CODE = -1;
 
-    // Raw GIF data from input source
+    private static final int INITIAL_FRAME_POINTER = -1;
+
+    // We can't tell if a gif has transparency to decode a partial frame on top of a previous frame, or if the final
+    // frame will actually have transparent pixels, so we must always use a format that supports transparency. We can't
+    // use ARGB_4444 because of framework issues drawing onto ARGB_4444 Bitmaps using Canvas.
+    private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
+
+    // Global File Header values and parsing flags.
+    // Active color table.
+    private int[] act;
+
+    // Raw GIF data from input source.
     private ByteBuffer rawData;
 
-    // Raw data read working array
-    private byte[] block = new byte[256]; // current data block
-    // LZW decoder working arrays
+    // Raw data read working array.
+    private final byte[] block = new byte[256];
+
+    private GifHeaderParser parser;
+
+    // LZW decoder working arrays.
     private short[] prefix;
     private byte[] suffix;
     private byte[] pixelStack;
     private byte[] mainPixels;
     private int[] mainScratch;
 
-    private int framePointer = -1;
+    private int framePointer;
     private byte[] data;
     private GifHeader header;
-    private String id;
     private BitmapProvider bitmapProvider;
+    private Bitmap previousImage;
+    private boolean savePrevious;
+    private int status;
 
+    /**
+     * An interface that can be used to provide reused {@link android.graphics.Bitmap}s to avoid GCs from constantly
+     * allocating {@link android.graphics.Bitmap}s for every frame.
+     */
     public interface BitmapProvider {
+        /**
+         * Returns an {@link Bitmap} with exactly the given dimensions and config, or null if no such {@link Bitmap}
+         * could be obtained.
+         *
+         * @param width The width in pixels of the desired {@link android.graphics.Bitmap}.
+         * @param height The height in pixels of the desired {@link android.graphics.Bitmap}.
+         * @param config The {@link android.graphics.Bitmap.Config} of the desired {@link android.graphics.Bitmap}.
+         */
         public Bitmap obtain(int width, int height, Bitmap.Config config);
+
+        /**
+         * Releases the given Bitmap back to the pool.
+         */
+        public void release(Bitmap bitmap);
     }
 
     public GifDecoder(BitmapProvider provider) {
@@ -126,25 +164,24 @@
         return header.height;
     }
 
-    public boolean isTransparent() {
-        return header.isTransparent;
-    }
-
-    public int getGifByteSize() {
-        return data.length;
-    }
-
     public byte[] getData() {
         return data;
     }
 
-    public int getDecodedFramesByteSizeSum() {
-        // 4 == ARGB_8888, 2 == RGB_565
-        return header.frameCount * header.width * header.height * (header.isTransparent ? 4 : 2);
+    /**
+     * Returns the current status of the decoder.
+     *
+     * <p>
+     *     Status will update per frame to allow the caller to tell whether or not the current frame was decoded
+     *     successfully and/or completely. Format and open failures persist across frames.
+     * </p>
+     */
+    public int getStatus() {
+        return status;
     }
 
     /**
-     * Move the animation frame counter forward
+     * Move the animation frame counter forward.
      */
     public void advance() {
         framePointer = (framePointer + 1) % header.frameCount;
@@ -153,8 +190,8 @@
     /**
      * Gets display duration for specified frame.
      *
-     * @param n int index of frame
-     * @return delay in milliseconds
+     * @param n int index of frame.
+     * @return delay in milliseconds.
      */
     public int getDelay(int n) {
         int delay = -1;
@@ -165,7 +202,7 @@
     }
 
     /**
-     * Gets display duration for the upcoming frame
+     * Gets display duration for the upcoming frame in ms.
      */
     public int getNextDelay() {
         if (header.frameCount <= 0 || framePointer < 0) {
@@ -178,23 +215,27 @@
     /**
      * Gets the number of frames read from file.
      *
-     * @return frame count
+     * @return frame count.
      */
     public int getFrameCount() {
         return header.frameCount;
     }
 
     /**
-     * Gets the current index of the animation frame, or -1 if animation hasn't not yet started
+     * Gets the current index of the animation frame, or -1 if animation hasn't not yet started.
      *
-     * @return frame index
+     * @return frame index.
      */
     public int getCurrentFrameIndex() {
         return framePointer;
     }
 
+    public void resetFrameIndex() {
+        framePointer = -1;
+    }
+
     /**
-     * Gets the "Netscape" iteration count, if any. A count of 0 means repeat indefinitiely.
+     * Gets the "Netscape" iteration count, if any. A count of 0 means repeat indefinitely.
      *
      * @return iteration count if one was specified, else 1.
      */
@@ -202,58 +243,74 @@
         return header.loopCount;
     }
 
-    public String getId() {
-        return id;
-    }
-
     /**
      * Get the next frame in the animation sequence.
      *
-     * @return Bitmap representation of frame
+     * @return Bitmap representation of frame.
      */
-    public Bitmap getNextFrame() {
-        if (header.frameCount <= 0 || framePointer < 0 ) {
+    public synchronized Bitmap getNextFrame() {
+        if (header.frameCount <= 0 || framePointer < 0) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "unable to decode frame, frameCount=" + header.frameCount + " framePointer=" + framePointer);
+            }
+            status = STATUS_FORMAT_ERROR;
+        }
+        if (status == STATUS_FORMAT_ERROR || status == STATUS_OPEN_ERROR) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Unable to decode frame, status=" + status);
+            }
             return null;
         }
+        status = STATUS_OK;
 
-        GifFrame frame = header.frames.get(framePointer);
+        GifFrame currentFrame = header.frames.get(framePointer);
+        GifFrame previousFrame = null;
+        int previousIndex = framePointer - 1;
+        if (previousIndex >= 0) {
+            previousFrame = header.frames.get(previousIndex);
+        }
 
-        //Set the appropriate color table
-        if (frame.lct == null) {
+        // Set the appropriate color table.
+        if (currentFrame.lct == null) {
             act = header.gct;
         } else {
-            act = frame.lct;
-            if (header.bgIndex == frame.transIndex) {
+            act = currentFrame.lct;
+            if (header.bgIndex == currentFrame.transIndex) {
                 header.bgColor = 0;
             }
         }
 
         int save = 0;
-        if (frame.transparency) {
-            save = act[frame.transIndex];
-            act[frame.transIndex] = 0; // set transparent color if specified
+        if (currentFrame.transparency) {
+            save = act[currentFrame.transIndex];
+            // Set transparent color if specified.
+            act[currentFrame.transIndex] = 0;
         }
         if (act == null) {
-            Log.w(TAG, "No Valid Color Table");
-            header.status = STATUS_FORMAT_ERROR; // no color table defined
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "No Valid Color Table");
+            }
+            // No color table defined.
+            status = STATUS_FORMAT_ERROR;
             return null;
         }
 
-        Bitmap result = setPixels(framePointer); // transfer pixel data to image
+        // Transfer pixel data to image.
+        Bitmap result = setPixels(currentFrame, previousFrame);
 
         // Reset the transparent pixel in the color table
-        if (frame.transparency) {
-            act[frame.transIndex] = save;
+        if (currentFrame.transparency) {
+            act[currentFrame.transIndex] = save;
         }
 
         return result;
     }
 
     /**
-     * Reads GIF image from stream
+     * Reads GIF image from stream.
      *
      * @param is containing GIF file.
-     * @return read status code (0 = no errors)
+     * @return read status code (0 = no errors).
      */
     public int read(InputStream is, int contentLength) {
         if (is != null) {
@@ -272,7 +329,7 @@
                 Log.w(TAG, "Error reading data from stream", e);
             }
         } else {
-            header.status = STATUS_OPEN_ERROR;
+            status = STATUS_OPEN_ERROR;
         }
 
         try {
@@ -283,95 +340,116 @@
             Log.w(TAG, "Error closing stream", e);
         }
 
-        return header.status;
+        return status;
     }
 
-    public void setData(String id, GifHeader header, byte[] data) {
-        this.id = id;
+    public void clear() {
+        header = null;
+        data = null;
+        mainPixels = null;
+        mainScratch = null;
+        if (previousImage != null) {
+            bitmapProvider.release(previousImage);
+        }
+        previousImage = null;
+    }
+
+    public void setData(GifHeader header, byte[] data) {
         this.header = header;
         this.data = data;
-        //Initialize the raw data buffer
+        this.status = STATUS_OK;
+        framePointer = INITIAL_FRAME_POINTER;
+        // Initialize the raw data buffer.
         rawData = ByteBuffer.wrap(data);
         rawData.rewind();
         rawData.order(ByteOrder.LITTLE_ENDIAN);
 
-        //Now that we know the size, init scratch arrays
+
+        // No point in specially saving an old frame if we're never going to use it.
+        savePrevious = false;
+        for (GifFrame frame : header.frames) {
+            if (frame.dispose == DISPOSAL_PREVIOUS) {
+                savePrevious = true;
+                break;
+            }
+        }
+
+        // Now that we know the size, init scratch arrays.
         mainPixels = new byte[header.width * header.height];
         mainScratch = new int[header.width * header.height];
     }
 
+    private GifHeaderParser getHeaderParser() {
+        if (parser == null) {
+            parser = new GifHeaderParser();
+        }
+        return parser;
+    }
+
     /**
-     * Reads GIF image from byte array
+     * Reads GIF image from byte array.
      *
      * @param data containing GIF file.
-     * @return read status code (0 = no errors)
+     * @return read status code (0 = no errors).
      */
     public int read(byte[] data) {
         this.data = data;
-        this.header = new GifHeaderParser(data).parseHeader();
+        this.header = getHeaderParser().setData(data).parseHeader();
         if (data != null) {
-            //Initialize the raw data buffer
+            // Initialize the raw data buffer.
             rawData = ByteBuffer.wrap(data);
             rawData.rewind();
             rawData.order(ByteOrder.LITTLE_ENDIAN);
 
-            //Now that we know the size, init scratch arrays
+            // Now that we know the size, init scratch arrays.
             mainPixels = new byte[header.width * header.height];
             mainScratch = new int[header.width * header.height];
+
+            // No point in specially saving an old frame if we're never going to use it.
+            savePrevious = false;
+            for (GifFrame frame : header.frames) {
+                if (frame.dispose == DISPOSAL_PREVIOUS) {
+                    savePrevious = true;
+                    break;
+                }
+            }
         }
 
-        return header.status;
+        return status;
     }
 
     /**
      * Creates new frame image from current data (and previous frames as specified by their disposition codes).
      */
-    private Bitmap setPixels(int frameIndex) {
-        GifFrame currentFrame = header.frames.get(frameIndex);
-        GifFrame previousFrame = null;
-        int previousIndex = frameIndex - 1;
-        if (previousIndex >= 0) {
-            previousFrame = header.frames.get(previousIndex);
-        }
+    private Bitmap setPixels(GifFrame currentFrame, GifFrame previousFrame) {
 
-        // final location of blended pixels
+        int width = header.width;
+        int height = header.height;
+
+        // Final location of blended pixels.
         final int[] dest = mainScratch;
 
         // fill in starting image contents based on last image's dispose code
         if (previousFrame != null && previousFrame.dispose > DISPOSAL_UNSPECIFIED) {
-//            if (previousFrame.dispose == DISPOSAL_NONE) {
-//                We don't need to do anything for this case, mainScratch should already have the pixels of the
-//                previous image.
-//                currentImage.getPixels(dest, 0, header.width, 0, 0, header.width, header.height);
-//            }
+            // We don't need to do anything for DISPOSAL_NONE, if it has the correct pixels so will our mainScratch
+            // and therefore so will our dest array.
             if (previousFrame.dispose == DISPOSAL_BACKGROUND) {
                 // Start with a canvas filled with the background color
                 int c = 0;
                 if (!currentFrame.transparency) {
                     c = header.bgColor;
                 }
-                for (int i = 0; i < previousFrame.ih; i++) {
-                    int n1 = (previousFrame.iy + i) * header.width + previousFrame.ix;
-                    int n2 = n1 + previousFrame.iw;
-                    for (int k = n1; k < n2; k++) {
-                        dest[k] = c;
-                    }
-                }
-            }
-        } else {
-            int c = 0;
-            if (!currentFrame.transparency) {
-                c = header.bgColor;
-            }
-            for (int i = 0; i < dest.length; i++) {
-                dest[i] = c;
+                Arrays.fill(dest, c);
+            } else if (previousFrame.dispose == DISPOSAL_PREVIOUS && previousImage != null) {
+                // Start with the previous frame
+                previousImage.getPixels(dest, 0, width, 0, 0, width, height);
             }
         }
 
-        // Decode pixels for this frame  into the global pixels[] scratch
-        decodeBitmapData(currentFrame, mainPixels); // decode pixel data
+        // Decode pixels for this frame  into the global pixels[] scratch.
+        decodeBitmapData(currentFrame);
 
-        // copy each source line to the appropriate place in the destination
+        // Copy each source line to the appropriate place in the destination.
         int pass = 1;
         int inc = 8;
         int iline = 0;
@@ -402,14 +480,18 @@
             line += currentFrame.iy;
             if (line < header.height) {
                 int k = line * header.width;
-                int dx = k + currentFrame.ix; // start of line in dest
-                int dlim = dx + currentFrame.iw; // end of dest line
+                // Start of line in dest.
+                int dx = k + currentFrame.ix;
+                // End of dest line.
+                int dlim = dx + currentFrame.iw;
                 if ((k + header.width) < dlim) {
-                    dlim = k + header.width; // past dest edge
+                    // Past dest edge.
+                    dlim = k + header.width;
                 }
-                int sx = i * currentFrame.iw; // start of line in source
+                // Start of line in source.
+                int sx = i * currentFrame.iw;
                 while (dx < dlim) {
-                    // map color and insert in destination
+                    // Map color and insert in destination.
                     int index = ((int) mainPixels[sx++]) & 0xff;
                     int c = act[index];
                     if (c != 0) {
@@ -420,27 +502,36 @@
             }
         }
 
-        //Set pixels for current image
+        // Copy pixels into previous image
+        if (savePrevious && currentFrame.dispose == DISPOSAL_UNSPECIFIED || currentFrame.dispose == DISPOSAL_NONE) {
+            if (previousImage == null) {
+                previousImage = getNextBitmap();
+            }
+            previousImage.setPixels(dest, 0, width, 0, 0, width, height);
+        }
+
+        // Set pixels for current image.
         Bitmap result = getNextBitmap();
-        result.setPixels(dest, 0, header.width, 0, 0, header.width, header.height);
+        result.setPixels(dest, 0, width, 0, 0, width, height);
         return result;
     }
 
     /**
      * Decodes LZW image data into pixel array. Adapted from John Cristy's BitmapMagick.
      */
-    private void decodeBitmapData(GifFrame frame, byte[] dstPixels) {
+    private void decodeBitmapData(GifFrame frame) {
         if (frame != null) {
-            //Jump to the frame start position
+            // Jump to the frame start position.
             rawData.position(frame.bufferFrameStart);
         }
 
-        int nullCode = -1;
         int npix = (frame == null) ? header.width * header.height : frame.iw * frame.ih;
-        int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi;
+        int available, clear, codeMask, codeSize, endOfInformation, inCode, oldCode, bits, code, count, i, datum,
+                dataSize, first, top, bi, pi;
 
-        if (dstPixels == null || dstPixels.length < npix) {
-            dstPixels = new byte[npix]; // allocate new pixel array
+        if (mainPixels == null || mainPixels.length < npix) {
+            // Allocate new pixel array.
+            mainPixels = new byte[npix];
         }
         if (prefix == null) {
             prefix = new short[MAX_STACK_SIZE];
@@ -453,92 +544,105 @@
         }
 
         // Initialize GIF data stream decoder.
-        data_size = read();
-        clear = 1 << data_size;
-        end_of_information = clear + 1;
+        dataSize = read();
+        clear = 1 << dataSize;
+        endOfInformation = clear + 1;
         available = clear + 2;
-        old_code = nullCode;
-        code_size = data_size + 1;
-        code_mask = (1 << code_size) - 1;
+        oldCode = NULL_CODE;
+        codeSize = dataSize + 1;
+        codeMask = (1 << codeSize) - 1;
         for (code = 0; code < clear; code++) {
-            prefix[code] = 0; // XXX ArrayIndexOutOfBoundsException
+            // XXX ArrayIndexOutOfBoundsException.
+            prefix[code] = 0;
             suffix[code] = (byte) code;
         }
 
         // Decode GIF pixel stream.
         datum = bits = count = first = top = pi = bi = 0;
         for (i = 0; i < npix; ) {
-            if (top == 0) {
-                if (bits < code_size) {
-                    // Load bytes until there are enough bits for a code.
-                    if (count == 0) {
-                        // Read a new data block.
-                        count = readBlock();
-                        if (count <= 0) {
-                            break;
-                        }
-                        bi = 0;
-                    }
-                    datum += (((int) block[bi]) & 0xff) << bits;
-                    bits += 8;
-                    bi++;
-                    count--;
-                    continue;
-                }
-                // Get the next code.
-                code = datum & code_mask;
-                datum >>= code_size;
-                bits -= code_size;
-                // Interpret the code
-                if ((code > available) || (code == end_of_information)) {
+            // Load bytes until there are enough bits for a code.
+            if (count == 0) {
+                // Read a new data block.
+                count = readBlock();
+                if (count <= 0) {
+                    status = STATUS_PARTIAL_DECODE;
                     break;
                 }
+                bi = 0;
+            }
+
+            datum += (((int) block[bi]) & 0xff) << bits;
+            bits += 8;
+            bi++;
+            count--;
+
+            while (bits >= codeSize) {
+                // Get the next code.
+                code = datum & codeMask;
+                datum >>= codeSize;
+                bits -= codeSize;
+
+                // Interpret the code.
                 if (code == clear) {
                     // Reset decoder.
-                    code_size = data_size + 1;
-                    code_mask = (1 << code_size) - 1;
+                    codeSize = dataSize + 1;
+                    codeMask = (1 << codeSize) - 1;
                     available = clear + 2;
-                    old_code = nullCode;
+                    oldCode = NULL_CODE;
                     continue;
                 }
-                if (old_code == nullCode) {
+
+                if (code > available) {
+                    status = STATUS_PARTIAL_DECODE;
+                    break;
+                }
+
+                if (code == endOfInformation) {
+                    break;
+                }
+
+                if (oldCode == NULL_CODE) {
                     pixelStack[top++] = suffix[code];
-                    old_code = code;
+                    oldCode = code;
                     first = code;
                     continue;
                 }
-                in_code = code;
-                if (code == available) {
+                inCode = code;
+                if (code >= available) {
                     pixelStack[top++] = (byte) first;
-                    code = old_code;
+                    code = oldCode;
                 }
-                while (code > clear) {
+                while (code >= clear) {
                     pixelStack[top++] = suffix[code];
                     code = prefix[code];
                 }
                 first = ((int) suffix[code]) & 0xff;
-                // Add a new string to the string table,
-                if (available >= MAX_STACK_SIZE) {
-                    break;
-                }
                 pixelStack[top++] = (byte) first;
-                prefix[available] = (short) old_code;
-                suffix[available] = (byte) first;
-                available++;
-                if (((available & code_mask) == 0) && (available < MAX_STACK_SIZE)) {
-                    code_size++;
-                    code_mask += available;
+
+                // Add a new string to the string table.
+                if (available < MAX_STACK_SIZE) {
+                    prefix[available] = (short) oldCode;
+                    suffix[available] = (byte) first;
+                    available++;
+                    if (((available & codeMask) == 0) && (available < MAX_STACK_SIZE)) {
+                        codeSize++;
+                        codeMask += available;
+                    }
                 }
-                old_code = in_code;
+                oldCode = inCode;
+
+                while (top > 0) {
+                    // Pop a pixel off the pixel stack.
+                    top--;
+                    mainPixels[pi++] = pixelStack[top];
+                    i++;
+                }
             }
-            // Pop a pixel off the pixel stack.
-            top--;
-            dstPixels[pi++] = pixelStack[top];
-            i++;
         }
 
+        // Clear missing pixels.
         for (i = pi; i < npix; i++) {
-            dstPixels[i] = 0; // clear missing pixels
+            mainPixels[i] = 0;
         }
     }
 
@@ -548,9 +652,9 @@
     private int read() {
         int curByte = 0;
         try {
-            curByte = (rawData.get() & 0xFF);
+            curByte = rawData.get() & 0xFF;
         } catch (Exception e) {
-            header.status = STATUS_FORMAT_ERROR;
+            status = STATUS_FORMAT_ERROR;
         }
         return curByte;
     }
@@ -558,7 +662,7 @@
     /**
      * Reads next variable length block from input.
      *
-     * @return number of bytes stored in "buffer"
+     * @return number of bytes stored in "buffer".
      */
     private int readBlock() {
         int blockSize = read();
@@ -574,21 +678,25 @@
                 }
             } catch (Exception e) {
                 Log.w(TAG, "Error Reading Block", e);
-                header.status = STATUS_FORMAT_ERROR;
+                status = STATUS_FORMAT_ERROR;
             }
         }
         return n;
     }
 
     private Bitmap getNextBitmap() {
-        Bitmap.Config targetConfig = header.isTransparent ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
-        Bitmap result = bitmapProvider.obtain(header.width, header.height, targetConfig);
+        Bitmap result = bitmapProvider.obtain(header.width, header.height, BITMAP_CONFIG);
         if (result == null) {
-            result = Bitmap.createBitmap(header.width, header.height, targetConfig);
-        } else {
-            // If we're reusing a bitmap it may have other things drawn in it which we need to remove.
-            result.eraseColor(Color.TRANSPARENT);
+            result = Bitmap.createBitmap(header.width, header.height, BITMAP_CONFIG);
         }
+        setAlpha(result);
         return result;
     }
+
+    @TargetApi(12)
+    private static void setAlpha(Bitmap bitmap) {
+        if (Build.VERSION.SDK_INT >= 12) {
+            bitmap.setHasAlpha(true);
+        }
+    }
 }
diff --git a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java
index a315350..1cfa684 100644
--- a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java
+++ b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java
@@ -1,21 +1,22 @@
 package com.bumptech.glide.gifdecoder;
 
 /**
- * Inner model class housing metadata for each frame
+ * Inner model class housing metadata for each frame.
  */
 class GifFrame {
-    public int ix, iy, iw, ih;
-    /* Control Flags */
-    public boolean interlace;
-    public boolean transparency;
-    /* Disposal Method */
-    public int dispose;
-    /* Transparency Index */
-    public int transIndex;
-    /* Delay, in ms, to next frame */
-    public int delay;
-    /* Index in the raw buffer where we need to start reading to decode */
-    public int bufferFrameStart;
-    /* Local Color Table */
-    public int[] lct;
+    int ix, iy, iw, ih;
+    /** Control Flag. */
+    boolean interlace;
+    /** Control Flag. */
+    boolean transparency;
+    /** Disposal Method. */
+    int dispose;
+    /** Transparency Index. */
+    int transIndex;
+    /** Delay, in ms, to next frame. */
+    int delay;
+    /** Index in the raw buffer where we need to start reading to decode. */
+    int bufferFrameStart;
+    /** Local Color Table. */
+    int[] lct;
 }
diff --git a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java
index 4c06208..9615290 100644
--- a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java
+++ b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java
@@ -3,29 +3,55 @@
 import java.util.ArrayList;
 import java.util.List;
 
+/**
+ * A header object containing the number of frames in an animated GIF image as well as basic metadata like width and
+ * height that can be used to decode each individual frame of the GIF. Can be shared by one or more
+ * {@link com.bumptech.glide.gifdecoder.GifDecoder}s to play the same animated GIF in multiple views.
+ */
 public class GifHeader {
 
-    public int[] gct = null;
-    /**
-     * Global status code of GIF data parsing
-     */
-    public int status = GifDecoder.STATUS_OK;
-    public int frameCount = 0;
+    int[] gct = null;
+    int status = GifDecoder.STATUS_OK;
+    int frameCount = 0;
 
-    public GifFrame currentFrame;
-    public List<GifFrame> frames = new ArrayList<GifFrame>();
-     // logical screen size
-    public int width; // full image width
-    public int height; // full image height
+    GifFrame currentFrame;
+    List<GifFrame> frames = new ArrayList<GifFrame>();
+    // Logical screen size.
+    // Full image width.
+    int width;
+    // Full image height.
+    int height;
 
-    public boolean gctFlag; // 1 : global color table flag
-    // 2-4 : color resolution
-    // 5 : gct sort flag
-    public int gctSize; // 6-8 : gct size
-    public int bgIndex; // background color index
-    public int pixelAspect; // pixel aspect ratio
+    // 1 : global color table flag.
+    boolean gctFlag;
+    // 2-4 : color resolution.
+    // 5 : gct sort flag.
+    // 6-8 : gct size.
+    int gctSize;
+    // Background color index.
+    int bgIndex;
+    // Pixel aspect ratio.
+    int pixelAspect;
     //TODO: this is set both during reading the header and while decoding frames...
-    public int bgColor;
-    public boolean isTransparent;
-    public int loopCount;
+    int bgColor;
+    int loopCount;
+
+    public int getHeight() {
+        return height;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public int getNumFrames() {
+        return frameCount;
+    }
+
+    /**
+     * Global status code of GIF data parsing.
+     */
+    public int getStatus() {
+        return status;
+    }
 }
diff --git a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java
index 6237c3c..cf61531 100644
--- a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java
+++ b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java
@@ -1,33 +1,36 @@
 package com.bumptech.glide.gifdecoder;
 
+import static com.bumptech.glide.gifdecoder.GifDecoder.STATUS_FORMAT_ERROR;
+
 import android.util.Log;
 
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.Arrays;
 
-import static com.bumptech.glide.gifdecoder.GifDecoder.STATUS_FORMAT_ERROR;
-
+/**
+ * A class responsible for creating {@link com.bumptech.glide.gifdecoder.GifHeader}s from data representing animated
+ * gifs.
+ */
 public class GifHeaderParser {
     public static final String TAG = "GifHeaderParser";
-    /**
-     * max decoder pixel stack size
-     */
-    private static final int MAX_STACK_SIZE = 4096;
 
-    private final ByteBuffer rawData;
-    private GifHeader header = new GifHeader();
+    // The minimum frame delay in hundredths of a second.
+    static final int MIN_FRAME_DELAY = 3;
+    // The default frame delay in hundredths of a second for GIFs with frame delays less than the minimum.
+    static final int DEFAULT_FRAME_DELAY = 10;
 
-    // Raw data read working array
-    protected byte[] block = new byte[256]; // current data block
-    protected int blockSize = 0; // block size last graphic control extension info
-    protected boolean lctFlag; // local color table flag
-    protected int lctSize; // local color table size
-    private short[] prefix;
-    private byte[] suffix;
-    private byte[] pixelStack;
+    private static final int MAX_BLOCK_SIZE = 256;
+    // Raw data read working array.
+    private final byte[] block = new byte[MAX_BLOCK_SIZE];
 
-    public GifHeaderParser(byte[] data) {
+    private ByteBuffer rawData;
+    private GifHeader header;
+    private int blockSize = 0;
+
+    public GifHeaderParser setData(byte[] data) {
+        reset();
         if (data != null) {
             rawData = ByteBuffer.wrap(data);
             rawData.rewind();
@@ -36,9 +39,25 @@
             rawData = null;
             header.status = GifDecoder.STATUS_OPEN_ERROR;
         }
+        return this;
+    }
+
+    public void clear() {
+        rawData = null;
+        header = null;
+    }
+
+    private void reset() {
+        rawData = null;
+        Arrays.fill(block, (byte) 0);
+        header = new GifHeader();
+        blockSize = 0;
     }
 
     public GifHeader parseHeader() {
+        if (rawData == null) {
+            throw new IllegalStateException("You must call setData() before parseHeader()");
+        }
         if (err()) {
             return header;
         }
@@ -57,24 +76,34 @@
     /**
      * Main file parser. Reads GIF content blocks.
      */
-    protected void readContents() {
-        // read GIF file content blocks
+    private void readContents() {
+        // Read GIF file content blocks.
         boolean done = false;
         while (!(done || err())) {
             int code = read();
             switch (code) {
-                case 0x2C: // image separator
+                // Image separator.
+                case 0x2C:
+                    // The graphics control extension is optional, but will always come first if it exists. If one did
+                    // exist, there will be a non-null current frame which we should use. However if one did not exist,
+                    // the current frame will be null and we must create it here. See issue #134.
+                    if (header.currentFrame == null) {
+                        header.currentFrame = new GifFrame();
+                    }
                     readBitmap();
                     break;
-                case 0x21: // extension
+                // Extension.
+                case 0x21:
                     code = read();
                     switch (code) {
-                        case 0xf9: // graphics control extension
-                            //Start a new frame
+                        // Graphics control extension.
+                        case 0xf9:
+                            // Start a new frame.
                             header.currentFrame = new GifFrame();
                             readGraphicControlExt();
                             break;
-                        case 0xff: // application extension
+                        // Application extension.
+                        case 0xff:
                             readBlock();
                             String app = "";
                             for (int i = 0; i < 11; i++) {
@@ -83,23 +112,29 @@
                             if (app.equals("NETSCAPE2.0")) {
                                 readNetscapeExt();
                             } else {
-                                skip(); // don't care
+                                // Don't care.
+                                skip();
                             }
                             break;
-                        case 0xfe:// comment extension
+                        // Comment extension.
+                        case 0xfe:
                             skip();
                             break;
-                        case 0x01:// plain text extension
+                        // Plain text extension.
+                        case 0x01:
                             skip();
                             break;
-                        default: // uninteresting extension
+                        // Uninteresting extension.
+                        default:
                             skip();
                     }
                     break;
-                case 0x3b: // terminator
+                // Terminator.
+                case 0x3b:
                     done = true;
                     break;
-                case 0x00: // bad byte, but keep going and see what happens break;
+                // Bad byte, but keep going and see what happens break;
+                case 0x00:
                 default:
                     header.status = STATUS_FORMAT_ERROR;
             }
@@ -107,64 +142,81 @@
     }
 
     /**
-     * Reads Graphics Control Extension values
+     * Reads Graphics Control Extension values.
      */
-    protected void readGraphicControlExt() {
-        read(); // block size
-        int packed = read(); // packed fields
-        header.currentFrame.dispose = (packed & 0x1c) >> 2; // disposal method
+    private void readGraphicControlExt() {
+        // Block size.
+        read();
+        // Packed fields.
+        int packed = read();
+        // Disposal method.
+        header.currentFrame.dispose = (packed & 0x1c) >> 2;
         if (header.currentFrame.dispose == 0) {
-            header.currentFrame.dispose = 1; // elect to keep old image if discretionary
+            // Elect to keep old image if discretionary.
+            header.currentFrame.dispose = 1;
         }
         header.currentFrame.transparency = (packed & 1) != 0;
-        header.isTransparent |= header.currentFrame.transparency;
-        header.currentFrame.delay = readShort() * 10; // delay in milliseconds
-        header.currentFrame.transIndex = read(); // transparent color index
-        read(); // block terminator
+        // Delay in milliseconds.
+        int delayInHundredthsOfASecond = readShort();
+        // TODO: consider allowing -1 to indicate show forever.
+        if (delayInHundredthsOfASecond < MIN_FRAME_DELAY) {
+            delayInHundredthsOfASecond = DEFAULT_FRAME_DELAY;
+        }
+        header.currentFrame.delay = delayInHundredthsOfASecond * 10;
+        // Transparent color index
+        header.currentFrame.transIndex = read();
+        // Block terminator
+        read();
     }
 
-     /**
-     * Reads next frame image
+    /**
+     * Reads next frame image.
      */
-    protected void readBitmap() {
-        header.currentFrame.ix = readShort(); // (sub)image position & size
+    private void readBitmap() {
+        // (sub)image position & size.
+        header.currentFrame.ix = readShort();
         header.currentFrame.iy = readShort();
         header.currentFrame.iw = readShort();
         header.currentFrame.ih = readShort();
 
         int packed = read();
-        lctFlag = (packed & 0x80) != 0; // 1 - local color table flag interlace
-        lctSize = (int) Math.pow(2, (packed & 0x07) + 1);
+        // 1 - local color table flag interlace
+        boolean lctFlag = (packed & 0x80) != 0;
+        int lctSize = (int) Math.pow(2, (packed & 0x07) + 1);
         // 3 - sort flag
         // 4-5 - reserved lctSize = 2 << (packed & 7); // 6-8 - local color
         // table size
         header.currentFrame.interlace = (packed & 0x40) != 0;
         if (lctFlag) {
-            header.currentFrame.lct = readColorTable(lctSize); // read table
+            // Read table.
+            header.currentFrame.lct = readColorTable(lctSize);
         } else {
-            header.currentFrame.lct = null; //No local color table
+            // No local color table.
+            header.currentFrame.lct = null;
         }
 
-        header.currentFrame.bufferFrameStart = rawData.position(); //Save this as the decoding position pointer
+        // Save this as the decoding position pointer.
+        header.currentFrame.bufferFrameStart = rawData.position();
 
-        skipBitmapData();  // false decode pixel data to advance buffer.
+        // False decode pixel data to advance buffer.
+        skipImageData();
 
-        skip();
         if (err()) {
             return;
         }
 
         header.frameCount++;
-        header.frames.add(header.currentFrame); // add image to frame
+        // Add image to frame.
+        header.frames.add(header.currentFrame);
     }
-       /**
-     * Reads Netscape extenstion to obtain iteration count
+    /**
+     * Reads Netscape extension to obtain iteration count.
      */
-    protected void readNetscapeExt() {
+    private void readNetscapeExt() {
         do {
             readBlock();
             if (block[0] == 1) {
-                // loop count sub-block
+                // Loop count sub-block.
                 int b1 = ((int) block[1]) & 0xff;
                 int b2 = ((int) block[2]) & 0xff;
                 header.loopCount = (b2 << 8) | b1;
@@ -173,7 +225,7 @@
     }
 
 
-       /**
+    /**
      * Reads GIF file header information.
      */
     private void readHeader() {
@@ -191,34 +243,34 @@
             header.bgColor = header.gct[header.bgIndex];
         }
     }
-     /**
-     * Reads Logical Screen Descriptor
+    /**
+     * Reads Logical Screen Descriptor.
      */
-    protected void readLSD() {
-        // logical screen size
+    private void readLSD() {
+        // Logical screen size.
         header.width = readShort();
         header.height = readShort();
-        // packed fields
+        // Packed fields
         int packed = read();
-        header.gctFlag = (packed & 0x80) != 0; // 1 : global color table flag
-        // 2-4 : color resolution
-        // 5 : gct sort flag
-        header.gctSize = 2 << (packed & 7); // 6-8 : gct size
-        header.bgIndex = read(); // background color index
-        header.pixelAspect = read(); // pixel aspect ratio
-
-        //Now that we know the size, init scratch arrays
-        //TODO: these shouldn't go here.
-//        mainPixels = new byte[header.width * header.height];
-//        mainScratch = new int[header.width * header.height];
+        // 1 : global color table flag.
+        header.gctFlag = (packed & 0x80) != 0;
+        // 2-4 : color resolution.
+        // 5 : gct sort flag.
+        // 6-8 : gct size.
+        header.gctSize = 2 << (packed & 7);
+        // Background color index.
+        header.bgIndex = read();
+        // Pixel aspect ratio
+        header.pixelAspect = read();
     }
-     /**
-     * Reads color table as 256 RGB integer values
+
+    /**
+     * Reads color table as 256 RGB integer values.
      *
-     * @param ncolors int number of colors to read
-     * @return int array containing 256 colors (packed ARGB with full alpha)
+     * @param ncolors int number of colors to read.
+     * @return int array containing 256 colors (packed ARGB with full alpha).
      */
-    protected int[] readColorTable(int ncolors) {
+    private int[] readColorTable(int ncolors) {
         int nbytes = 3 * ncolors;
         int[] tab = null;
         byte[] c = new byte[nbytes];
@@ -226,7 +278,9 @@
         try {
             rawData.get(c);
 
-            tab = new int[256]; // max size to avoid bounds checks
+            // TODO: what bounds checks are we avoiding if we know the number of colors?
+            // Max size to avoid bounds checks.
+            tab = new int[MAX_BLOCK_SIZE];
             int i = 0;
             int j = 0;
             while (i < ncolors) {
@@ -236,140 +290,47 @@
                 tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
             }
         } catch (BufferUnderflowException e) {
-            Log.w(TAG, "Format Error Reading Color Table", e);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Format Error Reading Color Table", e);
+            }
             header.status = STATUS_FORMAT_ERROR;
         }
 
         return tab;
     }
 
-      /**
-     * Decodes LZW image data into pixel array. Adapted from John Cristy's BitmapMagick.
+    /**
+     * Skips LZW image data for a single frame to advance buffer.
      */
-    protected void skipBitmapData() {
-        int nullCode = -1;
-        int npix = header.width * header.height;
-        int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi;
-
-        if (prefix == null) {
-            prefix = new short[MAX_STACK_SIZE];
-        }
-        if (suffix == null) {
-            suffix = new byte[MAX_STACK_SIZE];
-        }
-        if (pixelStack == null) {
-            pixelStack = new byte[MAX_STACK_SIZE + 1];
-        }
-
-        // Initialize GIF data stream decoder.
-        data_size = read();
-        clear = 1 << data_size;
-        end_of_information = clear + 1;
-        available = clear + 2;
-        old_code = nullCode;
-        code_size = data_size + 1;
-        code_mask = (1 << code_size) - 1;
-        long start = System.currentTimeMillis();
-        for (code = 0; code < clear; code++) {
-            prefix[code] = 0; // XXX ArrayIndexOutOfBoundsException
-            suffix[code] = (byte) code;
-        }
-
-        start = System.currentTimeMillis();
-        // Decode GIF pixel stream.
-        datum = bits = count = first = top = pi = bi = 0;
-        int iterations = 0;
-        for (i = 0; i < npix; ) {
-            iterations++;
-            if (top == 0) {
-                if (bits < code_size) {
-                    // Load bytes until there are enough bits for a code.
-                    if (count == 0) {
-                        // Read a new data block.
-                        count = readBlock();
-                        if (count <= 0) {
-                            break;
-                        }
-                        bi = 0;
-                    }
-                    datum += (((int) block[bi]) & 0xff) << bits;
-                    bits += 8;
-                    bi++;
-                    count--;
-                    continue;
-                }
-                // Get the next code.
-                code = datum & code_mask;
-                datum >>= code_size;
-                bits -= code_size;
-                // Interpret the code
-                if ((code > available) || (code == end_of_information)) {
-                    break;
-                }
-                if (code == clear) {
-                    // Reset decoder.
-                    code_size = data_size + 1;
-                    code_mask = (1 << code_size) - 1;
-                    available = clear + 2;
-                    old_code = nullCode;
-                    continue;
-                }
-                if (old_code == nullCode) {
-                    pixelStack[top++] = suffix[code];
-                    old_code = code;
-                    first = code;
-                    continue;
-                }
-                in_code = code;
-                if (code == available) {
-                    pixelStack[top++] = (byte) first;
-                    code = old_code;
-                }
-                while (code > clear) {
-                    pixelStack[top++] = suffix[code];
-                    code = prefix[code];
-                }
-                first = ((int) suffix[code]) & 0xff;
-                // Add a new string to the string table,
-                if (available >= MAX_STACK_SIZE) {
-                    break;
-                }
-                pixelStack[top++] = (byte) first;
-                prefix[available] = (short) old_code;
-                suffix[available] = (byte) first;
-                available++;
-                if (((available & code_mask) == 0) && (available < MAX_STACK_SIZE)) {
-                    code_size++;
-                    code_mask += available;
-                }
-                old_code = in_code;
-            }
-            // Pop a pixel off the pixel stack.
-            top--;
-            i++;
-        }
+    private void skipImageData() {
+        // lzwMinCodeSize
+        read();
+        // data sub-blocks
+        skip();
     }
 
-      /**
+    /**
      * Skips variable length blocks up to and including next zero length block.
      */
-    protected void skip() {
+    private void skip() {
+        int blockSize;
         do {
-            readBlock();
-        } while ((blockSize > 0) && !err());
+            blockSize = read();
+            rawData.position(rawData.position() + blockSize);
+        } while (blockSize > 0);
     }
 
-     /**
+    /**
      * Reads next variable length block from input.
      *
      * @return number of bytes stored in "buffer"
      */
-    protected int readBlock() {
+    private int readBlock() {
         blockSize = read();
         int n = 0;
         if (blockSize > 0) {
+            int count = 0;
             try {
-                int count;
                 while (n < blockSize) {
                     count = blockSize - n;
                     rawData.get(block, n, count);
@@ -377,20 +338,22 @@
                     n += count;
                 }
             } catch (Exception e) {
-                Log.w(TAG, "Error Reading Block", e);
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Error Reading Block n: " + n + " count: " + count + " blockSize: " + blockSize, e);
+                }
                 header.status = STATUS_FORMAT_ERROR;
             }
         }
         return n;
     }
 
-      /**
+    /**
      * Reads a single byte from the input stream.
      */
     private int read() {
         int curByte = 0;
         try {
-            curByte = (rawData.get() & 0xFF);
+            curByte = rawData.get() & 0xFF;
         } catch (Exception e) {
             header.status = STATUS_FORMAT_ERROR;
         }
@@ -398,10 +361,10 @@
     }
 
     /**
-     * Reads next 16-bit value, LSB first
+     * Reads next 16-bit value, LSB first.
      */
-    protected int readShort() {
-        // read 16-bit value
+    private int readShort() {
+        // Read 16-bit value.
         return rawData.getShort();
     }
 
diff --git a/third_party/gif_encoder/LICENSE b/third_party/gif_encoder/LICENSE
new file mode 100644
index 0000000..2bbbf24
--- /dev/null
+++ b/third_party/gif_encoder/LICENSE
@@ -0,0 +1,25 @@
+License for AnimatedGifEncoder.java and LZWEncoder.java
+
+No copyright asserted on the source code of this class. May be used for any
+purpose, however, refer to the Unisys LZW patent for restrictions on use of
+the associated LZWEncoder class. Please forward any corrections to
+kweiner@fmsware.com.
+
+-----------------------------------------------------------------------------
+License for NeuQuant.java
+
+Copyright (c) 1994 Anthony Dekker
+
+NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
+"Kohonen neural networks for optimal colour quantization" in "Network:
+Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
+the algorithm.
+
+Any party obtaining a copy of these files from the author, directly or
+indirectly, is granted, free of charge, a full and unrestricted irrevocable,
+world-wide, paid up, royalty-free, nonexclusive right and license to deal in
+this software and documentation files (the "Software"), including without
+limitation the rights to use, copy, modify, merge, publish, distribute,
+sublicense, and/or sell copies of the Software, and to permit persons who
+receive copies from any such party to do so, with the only requirement being
+that this copyright notice remain intact.
diff --git a/third_party/gif_encoder/README.third_party b/third_party/gif_encoder/README.third_party
new file mode 100644
index 0000000..981a8a0
--- /dev/null
+++ b/third_party/gif_encoder/README.third_party
@@ -0,0 +1,14 @@
+URL: http://java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm
+Version: Downloaded 9/4/2014.
+License: Notice
+License File: LICENSE
+
+Description:
+Android port of a gif encoder.
+
+See also:
+http://members.ozemail.com.au/~dekker/NEUQUANT.HTML
+
+Local Modifications:
+Converted BufferedImage to Android's Bitmap class, split apart classes into individual files.
+Support setting transIndex based on the presence of transparent pixels in the Bitmap.
diff --git a/third_party/gif_encoder/lint.xml b/third_party/gif_encoder/lint.xml
new file mode 100644
index 0000000..d9d6c9f
--- /dev/null
+++ b/third_party/gif_encoder/lint.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lint>
+    <issue id="AllowBackup" severity="ignore" />
+</lint>
diff --git a/third_party/gif_encoder/src/main/AndroidManifest.xml b/third_party/gif_encoder/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..77f17b1
--- /dev/null
+++ b/third_party/gif_encoder/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.bumptech.glide.gifencoder">
+  <application />
+</manifest>
diff --git a/third_party/gif_encoder/src/main/java/com/bumptech/glide/gifencoder/AnimatedGifEncoder.java b/third_party/gif_encoder/src/main/java/com/bumptech/glide/gifencoder/AnimatedGifEncoder.java
new file mode 100644
index 0000000..aa1123f
--- /dev/null
+++ b/third_party/gif_encoder/src/main/java/com/bumptech/glide/gifencoder/AnimatedGifEncoder.java
@@ -0,0 +1,531 @@
+package com.bumptech.glide.gifencoder;
+
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or more
+ * frames.
+ *
+ * <pre>
+ *  Example:
+ *     AnimatedGifEncoder e = new AnimatedGifEncoder();
+ *     e.start(outputFileName);
+ *     e.setDelay(1000);   // 1 frame per sec
+ *     e.addFrame(image1);
+ *     e.addFrame(image2);
+ *     e.finish();
+ * </pre>
+ *
+ * No copyright asserted on the source code of this class. May be used for any
+ * purpose, however, refer to the Unisys LZW patent for restrictions on use of
+ * the associated LZWEncoder class. Please forward any corrections to
+ * kweiner@fmsware.com.
+ *
+ * @author Kevin Weiner, FM Software
+ * @version 1.03 November 2003
+ *
+ */
+
+public class AnimatedGifEncoder {
+    private static final String TAG = "AnimatedGifEncoder";
+
+    // The minimum % of an images pixels that must be transparent for us to set a transparent index automatically.
+    private static final double MIN_TRANSPARENT_PERCENTAGE = 4d;
+
+    private int width; // image size
+
+    private int height;
+
+    private Integer transparent = null; // transparent color if given
+
+    private int transIndex; // transparent index in color table
+
+    private int repeat = -1; // no repeat
+
+    private int delay = 0; // frame delay (hundredths)
+
+    private boolean started = false; // ready to output frames
+
+    private OutputStream out;
+
+    private Bitmap image; // current frame
+
+    private byte[] pixels; // BGR byte array from frame
+
+    private byte[] indexedPixels; // converted frame indexed to palette
+
+    private int colorDepth; // number of bit planes
+
+    private byte[] colorTab; // RGB palette
+
+    private boolean[] usedEntry = new boolean[256]; // active palette entries
+
+    private int palSize = 7; // color table size (bits-1)
+
+    private int dispose = -1; // disposal code (-1 = use default)
+
+    private boolean closeStream = false; // close stream when finished
+
+    private boolean firstFrame = true;
+
+    private boolean sizeSet = false; // if false, get size from first frame
+
+    private int sample = 10; // default sample interval for quantizer
+
+    private boolean hasTransparentPixels;
+
+    /**
+     * Sets the delay time between each frame, or changes it for subsequent frames
+     * (applies to last frame added).
+     *
+     * @param ms
+     *          int delay time in milliseconds
+     */
+    public void setDelay(int ms) {
+        delay = Math.round(ms / 10.0f);
+    }
+
+    /**
+     * Sets the GIF frame disposal code for the last added frame and any
+     * subsequent frames. Default is 0 if no transparent color has been set,
+     * otherwise 2.
+     *
+     * @param code
+     *          int disposal code.
+     */
+    public void setDispose(int code) {
+        if (code >= 0) {
+            dispose = code;
+        }
+    }
+
+    /**
+     * Sets the number of times the set of GIF frames should be played. Default is
+     * 1; 0 means play indefinitely. Must be invoked before the first image is
+     * added.
+     *
+     * @param iter
+     *          int number of iterations.
+     */
+    public void setRepeat(int iter) {
+        if (iter >= 0) {
+            repeat = iter;
+        }
+    }
+
+    /**
+     * Sets the transparent color for the last added frame and any subsequent
+     * frames. Since all colors are subject to modification in the quantization
+     * process, the color in the final palette for each frame closest to the given
+     * color becomes the transparent color for that frame. May be set to null to
+     * indicate no transparent color.
+     *
+     * @param color
+     *          Color to be treated as transparent on display.
+     */
+    public void setTransparent(int color) {
+        transparent = color;
+    }
+
+    /**
+     * Adds next GIF frame. The frame is not written immediately, but is actually
+     * deferred until the next frame is received so that timing data can be
+     * inserted. Invoking <code>finish()</code> flushes all frames. If
+     * <code>setSize</code> was not invoked, the size of the first image is used
+     * for all subsequent frames.
+     *
+     * @param im
+     *          BufferedImage containing frame to write.
+     * @return true if successful.
+     */
+    public boolean addFrame(Bitmap im) {
+        if ((im == null) || !started) {
+            return false;
+        }
+        boolean ok = true;
+        try {
+            if (!sizeSet) {
+                // use first frame's size
+                setSize(im.getWidth(), im.getHeight());
+            }
+            image = im;
+            getImagePixels(); // convert to correct format if necessary
+            analyzePixels(); // build color table & map pixels
+            if (firstFrame) {
+                writeLSD(); // logical screen descriptior
+                writePalette(); // global color table
+                if (repeat >= 0) {
+                    // use NS app extension to indicate reps
+                    writeNetscapeExt();
+                }
+            }
+            writeGraphicCtrlExt(); // write graphic control extension
+            writeImageDesc(); // image descriptor
+            if (!firstFrame) {
+                writePalette(); // local color table
+            }
+            writePixels(); // encode and write pixel data
+            firstFrame = false;
+        } catch (IOException e) {
+            ok = false;
+        }
+
+        return ok;
+    }
+
+    /**
+     * Flushes any pending data and closes output file. If writing to an
+     * OutputStream, the stream is not closed.
+     */
+    public boolean finish() {
+        if (!started)
+            return false;
+        boolean ok = true;
+        started = false;
+        try {
+            out.write(0x3b); // gif trailer
+            out.flush();
+            if (closeStream) {
+                out.close();
+            }
+        } catch (IOException e) {
+            ok = false;
+        }
+
+        // reset for subsequent use
+        transIndex = 0;
+        out = null;
+        image = null;
+        pixels = null;
+        indexedPixels = null;
+        colorTab = null;
+        closeStream = false;
+        firstFrame = true;
+
+        return ok;
+    }
+
+    /**
+     * Sets frame rate in frames per second. Equivalent to
+     * <code>setDelay(1000/fps)</code>.
+     *
+     * @param fps
+     *          float frame rate (frames per second)
+     */
+    public void setFrameRate(float fps) {
+        if (fps != 0f) {
+            delay = Math.round(100f / fps);
+        }
+    }
+
+    /**
+     * Sets quality of color quantization (conversion of images to the maximum 256
+     * colors allowed by the GIF specification). Lower values (minimum = 1)
+     * produce better colors, but slow processing significantly. 10 is the
+     * default, and produces good color mapping at reasonable speeds. Values
+     * greater than 20 do not yield significant improvements in speed.
+     *
+     * @param quality int greater than 0.
+     */
+    public void setQuality(int quality) {
+        if (quality < 1)
+            quality = 1;
+        sample = quality;
+    }
+
+    /**
+     * Sets the GIF frame size. The default size is the size of the first frame
+     * added if this method is not invoked.
+     *
+     * @param w
+     *          int frame width.
+     * @param h
+     *          int frame width.
+     */
+    public void setSize(int w, int h) {
+        if (started && !firstFrame)
+            return;
+        width = w;
+        height = h;
+        if (width < 1)
+            width = 320;
+        if (height < 1)
+            height = 240;
+        sizeSet = true;
+    }
+
+    /**
+     * Initiates GIF file creation on the given stream. The stream is not closed
+     * automatically.
+     *
+     * @param os
+     *          OutputStream on which GIF images are written.
+     * @return false if initial write failed.
+     */
+    public boolean start(OutputStream os) {
+        if (os == null)
+            return false;
+        boolean ok = true;
+        closeStream = false;
+        out = os;
+        try {
+            writeString("GIF89a"); // header
+        } catch (IOException e) {
+            ok = false;
+        }
+        return started = ok;
+    }
+
+    /**
+     * Initiates writing of a GIF file with the specified name.
+     *
+     * @param file
+     *          String containing output file name.
+     * @return false if open or initial write failed.
+     */
+    public boolean start(String file) {
+        boolean ok = true;
+        try {
+            out = new BufferedOutputStream(new FileOutputStream(file));
+            ok = start(out);
+            closeStream = true;
+        } catch (IOException e) {
+            ok = false;
+        }
+        return started = ok;
+    }
+
+    /**
+     * Analyzes image colors and creates color map.
+     */
+    private void analyzePixels() {
+        int len = pixels.length;
+        int nPix = len / 3;
+        indexedPixels = new byte[nPix];
+        NeuQuant nq = new NeuQuant(pixels, len, sample);
+        // initialize quantizer
+        colorTab = nq.process(); // create reduced palette
+        // convert map from BGR to RGB
+        for (int i = 0; i < colorTab.length; i += 3) {
+            byte temp = colorTab[i];
+            colorTab[i] = colorTab[i + 2];
+            colorTab[i + 2] = temp;
+            usedEntry[i / 3] = false;
+        }
+        // map image pixels to new palette
+        int k = 0;
+        for (int i = 0; i < nPix; i++) {
+            int index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
+            usedEntry[index] = true;
+            indexedPixels[i] = (byte) index;
+        }
+        pixels = null;
+        colorDepth = 8;
+        palSize = 7;
+        // get closest match to transparent color if specified
+        if (transparent != null) {
+            transIndex = findClosest(transparent);
+        } else if (hasTransparentPixels) {
+            transIndex = findClosest(Color.TRANSPARENT);
+        }
+    }
+
+    /**
+     * Returns index of palette color closest to c
+     *
+     */
+    private int findClosest(int color) {
+        if (colorTab == null)
+            return -1;
+        int r = Color.red(color);
+        int g = Color.green(color);
+        int b = Color.blue(color);
+        int minpos = 0;
+        int dmin = 256 * 256 * 256;
+        int len = colorTab.length;
+        for (int i = 0; i < len;) {
+            int dr = r - (colorTab[i++] & 0xff);
+            int dg = g - (colorTab[i++] & 0xff);
+            int db = b - (colorTab[i] & 0xff);
+            int d = dr * dr + dg * dg + db * db;
+            int index = i / 3;
+            if (usedEntry[index] && (d < dmin)) {
+                dmin = d;
+                minpos = index;
+            }
+            i++;
+        }
+        return minpos;
+    }
+
+    /**
+     * Extracts image pixels into byte array "pixels"
+     */
+    private void getImagePixels() {
+        int w = image.getWidth();
+        int h = image.getHeight();
+
+        if ((w != width) || (h != height)) {
+            // create new image with right size/format
+            Bitmap temp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(temp);
+            canvas.drawBitmap(temp, 0, 0, null);
+            image = temp;
+        }
+        int[] pixelsInt = new int[w * h];
+        image.getPixels(pixelsInt, 0, w, 0, 0, w, h);
+
+        // The algorithm requires 3 bytes per pixel as RGB.
+        pixels = new byte[pixelsInt.length * 3];
+
+        int pixelsIndex = 0;
+        hasTransparentPixels = false;
+        int totalTransparentPixels = 0;
+        for (final int pixel : pixelsInt) {
+            if (pixel == Color.TRANSPARENT) {
+                totalTransparentPixels++;
+            }
+            pixels[pixelsIndex++] = (byte) (pixel & 0xFF);
+            pixels[pixelsIndex++] = (byte) ((pixel >> 8) & 0xFF);
+            pixels[pixelsIndex++] = (byte) ((pixel >> 16) & 0xFF);
+        }
+
+        double transparentPercentage = 100 * totalTransparentPixels / (double) pixelsInt.length;
+        // Assume images with greater where more than n% of the pixels are transparent actually have transparency.
+        // See issue #214.
+        hasTransparentPixels = transparentPercentage > MIN_TRANSPARENT_PERCENTAGE;
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "got pixels for frame with " + transparentPercentage + "% transparent pixels");
+        }
+    }
+
+    /**
+     * Writes Graphic Control Extension
+     */
+    private void writeGraphicCtrlExt() throws IOException {
+        out.write(0x21); // extension introducer
+        out.write(0xf9); // GCE label
+        out.write(4); // data block size
+        int transp, disp;
+        if (transparent == null && !hasTransparentPixels) {
+            transp = 0;
+            disp = 0; // dispose = no action
+        } else {
+            transp = 1;
+            disp = 2; // force clear if using transparent color
+        }
+        if (dispose >= 0) {
+            disp = dispose & 7; // user override
+        }
+        disp <<= 2;
+
+        // packed fields
+        out.write(0 | // 1:3 reserved
+                disp | // 4:6 disposal
+                0 | // 7 user input - 0 = none
+                transp); // 8 transparency flag
+
+        writeShort(delay); // delay x 1/100 sec
+        out.write(transIndex); // transparent color index
+        out.write(0); // block terminator
+    }
+
+    /**
+     * Writes Image Descriptor
+     */
+    private void writeImageDesc() throws IOException {
+        out.write(0x2c); // image separator
+        writeShort(0); // image position x,y = 0,0
+        writeShort(0);
+        writeShort(width); // image size
+        writeShort(height);
+        // packed fields
+        if (firstFrame) {
+            // no LCT - GCT is used for first (or only) frame
+            out.write(0);
+        } else {
+            // specify normal LCT
+            out.write(0x80 | // 1 local color table 1=yes
+                    0 | // 2 interlace - 0=no
+                    0 | // 3 sorted - 0=no
+                    0 | // 4-5 reserved
+                    palSize); // 6-8 size of color table
+        }
+    }
+
+    /**
+     * Writes Logical Screen Descriptor
+     */
+    private void writeLSD() throws IOException {
+        // logical screen size
+        writeShort(width);
+        writeShort(height);
+        // packed fields
+        out.write((0x80 | // 1 : global color table flag = 1 (gct used)
+                0x70 | // 2-4 : color resolution = 7
+                0x00 | // 5 : gct sort flag = 0
+                palSize)); // 6-8 : gct size
+
+        out.write(0); // background color index
+        out.write(0); // pixel aspect ratio - assume 1:1
+    }
+
+    /**
+     * Writes Netscape application extension to define repeat count.
+     */
+    private void writeNetscapeExt() throws IOException {
+        out.write(0x21); // extension introducer
+        out.write(0xff); // app extension label
+        out.write(11); // block size
+        writeString("NETSCAPE" + "2.0"); // app id + auth code
+        out.write(3); // sub-block size
+        out.write(1); // loop sub-block id
+        writeShort(repeat); // loop count (extra iterations, 0=repeat forever)
+        out.write(0); // block terminator
+    }
+
+    /**
+     * Writes color table
+     */
+    private void writePalette() throws IOException {
+        out.write(colorTab, 0, colorTab.length);
+        int n = (3 * 256) - colorTab.length;
+        for (int i = 0; i < n; i++) {
+            out.write(0);
+        }
+    }
+
+    /**
+     * Encodes and writes pixel data
+     */
+    private void writePixels() throws IOException {
+        LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
+        encoder.encode(out);
+    }
+
+    /**
+     * Write 16-bit value to output stream, LSB first
+     */
+    private void writeShort(int value) throws IOException {
+        out.write(value & 0xff);
+        out.write((value >> 8) & 0xff);
+    }
+
+    /**
+     * Writes string to output stream
+     */
+    private void writeString(String s) throws IOException {
+        for (int i = 0; i < s.length(); i++) {
+            out.write((byte) s.charAt(i));
+        }
+    }
+}
diff --git a/third_party/gif_encoder/src/main/java/com/bumptech/glide/gifencoder/LZWEncoder.java b/third_party/gif_encoder/src/main/java/com/bumptech/glide/gifencoder/LZWEncoder.java
new file mode 100644
index 0000000..0d61ef4
--- /dev/null
+++ b/third_party/gif_encoder/src/main/java/com/bumptech/glide/gifencoder/LZWEncoder.java
@@ -0,0 +1,297 @@
+package com.bumptech.glide.gifencoder;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+// ==============================================================================
+// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
+// K Weiner 12/00
+
+class LZWEncoder {
+
+    private static final int EOF = -1;
+
+    private int imgW, imgH;
+
+    private byte[] pixAry;
+
+    private int initCodeSize;
+
+    private int remaining;
+
+    private int curPixel;
+
+    // GIFCOMPR.C - GIF Image compression routines
+    //
+    // Lempel-Ziv compression based on 'compress'. GIF modifications by
+    // David Rowley (mgardi@watdcsu.waterloo.edu)
+
+    // General DEFINEs
+
+    static final int BITS = 12;
+
+    static final int HSIZE = 5003; // 80% occupancy
+
+    // GIF Image compression - modified 'compress'
+    //
+    // Based on: compress.c - File compression ala IEEE Computer, June 1984.
+    //
+    // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
+    // Jim McKie (decvax!mcvax!jim)
+    // Steve Davies (decvax!vax135!petsd!peora!srd)
+    // Ken Turkowski (decvax!decwrl!turtlevax!ken)
+    // James A. Woods (decvax!ihnp4!ames!jaw)
+    // Joe Orost (decvax!vax135!petsd!joe)
+
+    int n_bits; // number of bits/code
+
+    int maxbits = BITS; // user settable max # bits/code
+
+    int maxcode; // maximum code, given n_bits
+
+    int maxmaxcode = 1 << BITS; // should NEVER generate this code
+
+    int[] htab = new int[HSIZE];
+
+    int[] codetab = new int[HSIZE];
+
+    int hsize = HSIZE; // for dynamic table sizing
+
+    int free_ent = 0; // first unused entry
+
+    // block compression parameters -- after all codes are used up,
+    // and compression rate changes, start over.
+    boolean clear_flg = false;
+
+    // Algorithm: use open addressing double hashing (no chaining) on the
+    // prefix code / next character combination. We do a variant of Knuth's
+    // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
+    // secondary probe. Here, the modular division first probe is gives way
+    // to a faster exclusive-or manipulation. Also do block compression with
+    // an adaptive reset, whereby the code table is cleared when the compression
+    // ratio decreases, but after the table fills. The variable-length output
+    // codes are re-sized at this point, and a special CLEAR code is generated
+    // for the decompressor. Late addition: construct the table according to
+    // file size for noticeable speed improvement on small files. Please direct
+    // questions about this implementation to ames!jaw.
+
+    int g_init_bits;
+
+    int ClearCode;
+
+    int EOFCode;
+
+    // output
+    //
+    // Output the given code.
+    // Inputs:
+    // code: A n_bits-bit integer. If == -1, then EOF. This assumes
+    // that n_bits =< wordsize - 1.
+    // Outputs:
+    // Outputs code to the file.
+    // Assumptions:
+    // Chars are 8 bits long.
+    // Algorithm:
+    // Maintain a BITS character long buffer (so that 8 codes will
+    // fit in it exactly). Use the VAX insv instruction to insert each
+    // code in turn. When the buffer fills up empty it and start over.
+
+    int cur_accum = 0;
+
+    int cur_bits = 0;
+
+    int masks[] = {0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF,
+            0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF};
+
+    // Number of characters so far in this 'packet'
+    int a_count;
+
+    // Define the storage for the packet accumulator
+    byte[] accum = new byte[256];
+
+    // ----------------------------------------------------------------------------
+    LZWEncoder(int width, int height, byte[] pixels, int color_depth) {
+        imgW = width;
+        imgH = height;
+        pixAry = pixels;
+        initCodeSize = Math.max(2, color_depth);
+    }
+
+    // Add a character to the end of the current packet, and if it is 254
+    // characters, flush the packet to disk.
+    void char_out(byte c, OutputStream outs) throws IOException {
+        accum[a_count++] = c;
+        if (a_count >= 254)
+            flush_char(outs);
+    }
+
+    // Clear out the hash table
+
+    // table clear for block compress
+    void cl_block(OutputStream outs) throws IOException {
+        cl_hash(hsize);
+        free_ent = ClearCode + 2;
+        clear_flg = true;
+
+        output(ClearCode, outs);
+    }
+
+    // reset code table
+    void cl_hash(int hsize) {
+        for (int i = 0; i < hsize; ++i)
+            htab[i] = -1;
+    }
+
+    void compress(int init_bits, OutputStream outs) throws IOException {
+        int fcode;
+        int i /* = 0 */;
+        int c;
+        int ent;
+        int disp;
+        int hsize_reg;
+        int hshift;
+
+        // Set up the globals: g_init_bits - initial number of bits
+        g_init_bits = init_bits;
+
+        // Set up the necessary values
+        clear_flg = false;
+        n_bits = g_init_bits;
+        maxcode = MAXCODE(n_bits);
+
+        ClearCode = 1 << (init_bits - 1);
+        EOFCode = ClearCode + 1;
+        free_ent = ClearCode + 2;
+
+        a_count = 0; // clear packet
+
+        ent = nextPixel();
+
+        hshift = 0;
+        for (fcode = hsize; fcode < 65536; fcode *= 2)
+            ++hshift;
+        hshift = 8 - hshift; // set hash code range bound
+
+        hsize_reg = hsize;
+        cl_hash(hsize_reg); // clear hash table
+
+        output(ClearCode, outs);
+
+        outer_loop:
+        while ((c = nextPixel()) != EOF) {
+            fcode = (c << maxbits) + ent;
+            i = (c << hshift) ^ ent; // xor hashing
+
+            if (htab[i] == fcode) {
+                ent = codetab[i];
+                continue;
+            } else if (htab[i] >= 0) // non-empty slot
+            {
+                disp = hsize_reg - i; // secondary hash (after G. Knott)
+                if (i == 0)
+                    disp = 1;
+                do {
+                    if ((i -= disp) < 0)
+                        i += hsize_reg;
+
+                    if (htab[i] == fcode) {
+                        ent = codetab[i];
+                        continue outer_loop;
+                    }
+                } while (htab[i] >= 0);
+            }
+            output(ent, outs);
+            ent = c;
+            if (free_ent < maxmaxcode) {
+                codetab[i] = free_ent++; // code -> hashtable
+                htab[i] = fcode;
+            } else
+                cl_block(outs);
+        }
+        // Put out the final code.
+        output(ent, outs);
+        output(EOFCode, outs);
+    }
+
+    // ----------------------------------------------------------------------------
+    void encode(OutputStream os) throws IOException {
+        os.write(initCodeSize); // write "initial code size" byte
+
+        remaining = imgW * imgH; // reset navigation variables
+        curPixel = 0;
+
+        compress(initCodeSize + 1, os); // compress and write the pixel data
+
+        os.write(0); // write block terminator
+    }
+
+    // Flush the packet to disk, and reset the accumulator
+    void flush_char(OutputStream outs) throws IOException {
+        if (a_count > 0) {
+            outs.write(a_count);
+            outs.write(accum, 0, a_count);
+            a_count = 0;
+        }
+    }
+
+    final int MAXCODE(int n_bits) {
+        return (1 << n_bits) - 1;
+    }
+
+    // ----------------------------------------------------------------------------
+    // Return the next pixel from the image
+    // ----------------------------------------------------------------------------
+    private int nextPixel() {
+        if (remaining == 0)
+            return EOF;
+
+        --remaining;
+
+        byte pix = pixAry[curPixel++];
+
+        return pix & 0xff;
+    }
+
+    void output(int code, OutputStream outs) throws IOException {
+        cur_accum &= masks[cur_bits];
+
+        if (cur_bits > 0)
+            cur_accum |= (code << cur_bits);
+        else
+            cur_accum = code;
+
+        cur_bits += n_bits;
+
+        while (cur_bits >= 8) {
+            char_out((byte) (cur_accum & 0xff), outs);
+            cur_accum >>= 8;
+            cur_bits -= 8;
+        }
+
+        // If the next entry is going to be too big for the code size,
+        // then increase it, if possible.
+        if (free_ent > maxcode || clear_flg) {
+            if (clear_flg) {
+                maxcode = MAXCODE(n_bits = g_init_bits);
+                clear_flg = false;
+            } else {
+                ++n_bits;
+                if (n_bits == maxbits)
+                    maxcode = maxmaxcode;
+                else
+                    maxcode = MAXCODE(n_bits);
+            }
+        }
+
+        if (code == EOFCode) {
+            // At EOF, write the rest of the buffer.
+            while (cur_bits > 0) {
+                char_out((byte) (cur_accum & 0xff), outs);
+                cur_accum >>= 8;
+                cur_bits -= 8;
+            }
+
+            flush_char(outs);
+        }
+    }
+}
diff --git a/third_party/gif_encoder/src/main/java/com/bumptech/glide/gifencoder/NeuQuant.java b/third_party/gif_encoder/src/main/java/com/bumptech/glide/gifencoder/NeuQuant.java
new file mode 100644
index 0000000..4ae1a1c
--- /dev/null
+++ b/third_party/gif_encoder/src/main/java/com/bumptech/glide/gifencoder/NeuQuant.java
@@ -0,0 +1,506 @@
+package com.bumptech.glide.gifencoder;
+
+/*
+ * NeuQuant Neural-Net Quantization Algorithm
+ * ------------------------------------------
+ *
+ * Copyright (c) 1994 Anthony Dekker
+ *
+ * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
+ * "Kohonen neural networks for optimal colour quantization" in "Network:
+ * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
+ * the algorithm.
+ *
+ * Any party obtaining a copy of these files from the author, directly or
+ * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
+ * world-wide, paid up, royalty-free, nonexclusive right and license to deal in
+ * this software and documentation files (the "Software"), including without
+ * limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons who
+ * receive copies from any such party to do so, with the only requirement being
+ * that this copyright notice remain intact.
+ */
+
+// Ported to Java 12/00 K Weiner
+class NeuQuant {
+
+    protected static final int netsize = 256; /* number of colours used */
+
+    /* four primes near 500 - assume no image has a length so large */
+  /* that it is divisible by all four primes */
+    protected static final int prime1 = 499;
+
+    protected static final int prime2 = 491;
+
+    protected static final int prime3 = 487;
+
+    protected static final int prime4 = 503;
+
+    protected static final int minpicturebytes = (3 * prime4);
+
+  /* minimum size for input image */
+
+  /*
+   * Program Skeleton ---------------- [select samplefac in range 1..30] [read
+   * image from input file] pic = (unsigned char*) malloc(3*width*height);
+   * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output
+   * image header, using writecolourmap(f)] inxbuild(); write output image using
+   * inxsearch(b,g,r)
+   */
+
+  /*
+   * Network Definitions -------------------
+   */
+
+    protected static final int maxnetpos = (netsize - 1);
+
+    protected static final int netbiasshift = 4; /* bias for colour values */
+
+    protected static final int ncycles = 100; /* no. of learning cycles */
+
+    /* defs for freq and bias */
+    protected static final int intbiasshift = 16; /* bias for fractions */
+
+    protected static final int intbias = (((int) 1) << intbiasshift);
+
+    protected static final int gammashift = 10; /* gamma = 1024 */
+
+    protected static final int gamma = (((int) 1) << gammashift);
+
+    protected static final int betashift = 10;
+
+    protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */
+
+    protected static final int betagamma = (intbias << (gammashift - betashift));
+
+    /* defs for decreasing radius factor */
+    protected static final int initrad = (netsize >> 3); /*
+                                                         * for 256 cols, radius
+                                                         * starts
+                                                         */
+
+    protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
+
+    protected static final int radiusbias = (((int) 1) << radiusbiasshift);
+
+    protected static final int initradius = (initrad * radiusbias); /*
+                                                                   * and
+                                                                   * decreases
+                                                                   * by a
+                                                                   */
+
+    protected static final int radiusdec = 30; /* factor of 1/30 each cycle */
+
+    /* defs for decreasing alpha factor */
+    protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */
+
+    protected static final int initalpha = (((int) 1) << alphabiasshift);
+
+    protected int alphadec; /* biased by 10 bits */
+
+    /* radbias and alpharadbias used for radpower calculation */
+    protected static final int radbiasshift = 8;
+
+    protected static final int radbias = (((int) 1) << radbiasshift);
+
+    protected static final int alpharadbshift = (alphabiasshift + radbiasshift);
+
+    protected static final int alpharadbias = (((int) 1) << alpharadbshift);
+
+  /*
+   * Types and Global Variables --------------------------
+   */
+
+    protected byte[] thepicture; /* the input image itself */
+
+    protected int lengthcount; /* lengthcount = H*W*3 */
+
+    protected int samplefac; /* sampling factor 1..30 */
+
+    // typedef int pixel[4]; /* BGRc */
+    protected int[][] network; /* the network itself - [netsize][4] */
+
+    protected int[] netindex = new int[256];
+
+  /* for network lookup - really 256 */
+
+    protected int[] bias = new int[netsize];
+
+    /* bias and freq arrays for learning */
+    protected int[] freq = new int[netsize];
+
+    protected int[] radpower = new int[initrad];
+
+  /* radpower for precomputation */
+
+    /*
+     * Initialise network in range (0,0,0) to (255,255,255) and set parameters
+     * -----------------------------------------------------------------------
+     */
+    public NeuQuant(byte[] thepic, int len, int sample) {
+
+        int i;
+        int[] p;
+
+        thepicture = thepic;
+        lengthcount = len;
+        samplefac = sample;
+
+        network = new int[netsize][];
+        for (i = 0; i < netsize; i++) {
+            network[i] = new int[4];
+            p = network[i];
+            p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
+            freq[i] = intbias / netsize; /* 1/netsize */
+            bias[i] = 0;
+        }
+    }
+
+    public byte[] colorMap() {
+        byte[] map = new byte[3 * netsize];
+        int[] index = new int[netsize];
+        for (int i = 0; i < netsize; i++)
+            index[network[i][3]] = i;
+        int k = 0;
+        for (int i = 0; i < netsize; i++) {
+            int j = index[i];
+            map[k++] = (byte) (network[j][0]);
+            map[k++] = (byte) (network[j][1]);
+            map[k++] = (byte) (network[j][2]);
+        }
+        return map;
+    }
+
+    /*
+     * Insertion sort of network and building of netindex[0..255] (to do after
+     * unbias)
+     * -------------------------------------------------------------------------------
+     */
+    public void inxbuild() {
+
+        int i, j, smallpos, smallval;
+        int[] p;
+        int[] q;
+        int previouscol, startpos;
+
+        previouscol = 0;
+        startpos = 0;
+        for (i = 0; i < netsize; i++) {
+            p = network[i];
+            smallpos = i;
+            smallval = p[1]; /* index on g */
+      /* find smallest in i..netsize-1 */
+            for (j = i + 1; j < netsize; j++) {
+                q = network[j];
+                if (q[1] < smallval) { /* index on g */
+                    smallpos = j;
+                    smallval = q[1]; /* index on g */
+                }
+            }
+            q = network[smallpos];
+      /* swap p (i) and q (smallpos) entries */
+            if (i != smallpos) {
+                j = q[0];
+                q[0] = p[0];
+                p[0] = j;
+                j = q[1];
+                q[1] = p[1];
+                p[1] = j;
+                j = q[2];
+                q[2] = p[2];
+                p[2] = j;
+                j = q[3];
+                q[3] = p[3];
+                p[3] = j;
+            }
+      /* smallval entry is now in position i */
+            if (smallval != previouscol) {
+                netindex[previouscol] = (startpos + i) >> 1;
+                for (j = previouscol + 1; j < smallval; j++)
+                    netindex[j] = i;
+                previouscol = smallval;
+                startpos = i;
+            }
+        }
+        netindex[previouscol] = (startpos + maxnetpos) >> 1;
+        for (j = previouscol + 1; j < 256; j++)
+            netindex[j] = maxnetpos; /* really 256 */
+    }
+
+    /*
+     * Main Learning Loop ------------------
+     */
+    public void learn() {
+
+        int i, j, b, g, r;
+        int radius, rad, alpha, step, delta, samplepixels;
+        byte[] p;
+        int pix, lim;
+
+        if (lengthcount < minpicturebytes)
+            samplefac = 1;
+        alphadec = 30 + ((samplefac - 1) / 3);
+        p = thepicture;
+        pix = 0;
+        lim = lengthcount;
+        samplepixels = lengthcount / (3 * samplefac);
+        delta = samplepixels / ncycles;
+        alpha = initalpha;
+        radius = initradius;
+
+        rad = radius >> radiusbiasshift;
+        if (rad <= 1)
+            rad = 0;
+        for (i = 0; i < rad; i++)
+            radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
+
+        // fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad);
+
+        if (lengthcount < minpicturebytes)
+            step = 3;
+        else if ((lengthcount % prime1) != 0)
+            step = 3 * prime1;
+        else {
+            if ((lengthcount % prime2) != 0)
+                step = 3 * prime2;
+            else {
+                if ((lengthcount % prime3) != 0)
+                    step = 3 * prime3;
+                else
+                    step = 3 * prime4;
+            }
+        }
+
+        i = 0;
+        while (i < samplepixels) {
+            b = (p[pix + 0] & 0xff) << netbiasshift;
+            g = (p[pix + 1] & 0xff) << netbiasshift;
+            r = (p[pix + 2] & 0xff) << netbiasshift;
+            j = contest(b, g, r);
+
+            altersingle(alpha, j, b, g, r);
+            if (rad != 0)
+                alterneigh(rad, j, b, g, r); /* alter neighbours */
+
+            pix += step;
+            if (pix >= lim)
+                pix -= lengthcount;
+
+            i++;
+            if (delta == 0)
+                delta = 1;
+            if (i % delta == 0) {
+                alpha -= alpha / alphadec;
+                radius -= radius / radiusdec;
+                rad = radius >> radiusbiasshift;
+                if (rad <= 1)
+                    rad = 0;
+                for (j = 0; j < rad; j++)
+                    radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
+            }
+        }
+        // fprintf(stderr,"finished 1D learning: final alpha=%f
+        // !\n",((float)alpha)/initalpha);
+    }
+
+    /*
+     * Search for BGR values 0..255 (after net is unbiased) and return colour
+     * index
+     * ----------------------------------------------------------------------------
+     */
+    public int map(int b, int g, int r) {
+
+        int i, j, dist, a, bestd;
+        int[] p;
+        int best;
+
+        bestd = 1000; /* biggest possible dist is 256*3 */
+        best = -1;
+        i = netindex[g]; /* index on g */
+        j = i - 1; /* start at netindex[g] and work outwards */
+
+        while ((i < netsize) || (j >= 0)) {
+            if (i < netsize) {
+                p = network[i];
+                dist = p[1] - g; /* inx key */
+                if (dist >= bestd)
+                    i = netsize; /* stop iter */
+                else {
+                    i++;
+                    if (dist < 0)
+                        dist = -dist;
+                    a = p[0] - b;
+                    if (a < 0)
+                        a = -a;
+                    dist += a;
+                    if (dist < bestd) {
+                        a = p[2] - r;
+                        if (a < 0)
+                            a = -a;
+                        dist += a;
+                        if (dist < bestd) {
+                            bestd = dist;
+                            best = p[3];
+                        }
+                    }
+                }
+            }
+            if (j >= 0) {
+                p = network[j];
+                dist = g - p[1]; /* inx key - reverse dif */
+                if (dist >= bestd)
+                    j = -1; /* stop iter */
+                else {
+                    j--;
+                    if (dist < 0)
+                        dist = -dist;
+                    a = p[0] - b;
+                    if (a < 0)
+                        a = -a;
+                    dist += a;
+                    if (dist < bestd) {
+                        a = p[2] - r;
+                        if (a < 0)
+                            a = -a;
+                        dist += a;
+                        if (dist < bestd) {
+                            bestd = dist;
+                            best = p[3];
+                        }
+                    }
+                }
+            }
+        }
+        return (best);
+    }
+
+    public byte[] process() {
+        learn();
+        unbiasnet();
+        inxbuild();
+        return colorMap();
+    }
+
+    /*
+     * Unbias network to give byte values 0..255 and record position i to prepare
+     * for sort
+     * -----------------------------------------------------------------------------------
+     */
+    public void unbiasnet() {
+
+        int i, j;
+
+        for (i = 0; i < netsize; i++) {
+            network[i][0] >>= netbiasshift;
+            network[i][1] >>= netbiasshift;
+            network[i][2] >>= netbiasshift;
+            network[i][3] = i; /* record colour no */
+        }
+    }
+
+    /*
+     * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in
+     * radpower[|i-j|]
+     * ---------------------------------------------------------------------------------
+     */
+    protected void alterneigh(int rad, int i, int b, int g, int r) {
+
+        int j, k, lo, hi, a, m;
+        int[] p;
+
+        lo = i - rad;
+        if (lo < -1)
+            lo = -1;
+        hi = i + rad;
+        if (hi > netsize)
+            hi = netsize;
+
+        j = i + 1;
+        k = i - 1;
+        m = 1;
+        while ((j < hi) || (k > lo)) {
+            a = radpower[m++];
+            if (j < hi) {
+                p = network[j++];
+                try {
+                    p[0] -= (a * (p[0] - b)) / alpharadbias;
+                    p[1] -= (a * (p[1] - g)) / alpharadbias;
+                    p[2] -= (a * (p[2] - r)) / alpharadbias;
+                } catch (Exception e) {
+                } // prevents 1.3 miscompilation
+            }
+            if (k > lo) {
+                p = network[k--];
+                try {
+                    p[0] -= (a * (p[0] - b)) / alpharadbias;
+                    p[1] -= (a * (p[1] - g)) / alpharadbias;
+                    p[2] -= (a * (p[2] - r)) / alpharadbias;
+                } catch (Exception e) {
+                }
+            }
+        }
+    }
+
+    /*
+     * Move neuron i towards biased (b,g,r) by factor alpha
+     * ----------------------------------------------------
+     */
+    protected void altersingle(int alpha, int i, int b, int g, int r) {
+
+    /* alter hit neuron */
+        int[] n = network[i];
+        n[0] -= (alpha * (n[0] - b)) / initalpha;
+        n[1] -= (alpha * (n[1] - g)) / initalpha;
+        n[2] -= (alpha * (n[2] - r)) / initalpha;
+    }
+
+    /*
+     * Search for biased BGR values ----------------------------
+     */
+    protected int contest(int b, int g, int r) {
+
+    /* finds closest neuron (min dist) and updates freq */
+    /* finds best neuron (min dist-bias) and returns position */
+    /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
+    /* bias[i] = gamma*((1/netsize)-freq[i]) */
+
+        int i, dist, a, biasdist, betafreq;
+        int bestpos, bestbiaspos, bestd, bestbiasd;
+        int[] n;
+
+        bestd = ~(((int) 1) << 31);
+        bestbiasd = bestd;
+        bestpos = -1;
+        bestbiaspos = bestpos;
+
+        for (i = 0; i < netsize; i++) {
+            n = network[i];
+            dist = n[0] - b;
+            if (dist < 0)
+                dist = -dist;
+            a = n[1] - g;
+            if (a < 0)
+                a = -a;
+            dist += a;
+            a = n[2] - r;
+            if (a < 0)
+                a = -a;
+            dist += a;
+            if (dist < bestd) {
+                bestd = dist;
+                bestpos = i;
+            }
+            biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
+            if (biasdist < bestbiasd) {
+                bestbiasd = biasdist;
+                bestbiaspos = i;
+            }
+            betafreq = (freq[i] >> betashift);
+            freq[i] -= betafreq;
+            bias[i] += (betafreq << gammashift);
+        }
+        freq[bestpos] += beta;
+        bias[bestpos] -= betagamma;
+        return (bestbiaspos);
+    }
+}
diff --git a/third_party/pom.xml b/third_party/pom.xml
deleted file mode 100644
index 5e73b52..0000000
--- a/third_party/pom.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.bumptech.glide</groupId>
-    <artifactId>glide-parent</artifactId>
-    <version>3.3.0-SNAPSHOT</version>
-    <relativePath>../pom.xml</relativePath>
-  </parent>
-
-  <artifactId>glide-third-party</artifactId>
-  <packaging>pom</packaging>
-  <name>Glide Third Party</name>
-  
-  <modules>
-    <module>gif_decoder</module>
-  </modules>
-</project>
diff --git a/update_files.sh b/update_files.sh
new file mode 100755
index 0000000..c6ad448
--- /dev/null
+++ b/update_files.sh
@@ -0,0 +1,82 @@
+#!/bin/bash -e
+# Pulls down the version of Glide specified by tag/branch, merges it with
+# the existing local version of Glide, and removes unneeded tests, source
+# directories, scripts, and build files.
+#
+# Usage: ./update_files.sh [glide_brach_name|glide_tag_name|glide_commit]
+#
+# WARNING: This script will rm -rf files in the directory in
+# which it is run!
+
+ANDROID_BRANCH_NAME=$(repo info . | sed -n 's/Current revision: \(.*\)/\1/p')
+
+# Validate that we were given something to checkout from Glide's remote.
+if [ $# -ne 1 ]
+then
+  echo "Usage: ./update_files.sh [glide_brach_name|glide_tag_name|glide_commit]"
+  exit 1
+fi
+
+GLIDE_BRANCH=$1
+
+# We may have already added bump's remote.
+if ! git remote | grep bump > /dev/null;
+then
+  git remote add bump https://github.com/bumptech/glide.git
+fi
+
+# Validate that the thing we were given to checkout exists and fetch it if it
+# does.
+git fetch bump ${GLIDE_BRANCH} || exit 1
+
+# Remove the existing disk cache source so it doesn't conflict with Glide's
+# submodule.
+rm -rf third_party/disklrucache
+
+# Switch to the branch in Android we want to update, sync and merge.
+git checkout ${ANDROID_BRANCH_NAME}
+repo sync .
+# FETCH_HEAD defined by the fetch of the tag/branch above
+git merge FETCH_HEAD || true
+
+# Remove source/build directories we don't care about.
+git rm -rf samples || true
+git rm -rf integration || true
+git rm -rf static || true
+git rm -rf glide || true
+git rm -rf .idea || true
+
+# Remove test directories we don't care about.
+git rm -rf library/src/androidTest || true
+git rm -rf third_party/gif_decoder/src/androidTest || true
+git rm -rf third_party/gif_encoder/src/androidTest || true
+
+# Special case disklrucache because it's a submodule that we can't keep with
+# repo.
+git submodule deinit third_party/disklrucache
+git rm -rf third_party/disklrucache
+rm -rf third_party/disklrucache
+
+# Clone outside of the normal path to avoid conflicts with the submodule.
+REMOTE_DISK_PATH=third_party/remote_disklrucache
+git clone https://github.com/sjudd/disklrucache $REMOTE_DISK_PATH
+# Remove tests we don't care about.
+rm -rf $REMOTE_DISK_PATH/src/test
+# Remove git related things to avoid re-adding a submodule.
+rm -rf $REMOTE_DISK_PATH/.git
+rm -rf $REMOTE_DISK_PATH/.gitmodule
+# Copy the safe source only code back into the appropriate path.
+mv $REMOTE_DISK_PATH third_party/disklrucache
+git add third_party/disklrucache
+
+# Remove build/static analysis related files we don't care about.
+find . -name "*gradle*" | xargs -r git rm -rf
+find . -name "*checkstyle*.xml" | xargs -r git rm -rf
+find . -name "*pmd*.xml" | xargs -r git rm -rf
+find . -name "*findbugs*.xml" | xargs -r git rm -rf
+find . -name "*.iml" | xargs -r git rm -rf
+
+# FETCH_HEAD defined by the fetch of the tag/branch above
+GIT_SHA=$(git rev-parse FETCH_HEAD)
+echo "Merged bump ${GLIDE_BRANCH} at revision ${GIT_SHA}"
+echo "Now fix any merge conflicts, commit, and run: git push goog ${ANDROID_BRANCH_NAME}"