Make Volley an optional dependency
diff --git a/.gitignore b/.gitignore
index eee00ad..07f3f11 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,8 @@
 samples/flickr/bin
 samples/flickr/local.properties
 samples/flickr/target
+integration/volley/target/**
+**/local.properties
 *.keystore
 **/.idea/*
 .settings
diff --git a/README.md b/README.md
index 4a65092..6310d61 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,17 @@
 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 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. Glide includes a flexible api allowing it to
+plug in to almost any network stack. By default Glide uses a custom HttpUrlConnection based stack, but also includes a
+utility library to plug in to Google's Volley project 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.
+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 to use the 3.0 alpha branch, use Gradle:
+You can download a jar from GitHub's [release page](https://github.com/bumptech/glide/releases) or to use the 3.0 alpha
+branch, use Gradle:
 
 ```groovy
 repositories {
@@ -38,12 +43,41 @@
 ```xml
 <dependency>
   <groupId>com.github.bumptech.glide</groupId>
-  <artifactId>glide</artifactId>
+  <artifactId>library</artifactId>
   <version>3.3.0-SNAPSHOT</version>
   <type>aar</type>
 </dependency>
 ```
 
+Volley
+-------
+Volley is now an optional dependency that can be included via a utility library. More utility libraries for other
+projects will hopefully be coming soon. To use the utility library with Gradle, add:
+
+```groovy
+dependencies {
+    compile group: 'com.github.bumptech.glide', name:'volley', version:'3.3.0-SNAPSHOT', changing:true
+    compile 'com.mcxiaoke.volley:library:1.0.+'
+}
+```
+
+Or with maven:
+
+```xml
+<dependency>
+    <groupId>com.github.bumptech.glide</groupId>
+    <artifactId>volley</artifactId>
+    <version>3.3.0-SNAPSHOT</version>
+    <type>aar</type>
+</dependency>
+<dependency>
+    <groupId>com.mcxiaoke.volley</groupId>
+    <artifactId>library</artifactId>
+    <version>1.0.5</version>
+    <type>aar</type>
+</dependency>
+```
+
 How do I use Glide?
 -------------------
 Checkout the GitHub wiki for pages on a variety of topics and links to javadocs.
diff --git a/build.gradle b/build.gradle
index 263cb08..0180772 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,7 +11,9 @@
     gradleVersion = '1.10'
 }
 
-evaluationDependsOnChildren();
+evaluationDependsOn(":integration:volley")
+evaluationDependsOn(":third_party:gif_decoder")
+evaluationDependsOn(":library")
 
 def getAndroidSdkDirectory() {
   project("library").android.sdkDirectory
@@ -44,7 +46,7 @@
     task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) {
 
         // Get the variant from each subproject that matches our current variant's name.
-        def childEquivalentChildVariants = getAndroidChildren().collect { project ->
+        def childEquivalentChildVariants = getAndroidChildren().collect { project -> 
             project.android.libraryVariants.findAll { type -> type.name == variant.name }
         }.sum()
 
diff --git a/integration/pom.xml b/integration/pom.xml
new file mode 100644
index 0000000..66241c2
--- /dev/null
+++ b/integration/pom.xml
@@ -0,0 +1,19 @@
+<?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.github.bumptech.glide</groupId>
+    <artifactId>glide-parent</artifactId>
+    <version>3.3.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <artifactId>glide-integration</artifactId>
+  <packaging>pom</packaging>
+  <name>Glide Integration</name>
+  
+  <modules>
+    <module>volley</module>
+  </modules>
+</project>
diff --git a/integration/volley/AndroidManifest.xml b/integration/volley/AndroidManifest.xml
new file mode 100644
index 0000000..b644aab
--- /dev/null
+++ b/integration/volley/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.bumptech.glide.volley"
+          android:versionCode="1"
+          android:versionName="1.0.0" >
+  <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="19" />
+  <application />
+</manifest>
diff --git a/integration/volley/build.gradle b/integration/volley/build.gradle
new file mode 100644
index 0000000..c8ab369
--- /dev/null
+++ b/integration/volley/build.gradle
@@ -0,0 +1,33 @@
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:0.11.2'
+    }
+}
+
+apply plugin: 'android-library'
+
+repositories {
+  mavenCentral()
+}
+
+dependencies {
+    compile project(':library')
+
+    compile 'com.mcxiaoke.volley:library:1.0.+'
+}
+
+android {
+    compileSdkVersion 19
+    buildToolsVersion = '19.1.0'
+    sourceSets {
+        main {
+            java.srcDirs         = ['src/main/java']
+            manifest.srcFile 'AndroidManifest.xml'
+        }
+    }
+}
+
+apply from: "https://raw.githubusercontent.com/mcxiaoke/gradle-mvn-push/master/gradle-mvn-push.gradle"
diff --git a/integration/volley/gradle.properties b/integration/volley/gradle.properties
new file mode 100644
index 0000000..0f9e81b
--- /dev/null
+++ b/integration/volley/gradle.properties
@@ -0,0 +1,6 @@
+POM_NAME=Glide Volley Integration
+POM_ARTIFACT_ID=glide-volley-integration
+POM_PACKAGING=aar
+POM_LICENCE_NAME=The Apache Software License, Version 2.0
+POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
+POM_LICENCE_DIST=repo
diff --git a/integration/volley/lint.xml b/integration/volley/lint.xml
new file mode 100644
index 0000000..d9d6c9f
--- /dev/null
+++ b/integration/volley/lint.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lint>
+    <issue id="AllowBackup" severity="ignore" />
+</lint>
diff --git a/integration/volley/pom.xml b/integration/volley/pom.xml
new file mode 100644
index 0000000..f1bf872
--- /dev/null
+++ b/integration/volley/pom.xml
@@ -0,0 +1,30 @@
+<?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.github.bumptech.glide</groupId>
+    <artifactId>glide-integration</artifactId>
+    <version>3.3.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <artifactId>glide-volley-integration</artifactId>
+  <packaging>aar</packaging>
+  <name>Glide Volley Integration</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.github.bumptech.glide</groupId>
+      <artifactId>library</artifactId>
+      <version>3.3.0-SNAPSHOT</version>
+      <type>aar</type>
+    </dependency>
+    <dependency>
+      <groupId>com.mcxiaoke.volley</groupId>
+      <artifactId>library</artifactId>
+      <version>1.0.4</version>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/integration/volley/project.properties b/integration/volley/project.properties
new file mode 100644
index 0000000..d226f51
--- /dev/null
+++ b/integration/volley/project.properties
@@ -0,0 +1,6 @@
+target=android-19
+
+# https://code.google.com/p/android/issues/detail?id=40487
+renderscript.opt.level=O0
+
+android.library=true
diff --git a/library/src/main/java/com/bumptech/glide/volley/RequestQueueWrapper.java b/integration/volley/src/main/java/com/bumptech/glide/volley/RequestQueueWrapper.java
similarity index 91%
rename from library/src/main/java/com/bumptech/glide/volley/RequestQueueWrapper.java
rename to integration/volley/src/main/java/com/bumptech/glide/volley/RequestQueueWrapper.java
index 10333b9..b31f44b 100644
--- a/library/src/main/java/com/bumptech/glide/volley/RequestQueueWrapper.java
+++ b/integration/volley/src/main/java/com/bumptech/glide/volley/RequestQueueWrapper.java
@@ -12,10 +12,15 @@
 import com.android.volley.toolbox.HttpStack;
 import com.android.volley.toolbox.HurlStack;
 import com.android.volley.toolbox.NoCache;
+import com.android.volley.toolbox.Volley;
 import com.bumptech.glide.load.engine.cache.DiskCache;
 
 import static android.content.pm.PackageManager.NameNotFoundException;
 
+/**
+ * A clone of the {@link Volley#newRequestQueue(Context)} allowing the user to set a disk cache and defaulting to
+ * no disk cache.
+ */
 public class RequestQueueWrapper {
 
     public static RequestQueue getRequestQueue(Context context) {
diff --git a/library/src/main/java/com/bumptech/glide/volley/VolleyDiskCacheWrapper.java b/integration/volley/src/main/java/com/bumptech/glide/volley/VolleyDiskCacheWrapper.java
similarity index 100%
rename from library/src/main/java/com/bumptech/glide/volley/VolleyDiskCacheWrapper.java
rename to integration/volley/src/main/java/com/bumptech/glide/volley/VolleyDiskCacheWrapper.java
diff --git a/library/src/main/java/com/bumptech/glide/volley/VolleyRequestFuture.java b/integration/volley/src/main/java/com/bumptech/glide/volley/VolleyRequestFuture.java
similarity index 100%
rename from library/src/main/java/com/bumptech/glide/volley/VolleyRequestFuture.java
rename to integration/volley/src/main/java/com/bumptech/glide/volley/VolleyRequestFuture.java
diff --git a/library/src/main/java/com/bumptech/glide/volley/VolleyStreamFetcher.java b/integration/volley/src/main/java/com/bumptech/glide/volley/VolleyStreamFetcher.java
similarity index 100%
rename from library/src/main/java/com/bumptech/glide/volley/VolleyStreamFetcher.java
rename to integration/volley/src/main/java/com/bumptech/glide/volley/VolleyStreamFetcher.java
diff --git a/library/src/main/java/com/bumptech/glide/volley/VolleyUrlLoader.java b/integration/volley/src/main/java/com/bumptech/glide/volley/VolleyUrlLoader.java
similarity index 100%
rename from library/src/main/java/com/bumptech/glide/volley/VolleyUrlLoader.java
rename to integration/volley/src/main/java/com/bumptech/glide/volley/VolleyUrlLoader.java
diff --git a/library/build.gradle b/library/build.gradle
index 0a8ed3e..5d3507e 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -73,7 +73,7 @@
 }
 
 def getJarName() {
-    return "glide-${getVersionName()}.jar"
+    "glide-${getVersionName()}.jar"
 }
 
 // Build a jar, from http://stackoverflow.com/a/19037807/1002054.
diff --git a/library/gradle.properties b/library/gradle.properties
index afe7543..6f3b842 100644
--- a/library/gradle.properties
+++ b/library/gradle.properties
@@ -1,3 +1,3 @@
 POM_NAME=Glide Library
-POM_ARTIFACT_ID=glide
+POM_ARTIFACT_ID=library
 POM_PACKAGING=aar
diff --git a/library/pom.xml b/library/pom.xml
index 9402581..63154ab 100644
--- a/library/pom.xml
+++ b/library/pom.xml
@@ -9,7 +9,7 @@
     <relativePath>../pom.xml</relativePath>
   </parent>
 
-  <artifactId>glide</artifactId>
+  <artifactId>library</artifactId>
   <packaging>aar</packaging>
   <name>Glide Library</name>
 
@@ -56,9 +56,10 @@
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>com.bumptech.glide</groupId>
+      <groupId>com.github.bumptech.glide</groupId>
       <artifactId>glide-gif-decoder</artifactId>
       <version>3.3.0-SNAPSHOT</version>
+      <type>aar</type>
     </dependency>
   </dependencies>
 </project>
diff --git a/library/src/main/java/com/bumptech/glide/Glide.java b/library/src/main/java/com/bumptech/glide/Glide.java
index cbe3200..1ab0d00 100644
--- a/library/src/main/java/com/bumptech/glide/Glide.java
+++ b/library/src/main/java/com/bumptech/glide/Glide.java
@@ -14,7 +14,6 @@
 import android.util.Log;
 import android.view.View;
 import android.widget.ImageView;
-import com.android.volley.RequestQueue;
 import com.bumptech.glide.load.engine.Engine;
 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
 import com.bumptech.glide.load.engine.cache.DiskCache;
@@ -29,6 +28,7 @@
 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.StreamFileLoader;
 import com.bumptech.glide.load.model.stream.StreamModelLoader;
 import com.bumptech.glide.load.model.stream.StreamResourceLoader;
@@ -58,7 +58,6 @@
 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 java.io.File;
 import java.io.InputStream;
@@ -81,7 +80,6 @@
     private static Glide GLIDE;
 
     private final GenericLoaderFactory loaderFactory = new GenericLoaderFactory();
-    private final RequestQueue requestQueue;
     private final Engine engine;
     private final BitmapPool bitmapPool;
     private final MemoryCache memoryCache;
@@ -170,10 +168,8 @@
         GLIDE = null;
     }
 
-    Glide(Engine engine, RequestQueue requestQueue, MemoryCache memoryCache, BitmapPool bitmapPool,
-            Context context) {
+    Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, Context context) {
         this.engine = engine;
-        this.requestQueue = requestQueue;
         this.bitmapPool = bitmapPool;
         this.memoryCache = memoryCache;
 
@@ -201,7 +197,7 @@
         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());
 
         transcoderFactory.register(Bitmap.class, BitmapDrawable.class,
                 new BitmapDrawableTranscoder(context.getResources(), bitmapPool));
@@ -258,13 +254,6 @@
     }
 
     /**
-     * Returns the {@link RequestQueue} Glide is using to fetch images over http/https.
-     */
-    public RequestQueue getRequestQueue() {
-        return requestQueue;
-    }
-
-    /**
      * Clears as much memory as possible.
      *
      * @see ComponentCallbacks2#onLowMemory()
diff --git a/library/src/main/java/com/bumptech/glide/GlideBuilder.java b/library/src/main/java/com/bumptech/glide/GlideBuilder.java
index 38c130e..b68f817 100644
--- a/library/src/main/java/com/bumptech/glide/GlideBuilder.java
+++ b/library/src/main/java/com/bumptech/glide/GlideBuilder.java
@@ -2,7 +2,6 @@
 
 import android.content.Context;
 import android.os.Build;
-import com.android.volley.RequestQueue;
 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,13 +13,11 @@
 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;
 
 public class GlideBuilder {
-    private RequestQueue requestQueue;
     private Context context;
     private Engine engine;
     private BitmapPool bitmapPool;
@@ -33,11 +30,6 @@
         this.context = context.getApplicationContext();
     }
 
-    public GlideBuilder setRequestQueue(RequestQueue requestQueue) {
-        this.requestQueue = requestQueue;
-        return this;
-    }
-
     public GlideBuilder setBitmapPool(BitmapPool bitmapPool) {
         this.bitmapPool = bitmapPool;
         return this;
@@ -77,10 +69,6 @@
             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) {
@@ -108,6 +96,6 @@
             engine = new Engine(memoryCache, diskCache, resizeService, diskCacheService);
         }
 
-        return new Glide(engine, requestQueue, memoryCache, bitmapPool, context);
+        return new Glide(engine, memoryCache, bitmapPool, context);
     }
 }
\ No newline at end of file
diff --git a/library/src/main/java/com/bumptech/glide/RequestManager.java b/library/src/main/java/com/bumptech/glide/RequestManager.java
index 88a8d1f..a40dd02 100644
--- a/library/src/main/java/com/bumptech/glide/RequestManager.java
+++ b/library/src/main/java/com/bumptech/glide/RequestManager.java
@@ -8,9 +8,11 @@
 import android.support.v4.app.FragmentActivity;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.ResourceEncoder;
+import com.bumptech.glide.load.data.HttpUrlFetcher;
 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.HttpUrlGlideUrlLoader;
 import com.bumptech.glide.load.model.stream.MediaStoreStreamLoader;
 import com.bumptech.glide.load.model.stream.StreamByteArrayLoader;
 import com.bumptech.glide.load.model.stream.StreamFileLoader;
@@ -21,7 +23,6 @@
 import com.bumptech.glide.manager.ConnectivityMonitor;
 import com.bumptech.glide.manager.ConnectivityMonitorFactory;
 import com.bumptech.glide.manager.RequestTracker;
-import com.bumptech.glide.volley.VolleyUrlLoader;
 
 import java.io.File;
 import java.io.InputStream;
@@ -268,8 +269,7 @@
 
     /**
      * Use the {@link ModelLoaderFactory} currently registered for {@link URL} to load the image represented by the
-     * given {@link URL}. Defaults to {@link com.bumptech.glide.volley.VolleyUrlLoader.Factory} and
-     * {@link VolleyUrlLoader} to load the given model.
+     * given {@link URL}. Defaults to {@link HttpUrlGlideUrlLoader} and {@link HttpUrlFetcher} to load the given model.
      *
      * @see #using(StreamModelLoader)
      *
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..ed6f40a
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/data/HttpUrlFetcher.java
@@ -0,0 +1,88 @@
+package com.bumptech.glide.load.data;
+
+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.URL;
+
+public class HttpUrlFetcher implements DataFetcher<InputStream> {
+    private static final HttpUrlConnectionFactory DEFAULT_FACTORY = new DefaultHttpUrlConnectionFactory();
+    private GlideUrl glideUrl;
+    private final HttpUrlConnectionFactory factory;
+    private HttpURLConnection urlConnection;
+    private boolean isConnected;
+    private volatile boolean isCancelled;
+
+    public HttpUrlFetcher(GlideUrl glideUrl) {
+        this(glideUrl, DEFAULT_FACTORY);
+    }
+
+    HttpUrlFetcher(GlideUrl glideUrl, HttpUrlConnectionFactory factory) {
+        this.glideUrl = glideUrl;
+        this.factory = factory;
+    }
+
+    @Override
+    public InputStream loadData(Priority priority) throws Exception {
+        urlConnection = factory.build(glideUrl.toURL());
+        urlConnection.setConnectTimeout(2500);
+        urlConnection.setReadTimeout(2500);
+        urlConnection.setUseCaches(false);
+        urlConnection.setDoInput(true);
+
+        // Connect explicitly to avoid errors in decoders if connection fails.
+        urlConnection.connect();
+        synchronized (this) {
+            isConnected = true;
+        }
+        if (isCancelled) {
+            return null;
+        }
+        final int statusCode = urlConnection.getResponseCode();
+        if (statusCode / 100 == 2) {
+            return urlConnection.getInputStream();
+        } 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 (urlConnection != null) {
+            urlConnection.disconnect();
+        }
+    }
+
+    @Override
+    public String getId() {
+        return glideUrl.toString();
+    }
+
+    @Override
+    public void cancel() {
+        synchronized (this) {
+            if (!isConnected && urlConnection != null) {
+                urlConnection.disconnect();
+            }
+        }
+        isCancelled = true;
+    }
+
+    interface HttpUrlConnectionFactory {
+        public 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/model/stream/HttpUrlGlideUrlLoader.java b/library/src/main/java/com/bumptech/glide/load/model/stream/HttpUrlGlideUrlLoader.java
new file mode 100644
index 0000000..ef3bfd6
--- /dev/null
+++ b/library/src/main/java/com/bumptech/glide/load/model/stream/HttpUrlGlideUrlLoader.java
@@ -0,0 +1,31 @@
+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.ModelLoader;
+import com.bumptech.glide.load.model.ModelLoaderFactory;
+
+import java.io.InputStream;
+
+public class HttpUrlGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
+
+    public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
+        @Override
+        public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
+            return new HttpUrlGlideUrlLoader();
+        }
+
+        @Override
+        public void teardown() {
+
+        }
+    }
+
+    @Override
+    public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
+        return new HttpUrlFetcher(model);
+    }
+}
diff --git a/library/src/test/java/com/bumptech/glide/GlideTest.java b/library/src/test/java/com/bumptech/glide/GlideTest.java
index 16d6f5d..bfc8ebc 100644
--- a/library/src/test/java/com/bumptech/glide/GlideTest.java
+++ b/library/src/test/java/com/bumptech/glide/GlideTest.java
@@ -13,10 +13,6 @@
 import android.os.ParcelFileDescriptor;
 import android.view.ViewGroup;
 import android.widget.ImageView;
-import com.android.volley.Network;
-import com.android.volley.NetworkResponse;
-import com.android.volley.RequestQueue;
-import com.android.volley.toolbox.NoCache;
 import com.bumptech.glide.load.Encoder;
 import com.bumptech.glide.load.ResourceDecoder;
 import com.bumptech.glide.load.ResourceEncoder;
@@ -39,8 +35,6 @@
 import com.bumptech.glide.request.RequestListener;
 import com.bumptech.glide.request.target.Target;
 import com.bumptech.glide.tests.GlideShadowLooper;
-import com.bumptech.glide.volley.VolleyRequestFuture;
-import com.bumptech.glide.volley.VolleyUrlLoader;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -62,12 +56,10 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 
 import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyObject;
@@ -118,45 +110,21 @@
             }
         });
 
-        // Make sure Volley does not actually perform any network requests.
-        Network network = mock(Network.class);
-        when(network.performRequest(any(com.android.volley.Request.class)))
-                .thenAnswer(new Answer<Object>() {
-                    @Override
-                    public Object answer(InvocationOnMock invocation) throws Throwable {
-                        return new NetworkResponse(new byte[0]);
-                    }
-                });
-
-        RequestQueue requestQueue = new RequestQueue(new NoCache(), network);
-        requestQueue.start();
         Glide.setup(new GlideBuilder(Robolectric.application)
                 .setMemoryCache(mock(MemoryCache.class))
                 .setDiskCache(mock(DiskCache.class))
                 .setResizeService(service)
-                .setDiskCacheService(service)
-                .setRequestQueue(requestQueue));
+                .setDiskCacheService(service));
+        DataFetcher<InputStream> mockStreamFetcher = mock(DataFetcher.class);
+        when(mockStreamFetcher.getId()).thenReturn("fakeId");
+        when(mockStreamFetcher.loadData(any(Priority.class))).thenReturn(new ByteArrayInputStream(new byte[0]));
+        ModelLoader<GlideUrl, InputStream> mockUrlLoader = mock(ModelLoader.class);
+        when(mockUrlLoader.getResourceFetcher(any(GlideUrl.class), anyInt(), anyInt())).thenReturn(mockStreamFetcher);
+        ModelLoaderFactory<GlideUrl, InputStream> mockUrlLoaderFactory = mock(ModelLoaderFactory.class);
+        when(mockUrlLoaderFactory.build(any(Context.class), any(GenericLoaderFactory.class)))
+                .thenReturn(mockUrlLoader);
 
-        // Sleep to avoid blocking the main thread while waiting for Volley's background thread to complete
-        // and for the result to be posted back to the main thread.
-        VolleyUrlLoader.FutureFactory futureFactory = mock(VolleyUrlLoader.FutureFactory.class);
-        VolleyRequestFuture<InputStream> future = new VolleyRequestFuture<InputStream>() {
-            @Override
-            public InputStream get() throws InterruptedException, ExecutionException {
-                for (int i = 0; i < 10 && !isDone(); i++) {
-                    Thread.sleep(10);
-                    // Make sure the result callback posted on the main thread actually runs.
-                    Robolectric.runUiThreadTasks();
-                }
-                if (!isDone()) {
-                    fail("Failed to get response from Volley in time");
-                }
-                return super.get();
-            }
-        };
-        when(futureFactory.build()).thenReturn(future);
-        Glide.get(getContext()).register(GlideUrl.class, InputStream.class,
-                new VolleyUrlLoader.Factory(Glide.get(getContext()).getRequestQueue(), futureFactory));
+        Glide.get(getContext()).register(GlideUrl.class, InputStream.class, mockUrlLoaderFactory);
     }
 
     @After
diff --git a/library/src/test/java/com/bumptech/glide/load/data/HttpUrlFetcherTest.java b/library/src/test/java/com/bumptech/glide/load/data/HttpUrlFetcherTest.java
new file mode 100644
index 0000000..207fda9
--- /dev/null
+++ b/library/src/test/java/com/bumptech/glide/load/data/HttpUrlFetcherTest.java
@@ -0,0 +1,158 @@
+package com.bumptech.glide.load.data;
+
+import com.bumptech.glide.Priority;
+import com.bumptech.glide.load.model.GlideUrl;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.concurrent.CountDownLatch;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class HttpUrlFetcherTest {
+    private HttpURLConnection urlConnection;
+    private HttpUrlFetcher fetcher;
+    private GlideUrl glideUrl;
+
+    @Before
+    public void setUp() throws IOException {
+        urlConnection =  mock(HttpURLConnection.class);
+        URL url = new URL("http://www.google.com");
+        HttpUrlFetcher.HttpUrlConnectionFactory factory = mock(HttpUrlFetcher.HttpUrlConnectionFactory.class);
+        when(factory.build(eq(url))).thenReturn(urlConnection);
+        glideUrl = mock(GlideUrl.class);
+        when(glideUrl.toURL()).thenReturn(url);
+        fetcher = new HttpUrlFetcher(glideUrl, factory);
+        when(urlConnection.getResponseCode()).thenReturn(200);
+    }
+
+    @Test
+    public void testReturnsModelAsString() {
+        final String expected = "fakeId";
+        when(glideUrl.toString()).thenReturn(expected);
+        assertEquals(expected, fetcher.getId());
+    }
+
+
+    @Test
+    public void testSetsReadTimeout() throws Exception {
+        fetcher.loadData(Priority.HIGH);
+        verify(urlConnection).setReadTimeout(eq(2500));
+    }
+
+    @Test
+    public void testSetsConnectTimeout() throws Exception {
+        fetcher.loadData(Priority.IMMEDIATE);
+        verify(urlConnection).setConnectTimeout(eq(2500));
+    }
+
+    @Test
+    public void testReturnsInputStreamOnStatusOk() throws Exception {
+        InputStream expected = new ByteArrayInputStream(new byte[0]);
+        when(urlConnection.getResponseCode()).thenReturn(200);
+        when(urlConnection.getInputStream()).thenReturn(expected);
+
+        assertEquals(expected, fetcher.loadData(Priority.NORMAL));
+    }
+
+    @Test(expected = IOException.class)
+    public void testThrowsIfStatusCodeIsNegativeOne() throws Exception {
+        when(urlConnection.getResponseCode()).thenReturn(-1);
+        fetcher.loadData(Priority.HIGH);
+    }
+
+    @Test(expected = IOException.class)
+    public void testThrowsIfStatusCodeIs300() throws Exception {
+        when(urlConnection.getResponseCode()).thenReturn(300);
+        fetcher.loadData(Priority.HIGH);
+    }
+
+    @Test(expected = IOException.class)
+    public void testThrowsIfStatusCodeIs500() throws Exception {
+        when(urlConnection.getResponseCode()).thenReturn(500);
+        fetcher.loadData(Priority.HIGH);
+    }
+
+    @Test
+    public void testReturnsNullIfCancelledBeforeConnects() throws Exception {
+        InputStream notExpected = new ByteArrayInputStream(new byte[0]);
+        when(urlConnection.getInputStream()).thenReturn(notExpected);
+
+        fetcher.cancel();
+        assertNull(fetcher.loadData(Priority.LOW));
+    }
+
+    @Test
+    public void testDisconnectsUrlOnCleanup() throws Exception {
+        fetcher.loadData(Priority.HIGH);
+        fetcher.cleanup();
+
+        verify(urlConnection).disconnect();
+    }
+
+    @Test
+    public void testDoesNotThrowIfCleanupCalledBeforeStarted() {
+        fetcher.cleanup();
+    }
+
+    @Test
+    public void testDoesNotThrowIfCancelCalledBeforeStart() {
+        fetcher.cancel();
+    }
+
+    @Test
+    public void testCancelDoesNotDisconnectIfAlreadyConnected() throws Exception {
+        fetcher.loadData(Priority.HIGH);
+        fetcher.cancel();
+
+        verify(urlConnection, never()).disconnect();
+    }
+
+    @Test
+    public void testDisconnectsUrlConnectionOnCancelIfNotYetCancelled() throws IOException, InterruptedException {
+        final CountDownLatch mainThreadLatch = new CountDownLatch(1);
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        doAnswer(new Answer() {
+            @Override
+            public Object answer(InvocationOnMock invocation) throws Throwable {
+                mainThreadLatch.countDown();
+                countDownLatch.await();
+                return null;
+            }
+        }).when(urlConnection).connect();
+
+        Thread bg = new Thread() {
+            @Override
+            public void run() {
+                try {
+                    fetcher.loadData(Priority.HIGH);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        };
+        bg.start();
+
+        mainThreadLatch.await();
+        fetcher.cancel();
+        countDownLatch.countDown();
+        bg.join();
+
+        verify(urlConnection).connect();
+        verify(urlConnection).disconnect();
+    }
+}
\ No newline at end of file
diff --git a/library/src/test/java/com/bumptech/glide/load/model/stream/HttpUrlGlideUrlLoaderTest.java b/library/src/test/java/com/bumptech/glide/load/model/stream/HttpUrlGlideUrlLoaderTest.java
new file mode 100644
index 0000000..640ba0b
--- /dev/null
+++ b/library/src/test/java/com/bumptech/glide/load/model/stream/HttpUrlGlideUrlLoaderTest.java
@@ -0,0 +1,29 @@
+package com.bumptech.glide.load.model.stream;
+
+import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.data.HttpUrlFetcher;
+import com.bumptech.glide.load.model.GlideUrl;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.InputStream;
+
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+public class HttpUrlGlideUrlLoaderTest {
+    private HttpUrlGlideUrlLoader loader;
+    private GlideUrl model;
+
+    @SuppressWarnings("unchecked")
+    @Before
+    public void setUp() {
+        loader = new HttpUrlGlideUrlLoader();
+        model = mock(GlideUrl.class);
+    }
+    @Test
+    public void testReturnsValidFetcher() {
+        DataFetcher<InputStream> result = loader.getResourceFetcher(model, 100, 100);
+        assertTrue(result instanceof HttpUrlFetcher);
+    }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index eee9405..94cb5d0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,7 @@
   <modules>
     <module>library</module>
     <module>third_party</module>
+    <module>integration</module>
     <module>samples</module>
   </modules>
 
@@ -33,11 +34,6 @@
       <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>
diff --git a/samples/flickr/pom.xml b/samples/flickr/pom.xml
index b61db08..e3f6620 100644
--- a/samples/flickr/pom.xml
+++ b/samples/flickr/pom.xml
@@ -16,10 +16,9 @@
   <dependencies>
     <dependency>
       <groupId>com.github.bumptech.glide</groupId>
-      <artifactId>glide</artifactId>
+      <artifactId>library</artifactId>
       <version>3.3.0-SNAPSHOT</version>
       <type>aar</type>
-      <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>com.actionbarsherlock</groupId>
@@ -33,5 +32,10 @@
         </exclusion>
       </exclusions> 
     </dependency>
+    <dependency>
+      <groupId>com.mcxiaoke.volley</groupId>
+      <artifactId>library</artifactId>
+      <version>1.0.4</version>
+    </dependency>
   </dependencies>
 </project>
diff --git a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java
index d8062a2..5ff521b 100644
--- a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java
+++ b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java
@@ -1,5 +1,6 @@
 package com.bumptech.glide.samples.flickr;
 
+import android.annotation.TargetApi;
 import android.os.Bundle;
 import android.os.StrictMode;
 import android.support.v4.app.Fragment;
@@ -70,6 +71,7 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
         Glide.get(this).register(Photo.class, InputStream.class, new FlickrModelLoader.Factory());
 
         setContentView(R.layout.flickr_search_activity);
@@ -127,7 +129,7 @@
 
         pager.setAdapter(new FlickrPagerAdapter(getSupportFragmentManager()));
 
-        Api.get(Glide.get(this).getRequestQueue()).registerSearchListener(searchListener);
+        Api.get(this).registerSearchListener(searchListener);
         if (savedInstanceState != null) {
             String savedSearchString = savedInstanceState.getString(STATE_SEARCH_STRING);
             if (!TextUtils.isEmpty(savedSearchString)) {
@@ -147,9 +149,10 @@
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        Api.get(Glide.get(this).getRequestQueue()).unregisterSearchListener(searchListener);
+        Api.get(this).unregisterSearchListener(searchListener);
     }
 
+    @TargetApi(14)
     @Override
     public void onTrimMemory(int level) {
         super.onTrimMemory(level);
@@ -179,7 +182,7 @@
         searchLoading.setVisibility(View.VISIBLE);
         searchTerm.setText(getString(R.string.searching_for, currentSearchString));
 
-        Api.get(Glide.get(this).getRequestQueue()).search(currentSearchString);
+        Api.get(this).search(currentSearchString);
     }
 
     private static class TabListener implements ActionBar.TabListener {
diff --git a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/Api.java b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/Api.java
index 995fd7e..91be8eb 100644
--- a/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/Api.java
+++ b/samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/Api.java
@@ -1,5 +1,6 @@
 package com.bumptech.glide.samples.flickr.api;
 
+import android.content.Context;
 import android.text.TextUtils;
 import android.util.Log;
 import com.android.volley.DefaultRetryPolicy;
@@ -8,6 +9,7 @@
 import com.android.volley.Response;
 import com.android.volley.VolleyError;
 import com.android.volley.toolbox.StringRequest;
+import com.android.volley.toolbox.Volley;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -85,9 +87,9 @@
         public void onSearchFailed(String searchString, Exception e);
     }
 
-    public static Api get(RequestQueue requestQueue) {
+    public static Api get(Context context) {
         if (API == null) {
-            API = new Api(requestQueue);
+            API = new Api(context);
         }
         return API;
     }
@@ -96,8 +98,8 @@
     private final Set<SearchListener> searchListeners = new HashSet<SearchListener>();
     private SearchResult lastSearchResult;
 
-    protected Api(RequestQueue requestQueue) {
-        this.requestQueue = requestQueue;
+    protected Api(Context context) {
+        this.requestQueue = Volley.newRequestQueue(context.getApplicationContext());
     }
 
     public void registerSearchListener(SearchListener searchListener) {
diff --git a/settings.gradle b/settings.gradle
index e926c91..1a1e7c3 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,4 @@
 include ':library'
 include ':third_party:gif_decoder'
-include ':samples:flickr'
\ No newline at end of file
+include ':samples:flickr'
+include ':integration:volley'
\ No newline at end of file