Przeglądaj źródła

v1.0.2开发:调整非androidX的Volley版本

#Suyghur 2 lat temu
rodzic
commit
962b9d10d2
46 zmienionych plików z 1073 dodań i 1553 usunięć
  1. 2 12
      build.gradle
  2. 0 10
      demo/build.gradle
  3. 0 2
      demo/src/main/java/com/yyxx/support/demo/DemoActivity.kt
  4. BIN
      demo/src/main/res/mipmap-xhdpi/ic_launcher.jpg
  5. BIN
      demo/src/main/res/mipmap-xhdpi/ic_launcher.png
  6. 5 1
      gradle.properties
  7. 3 3
      gradle/wrapper/gradle-wrapper.properties
  8. 1 11
      library_support/build.gradle
  9. 5 5
      library_support/src/main/java/cn/yyxx/support/permission/PermissionKitActivity.java
  10. 1 11
      library_volley/build.gradle
  11. 0 47
      library_volley/src/main/java/android/support/annotation/GuardedBy.java
  12. 70 78
      library_volley/src/main/java/cn/yyxx/support/volley/source/CacheDispatcher.java
  13. 12 18
      library_volley/src/main/java/cn/yyxx/support/volley/source/DefaultRetryPolicy.java
  14. 7 6
      library_volley/src/main/java/cn/yyxx/support/volley/source/ExecutorDelivery.java
  15. 0 12
      library_volley/src/main/java/cn/yyxx/support/volley/source/NetworkDispatcher.java
  16. 34 49
      library_volley/src/main/java/cn/yyxx/support/volley/source/NetworkResponse.java
  17. 106 155
      library_volley/src/main/java/cn/yyxx/support/volley/source/Request.java
  18. 28 134
      library_volley/src/main/java/cn/yyxx/support/volley/source/RequestQueue.java
  19. 42 45
      library_volley/src/main/java/cn/yyxx/support/volley/source/Response.java
  20. 0 18
      library_volley/src/main/java/cn/yyxx/support/volley/source/RetryPolicy.java
  21. 4 5
      library_volley/src/main/java/cn/yyxx/support/volley/source/VolleyError.java
  22. 15 18
      library_volley/src/main/java/cn/yyxx/support/volley/source/VolleyLog.java
  23. 4 4
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/AdaptedHttpStack.java
  24. 3 8
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/AndroidAuthenticator.java
  25. 11 9
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/BaseHttpStack.java
  26. 101 104
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/BasicNetwork.java
  27. 13 17
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/ByteArrayPool.java
  28. 160 183
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/DiskBasedCache.java
  29. 23 26
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/HttpClientStack.java
  30. 5 5
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/HttpHeaderParser.java
  31. 3 3
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/HttpResponse.java
  32. 5 4
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/HttpStack.java
  33. 100 140
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/HurlStack.java
  34. 187 209
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/ImageLoader.java
  35. 35 43
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/ImageRequest.java
  36. 15 13
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/JsonArrayRequest.java
  37. 10 12
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/JsonObjectRequest.java
  38. 20 16
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/JsonRequest.java
  39. 7 66
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/NetworkImageView.java
  40. 8 8
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/RequestFuture.java
  41. 4 12
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/StringRequest.java
  42. 0 14
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/Threads.java
  43. 6 5
      library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/Volley.java
  44. 1 11
      library_volleyx/build.gradle
  45. BIN
      libs/yyxx_support_volley_1.0.0.jar
  46. 17 1
      settings.gradle

+ 2 - 12
build.gradle

@@ -4,7 +4,7 @@ apply from: "config.gradle"
 buildscript {
 
     ext {
-        kotlin_version = '1.5.30'
+        kotlin_version = '1.6.0'
     }
 
     repositories {
@@ -15,23 +15,13 @@ buildscript {
         maven { url 'https://jitpack.io' }
     }
     dependencies {
-        classpath "com.android.tools.build:gradle:4.1.3"
+        classpath 'com.android.tools.build:gradle:7.0.4'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
     }
 }
 
-allprojects {
-    repositories {
-        google()
-        mavenCentral()
-        jcenter()
-        maven { url 'https://maven.aliyun.com/repository/public' }
-        maven { url 'https://jitpack.io' }
-    }
-}
-
 task clean(type: Delete) {
     delete rootProject.buildDir
 }

+ 0 - 10
demo/build.gradle

@@ -51,16 +51,6 @@ android {
         abortOnError false
     }
 
-    repositories {
-        flatDir {
-            dirs 'libs'
-        }
-    }
-
-    dexOptions {
-        preDexLibraries = false
-    }
-
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_8
         targetCompatibility JavaVersion.VERSION_1_8

+ 0 - 2
demo/src/main/java/com/yyxx/support/demo/DemoActivity.kt

@@ -9,11 +9,9 @@ import android.widget.*
 import cn.yyxx.support.AppUtils
 import cn.yyxx.support.DensityUtils
 import cn.yyxx.support.device.DeviceInfoUtils
-import cn.yyxx.support.encryption.rsa.RsaUtils
 import cn.yyxx.support.hawkeye.LogUtils
 import cn.yyxx.support.hawkeye.OwnDebugUtils
 import cn.yyxx.support.msa.MsaDeviceIdsHandler
-import cn.yyxx.support.permission.PermissionKitActivity.start
 import cn.yyxx.support.ui.scaleprogress.ScaleLoadingView
 import cn.yyxx.support.volley.VolleySingleton
 import cn.yyxx.support.volley.source.Response

BIN
demo/src/main/res/mipmap-xhdpi/ic_launcher.jpg


BIN
demo/src/main/res/mipmap-xhdpi/ic_launcher.png


+ 5 - 1
gradle.properties

@@ -13,5 +13,9 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
 # org.gradle.parallel=true
 android.injected.testOnly=false
 android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
 #android.enableJetifier=true
-#android.enableAapt2=false
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+org.gradle.parallel=true
+org.gradle.configureondemand=true

+ 3 - 3
gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,6 @@
-#Thu Apr 22 14:17:55 CST 2021
+#Mon Jan 10 14:11:04 CST 2022
 distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
 distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
+zipStoreBase=GRADLE_USER_HOME

+ 1 - 11
library_support/build.gradle

@@ -19,23 +19,13 @@ android {
     }
 
     buildFeatures {
-        buildConfig = false
+        buildConfig false
     }
 
     lintOptions {
         abortOnError false
     }
 
-    repositories {
-        flatDir {
-            dirs 'libs'
-        }
-    }
-
-    dexOptions {
-        preDexLibraries = false
-    }
-
     //api23以上使用 httpClient
     useLibrary 'org.apache.http.legacy'
 }

+ 5 - 5
library_support/src/main/java/cn/yyxx/support/permission/PermissionKitActivity.java

@@ -35,6 +35,10 @@ public class PermissionKitActivity extends FragmentActivity {
     }
 
     public static void finish(Context context) {
+        if (mCallback != null) {
+            mCallback.onProxyFinish();
+        }
+
         if (context instanceof PermissionKitActivity) {
             ((PermissionKitActivity) context).finish();
         }
@@ -51,7 +55,6 @@ public class PermissionKitActivity extends FragmentActivity {
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        LogUtils.d("onDestroy");
         if (mPermissions != null) {
             mPermissions.clear();
             mPermissions = null;
@@ -62,7 +65,6 @@ public class PermissionKitActivity extends FragmentActivity {
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
-        LogUtils.d("onActivityResult");
         if (requestCode == PermissionKit.REQUEST_CODE && !isFinishing()) {
             if (mCallback != null) {
                 mCallback.onProxyFinish();
@@ -115,9 +117,7 @@ public class PermissionKitActivity extends FragmentActivity {
         if (PermissionUtils.isGrantedPermissions(this, permissions)) {
             // 证明这些权限已经全部授予过,直接回调成功
             if (mCallback != null) {
-                LogUtils.d("证明这些权限已经全部授予过,直接回调成功");
-                mCallback.onGranted(mPermissions, true);
-                activity.finish();
+                PermissionKit.getInterceptor().grantedPermissions(activity, mPermissions, true, mCallback);
             }
             return;
         }

+ 1 - 11
library_volley/build.gradle

@@ -19,23 +19,13 @@ android {
     }
 
     buildFeatures {
-        buildConfig = false
+        buildConfig  false
     }
 
     lintOptions {
         abortOnError false
     }
 
-    repositories {
-        flatDir {
-            dirs 'libs'
-        }
-    }
-
-    dexOptions {
-        preDexLibraries = false
-    }
-
     //api23以上使用 httpClient
     useLibrary 'org.apache.http.legacy'
 }

+ 0 - 47
library_volley/src/main/java/android/support/annotation/GuardedBy.java

@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.support.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Denotes that the annotated method or field can only be accessed when holding the referenced lock.
- * <p>
- * Example:
- * <pre>
- * final Object objectLock = new Object();
- *
- * {@literal @}GuardedBy("objectLock")
- * volatile Object object;
- *
- * Object getObject() {
- *     synchronized (objectLock) {
- *         if (object == null) {
- *             object = new Object();
- *         }
- *     }
- *     return object;
- * }</pre>
- */
-@Target({ ElementType.FIELD, ElementType.METHOD })
-@Retention(RetentionPolicy.CLASS)
-public @interface GuardedBy {
-    String value();
-}

+ 70 - 78
library_volley/src/main/java/cn/yyxx/support/volley/source/CacheDispatcher.java

@@ -17,7 +17,6 @@
 package cn.yyxx.support.volley.source;
 
 import android.os.Process;
-import android.support.annotation.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -56,16 +55,14 @@ public class CacheDispatcher extends Thread {
      * For posting responses.
      */
     private final ResponseDelivery mDelivery;
-
-    /**
-     * Used for telling us to die.
-     */
-    private volatile boolean mQuit = false;
-
     /**
      * Manage list of waiting requests and de-duplicate requests with same cache key.
      */
     private final WaitingRequestManager mWaitingRequestManager;
+    /**
+     * Used for telling us to die.
+     */
+    private volatile boolean mQuit = false;
 
     /**
      * Creates a new cache triage dispatcher thread. You must call {@link #start()} in order to
@@ -111,12 +108,8 @@ public class CacheDispatcher extends Thread {
             } catch (InterruptedException e) {
                 // We may have been interrupted because it was time to quit.
                 if (mQuit) {
-                    Thread.currentThread().interrupt();
                     return;
                 }
-                VolleyLog.e(
-                        "Ignoring spurious interrupt of CacheDispatcher thread; "
-                                + "use quit() to terminate it");
             }
         }
     }
@@ -129,81 +122,76 @@ public class CacheDispatcher extends Thread {
         // Get a request from the cache triage queue, blocking until
         // at least one is available.
         final Request<?> request = mCacheQueue.take();
-        processRequest(request);
-    }
-
-    @VisibleForTesting
-    void processRequest(final Request<?> request) throws InterruptedException {
         request.addMarker("cache-queue-take");
-        request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED);
 
-        try {
-            // If the request has been canceled, don't bother dispatching it.
-            if (request.isCanceled()) {
-                request.finish("cache-discard-canceled");
-                return;
-            }
+        // If the request has been canceled, don't bother dispatching it.
+        if (request.isCanceled()) {
+            request.finish("cache-discard-canceled");
+            return;
+        }
 
-            // Attempt to retrieve this item from cache.
-            Cache.Entry entry = mCache.get(request.getCacheKey());
-            if (entry == null) {
-                request.addMarker("cache-miss");
-                // Cache miss; send off to the network dispatcher.
-                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
-                    mNetworkQueue.put(request);
-                }
-                return;
+        // Attempt to retrieve this item from cache.
+        Cache.Entry entry = mCache.get(request.getCacheKey());
+        if (entry == null) {
+            request.addMarker("cache-miss");
+            // Cache miss; send off to the network dispatcher.
+            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
+                mNetworkQueue.put(request);
             }
+            return;
+        }
 
-            // If it is completely expired, just send it to the network.
-            if (entry.isExpired()) {
-                request.addMarker("cache-hit-expired");
-                request.setCacheEntry(entry);
-                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
-                    mNetworkQueue.put(request);
-                }
-                return;
+        // If it is completely expired, just send it to the network.
+        if (entry.isExpired()) {
+            request.addMarker("cache-hit-expired");
+            request.setCacheEntry(entry);
+            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
+                mNetworkQueue.put(request);
             }
+            return;
+        }
 
-            // We have a cache hit; parse its data for delivery back to the request.
-            request.addMarker("cache-hit");
-            Response<?> response = request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
-            request.addMarker("cache-hit-parsed");
+        // We have a cache hit; parse its data for delivery back to the request.
+        request.addMarker("cache-hit");
+        Response<?> response =
+                request.parseNetworkResponse(
+                        new NetworkResponse(entry.data, entry.responseHeaders));
+        request.addMarker("cache-hit-parsed");
 
-            if (!entry.refreshNeeded()) {
-                // Completely unexpired cache hit. Just deliver the response.
-                mDelivery.postResponse(request, response);
-            } else {
-                // Soft-expired cache hit. We can deliver the cached response,
-                // but we need to also send the request to the network for
-                // refreshing.
-                request.addMarker("cache-hit-refresh-needed");
-                request.setCacheEntry(entry);
-                // Mark the response as intermediate.
-                response.intermediate = true;
+        if (!entry.refreshNeeded()) {
+            // Completely unexpired cache hit. Just deliver the response.
+            mDelivery.postResponse(request, response);
+        } else {
+            // Soft-expired cache hit. We can deliver the cached response,
+            // but we need to also send the request to the network for
+            // refreshing.
+            request.addMarker("cache-hit-refresh-needed");
+            request.setCacheEntry(entry);
+            // Mark the response as intermediate.
+            response.intermediate = true;
 
-                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
-                    // Post the intermediate response back to the user and have
-                    // the delivery then forward the request along to the network.
-                    mDelivery.postResponse(request, response, new Runnable() {
-                        @Override
-                        public void run() {
-                            try {
-                                mNetworkQueue.put(request);
-                            } catch (InterruptedException e) {
-                                // Restore the interrupted status
-                                Thread.currentThread().interrupt();
+            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
+                // Post the intermediate response back to the user and have
+                // the delivery then forward the request along to the network.
+                mDelivery.postResponse(
+                        request,
+                        response,
+                        new Runnable() {
+                            @Override
+                            public void run() {
+                                try {
+                                    mNetworkQueue.put(request);
+                                } catch (InterruptedException e) {
+                                    // Restore the interrupted status
+                                    Thread.currentThread().interrupt();
+                                }
                             }
-                        }
-                    });
-                } else {
-                    // request has been added to list of waiting requests
-                    // to receive the network response from the first request once it returns.
-                    mDelivery.postResponse(request, response);
-                }
+                        });
+            } else {
+                // request has been added to list of waiting requests
+                // to receive the network response from the first request once it returns.
+                mDelivery.postResponse(request, response);
             }
-        } finally {
-            request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED);
         }
     }
 
@@ -243,7 +231,9 @@ public class CacheDispatcher extends Thread {
             }
             if (waitingRequests != null) {
                 if (VolleyLog.DEBUG) {
-                    VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.", waitingRequests.size(), cacheKey);
+                    VolleyLog.v(
+                            "Releasing %d waiting requests for cacheKey=%s.",
+                            waitingRequests.size(), cacheKey);
                 }
                 // Process all queued up requests.
                 for (Request<?> waiting : waitingRequests) {
@@ -261,7 +251,9 @@ public class CacheDispatcher extends Thread {
             List<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
             if (waitingRequests != null && !waitingRequests.isEmpty()) {
                 if (VolleyLog.DEBUG) {
-                    VolleyLog.v("%d waiting requests for cacheKey=%s; resend to network", waitingRequests.size(), cacheKey);
+                    VolleyLog.v(
+                            "%d waiting requests for cacheKey=%s; resend to network",
+                            waitingRequests.size(), cacheKey);
                 }
                 Request<?> nextInLine = waitingRequests.remove(0);
                 mWaitingRequests.put(cacheKey, waitingRequests);
@@ -294,7 +286,7 @@ public class CacheDispatcher extends Thread {
                 // There is already a request in flight. Queue up.
                 List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                 if (stagedRequests == null) {
-                    stagedRequests = new ArrayList<>();
+                    stagedRequests = new ArrayList<Request<?>>();
                 }
                 request.addMarker("waiting-for-response");
                 stagedRequests.add(request);

+ 12 - 18
library_volley/src/main/java/cn/yyxx/support/volley/source/DefaultRetryPolicy.java

@@ -21,39 +21,33 @@ package cn.yyxx.support.volley.source;
  */
 public class DefaultRetryPolicy implements RetryPolicy {
     /**
-     * The current timeout in milliseconds.
+     * The default socket timeout in milliseconds
      */
-    private int mCurrentTimeoutMs;
-
+    public static final int DEFAULT_TIMEOUT_MS = 2500;
     /**
-     * The current retry count.
+     * The default number of retries
      */
-    private int mCurrentRetryCount;
-
+    public static final int DEFAULT_MAX_RETRIES = 1;
+    /**
+     * The default backoff multiplier
+     */
+    public static final float DEFAULT_BACKOFF_MULT = 1f;
     /**
      * The maximum number of attempts.
      */
     private final int mMaxNumRetries;
-
     /**
      * The backoff multiplier for the policy.
      */
     private final float mBackoffMultiplier;
-
-    /**
-     * The default socket timeout in milliseconds
-     */
-    public static final int DEFAULT_TIMEOUT_MS = 2500;
-
     /**
-     * The default number of retries
+     * The current timeout in milliseconds.
      */
-    public static final int DEFAULT_MAX_RETRIES = 1;
-
+    private int mCurrentTimeoutMs;
     /**
-     * The default backoff multiplier
+     * The current retry count.
      */
-    public static final float DEFAULT_BACKOFF_MULT = 1f;
+    private int mCurrentRetryCount;
 
     /**
      * Constructs a new retry policy using the default timeouts.

+ 7 - 6
library_volley/src/main/java/cn/yyxx/support/volley/source/ExecutorDelivery.java

@@ -36,12 +36,13 @@ public class ExecutorDelivery implements ResponseDelivery {
      */
     public ExecutorDelivery(final Handler handler) {
         // Make an Executor that just wraps the handler.
-        mResponsePoster = new Executor() {
-            @Override
-            public void execute(Runnable command) {
-                handler.post(command);
-            }
-        };
+        mResponsePoster =
+                new Executor() {
+                    @Override
+                    public void execute(Runnable command) {
+                        handler.post(command);
+                    }
+                };
     }
 
     /**

+ 0 - 12
library_volley/src/main/java/cn/yyxx/support/volley/source/NetworkDispatcher.java

@@ -21,7 +21,6 @@ import android.net.TrafficStats;
 import android.os.Build;
 import android.os.Process;
 import android.os.SystemClock;
-import android.support.annotation.VisibleForTesting;
 
 import java.util.concurrent.BlockingQueue;
 
@@ -102,12 +101,8 @@ public class NetworkDispatcher extends Thread {
             } catch (InterruptedException e) {
                 // We may have been interrupted because it was time to quit.
                 if (mQuit) {
-                    Thread.currentThread().interrupt();
                     return;
                 }
-                VolleyLog.e(
-                        "Ignoring spurious interrupt of NetworkDispatcher thread; "
-                                + "use quit() to terminate it");
             }
         }
     }
@@ -119,13 +114,8 @@ public class NetworkDispatcher extends Thread {
     private void processRequest() throws InterruptedException {
         // Take a request from the queue.
         Request<?> request = mQueue.take();
-        processRequest(request);
-    }
 
-    @VisibleForTesting
-    void processRequest(Request<?> request) {
         long startTimeMs = SystemClock.elapsedRealtime();
-        request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
         try {
             request.addMarker("network-queue-take");
 
@@ -176,8 +166,6 @@ public class NetworkDispatcher extends Thread {
             volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
             mDelivery.postError(request, volleyError);
             request.notifyListenerResponseNotUsable();
-        } finally {
-            request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED);
         }
     }
 

+ 34 - 49
library_volley/src/main/java/cn/yyxx/support/volley/source/NetworkResponse.java

@@ -28,6 +28,37 @@ import java.util.TreeMap;
  */
 public class NetworkResponse {
 
+    /**
+     * The HTTP status code.
+     */
+    public final int statusCode;
+    /**
+     * Raw data from this response.
+     */
+    public final byte[] data;
+    /**
+     * Response headers.
+     *
+     * <p>This map is case-insensitive. It should not be mutated directly.
+     *
+     * <p>Note that if the server returns two headers with the same (case-insensitive) name, this
+     * map will only contain the last one. Use {@link #allHeaders} to inspect all headers returned
+     * by the server.
+     */
+    public final Map<String, String> headers;
+    /**
+     * All response headers. Must not be mutated directly.
+     */
+    public final List<Header> allHeaders;
+    /**
+     * True if the server returned a 304 (Not Modified).
+     */
+    public final boolean notModified;
+    /**
+     * Network roundtrip time in milliseconds.
+     */
+    public final long networkTimeMs;
+
     /**
      * Creates a new network response.
      *
@@ -82,7 +113,7 @@ public class NetworkResponse {
     @Deprecated
     public NetworkResponse(
             int statusCode, byte[] data, Map<String, String> headers, boolean notModified) {
-        this(statusCode, data, headers, notModified, /* networkTimeMs= */ 0);
+        this(statusCode, data, headers, notModified, 0);
     }
 
     /**
@@ -91,12 +122,7 @@ public class NetworkResponse {
      * @param data Response body
      */
     public NetworkResponse(byte[] data) {
-        this(
-                HttpURLConnection.HTTP_OK,
-                data,
-                /* notModified= */ false,
-                /* networkTimeMs= */ 0,
-                Collections.<Header>emptyList());
+        this(HttpURLConnection.HTTP_OK, data, false, 0, Collections.<Header>emptyList());
     }
 
     /**
@@ -110,12 +136,7 @@ public class NetworkResponse {
      */
     @Deprecated
     public NetworkResponse(byte[] data, Map<String, String> headers) {
-        this(
-                HttpURLConnection.HTTP_OK,
-                data,
-                headers,
-                /* notModified= */ false,
-                /* networkTimeMs= */ 0);
+        this(HttpURLConnection.HTTP_OK, data, headers, false, 0);
     }
 
     private NetworkResponse(
@@ -137,42 +158,6 @@ public class NetworkResponse {
         this.networkTimeMs = networkTimeMs;
     }
 
-    /**
-     * The HTTP status code.
-     */
-    public final int statusCode;
-
-    /**
-     * Raw data from this response.
-     */
-    public final byte[] data;
-
-    /**
-     * Response headers.
-     *
-     * <p>This map is case-insensitive. It should not be mutated directly.
-     *
-     * <p>Note that if the server returns two headers with the same (case-insensitive) name, this
-     * map will only contain the last one. Use {@link #allHeaders} to inspect all headers returned
-     * by the server.
-     */
-    public final Map<String, String> headers;
-
-    /**
-     * All response headers. Must not be mutated directly.
-     */
-    public final List<Header> allHeaders;
-
-    /**
-     * True if the server returned a 304 (Not Modified).
-     */
-    public final boolean notModified;
-
-    /**
-     * Network roundtrip time in milliseconds.
-     */
-    public final long networkTimeMs;
-
     private static Map<String, String> toHeaderMap(List<Header> allHeaders) {
         if (allHeaders == null) {
             return null;

+ 106 - 155
library_volley/src/main/java/cn/yyxx/support/volley/source/Request.java

@@ -20,18 +20,15 @@ import android.net.TrafficStats;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
-import android.support.annotation.CallSuper;
-import android.support.annotation.GuardedBy;
-import android.support.annotation.Nullable;
 import android.text.TextUtils;
 
-import cn.yyxx.support.volley.source.VolleyLog.MarkerLog;
-
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.Collections;
 import java.util.Map;
 
+import cn.yyxx.support.volley.source.VolleyLog.MarkerLog;
+
 /**
  * Base class for all network requests.
  *
@@ -43,125 +40,76 @@ public abstract class Request<T> implements Comparable<Request<T>> {
      * Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}.
      */
     private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
-
-    /**
-     * Supported request methods.
-     */
-    public interface Method {
-        int DEPRECATED_GET_OR_POST = -1;
-        int GET = 0;
-        int POST = 1;
-        int PUT = 2;
-        int DELETE = 3;
-        int HEAD = 4;
-        int OPTIONS = 5;
-        int TRACE = 6;
-        int PATCH = 7;
-    }
-
-    /**
-     * Callback to notify when the network request returns.
-     */
-    /* package */ interface NetworkRequestCompleteListener {
-
-        /**
-         * Callback when a network response has been received.
-         */
-        void onResponseReceived(Request<?> request, Response<?> response);
-
-        /**
-         * Callback when request returns from network without valid response.
-         */
-        void onNoUsableResponseReceived(Request<?> request);
-    }
-
     /**
      * An event log tracing the lifetime of this request; for debugging.
      */
     private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null;
-
     /**
      * Request method of this request. Currently supports GET, POST, PUT, DELETE, HEAD, OPTIONS,
      * TRACE, and PATCH.
      */
     private final int mMethod;
-
     /**
      * URL of this request.
      */
     private final String mUrl;
-
     /**
      * Default tag for {@link TrafficStats}.
      */
     private final int mDefaultTrafficStatsTag;
-
     /**
      * Lock to guard state which can be mutated after a request is added to the queue.
      */
     private final Object mLock = new Object();
-
     /**
      * Listener interface for errors.
      */
-    @Nullable
-    @GuardedBy("mLock")
+    // @GuardedBy("mLock")
     private Response.ErrorListener mErrorListener;
-
     /**
      * Sequence number of this request, used to enforce FIFO ordering.
      */
     private Integer mSequence;
-
     /**
      * The request queue this request is associated with.
      */
     private RequestQueue mRequestQueue;
-
     /**
      * Whether or not responses to this request should be cached.
      */
-    // TODO(#190): Turn this off by default for anything other than GET requests.
     private boolean mShouldCache = true;
-
     /**
      * Whether or not this request has been canceled.
      */
-    @GuardedBy("mLock")
+    // @GuardedBy("mLock")
     private boolean mCanceled = false;
-
     /**
      * Whether or not a response has been delivered for this request yet.
      */
-    @GuardedBy("mLock")
+    // @GuardedBy("mLock")
     private boolean mResponseDelivered = false;
-
     /**
      * Whether the request should be retried in the event of an HTTP 5xx (server) error.
      */
     private boolean mShouldRetryServerErrors = false;
-
     /**
      * The retry policy for this request.
      */
     private RetryPolicy mRetryPolicy;
-
     /**
      * When a request can be retrieved from cache but must be refreshed from the network, the cache
      * entry will be stored here so that in the event of a "Not Modified" response, we can be sure
      * it hasn't been evicted from cache.
      */
     private Cache.Entry mCacheEntry = null;
-
     /**
      * An opaque token tagging this request; used for bulk cancellation.
      */
     private Object mTag;
-
     /**
      * Listener that will be notified when a response has been delivered.
      */
-    @GuardedBy("mLock")
+    // @GuardedBy("mLock")
     private NetworkRequestCompleteListener mRequestCompleteListener;
 
     /**
@@ -182,7 +130,7 @@ public abstract class Request<T> implements Comparable<Request<T>> {
      * responses is provided by subclasses, who have a better idea of how to deliver an
      * already-parsed response.
      */
-    public Request(int method, String url, @Nullable Response.ErrorListener listener) {
+    public Request(int method, String url, Response.ErrorListener listener) {
         mMethod = method;
         mUrl = url;
         mErrorListener = listener;
@@ -191,6 +139,22 @@ public abstract class Request<T> implements Comparable<Request<T>> {
         mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
     }
 
+    /**
+     * @return The hashcode of the URL's host component, or 0 if there is none.
+     */
+    private static int findDefaultTrafficStatsTag(String url) {
+        if (!TextUtils.isEmpty(url)) {
+            Uri uri = Uri.parse(url);
+            if (uri != null) {
+                String host = uri.getHost();
+                if (host != null) {
+                    return host.hashCode();
+                }
+            }
+        }
+        return 0;
+    }
+
     /**
      * Return the method for this request. Can be one of the values in {@link Method}.
      */
@@ -198,6 +162,15 @@ public abstract class Request<T> implements Comparable<Request<T>> {
         return mMethod;
     }
 
+    /**
+     * Returns this request's tag.
+     *
+     * @see Request#setTag(Object)
+     */
+    public Object getTag() {
+        return mTag;
+    }
+
     /**
      * Set a tag on this request. Can be used to cancel all requests with this tag by {@link
      * RequestQueue#cancelAll(Object)}.
@@ -209,23 +182,11 @@ public abstract class Request<T> implements Comparable<Request<T>> {
         return this;
     }
 
-    /**
-     * Returns this request's tag.
-     *
-     * @see Request#setTag(Object)
-     */
-    public Object getTag() {
-        return mTag;
-    }
-
     /**
      * @return this request's {@link Response.ErrorListener}.
      */
-    @Nullable
     public Response.ErrorListener getErrorListener() {
-        synchronized (mLock) {
-            return mErrorListener;
-        }
+        return mErrorListener;
     }
 
     /**
@@ -235,32 +196,6 @@ public abstract class Request<T> implements Comparable<Request<T>> {
         return mDefaultTrafficStatsTag;
     }
 
-    /**
-     * @return The hashcode of the URL's host component, or 0 if there is none.
-     */
-    private static int findDefaultTrafficStatsTag(String url) {
-        if (!TextUtils.isEmpty(url)) {
-            Uri uri = Uri.parse(url);
-            if (uri != null) {
-                String host = uri.getHost();
-                if (host != null) {
-                    return host.hashCode();
-                }
-            }
-        }
-        return 0;
-    }
-
-    /**
-     * Sets the retry policy for this request.
-     *
-     * @return This Request object to allow for chaining.
-     */
-    public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
-        mRetryPolicy = retryPolicy;
-        return this;
-    }
-
     /**
      * Adds an event to this request's event log; for debugging.
      */
@@ -301,12 +236,6 @@ public abstract class Request<T> implements Comparable<Request<T>> {
         }
     }
 
-    void sendEvent(@RequestQueue.RequestEvent int event) {
-        if (mRequestQueue != null) {
-            mRequestQueue.sendRequestEvent(this, event);
-        }
-    }
-
     /**
      * Associates this request with the given queue. The request queue will be notified when this
      * request has finished.
@@ -318,16 +247,6 @@ public abstract class Request<T> implements Comparable<Request<T>> {
         return this;
     }
 
-    /**
-     * Sets the sequence number of this request. Used by {@link RequestQueue}.
-     *
-     * @return This Request object to allow for chaining.
-     */
-    public final Request<?> setSequence(int sequence) {
-        mSequence = sequence;
-        return this;
-    }
-
     /**
      * Returns the sequence number of this request.
      */
@@ -338,6 +257,16 @@ public abstract class Request<T> implements Comparable<Request<T>> {
         return mSequence;
     }
 
+    /**
+     * Sets the sequence number of this request. Used by {@link RequestQueue}.
+     *
+     * @return This Request object to allow for chaining.
+     */
+    public final Request<?> setSequence(int sequence) {
+        mSequence = sequence;
+        return this;
+    }
+
     /**
      * Returns the URL of this request.
      */
@@ -349,18 +278,14 @@ public abstract class Request<T> implements Comparable<Request<T>> {
      * Returns the cache key for this request. By default, this is the URL.
      */
     public String getCacheKey() {
-        String url = getUrl();
-        // If this is a GET request, just use the URL as the key.
-        // For callers using DEPRECATED_GET_OR_POST, we assume the method is GET, which matches
-        // legacy behavior where all methods had the same cache key. We can't determine which method
-        // will be used because doing so requires calling getPostBody() which is expensive and may
-        // throw AuthFailureError.
-        // TODO(#190): Remove support for non-GET methods.
-        int method = getMethod();
-        if (method == Method.GET || method == Method.DEPRECATED_GET_OR_POST) {
-            return url;
-        }
-        return Integer.toString(method) + '-' + url;
+        return getUrl();
+    }
+
+    /**
+     * Returns the annotated cache entry, or null if there isn't one.
+     */
+    public Cache.Entry getCacheEntry() {
+        return mCacheEntry;
     }
 
     /**
@@ -374,13 +299,6 @@ public abstract class Request<T> implements Comparable<Request<T>> {
         return this;
     }
 
-    /**
-     * Returns the annotated cache entry, or null if there isn't one.
-     */
-    public Cache.Entry getCacheEntry() {
-        return mCacheEntry;
-    }
-
     /**
      * Mark this request as canceled.
      *
@@ -396,7 +314,7 @@ public abstract class Request<T> implements Comparable<Request<T>> {
      *
      * <p>There are no guarantees if both of these conditions aren't met.
      */
-    @CallSuper
+    // @CallSuper
     public void cancel() {
         synchronized (mLock) {
             mCanceled = true;
@@ -545,14 +463,6 @@ public abstract class Request<T> implements Comparable<Request<T>> {
         StringBuilder encodedParams = new StringBuilder();
         try {
             for (Map.Entry<String, String> entry : params.entrySet()) {
-                if (entry.getKey() == null || entry.getValue() == null) {
-                    throw new IllegalArgumentException(
-                            String.format(
-                                    "Request#getParams() or Request#getPostParams() returned a map "
-                                            + "containing a null key or value: (%s, %s). All keys "
-                                            + "and values must be non-null.",
-                                    entry.getKey(), entry.getValue()));
-                }
                 encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
                 encodedParams.append('=');
                 encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
@@ -598,17 +508,6 @@ public abstract class Request<T> implements Comparable<Request<T>> {
         return mShouldRetryServerErrors;
     }
 
-    /**
-     * Priority values. Requests will be processed from higher priorities to lower priorities, in
-     * FIFO order.
-     */
-    public enum Priority {
-        LOW,
-        NORMAL,
-        HIGH,
-        IMMEDIATE
-    }
-
     /**
      * Returns the {@link Priority} of this request; {@link Priority#NORMAL} by default.
      */
@@ -622,7 +521,7 @@ public abstract class Request<T> implements Comparable<Request<T>> {
      * remaining, this will cause delivery of a {@link TimeoutError} error.
      */
     public final int getTimeoutMs() {
-        return getRetryPolicy().getCurrentTimeout();
+        return mRetryPolicy.getCurrentTimeout();
     }
 
     /**
@@ -632,6 +531,16 @@ public abstract class Request<T> implements Comparable<Request<T>> {
         return mRetryPolicy;
     }
 
+    /**
+     * Sets the retry policy for this request.
+     *
+     * @return This Request object to allow for chaining.
+     */
+    public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
+        mRetryPolicy = retryPolicy;
+        return this;
+    }
+
     /**
      * Mark this request as having a response delivered on it. This can be used later in the
      * request's lifetime for suppressing identical responses.
@@ -756,7 +665,7 @@ public abstract class Request<T> implements Comparable<Request<T>> {
     @Override
     public String toString() {
         String trafficStatsTag = "0x" + Integer.toHexString(getTrafficStatsTag());
-        return (isCanceled() ? "[X] " : "[ ] ")
+        return (mCanceled ? "[X] " : "[ ] ")
                 + getUrl()
                 + " "
                 + trafficStatsTag
@@ -765,4 +674,46 @@ public abstract class Request<T> implements Comparable<Request<T>> {
                 + " "
                 + mSequence;
     }
+
+    /**
+     * Priority values. Requests will be processed from higher priorities to lower priorities, in
+     * FIFO order.
+     */
+    public enum Priority {
+        LOW,
+        NORMAL,
+        HIGH,
+        IMMEDIATE
+    }
+
+    /**
+     * Supported request methods.
+     */
+    public interface Method {
+        int DEPRECATED_GET_OR_POST = -1;
+        int GET = 0;
+        int POST = 1;
+        int PUT = 2;
+        int DELETE = 3;
+        int HEAD = 4;
+        int OPTIONS = 5;
+        int TRACE = 6;
+        int PATCH = 7;
+    }
+
+    /**
+     * Callback to notify when the network request returns.
+     */
+    /* package */ interface NetworkRequestCompleteListener {
+
+        /**
+         * Callback when a network response has been received.
+         */
+        void onResponseReceived(Request<?> request, Response<?> response);
+
+        /**
+         * Callback when request returns from network without valid response.
+         */
+        void onNoUsableResponseReceived(Request<?> request);
+    }
 }

+ 28 - 134
library_volley/src/main/java/cn/yyxx/support/volley/source/RequestQueue.java

@@ -18,10 +18,7 @@ package cn.yyxx.support.volley.source;
 
 import android.os.Handler;
 import android.os.Looper;
-import android.support.annotation.IntDef;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -39,72 +36,9 @@ import java.util.concurrent.atomic.AtomicInteger;
 public class RequestQueue {
 
     /**
-     * Callback interface for completed requests.
-     */
-    // TODO: This should not be a generic class, because the request type can't be determined at
-    // compile time, so all calls to onRequestFinished are unsafe. However, changing this would be
-    // an API-breaking change. See also: https://github.com/google/volley/pull/109
-    @Deprecated // Use RequestEventListener instead.
-    public interface RequestFinishedListener<T> {
-        /**
-         * Called when a request has finished processing.
-         */
-        void onRequestFinished(Request<T> request);
-    }
-
-    /**
-     * Request event types the listeners {@link RequestEventListener} will be notified about.
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-            RequestEvent.REQUEST_QUEUED,
-            RequestEvent.REQUEST_CACHE_LOOKUP_STARTED,
-            RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED,
-            RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED,
-            RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED,
-            RequestEvent.REQUEST_FINISHED
-    })
-    public @interface RequestEvent {
-        /**
-         * The request was added to the queue.
-         */
-        int REQUEST_QUEUED = 0;
-        /**
-         * Cache lookup started for the request.
-         */
-        int REQUEST_CACHE_LOOKUP_STARTED = 1;
-        /**
-         * Cache lookup finished for the request and cached response is delivered or request is
-         * queued for network dispatching.
-         */
-        int REQUEST_CACHE_LOOKUP_FINISHED = 2;
-        /**
-         * Network dispatch started for the request.
-         */
-        int REQUEST_NETWORK_DISPATCH_STARTED = 3;
-        /**
-         * The network dispatch finished for the request and response (if any) is delivered.
-         */
-        int REQUEST_NETWORK_DISPATCH_FINISHED = 4;
-        /**
-         * All the work associated with the request is finished and request is removed from all the
-         * queues.
-         */
-        int REQUEST_FINISHED = 5;
-    }
-
-    /**
-     * Callback interface for request life cycle events.
+     * Number of network request dispatcher threads to start.
      */
-    public interface RequestEventListener {
-        /**
-         * Called on every request lifecycle event. Can be called from different threads. The call
-         * is blocking request processing, so any processing should be kept at minimum or moved to
-         * another thread.
-         */
-        void onRequestEvent(Request<?> request, @RequestEvent int event);
-    }
-
+    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
     /**
      * Used for generating monotonically-increasing sequence numbers for requests.
      */
@@ -114,7 +48,7 @@ public class RequestQueue {
      * The set of all requests currently being processed by this RequestQueue. A Request will be in
      * this set if it is waiting in any queue or currently being processed by any dispatcher.
      */
-    private final Set<Request<?>> mCurrentRequests = new HashSet<>();
+    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
 
     /**
      * The cache triage queue.
@@ -125,44 +59,28 @@ public class RequestQueue {
      * The queue of requests that are actually going out to the network.
      */
     private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<>();
-
-    /**
-     * Number of network request dispatcher threads to start.
-     */
-    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
-
     /**
      * Cache interface for retrieving and storing responses.
      */
     private final Cache mCache;
-
     /**
      * Network interface for performing requests.
      */
     private final Network mNetwork;
-
     /**
      * Response delivery mechanism.
      */
     private final ResponseDelivery mDelivery;
-
     /**
      * The network dispatchers.
      */
     private final NetworkDispatcher[] mDispatchers;
-
+    private final List<RequestFinishedListener> mFinishedListeners = new ArrayList<>();
     /**
      * The cache dispatcher.
      */
     private CacheDispatcher mCacheDispatcher;
 
-    private final List<RequestFinishedListener> mFinishedListeners = new ArrayList<>();
-
-    /**
-     * Collection of listeners for request life cycle events.
-     */
-    private final List<RequestEventListener> mEventListeners = new ArrayList<>();
-
     /**
      * Creates the worker pool. Processing will not begin until {@link #start()} is called.
      *
@@ -250,14 +168,6 @@ public class RequestQueue {
         return mCache;
     }
 
-    /**
-     * A simple predicate or filter interface for Requests, for use by {@link
-     * RequestQueue#cancelAll(RequestFilter)}.
-     */
-    public interface RequestFilter {
-        boolean apply(Request<?> request);
-    }
-
     /**
      * Cancels all requests in this queue for which the given filter applies.
      *
@@ -281,12 +191,13 @@ public class RequestQueue {
         if (tag == null) {
             throw new IllegalArgumentException("Cannot cancelAll with a null tag");
         }
-        cancelAll(new RequestFilter() {
-            @Override
-            public boolean apply(Request<?> request) {
-                return request.getTag() == tag;
-            }
-        });
+        cancelAll(
+                new RequestFilter() {
+                    @Override
+                    public boolean apply(Request<?> request) {
+                        return request.getTag() == tag;
+                    }
+                });
     }
 
     /**
@@ -305,7 +216,6 @@ public class RequestQueue {
         // Process requests in the order they are added.
         request.setSequence(getSequenceNumber());
         request.addMarker("add-to-queue");
-        sendRequestEvent(request, RequestEvent.REQUEST_QUEUED);
 
         // If the request is uncacheable, skip the cache queue and go straight to the network.
         if (!request.shouldCache()) {
@@ -320,8 +230,6 @@ public class RequestQueue {
      * Called from {@link Request#finish(String)}, indicating that processing of the given request
      * has finished.
      */
-    @SuppressWarnings("unchecked")
-    // see above note on RequestFinishedListener
     <T> void finish(Request<T> request) {
         // Remove from the set of requests currently being processed.
         synchronized (mCurrentRequests) {
@@ -332,52 +240,38 @@ public class RequestQueue {
                 listener.onRequestFinished(request);
             }
         }
-        sendRequestEvent(request, RequestEvent.REQUEST_FINISHED);
     }
 
-    /**
-     * Sends a request life cycle event to the listeners.
-     */
-    void sendRequestEvent(Request<?> request, @RequestEvent int event) {
-        synchronized (mEventListeners) {
-            for (RequestEventListener listener : mEventListeners) {
-                listener.onRequestEvent(request, event);
-            }
+    public <T> void addRequestFinishedListener(RequestFinishedListener<T> listener) {
+        synchronized (mFinishedListeners) {
+            mFinishedListeners.add(listener);
         }
     }
 
     /**
-     * Add a listener for request life cycle events.
+     * Remove a RequestFinishedListener. Has no effect if listener was not previously added.
      */
-    public void addRequestEventListener(RequestEventListener listener) {
-        synchronized (mEventListeners) {
-            mEventListeners.add(listener);
+    public <T> void removeRequestFinishedListener(RequestFinishedListener<T> listener) {
+        synchronized (mFinishedListeners) {
+            mFinishedListeners.remove(listener);
         }
     }
 
     /**
-     * Remove a listener for request life cycle events.
+     * Callback interface for completed requests.
      */
-    public void removeRequestEventListener(RequestEventListener listener) {
-        synchronized (mEventListeners) {
-            mEventListeners.remove(listener);
-        }
-    }
-
-    @Deprecated // Use RequestEventListener instead.
-    public <T> void addRequestFinishedListener(RequestFinishedListener<T> listener) {
-        synchronized (mFinishedListeners) {
-            mFinishedListeners.add(listener);
-        }
+    public interface RequestFinishedListener<T> {
+        /**
+         * Called when a request has finished processing.
+         */
+        void onRequestFinished(Request<T> request);
     }
 
     /**
-     * Remove a RequestFinishedListener. Has no effect if listener was not previously added.
+     * A simple predicate or filter interface for Requests, for use by {@link
+     * RequestQueue#cancelAll(RequestFilter)}.
      */
-    @Deprecated // Use RequestEventListener instead.
-    public <T> void removeRequestFinishedListener(RequestFinishedListener<T> listener) {
-        synchronized (mFinishedListeners) {
-            mFinishedListeners.remove(listener);
-        }
+    public interface RequestFilter {
+        boolean apply(Request<?> request);
     }
 }

+ 42 - 45
library_volley/src/main/java/cn/yyxx/support/volley/source/Response.java

@@ -24,31 +24,39 @@ package cn.yyxx.support.volley.source;
 public class Response<T> {
 
     /**
-     * Callback interface for delivering parsed responses.
+     * Parsed response, or null in the case of error.
      */
-    public interface Listener<T> {
-        /**
-         * Called when a response is received.
-         */
-        void onResponse(T response);
-    }
-
+    public final T result;
     /**
-     * Callback interface for delivering error responses.
+     * Cache metadata for this response, or null in the case of error.
      */
-    public interface ErrorListener {
-        /**
-         * Callback method that an error has been occurred with the provided error code and optional
-         * user-readable message.
-         */
-        void onErrorResponse(VolleyError error);
+    public final Cache.Entry cacheEntry;
+    /**
+     * Detailed error information if <code>errorCode != OK</code>.
+     */
+    public final VolleyError error;
+    /**
+     * True if this response was a soft-expired one and a second one MAY be coming.
+     */
+    public boolean intermediate = false;
+
+    private Response(T result, Cache.Entry cacheEntry) {
+        this.result = result;
+        this.cacheEntry = cacheEntry;
+        this.error = null;
+    }
+
+    private Response(VolleyError error) {
+        this.result = null;
+        this.cacheEntry = null;
+        this.error = error;
     }
 
     /**
      * Returns a successful response containing the parsed result.
      */
     public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
-        return new Response<>(result, cacheEntry);
+        return new Response<T>(result, cacheEntry);
     }
 
     /**
@@ -56,29 +64,9 @@ public class Response<T> {
      * displayed to the user.
      */
     public static <T> Response<T> error(VolleyError error) {
-        return new Response<>(error);
+        return new Response<T>(error);
     }
 
-    /**
-     * Parsed response, or null in the case of error.
-     */
-    public final T result;
-
-    /**
-     * Cache metadata for this response, or null in the case of error.
-     */
-    public final Cache.Entry cacheEntry;
-
-    /**
-     * Detailed error information if <code>errorCode != OK</code>.
-     */
-    public final VolleyError error;
-
-    /**
-     * True if this response was a soft-expired one and a second one MAY be coming.
-     */
-    public boolean intermediate = false;
-
     /**
      * Returns whether this response is considered successful.
      */
@@ -86,15 +74,24 @@ public class Response<T> {
         return error == null;
     }
 
-    private Response(T result, Cache.Entry cacheEntry) {
-        this.result = result;
-        this.cacheEntry = cacheEntry;
-        this.error = null;
+    /**
+     * Callback interface for delivering parsed responses.
+     */
+    public interface Listener<T> {
+        /**
+         * Called when a response is received.
+         */
+        void onResponse(T response);
     }
 
-    private Response(VolleyError error) {
-        this.result = null;
-        this.cacheEntry = null;
-        this.error = error;
+    /**
+     * Callback interface for delivering error responses.
+     */
+    public interface ErrorListener {
+        /**
+         * Callback method that an error has been occurred with the provided error code and optional
+         * user-readable message.
+         */
+        void onErrorResponse(VolleyError error);
     }
 }

+ 0 - 18
library_volley/src/main/java/cn/yyxx/support/volley/source/RetryPolicy.java

@@ -18,24 +18,6 @@ package cn.yyxx.support.volley.source;
 
 /**
  * Retry policy for a request.
- *
- * <p>A retry policy can control two parameters:
- *
- * <ul>
- *   <li>The number of tries. This can be a simple counter or more complex logic based on the type
- *       of error passed to {@link #retry(VolleyError)}, although {@link #getCurrentRetryCount()}
- *       should always return the current retry count for logging purposes.
- *   <li>The request timeout for each try, via {@link #getCurrentTimeout()}. In the common case that
- *       a request times out before the response has been received from the server, retrying again
- *       with a longer timeout can increase the likelihood of success (at the expense of causing the
- *       user to wait longer, especially if the request still fails).
- * </ul>
- *
- * <p>Note that currently, retries triggered by a retry policy are attempted immediately in sequence
- * with no delay between them (although the time between tries may increase if the requests are
- * timing out and {@link #getCurrentTimeout()} is returning increasing values).
- *
- * <p>By default, Volley uses {@link DefaultRetryPolicy}.
  */
 public interface RetryPolicy {
 

+ 4 - 5
library_volley/src/main/java/cn/yyxx/support/volley/source/VolleyError.java

@@ -47,12 +47,11 @@ public class VolleyError extends Exception {
         networkResponse = null;
     }
 
-    /* package */
-    void setNetworkTimeMs(long networkTimeMs) {
-        this.networkTimeMs = networkTimeMs;
-    }
-
     public long getNetworkTimeMs() {
         return networkTimeMs;
     }
+
+    /* package */ void setNetworkTimeMs(long networkTimeMs) {
+        this.networkTimeMs = networkTimeMs;
+    }
 }

+ 15 - 18
library_volley/src/main/java/cn/yyxx/support/volley/source/VolleyLog.java

@@ -30,15 +30,13 @@ import java.util.Locale;
  * {@code <android-sdk>/platform-tools/adb shell setprop log.tag.Volley VERBOSE}
  */
 public class VolleyLog {
-    public static String TAG = "Volley";
-
-    public static boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
-
     /**
      * {@link Class#getName()} uses reflection and calling it on a potentially hot code path may
      * have some cost. To minimize this cost we fetch class name once here and use it later.
      */
     private static final String CLASS_NAME = VolleyLog.class.getName();
+    public static String TAG = "Volley";
+    public static boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
 
     /**
      * Customize the log tag for your application, so that other apps using Volley don't mix their
@@ -115,20 +113,7 @@ public class VolleyLog {
          * Minimum duration from first marker to last in an marker log to warrant logging.
          */
         private static final long MIN_DURATION_FOR_LOGGING_MS = 0;
-
-        private static class Marker {
-            public final String name;
-            public final long thread;
-            public final long time;
-
-            public Marker(String name, long thread, long time) {
-                this.name = name;
-                this.thread = thread;
-                this.time = time;
-            }
-        }
-
-        private final List<Marker> mMarkers = new ArrayList<>();
+        private final List<Marker> mMarkers = new ArrayList<Marker>();
         private boolean mFinished = false;
 
         /**
@@ -187,5 +172,17 @@ public class VolleyLog {
             long last = mMarkers.get(mMarkers.size() - 1).time;
             return last - first;
         }
+
+        private static class Marker {
+            public final String name;
+            public final long thread;
+            public final long time;
+
+            public Marker(String name, long thread, long time) {
+                this.name = name;
+                this.thread = thread;
+                this.time = time;
+            }
+        }
     }
 }

+ 4 - 4
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/AdaptedHttpStack.java

@@ -15,10 +15,6 @@
  */
 package cn.yyxx.support.volley.source.toolbox;
 
-import cn.yyxx.support.volley.source.AuthFailureError;
-import cn.yyxx.support.volley.source.Header;
-import cn.yyxx.support.volley.source.Request;
-
 import org.apache.http.conn.ConnectTimeoutException;
 
 import java.io.IOException;
@@ -27,6 +23,10 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
+import cn.yyxx.support.volley.source.AuthFailureError;
+import cn.yyxx.support.volley.source.Header;
+import cn.yyxx.support.volley.source.Request;
+
 /**
  * {@link BaseHttpStack} implementation wrapping a {@link HttpStack}.
  *

+ 3 - 8
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/AndroidAuthenticator.java

@@ -23,7 +23,6 @@ import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
-import android.support.annotation.VisibleForTesting;
 
 import cn.yyxx.support.volley.source.AuthFailureError;
 
@@ -47,7 +46,7 @@ public class AndroidAuthenticator implements Authenticator {
      * @param authTokenType Auth token type passed to AccountManager
      */
     public AndroidAuthenticator(Context context, Account account, String authTokenType) {
-        this(context, account, authTokenType, /* notifyAuthFailure= */ false);
+        this(context, account, authTokenType, false);
     }
 
     /**
@@ -63,7 +62,7 @@ public class AndroidAuthenticator implements Authenticator {
         this(AccountManager.get(context), account, authTokenType, notifyAuthFailure);
     }
 
-    @VisibleForTesting
+    // Visible for testing. Allows injection of a mock AccountManager.
     AndroidAuthenticator(
             AccountManager accountManager,
             Account account,
@@ -95,11 +94,7 @@ public class AndroidAuthenticator implements Authenticator {
     public String getAuthToken() throws AuthFailureError {
         AccountManagerFuture<Bundle> future =
                 mAccountManager.getAuthToken(
-                        mAccount,
-                        mAuthTokenType,
-                        mNotifyAuthFailure,
-                        /* callback= */ null,
-                        /* handler= */ null);
+                        mAccount, mAuthTokenType, mNotifyAuthFailure, null, null);
         Bundle result;
         try {
             result = future.getResult();

+ 11 - 9
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/BaseHttpStack.java

@@ -15,10 +15,6 @@
  */
 package cn.yyxx.support.volley.source.toolbox;
 
-import cn.yyxx.support.volley.source.AuthFailureError;
-import cn.yyxx.support.volley.source.Header;
-import cn.yyxx.support.volley.source.Request;
-
 import org.apache.http.ProtocolVersion;
 import org.apache.http.StatusLine;
 import org.apache.http.entity.BasicHttpEntity;
@@ -33,11 +29,14 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
+import cn.yyxx.support.volley.source.AuthFailureError;
+import cn.yyxx.support.volley.source.Header;
+import cn.yyxx.support.volley.source.Request;
+
 /**
  * An HTTP stack abstraction.
  */
-@SuppressWarnings("deprecation")
-// for HttpStack
+@SuppressWarnings("deprecation") // for HttpStack
 public abstract class BaseHttpStack implements HttpStack {
 
     /**
@@ -67,19 +66,22 @@ public abstract class BaseHttpStack implements HttpStack {
      */
     @Deprecated
     @Override
-    public final org.apache.http.HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
+    public final org.apache.http.HttpResponse performRequest(
+            Request<?> request, Map<String, String> additionalHeaders)
             throws IOException, AuthFailureError {
         HttpResponse response = executeRequest(request, additionalHeaders);
 
         ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
-        StatusLine statusLine = new BasicStatusLine(protocolVersion, response.getStatusCode(), /* reasonPhrase= */ "");
+        StatusLine statusLine =
+                new BasicStatusLine(
+                        protocolVersion, response.getStatusCode(), "" /* reasonPhrase */);
         BasicHttpResponse apacheResponse = new BasicHttpResponse(statusLine);
 
         List<org.apache.http.Header> headers = new ArrayList<>();
         for (Header header : response.getHeaders()) {
             headers.add(new BasicHeader(header.getName(), header.getValue()));
         }
-        apacheResponse.setHeaders(headers.toArray(new org.apache.http.Header[0]));
+        apacheResponse.setHeaders(headers.toArray(new org.apache.http.Header[headers.size()]));
 
         InputStream responseStream = response.getContent();
         if (responseStream != null) {

+ 101 - 104
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/BasicNetwork.java

@@ -18,8 +18,21 @@ package cn.yyxx.support.volley.source.toolbox;
 
 import android.os.SystemClock;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
 import cn.yyxx.support.volley.source.AuthFailureError;
-import cn.yyxx.support.volley.source.Cache;
 import cn.yyxx.support.volley.source.Cache.Entry;
 import cn.yyxx.support.volley.source.ClientError;
 import cn.yyxx.support.volley.source.Header;
@@ -34,20 +47,6 @@ import cn.yyxx.support.volley.source.TimeoutError;
 import cn.yyxx.support.volley.source.VolleyError;
 import cn.yyxx.support.volley.source.VolleyLog;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.SocketTimeoutException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
 /**
  * A network performing Volley requests over an {@link HttpStack}.
  */
@@ -64,10 +63,8 @@ public class BasicNetwork implements Network {
      */
     @Deprecated
     protected final HttpStack mHttpStack;
-
-    private final BaseHttpStack mBaseHttpStack;
-
     protected final ByteArrayPool mPool;
+    private final BaseHttpStack mBaseHttpStack;
 
     /**
      * @param httpStack HTTP stack to be used
@@ -116,6 +113,87 @@ public class BasicNetwork implements Network {
         mPool = pool;
     }
 
+    /**
+     * Attempts to prepare the request for a retry. If there are no more attempts remaining in the
+     * request's retry policy, a timeout exception is thrown.
+     *
+     * @param request The request to use.
+     */
+    private static void attemptRetryOnException(
+            String logPrefix, Request<?> request, VolleyError exception) throws VolleyError {
+        RetryPolicy retryPolicy = request.getRetryPolicy();
+        int oldTimeout = request.getTimeoutMs();
+
+        try {
+            retryPolicy.retry(exception);
+        } catch (VolleyError e) {
+            request.addMarker(
+                    String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
+            throw e;
+        }
+        request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
+    }
+
+    /**
+     * Converts Headers[] to Map&lt;String, String&gt;.
+     *
+     * @deprecated Should never have been exposed in the API. This method may be removed in a future
+     * release of Volley.
+     */
+    @Deprecated
+    protected static Map<String, String> convertHeaders(Header[] headers) {
+        Map<String, String> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        for (int i = 0; i < headers.length; i++) {
+            result.put(headers[i].getName(), headers[i].getValue());
+        }
+        return result;
+    }
+
+    /**
+     * Combine cache headers with network response headers for an HTTP 304 response.
+     *
+     * <p>An HTTP 304 response does not have all header fields. We have to use the header fields
+     * from the cache entry plus the new ones from the response. See also:
+     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
+     *
+     * @param responseHeaders Headers from the network response.
+     * @param entry           The cached response.
+     * @return The combined list of headers.
+     */
+    private static List<Header> combineHeaders(List<Header> responseHeaders, Entry entry) {
+        // First, create a case-insensitive set of header names from the network
+        // response.
+        Set<String> headerNamesFromNetworkResponse = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+        if (!responseHeaders.isEmpty()) {
+            for (Header header : responseHeaders) {
+                headerNamesFromNetworkResponse.add(header.getName());
+            }
+        }
+
+        // Second, add headers from the cache entry to the network response as long as
+        // they didn't appear in the network response, which should take precedence.
+        List<Header> combinedHeaders = new ArrayList<>(responseHeaders);
+        if (entry.allResponseHeaders != null) {
+            if (!entry.allResponseHeaders.isEmpty()) {
+                for (Header header : entry.allResponseHeaders) {
+                    if (!headerNamesFromNetworkResponse.contains(header.getName())) {
+                        combinedHeaders.add(header);
+                    }
+                }
+            }
+        } else {
+            // Legacy caches only have entry.responseHeaders.
+            if (!entry.responseHeaders.isEmpty()) {
+                for (Map.Entry<String, String> header : entry.responseHeaders.entrySet()) {
+                    if (!headerNamesFromNetworkResponse.contains(header.getKey())) {
+                        combinedHeaders.add(new Header(header.getKey(), header.getValue()));
+                    }
+                }
+            }
+        }
+        return combinedHeaders;
+    }
+
     @Override
     public NetworkResponse performRequest(Request<?> request) throws VolleyError {
         long requestStart = SystemClock.elapsedRealtime();
@@ -137,8 +215,8 @@ public class BasicNetwork implements Network {
                     if (entry == null) {
                         return new NetworkResponse(
                                 HttpURLConnection.HTTP_NOT_MODIFIED,
-                                /* data= */ null,
-                                /* notModified= */ true,
+                                null,
+                                true,
                                 SystemClock.elapsedRealtime() - requestStart,
                                 responseHeaders);
                     }
@@ -147,7 +225,7 @@ public class BasicNetwork implements Network {
                     return new NetworkResponse(
                             HttpURLConnection.HTTP_NOT_MODIFIED,
                             entry.data,
-                            /* notModified= */ true,
+                            true,
                             SystemClock.elapsedRealtime() - requestStart,
                             combinedHeaders);
                 }
@@ -173,7 +251,7 @@ public class BasicNetwork implements Network {
                 return new NetworkResponse(
                         statusCode,
                         responseContents,
-                        /* notModified= */ false,
+                        false,
                         SystemClock.elapsedRealtime() - requestStart,
                         responseHeaders);
             } catch (SocketTimeoutException e) {
@@ -194,7 +272,7 @@ public class BasicNetwork implements Network {
                             new NetworkResponse(
                                     statusCode,
                                     responseContents,
-                                    /* notModified= */ false,
+                                    false,
                                     SystemClock.elapsedRealtime() - requestStart,
                                     responseHeaders);
                     if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED
@@ -239,27 +317,6 @@ public class BasicNetwork implements Network {
         }
     }
 
-    /**
-     * Attempts to prepare the request for a retry. If there are no more attempts remaining in the
-     * request's retry policy, a timeout exception is thrown.
-     *
-     * @param request The request to use.
-     */
-    private static void attemptRetryOnException(
-            String logPrefix, Request<?> request, VolleyError exception) throws VolleyError {
-        RetryPolicy retryPolicy = request.getRetryPolicy();
-        int oldTimeout = request.getTimeoutMs();
-
-        try {
-            retryPolicy.retry(exception);
-        } catch (VolleyError e) {
-            request.addMarker(
-                    String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
-            throw e;
-        }
-        request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
-    }
-
     private Map<String, String> getCacheHeaders(Entry entry) {
         // If there's no cache entry, we're done.
         if (entry == null) {
@@ -317,64 +374,4 @@ public class BasicNetwork implements Network {
             bytes.close();
         }
     }
-
-    /**
-     * Converts Headers[] to Map&lt;String, String&gt;.
-     *
-     * @deprecated Should never have been exposed in the API. This method may be removed in a future
-     * release of Volley.
-     */
-    @Deprecated
-    protected static Map<String, String> convertHeaders(Header[] headers) {
-        Map<String, String> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-        for (int i = 0; i < headers.length; i++) {
-            result.put(headers[i].getName(), headers[i].getValue());
-        }
-        return result;
-    }
-
-    /**
-     * Combine cache headers with network response headers for an HTTP 304 response.
-     *
-     * <p>An HTTP 304 response does not have all header fields. We have to use the header fields
-     * from the cache entry plus the new ones from the response. See also:
-     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
-     *
-     * @param responseHeaders Headers from the network response.
-     * @param entry           The cached response.
-     * @return The combined list of headers.
-     */
-    private static List<Header> combineHeaders(List<Header> responseHeaders, Entry entry) {
-        // First, create a case-insensitive set of header names from the network
-        // response.
-        Set<String> headerNamesFromNetworkResponse = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
-        if (!responseHeaders.isEmpty()) {
-            for (Header header : responseHeaders) {
-                headerNamesFromNetworkResponse.add(header.getName());
-            }
-        }
-
-        // Second, add headers from the cache entry to the network response as long as
-        // they didn't appear in the network response, which should take precedence.
-        List<Header> combinedHeaders = new ArrayList<>(responseHeaders);
-        if (entry.allResponseHeaders != null) {
-            if (!entry.allResponseHeaders.isEmpty()) {
-                for (Header header : entry.allResponseHeaders) {
-                    if (!headerNamesFromNetworkResponse.contains(header.getName())) {
-                        combinedHeaders.add(header);
-                    }
-                }
-            }
-        } else {
-            // Legacy caches only have entry.responseHeaders.
-            if (!entry.responseHeaders.isEmpty()) {
-                for (Map.Entry<String, String> header : entry.responseHeaders.entrySet()) {
-                    if (!headerNamesFromNetworkResponse.contains(header.getKey())) {
-                        combinedHeaders.add(new Header(header.getKey(), header.getValue()));
-                    }
-                }
-            }
-        }
-        return combinedHeaders;
-    }
 }

+ 13 - 17
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/ByteArrayPool.java

@@ -51,33 +51,29 @@ import java.util.List;
  */
 public class ByteArrayPool {
     /**
-     * The buffer pool, arranged both by last use and by buffer size
+     * Compares buffers by size
      */
-    private final List<byte[]> mBuffersByLastUse = new ArrayList<>();
-
-    private final List<byte[]> mBuffersBySize = new ArrayList<>(64);
-
+    protected static final Comparator<byte[]> BUF_COMPARATOR =
+            new Comparator<byte[]>() {
+                @Override
+                public int compare(byte[] lhs, byte[] rhs) {
+                    return lhs.length - rhs.length;
+                }
+            };
     /**
-     * The total size of the buffers in the pool
+     * The buffer pool, arranged both by last use and by buffer size
      */
-    private int mCurrentSize = 0;
-
+    private final List<byte[]> mBuffersByLastUse = new ArrayList<>();
+    private final List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64);
     /**
      * The maximum aggregate size of the buffers in the pool. Old buffers are discarded to stay
      * under this limit.
      */
     private final int mSizeLimit;
-
     /**
-     * Compares buffers by size
+     * The total size of the buffers in the pool
      */
-    protected static final Comparator<byte[]> BUF_COMPARATOR =
-            new Comparator<byte[]>() {
-                @Override
-                public int compare(byte[] lhs, byte[] rhs) {
-                    return lhs.length - rhs.length;
-                }
-            };
+    private int mCurrentSize = 0;
 
     /**
      * @param sizeLimit the maximum size of the pool, in bytes

+ 160 - 183
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/DiskBasedCache.java

@@ -17,13 +17,8 @@
 package cn.yyxx.support.volley.source.toolbox;
 
 import android.os.SystemClock;
-import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 
-import cn.yyxx.support.volley.source.Cache;
-import cn.yyxx.support.volley.source.Header;
-import cn.yyxx.support.volley.source.VolleyLog;
-
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.DataInputStream;
@@ -36,6 +31,7 @@ import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -43,6 +39,10 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
+import cn.yyxx.support.volley.source.Cache;
+import cn.yyxx.support.volley.source.Header;
+import cn.yyxx.support.volley.source.VolleyLog;
+
 /**
  * Cache implementation that caches files directly onto the hard disk in the specified directory.
  * The default disk usage size is 5MB, but is configurable.
@@ -52,48 +52,40 @@ import java.util.Map;
 public class DiskBasedCache implements Cache {
 
     /**
-     * Map of the Key, CacheHeader pairs
+     * Default maximum disk usage in bytes.
      */
-    private final Map<String, CacheHeader> mEntries = new LinkedHashMap<>(16, .75f, true);
-
+    private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
     /**
-     * Total amount of space currently used by the cache in bytes.
+     * High water mark percentage for the cache
      */
-    private long mTotalSize = 0;
-
+    private static final float HYSTERESIS_FACTOR = 0.9f;
     /**
-     * The root directory to use for the cache.
+     * Magic number for current version of cache file format.
      */
-    private final File mRootDirectory;
-
+    private static final int CACHE_MAGIC = 0x20150306;
     /**
-     * The maximum size of the cache in bytes.
+     * Map of the Key, CacheHeader pairs
      */
-    private final int mMaxCacheSizeInBytes;
-
+    private final Map<String, CacheHeader> mEntries =
+            new LinkedHashMap<String, CacheHeader>(16, .75f, true);
     /**
-     * Default maximum disk usage in bytes.
+     * The root directory to use for the cache.
      */
-    private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
-
+    private final File mRootDirectory;
     /**
-     * High water mark percentage for the cache
+     * The maximum size of the cache in bytes.
      */
-    @VisibleForTesting
-    static final float HYSTERESIS_FACTOR = 0.9f;
-
+    private final int mMaxCacheSizeInBytes;
     /**
-     * Magic number for current version of cache file format.
+     * Total amount of space currently used by the cache in bytes.
      */
-    private static final int CACHE_MAGIC = 0x20150306;
+    private long mTotalSize = 0;
 
     /**
      * Constructs an instance of the DiskBasedCache at the specified directory.
      *
      * @param rootDirectory       The root directory of the cache.
-     * @param maxCacheSizeInBytes The maximum size of the cache in bytes. Note that the cache may
-     *                            briefly exceed this size on disk when writing a new entry that pushes it over the limit
-     *                            until the ensuing pruning completes.
+     * @param maxCacheSizeInBytes The maximum size of the cache in bytes.
      */
     public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
         mRootDirectory = rootDirectory;
@@ -110,6 +102,116 @@ public class DiskBasedCache implements Cache {
         this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
     }
 
+    /**
+     * Reads length bytes from CountingInputStream into byte array.
+     *
+     * @param cis    input stream
+     * @param length number of bytes to read
+     * @throws IOException if fails to read all bytes
+     */
+    // VisibleForTesting
+    static byte[] streamToBytes(CountingInputStream cis, long length) throws IOException {
+        long maxLength = cis.bytesRemaining();
+        // Length cannot be negative or greater than bytes remaining, and must not overflow int.
+        if (length < 0 || length > maxLength || (int) length != length) {
+            throw new IOException("streamToBytes length=" + length + ", maxLength=" + maxLength);
+        }
+        byte[] bytes = new byte[(int) length];
+        new DataInputStream(cis).readFully(bytes);
+        return bytes;
+    }
+
+    /**
+     * 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(StandardCharsets.UTF_8);
+        writeLong(os, b.length);
+        os.write(b, 0, b.length);
+    }
+
+    static String readString(CountingInputStream cis) throws IOException {
+        long n = readLong(cis);
+        byte[] b = streamToBytes(cis, n);
+        return new String(b, StandardCharsets.UTF_8);
+    }
+
+    static void writeHeaderList(List<Header> headers, OutputStream os) throws IOException {
+        if (headers != null) {
+            writeInt(os, headers.size());
+            for (Header header : headers) {
+                writeString(os, header.getName());
+                writeString(os, header.getValue());
+            }
+        } else {
+            writeInt(os, 0);
+        }
+    }
+
+    static List<Header> readHeaderList(CountingInputStream cis) throws IOException {
+        int size = readInt(cis);
+        if (size < 0) {
+            throw new IOException("readHeaderList size=" + size);
+        }
+        List<Header> result =
+                (size == 0) ? Collections.<Header>emptyList() : new ArrayList<Header>();
+        for (int i = 0; i < size; i++) {
+            String name = readString(cis).intern();
+            String value = readString(cis).intern();
+            result.add(new Header(name, value));
+        }
+        return result;
+    }
+
     /**
      * Clears the cache. Deletes all cached files from disk.
      */
@@ -189,6 +291,8 @@ public class DiskBasedCache implements Cache {
                                 new BufferedInputStream(createInputStream(file)), entrySize);
                 try {
                     CacheHeader entry = CacheHeader.readHeader(cis);
+                    // NOTE: When this entry was put, its size was recorded as data.length, but
+                    // when the entry is initialized below, its size is recorded as file.length()
                     entry.size = entrySize;
                     putEntry(entry.key, entry);
                 } finally {
@@ -226,14 +330,7 @@ public class DiskBasedCache implements Cache {
      */
     @Override
     public synchronized void put(String key, Entry entry) {
-        // If adding this entry would trigger a prune, but pruning would cause the new entry to be
-        // deleted, then skip writing the entry in the first place, as this is just churn.
-        // Note that we don't include the cache header overhead in this calculation for simplicity,
-        // so putting entries which are just below the threshold may still cause this churn.
-        if (mTotalSize + entry.data.length > mMaxCacheSizeInBytes
-                && entry.data.length > mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
-            return;
-        }
+        pruneIfNeeded(entry.data.length);
         File file = getFileForKey(key);
         try {
             BufferedOutputStream fos = new BufferedOutputStream(createOutputStream(file));
@@ -246,9 +343,7 @@ public class DiskBasedCache implements Cache {
             }
             fos.write(entry.data);
             fos.close();
-            e.size = file.length();
             putEntry(key, e);
-            pruneIfNeeded();
             return;
         } catch (IOException e) {
         }
@@ -272,6 +367,15 @@ public class DiskBasedCache implements Cache {
         }
     }
 
+    /*
+     * 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.
+     *
+     * TODO: Replace by standard DataInput and DataOutput in next cache version.
+     */
+
     /**
      * Creates a pseudo-unique filename for the specified cache key.
      *
@@ -293,10 +397,12 @@ public class DiskBasedCache implements Cache {
     }
 
     /**
-     * Prunes the cache to fit the maximum size.
+     * Prunes the cache to fit the amount of bytes specified.
+     *
+     * @param neededSpace The amount of bytes we are trying to fit into the cache.
      */
-    private void pruneIfNeeded() {
-        if (mTotalSize < mMaxCacheSizeInBytes) {
+    private void pruneIfNeeded(int neededSpace) {
+        if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
             return;
         }
         if (VolleyLog.DEBUG) {
@@ -322,7 +428,7 @@ public class DiskBasedCache implements Cache {
             iterator.remove();
             prunedFiles++;
 
-            if (mTotalSize < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
+            if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
                 break;
             }
         }
@@ -360,31 +466,12 @@ public class DiskBasedCache implements Cache {
         }
     }
 
-    /**
-     * Reads length bytes from CountingInputStream into byte array.
-     *
-     * @param cis    input stream
-     * @param length number of bytes to read
-     * @throws IOException if fails to read all bytes
-     */
-    @VisibleForTesting
-    static byte[] streamToBytes(CountingInputStream cis, long length) throws IOException {
-        long maxLength = cis.bytesRemaining();
-        // Length cannot be negative or greater than bytes remaining, and must not overflow int.
-        if (length < 0 || length > maxLength || (int) length != length) {
-            throw new IOException("streamToBytes length=" + length + ", maxLength=" + maxLength);
-        }
-        byte[] bytes = new byte[(int) length];
-        new DataInputStream(cis).readFully(bytes);
-        return bytes;
-    }
-
-    @VisibleForTesting
+    // VisibleForTesting
     InputStream createInputStream(File file) throws FileNotFoundException {
         return new FileInputStream(file);
     }
 
-    @VisibleForTesting
+    // VisibleForTesting
     OutputStream createOutputStream(File file) throws FileNotFoundException {
         return new FileOutputStream(file);
     }
@@ -392,51 +479,40 @@ public class DiskBasedCache implements Cache {
     /**
      * Handles holding onto the cache headers for an entry.
      */
-    @VisibleForTesting
+    // VisibleForTesting
     static class CacheHeader {
-        /**
-         * The size of the data identified by this CacheHeader on disk (both header and data).
-         *
-         * <p>Must be set by the caller after it has been calculated.
-         *
-         * <p>This is not serialized to disk.
-         */
-        long size;
-
         /**
          * The key that identifies the cache entry.
          */
         final String key;
-
         /**
          * ETag for cache coherence.
          */
         final String etag;
-
         /**
          * Date of this response as reported by the server.
          */
         final long serverDate;
-
         /**
          * The last modified date for the requested object.
          */
         final long lastModified;
-
         /**
          * TTL for this record.
          */
         final long ttl;
-
         /**
          * Soft TTL for this record.
          */
         final long softTtl;
-
         /**
          * Headers from the response resulting in this cache entry.
          */
         final List<Header> allResponseHeaders;
+        /**
+         * The size of the data identified by this CacheHeader. (This is not serialized to disk.
+         */
+        long size;
 
         private CacheHeader(
                 String key,
@@ -447,7 +523,7 @@ public class DiskBasedCache implements Cache {
                 long softTtl,
                 List<Header> allResponseHeaders) {
             this.key = key;
-            this.etag = "".equals(etag) ? null : etag;
+            this.etag = ("".equals(etag)) ? null : etag;
             this.serverDate = serverDate;
             this.lastModified = lastModified;
             this.ttl = ttl;
@@ -470,6 +546,7 @@ public class DiskBasedCache implements Cache {
                     entry.ttl,
                     entry.softTtl,
                     getAllResponseHeaders(entry));
+            size = entry.data.length;
         }
 
         private static List<Header> getAllResponseHeaders(Entry entry) {
@@ -543,7 +620,7 @@ public class DiskBasedCache implements Cache {
         }
     }
 
-    @VisibleForTesting
+    // VisibleForTesting
     static class CountingInputStream extends FilterInputStream {
         private final long length;
         private long bytesRead;
@@ -571,7 +648,7 @@ public class DiskBasedCache implements Cache {
             return result;
         }
 
-        @VisibleForTesting
+        // VisibleForTesting
         long bytesRead() {
             return bytesRead;
         }
@@ -580,104 +657,4 @@ public class DiskBasedCache implements Cache {
             return length - bytesRead;
         }
     }
-
-    /*
-     * 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.
-     *
-     * TODO: Replace by standard DataInput and DataOutput in next cache version.
-     */
-
-    /**
-     * 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);
-    }
-
-    static String readString(CountingInputStream cis) throws IOException {
-        long n = readLong(cis);
-        byte[] b = streamToBytes(cis, n);
-        return new String(b, "UTF-8");
-    }
-
-    static void writeHeaderList(List<Header> headers, OutputStream os) throws IOException {
-        if (headers != null) {
-            writeInt(os, headers.size());
-            for (Header header : headers) {
-                writeString(os, header.getName());
-                writeString(os, header.getValue());
-            }
-        } else {
-            writeInt(os, 0);
-        }
-    }
-
-    static List<Header> readHeaderList(CountingInputStream cis) throws IOException {
-        int size = readInt(cis);
-        if (size < 0) {
-            throw new IOException("readHeaderList size=" + size);
-        }
-        List<Header> result =
-                (size == 0) ? Collections.<Header>emptyList() : new ArrayList<Header>();
-        for (int i = 0; i < size; i++) {
-            String name = readString(cis).intern();
-            String value = readString(cis).intern();
-            result.add(new Header(name, value));
-        }
-        return result;
-    }
 }

+ 23 - 26
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/HttpClientStack.java

@@ -16,10 +16,6 @@
 
 package cn.yyxx.support.volley.source.toolbox;
 
-import cn.yyxx.support.volley.source.AuthFailureError;
-import cn.yyxx.support.volley.source.Request;
-import cn.yyxx.support.volley.source.Request.Method;
-
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.NameValuePair;
@@ -44,6 +40,10 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
+import cn.yyxx.support.volley.source.AuthFailureError;
+import cn.yyxx.support.volley.source.Request;
+import cn.yyxx.support.volley.source.Request.Method;
+
 /**
  * An HttpStack that performs request over an {@link HttpClient}.
  *
@@ -52,15 +52,14 @@ import java.util.Map;
  */
 @Deprecated
 public class HttpClientStack implements HttpStack {
-    protected final HttpClient mClient;
-
     private static final String HEADER_CONTENT_TYPE = "Content-Type";
+    protected final HttpClient mClient;
 
     public HttpClientStack(HttpClient client) {
         mClient = client;
     }
 
-    private static void setHeaders(HttpUriRequest httpRequest, Map<String, String> headers) {
+    private static void addHeaders(HttpUriRequest httpRequest, Map<String, String> headers) {
         for (String key : headers.keySet()) {
             httpRequest.setHeader(key, headers.get(key));
         }
@@ -68,31 +67,13 @@ public class HttpClientStack implements HttpStack {
 
     @SuppressWarnings("unused")
     private static List<NameValuePair> getPostParameterPairs(Map<String, String> postParams) {
-        List<NameValuePair> result = new ArrayList<>(postParams.size());
+        List<NameValuePair> result = new ArrayList<NameValuePair>(postParams.size());
         for (String key : postParams.keySet()) {
             result.add(new BasicNameValuePair(key, postParams.get(key)));
         }
         return result;
     }
 
-    @Override
-    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
-            throws IOException, AuthFailureError {
-        HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
-        setHeaders(httpRequest, additionalHeaders);
-        // Request.getHeaders() takes precedence over the given additional (cache) headers) and any
-        // headers set by createHttpRequest (like the Content-Type header).
-        setHeaders(httpRequest, request.getHeaders());
-        onPrepareRequest(httpRequest);
-        HttpParams httpParams = httpRequest.getParams();
-        int timeoutMs = request.getTimeoutMs();
-        // TODO: Reevaluate this connection timeout based on more wide-scale
-        // data collection and possibly different for wifi vs. 3G.
-        HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
-        HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
-        return mClient.execute(httpRequest);
-    }
-
     /**
      * Creates the appropriate subclass of HttpUriRequest for passed in request.
      */
@@ -162,6 +143,22 @@ public class HttpClientStack implements HttpStack {
         }
     }
 
+    @Override
+    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
+            throws IOException, AuthFailureError {
+        HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
+        addHeaders(httpRequest, additionalHeaders);
+        addHeaders(httpRequest, request.getHeaders());
+        onPrepareRequest(httpRequest);
+        HttpParams httpParams = httpRequest.getParams();
+        int timeoutMs = request.getTimeoutMs();
+        // TODO: Reevaluate this connection timeout based on more wide-scale
+        // data collection and possibly different for wifi vs. 3G.
+        HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
+        HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
+        return mClient.execute(httpRequest);
+    }
+
     /**
      * Called before the request is executed using the underlying HttpClient.
      *

+ 5 - 5
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/HttpHeaderParser.java

@@ -16,11 +16,6 @@
 
 package cn.yyxx.support.volley.source.toolbox;
 
-import cn.yyxx.support.volley.source.Cache;
-import cn.yyxx.support.volley.source.Header;
-import cn.yyxx.support.volley.source.NetworkResponse;
-import cn.yyxx.support.volley.source.VolleyLog;
-
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -31,6 +26,11 @@ import java.util.Map;
 import java.util.TimeZone;
 import java.util.TreeMap;
 
+import cn.yyxx.support.volley.source.Cache;
+import cn.yyxx.support.volley.source.Header;
+import cn.yyxx.support.volley.source.NetworkResponse;
+import cn.yyxx.support.volley.source.VolleyLog;
+
 /**
  * Utility methods for parsing HTTP headers.
  */

+ 3 - 3
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/HttpResponse.java

@@ -15,12 +15,12 @@
  */
 package cn.yyxx.support.volley.source.toolbox;
 
-import cn.yyxx.support.volley.source.Header;
-
 import java.io.InputStream;
 import java.util.Collections;
 import java.util.List;
 
+import cn.yyxx.support.volley.source.Header;
+
 /**
  * A response from an HTTP server.
  */
@@ -38,7 +38,7 @@ public final class HttpResponse {
      * @param headers    the response headers
      */
     public HttpResponse(int statusCode, List<Header> headers) {
-        this(statusCode, headers, /* contentLength= */ -1, /* content= */ null);
+        this(statusCode, headers, -1 /* contentLength */, null /* content */);
     }
 
     /**

+ 5 - 4
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/HttpStack.java

@@ -16,14 +16,14 @@
 
 package cn.yyxx.support.volley.source.toolbox;
 
-import cn.yyxx.support.volley.source.AuthFailureError;
-import cn.yyxx.support.volley.source.Request;
-
 import org.apache.http.HttpResponse;
 
 import java.io.IOException;
 import java.util.Map;
 
+import cn.yyxx.support.volley.source.AuthFailureError;
+import cn.yyxx.support.volley.source.Request;
+
 /**
  * An HTTP stack abstraction.
  *
@@ -44,5 +44,6 @@ public interface HttpStack {
      *                          Request#getHeaders()}
      * @return the HTTP response
      */
-    HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError;
+    HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
+            throws IOException, AuthFailureError;
 }

+ 100 - 140
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/HurlStack.java

@@ -16,15 +16,7 @@
 
 package cn.yyxx.support.volley.source.toolbox;
 
-import android.support.annotation.VisibleForTesting;
-
-import cn.yyxx.support.volley.source.AuthFailureError;
-import cn.yyxx.support.volley.source.Header;
-import cn.yyxx.support.volley.source.Request;
-import cn.yyxx.support.volley.source.Request.Method;
-
 import java.io.DataOutputStream;
-import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
@@ -37,36 +29,28 @@ import java.util.Map;
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLSocketFactory;
 
+import cn.yyxx.support.volley.source.AuthFailureError;
+import cn.yyxx.support.volley.source.Header;
+import cn.yyxx.support.volley.source.Request;
+import cn.yyxx.support.volley.source.Request.Method;
+
 /**
- * A {@link BaseHttpStack} based on {@link HttpURLConnection}.
+ * An {@link HttpStack} based on {@link HttpURLConnection}.
  */
 public class HurlStack extends BaseHttpStack {
 
     private static final int HTTP_CONTINUE = 100;
-
-    /**
-     * An interface for transforming URLs before use.
-     */
-    public interface UrlRewriter {
-        /**
-         * Returns a URL to use instead of the provided one, or null to indicate this URL should not
-         * be used at all.
-         */
-        String rewriteUrl(String originalUrl);
-    }
-
     private final UrlRewriter mUrlRewriter;
     private final SSLSocketFactory mSslSocketFactory;
-
     public HurlStack() {
-        this(/* urlRewriter = */ null);
+        this(null);
     }
 
     /**
      * @param urlRewriter Rewriter to use for request URLs
      */
     public HurlStack(UrlRewriter urlRewriter) {
-        this(urlRewriter, /* sslSocketFactory = */ null);
+        this(urlRewriter, null);
     }
 
     /**
@@ -78,57 +62,7 @@ public class HurlStack extends BaseHttpStack {
         mSslSocketFactory = sslSocketFactory;
     }
 
-    @Override
-    public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders)
-            throws IOException, AuthFailureError {
-        String url = request.getUrl();
-        HashMap<String, String> map = new HashMap<>();
-        map.putAll(additionalHeaders);
-        // Request.getHeaders() takes precedence over the given additional (cache) headers).
-        map.putAll(request.getHeaders());
-        if (mUrlRewriter != null) {
-            String rewritten = mUrlRewriter.rewriteUrl(url);
-            if (rewritten == null) {
-                throw new IOException("URL blocked by rewriter: " + url);
-            }
-            url = rewritten;
-        }
-        URL parsedUrl = new URL(url);
-        HttpURLConnection connection = openConnection(parsedUrl, request);
-        boolean keepConnectionOpen = false;
-        try {
-            for (String headerName : map.keySet()) {
-                connection.setRequestProperty(headerName, map.get(headerName));
-            }
-            setConnectionParametersForRequest(connection, request);
-            // Initialize HttpResponse with data from the HttpURLConnection.
-            int responseCode = connection.getResponseCode();
-            if (responseCode == -1) {
-                // -1 is returned by getResponseCode() if the response code could not be retrieved.
-                // Signal to the caller that something was wrong with the connection.
-                throw new IOException("Could not retrieve response code from HttpUrlConnection.");
-            }
-
-            if (!hasResponseBody(request.getMethod(), responseCode)) {
-                return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()));
-            }
-
-            // Need to keep the connection open until the stream is consumed by the caller. Wrap the
-            // stream such that close() will disconnect the connection.
-            keepConnectionOpen = true;
-            return new HttpResponse(
-                    responseCode,
-                    convertHeaders(connection.getHeaderFields()),
-                    connection.getContentLength(),
-                    new UrlConnectionInputStream(connection));
-        } finally {
-            if (!keepConnectionOpen) {
-                connection.disconnect();
-            }
-        }
-    }
-
-    @VisibleForTesting
+    // VisibleForTesting
     static List<Header> convertHeaders(Map<String, List<String>> responseHeaders) {
         List<Header> headerList = new ArrayList<>(responseHeaders.size());
         for (Map.Entry<String, List<String>> entry : responseHeaders.entrySet()) {
@@ -158,25 +92,6 @@ public class HurlStack extends BaseHttpStack {
                 && responseCode != HttpURLConnection.HTTP_NOT_MODIFIED;
     }
 
-    /**
-     * Wrapper for a {@link HttpURLConnection}'s InputStream which disconnects the connection on
-     * stream close.
-     */
-    static class UrlConnectionInputStream extends FilterInputStream {
-        private final HttpURLConnection mConnection;
-
-        UrlConnectionInputStream(HttpURLConnection connection) {
-            super(inputStreamFromConnection(connection));
-            mConnection = connection;
-        }
-
-        @Override
-        public void close() throws IOException {
-            super.close();
-            mConnection.disconnect();
-        }
-    }
-
     /**
      * Initializes an {@link InputStream} from the given {@link HttpURLConnection}.
      *
@@ -193,46 +108,6 @@ public class HurlStack extends BaseHttpStack {
         return inputStream;
     }
 
-    /**
-     * Create an {@link HttpURLConnection} for the specified {@code url}.
-     */
-    protected HttpURLConnection createConnection(URL url) throws IOException {
-        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-
-        // Workaround for the M release HttpURLConnection not observing the
-        // HttpURLConnection.setFollowRedirects() property.
-        // https://code.google.com/p/android/issues/detail?id=194495
-        connection.setInstanceFollowRedirects(HttpURLConnection.getFollowRedirects());
-
-        return connection;
-    }
-
-    /**
-     * Opens an {@link HttpURLConnection} with parameters.
-     *
-     * @param url
-     * @return an open connection
-     * @throws IOException
-     */
-    private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
-        HttpURLConnection connection = createConnection(url);
-
-        int timeoutMs = request.getTimeoutMs();
-        connection.setConnectTimeout(timeoutMs);
-        connection.setReadTimeout(timeoutMs);
-        connection.setUseCaches(false);
-        connection.setDoInput(true);
-
-        // use caller-provided custom SslSocketFactory, if any, for HTTPS
-        if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
-            ((HttpsURLConnection) connection).setSSLSocketFactory(mSslSocketFactory);
-        }
-
-        return connection;
-    }
-
-    // NOTE: Any request headers added here (via setRequestProperty or addRequestProperty) should be
-    // checked against the existing properties in the connection and not overridden if already set.
     @SuppressWarnings("deprecation")
     /* package */ static void setConnectionParametersForRequest(
             HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError {
@@ -290,18 +165,103 @@ public class HurlStack extends BaseHttpStack {
     }
 
     private static void addBody(HttpURLConnection connection, Request<?> request, byte[] body)
-            throws IOException {
+            throws IOException, AuthFailureError {
         // Prepare output. There is no need to set Content-Length explicitly,
         // since this is handled by HttpURLConnection using the size of the prepared
         // output stream.
         connection.setDoOutput(true);
-        // Set the content-type unless it was already set (by Request#getHeaders).
-        if (!connection.getRequestProperties().containsKey(HttpHeaderParser.HEADER_CONTENT_TYPE)) {
-            connection.setRequestProperty(
-                    HttpHeaderParser.HEADER_CONTENT_TYPE, request.getBodyContentType());
-        }
+        connection.addRequestProperty(
+                HttpHeaderParser.HEADER_CONTENT_TYPE, request.getBodyContentType());
         DataOutputStream out = new DataOutputStream(connection.getOutputStream());
         out.write(body);
         out.close();
     }
+
+    @Override
+    public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders)
+            throws IOException, AuthFailureError {
+        String url = request.getUrl();
+        HashMap<String, String> map = new HashMap<>();
+        map.putAll(request.getHeaders());
+        map.putAll(additionalHeaders);
+        if (mUrlRewriter != null) {
+            String rewritten = mUrlRewriter.rewriteUrl(url);
+            if (rewritten == null) {
+                throw new IOException("URL blocked by rewriter: " + url);
+            }
+            url = rewritten;
+        }
+        URL parsedUrl = new URL(url);
+        HttpURLConnection connection = openConnection(parsedUrl, request);
+        for (String headerName : map.keySet()) {
+            connection.addRequestProperty(headerName, map.get(headerName));
+        }
+        setConnectionParametersForRequest(connection, request);
+        // Initialize HttpResponse with data from the HttpURLConnection.
+        int responseCode = connection.getResponseCode();
+        if (responseCode == -1) {
+            // -1 is returned by getResponseCode() if the response code could not be retrieved.
+            // Signal to the caller that something was wrong with the connection.
+            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
+        }
+
+        if (!hasResponseBody(request.getMethod(), responseCode)) {
+            return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()));
+        }
+
+        return new HttpResponse(
+                responseCode,
+                convertHeaders(connection.getHeaderFields()),
+                connection.getContentLength(),
+                inputStreamFromConnection(connection));
+    }
+
+    /**
+     * Create an {@link HttpURLConnection} for the specified {@code url}.
+     */
+    protected HttpURLConnection createConnection(URL url) throws IOException {
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+
+        // Workaround for the M release HttpURLConnection not observing the
+        // HttpURLConnection.setFollowRedirects() property.
+        // https://code.google.com/p/android/issues/detail?id=194495
+        connection.setInstanceFollowRedirects(HttpURLConnection.getFollowRedirects());
+
+        return connection;
+    }
+
+    /**
+     * Opens an {@link HttpURLConnection} with parameters.
+     *
+     * @param url
+     * @return an open connection
+     * @throws IOException
+     */
+    private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
+        HttpURLConnection connection = createConnection(url);
+
+        int timeoutMs = request.getTimeoutMs();
+        connection.setConnectTimeout(timeoutMs);
+        connection.setReadTimeout(timeoutMs);
+        connection.setUseCaches(false);
+        connection.setDoInput(true);
+
+        // use caller-provided custom SslSocketFactory, if any, for HTTPS
+        if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
+            ((HttpsURLConnection) connection).setSSLSocketFactory(mSslSocketFactory);
+        }
+
+        return connection;
+    }
+
+    /**
+     * An interface for transforming URLs before use.
+     */
+    public interface UrlRewriter {
+        /**
+         * Returns a URL to use instead of the provided one, or null to indicate this URL should not
+         * be used at all.
+         */
+        String rewriteUrl(String originalUrl);
+    }
 }

+ 187 - 209
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/ImageLoader.java

@@ -1,12 +1,12 @@
-/*
+/**
  * Copyright (C) 2013 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * <p>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
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software distributed under the
+ * <p>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.
@@ -17,77 +17,59 @@ import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.os.Handler;
 import android.os.Looper;
-import android.support.annotation.MainThread;
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
 import cn.yyxx.support.volley.source.Request;
 import cn.yyxx.support.volley.source.RequestQueue;
 import cn.yyxx.support.volley.source.Response.ErrorListener;
 import cn.yyxx.support.volley.source.Response.Listener;
-import cn.yyxx.support.volley.source.ResponseDelivery;
 import cn.yyxx.support.volley.source.VolleyError;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
 /**
  * Helper that handles loading and caching images from remote URLs.
  *
  * <p>The simple way to use this class is to call {@link ImageLoader#get(String, ImageListener)} and
  * to pass in the default image listener provided by {@link ImageLoader#getImageListener(ImageView,
- * int, int)}. Note that all function calls to this class must be made from the main thread, and all
- * responses will be delivered to the main thread as well. Custom {@link ResponseDelivery}s which
- * don't use the main thread are not supported.
+ * int, int)}. Note that all function calls to this class must be made from the main thead, and all
+ * responses will be delivered to the main thread as well.
  */
 public class ImageLoader {
     /**
      * RequestQueue for dispatching ImageRequests onto.
      */
     private final RequestQueue mRequestQueue;
-
-    /**
-     * Amount of time to wait after first response arrives before delivering all responses.
-     */
-    private int mBatchResponseDelayMs = 100;
-
     /**
      * The cache implementation to be used as an L1 cache before calling into volley.
      */
     private final ImageCache mCache;
-
     /**
      * HashMap of Cache keys -> BatchedImageRequest used to track in-flight requests so that we can
      * coalesce multiple requests to the same URL into a single network request.
      */
-    private final HashMap<String, BatchedImageRequest> mInFlightRequests = new HashMap<>();
-
+    private final HashMap<String, BatchedImageRequest> mInFlightRequests =
+            new HashMap<String, BatchedImageRequest>();
     /**
      * HashMap of the currently pending responses (waiting to be delivered).
      */
-    private final HashMap<String, BatchedImageRequest> mBatchedResponses = new HashMap<>();
-
+    private final HashMap<String, BatchedImageRequest> mBatchedResponses =
+            new HashMap<String, BatchedImageRequest>();
     /**
      * Handler to the main thread.
      */
     private final Handler mHandler = new Handler(Looper.getMainLooper());
-
     /**
-     * Runnable for in-flight response delivery.
+     * Amount of time to wait after first response arrives before delivering all responses.
      */
-    private Runnable mRunnable;
-
+    private int mBatchResponseDelayMs = 100;
     /**
-     * Simple cache adapter interface. If provided to the ImageLoader, it will be used as an L1
-     * cache before dispatch to Volley. Implementations must not block. Implementation with an
-     * LruCache is recommended.
+     * Runnable for in-flight response delivery.
      */
-    public interface ImageCache {
-        Bitmap getBitmap(String url);
-
-        void putBitmap(String url, Bitmap bitmap);
-    }
+    private Runnable mRunnable;
 
     /**
      * Constructs a new ImageLoader.
@@ -131,27 +113,24 @@ public class ImageLoader {
     }
 
     /**
-     * Interface for the response handlers on image requests.
-     *
-     * <p>The call flow is this: 1. Upon being attached to a request, onResponse(response, true)
-     * will be invoked to reflect any cached data that was already available. If the data was
-     * available, response.getBitmap() will be non-null.
+     * Creates a cache key for use with the L1 cache.
      *
-     * <p>2. After a network response returns, only one of the following cases will happen: -
-     * onResponse(response, false) will be called if the image was loaded. or - onErrorResponse will
-     * be called if there was an error loading the image.
+     * @param url       The URL of the request.
+     * @param maxWidth  The max-width of the output.
+     * @param maxHeight The max-height of the output.
+     * @param scaleType The scaleType of the imageView.
      */
-    public interface ImageListener extends ErrorListener {
-        /**
-         * Listens for non-error changes to the loading of the image request.
-         *
-         * @param response    Holds all information pertaining to the request, as well as the bitmap
-         *                    (if it is loaded).
-         * @param isImmediate True if this was called during ImageLoader.get() variants. This can be
-         *                    used to differentiate between a cached image loading and a network image loading in
-         *                    order to, for example, run an animation to fade in network loaded images.
-         */
-        void onResponse(ImageContainer response, boolean isImmediate);
+    private static String getCacheKey(
+            String url, int maxWidth, int maxHeight, ScaleType scaleType) {
+        return new StringBuilder(url.length() + 12)
+                .append("#W")
+                .append(maxWidth)
+                .append("#H")
+                .append(maxHeight)
+                .append("#S")
+                .append(scaleType.ordinal())
+                .append(url)
+                .toString();
     }
 
     /**
@@ -169,17 +148,14 @@ public class ImageLoader {
     /**
      * Checks if the item is available in the cache.
      *
-     * <p>Must be called from the main thread.
-     *
      * @param requestUrl The url of the remote image
      * @param maxWidth   The maximum width of the returned image.
      * @param maxHeight  The maximum height of the returned image.
      * @param scaleType  The scaleType of the imageView.
      * @return True if the item exists in cache, false otherwise.
      */
-    @MainThread
     public boolean isCached(String requestUrl, int maxWidth, int maxHeight, ScaleType scaleType) {
-        Threads.throwIfNotOnMainThread();
+        throwIfNotOnMainThread();
 
         String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
         return mCache.getBitmap(cacheKey) != null;
@@ -195,7 +171,7 @@ public class ImageLoader {
      * @param requestUrl The URL of the image to be loaded.
      */
     public ImageContainer get(String requestUrl, final ImageListener listener) {
-        return get(requestUrl, listener, /* maxWidth= */ 0, /* maxHeight= */ 0);
+        return get(requestUrl, listener, 0, 0);
     }
 
     /**
@@ -212,8 +188,6 @@ public class ImageLoader {
      * returns a bitmap container that contains all of the data relating to the request (as well as
      * the default image if the requested image is not available).
      *
-     * <p>Must be called from the main thread.
-     *
      * @param requestUrl    The url of the remote image
      * @param imageListener The listener to call when the remote image is loaded
      * @param maxWidth      The maximum width of the returned image.
@@ -222,7 +196,6 @@ public class ImageLoader {
      * @return A container object that contains all of the properties of the request, as well as the
      * currently available image (default if remote is not loaded).
      */
-    @MainThread
     public ImageContainer get(
             String requestUrl,
             ImageListener imageListener,
@@ -231,7 +204,7 @@ public class ImageLoader {
             ScaleType scaleType) {
 
         // only fulfill requests that were initiated from the main thread.
-        Threads.throwIfNotOnMainThread();
+        throwIfNotOnMainThread();
 
         final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
 
@@ -239,9 +212,7 @@ public class ImageLoader {
         Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
         if (cachedBitmap != null) {
             // Return the cached bitmap.
-            ImageContainer container =
-                    new ImageContainer(
-                            cachedBitmap, requestUrl, /* cacheKey= */ null, /* listener= */ null);
+            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
             imageListener.onResponse(container, true);
             return container;
         }
@@ -253,11 +224,8 @@ public class ImageLoader {
         // Update the caller to let them know that they should use the default bitmap.
         imageListener.onResponse(imageContainer, true);
 
-        // Check to see if a request is already in-flight or completed but pending batch delivery.
+        // Check to see if a request is already in-flight.
         BatchedImageRequest request = mInFlightRequests.get(cacheKey);
-        if (request == null) {
-            request = mBatchedResponses.get(cacheKey);
-        }
         if (request != null) {
             // If it is, add this request to the list of listeners.
             request.addContainer(imageContainer);
@@ -352,86 +320,85 @@ public class ImageLoader {
     }
 
     /**
-     * Container object for all of the data surrounding an image request.
+     * Starts the runnable for batched delivery of responses if it is not already started.
+     *
+     * @param cacheKey The cacheKey of the response being delivered.
+     * @param request  The BatchedImageRequest to be delivered.
      */
-    public class ImageContainer {
-        /**
-         * The most relevant bitmap for the container. If the image was in cache, the Holder to use
-         * for the final bitmap (the one that pairs to the requested URL).
-         */
-        private Bitmap mBitmap;
-
-        private final ImageListener mListener;
-
-        /**
-         * The cache key that was associated with the request
-         */
-        private final String mCacheKey;
-
-        /**
-         * The request URL that was specified
-         */
-        private final String mRequestUrl;
-
-        /**
-         * Constructs a BitmapContainer object.
-         *
-         * @param bitmap     The final bitmap (if it exists).
-         * @param requestUrl The requested URL for this container.
-         * @param cacheKey   The cache key that identifies the requested URL for this container.
-         */
-        public ImageContainer(
-                Bitmap bitmap, String requestUrl, String cacheKey, ImageListener listener) {
-            mBitmap = bitmap;
-            mRequestUrl = requestUrl;
-            mCacheKey = cacheKey;
-            mListener = listener;
+    private void batchResponse(String cacheKey, BatchedImageRequest request) {
+        mBatchedResponses.put(cacheKey, request);
+        // If we don't already have a batch delivery runnable in flight, make a new one.
+        // Note that this will be used to deliver responses to all callers in mBatchedResponses.
+        if (mRunnable == null) {
+            mRunnable =
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            for (BatchedImageRequest bir : mBatchedResponses.values()) {
+                                for (ImageContainer container : bir.mContainers) {
+                                    // If one of the callers in the batched request canceled the
+                                    // request
+                                    // after the response was received but before it was delivered,
+                                    // skip them.
+                                    if (container.mListener == null) {
+                                        continue;
+                                    }
+                                    if (bir.getError() == null) {
+                                        container.mBitmap = bir.mResponseBitmap;
+                                        container.mListener.onResponse(container, false);
+                                    } else {
+                                        container.mListener.onErrorResponse(bir.getError());
+                                    }
+                                }
+                            }
+                            mBatchedResponses.clear();
+                            mRunnable = null;
+                        }
+                    };
+            // Post the runnable.
+            mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
         }
+    }
 
-        /**
-         * Releases interest in the in-flight request (and cancels it if no one else is listening).
-         *
-         * <p>Must be called from the main thread.
-         */
-        @MainThread
-        public void cancelRequest() {
-            Threads.throwIfNotOnMainThread();
-
-            if (mListener == null) {
-                return;
-            }
-
-            BatchedImageRequest request = mInFlightRequests.get(mCacheKey);
-            if (request != null) {
-                boolean canceled = request.removeContainerAndCancelIfNecessary(this);
-                if (canceled) {
-                    mInFlightRequests.remove(mCacheKey);
-                }
-            } else {
-                // check to see if it is already batched for delivery.
-                request = mBatchedResponses.get(mCacheKey);
-                if (request != null) {
-                    request.removeContainerAndCancelIfNecessary(this);
-                    if (request.mContainers.size() == 0) {
-                        mBatchedResponses.remove(mCacheKey);
-                    }
-                }
-            }
+    private void throwIfNotOnMainThread() {
+        if (Looper.myLooper() != Looper.getMainLooper()) {
+            throw new IllegalStateException("ImageLoader must be invoked from the main thread.");
         }
+    }
 
-        /**
-         * Returns the bitmap associated with the request URL if it has been loaded, null otherwise.
-         */
-        public Bitmap getBitmap() {
-            return mBitmap;
-        }
+    /**
+     * Simple cache adapter interface. If provided to the ImageLoader, it will be used as an L1
+     * cache before dispatch to Volley. Implementations must not block. Implementation with an
+     * LruCache is recommended.
+     */
+    public interface ImageCache {
+        Bitmap getBitmap(String url);
+
+        void putBitmap(String url, Bitmap bitmap);
+    }
 
+    /**
+     * Interface for the response handlers on image requests.
+     *
+     * <p>The call flow is this: 1. Upon being attached to a request, onResponse(response, true)
+     * will be invoked to reflect any cached data that was already available. If the data was
+     * available, response.getBitmap() will be non-null.
+     *
+     * <p>2. After a network response returns, only one of the following cases will happen: -
+     * onResponse(response, false) will be called if the image was loaded. or - onErrorResponse will
+     * be called if there was an error loading the image.
+     */
+    public interface ImageListener extends ErrorListener {
         /**
-         * Returns the requested URL for this container.
+         * Listens for non-error changes to the loading of the image request.
+         *
+         * @param response    Holds all information pertaining to the request, as well as the bitmap
+         *                    (if it is loaded).
+         * @param isImmediate True if this was called during ImageLoader.get() variants. This can be
+         *                    used to differentiate between a cached image loading and a network image loading in
+         *                    order to, for example, run an animation to fade in network loaded images.
          */
-        public String getRequestUrl() {
-            return mRequestUrl;
-        }
+        void onResponse(ImageContainer response, boolean isImmediate);
     }
 
     /**
@@ -443,22 +410,19 @@ public class ImageLoader {
          * The request being tracked
          */
         private final Request<?> mRequest;
-
+        /**
+         * List of all of the active ImageContainers that are interested in the request
+         */
+        private final List<ImageContainer> mContainers = new ArrayList<>();
         /**
          * The result of the request being tracked by this item
          */
         private Bitmap mResponseBitmap;
-
         /**
          * Error if one occurred for this response
          */
         private VolleyError mError;
 
-        /**
-         * List of all of the active ImageContainers that are interested in the request
-         */
-        private final List<ImageContainer> mContainers = new ArrayList<>();
-
         /**
          * Constructs a new BatchedImageRequest object
          *
@@ -471,17 +435,17 @@ public class ImageLoader {
         }
 
         /**
-         * Set the error for this response
+         * Get the error for this response
          */
-        public void setError(VolleyError error) {
-            mError = error;
+        public VolleyError getError() {
+            return mError;
         }
 
         /**
-         * Get the error for this response
+         * Set the error for this response
          */
-        public VolleyError getError() {
-            return mError;
+        public void setError(VolleyError error) {
+            mError = error;
         }
 
         /**
@@ -510,63 +474,77 @@ public class ImageLoader {
     }
 
     /**
-     * Starts the runnable for batched delivery of responses if it is not already started.
-     *
-     * @param cacheKey The cacheKey of the response being delivered.
-     * @param request  The BatchedImageRequest to be delivered.
+     * Container object for all of the data surrounding an image request.
      */
-    private void batchResponse(String cacheKey, BatchedImageRequest request) {
-        mBatchedResponses.put(cacheKey, request);
-        // If we don't already have a batch delivery runnable in flight, make a new one.
-        // Note that this will be used to deliver responses to all callers in mBatchedResponses.
-        if (mRunnable == null) {
-            mRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    for (BatchedImageRequest bir : mBatchedResponses.values()) {
-                        for (ImageContainer container : bir.mContainers) {
-                            // If one of the callers in the batched request canceled the
-                            // request
-                            // after the response was received but before it was delivered,
-                            // skip them.
-                            if (container.mListener == null) {
-                                continue;
-                            }
-                            if (bir.getError() == null) {
-                                container.mBitmap = bir.mResponseBitmap;
-                                container.mListener.onResponse(container, false);
-                            } else {
-                                container.mListener.onErrorResponse(bir.getError());
-                            }
-                        }
+    public class ImageContainer {
+        private final ImageListener mListener;
+        /**
+         * The cache key that was associated with the request
+         */
+        private final String mCacheKey;
+        /**
+         * The request URL that was specified
+         */
+        private final String mRequestUrl;
+        /**
+         * The most relevant bitmap for the container. If the image was in cache, the Holder to use
+         * for the final bitmap (the one that pairs to the requested URL).
+         */
+        private Bitmap mBitmap;
+
+        /**
+         * Constructs a BitmapContainer object.
+         *
+         * @param bitmap     The final bitmap (if it exists).
+         * @param requestUrl The requested URL for this container.
+         * @param cacheKey   The cache key that identifies the requested URL for this container.
+         */
+        public ImageContainer(
+                Bitmap bitmap, String requestUrl, String cacheKey, ImageListener listener) {
+            mBitmap = bitmap;
+            mRequestUrl = requestUrl;
+            mCacheKey = cacheKey;
+            mListener = listener;
+        }
+
+        /**
+         * Releases interest in the in-flight request (and cancels it if no one else is listening).
+         */
+        public void cancelRequest() {
+            if (mListener == null) {
+                return;
+            }
+
+            BatchedImageRequest request = mInFlightRequests.get(mCacheKey);
+            if (request != null) {
+                boolean canceled = request.removeContainerAndCancelIfNecessary(this);
+                if (canceled) {
+                    mInFlightRequests.remove(mCacheKey);
+                }
+            } else {
+                // check to see if it is already batched for delivery.
+                request = mBatchedResponses.get(mCacheKey);
+                if (request != null) {
+                    request.removeContainerAndCancelIfNecessary(this);
+                    if (request.mContainers.size() == 0) {
+                        mBatchedResponses.remove(mCacheKey);
                     }
-                    mBatchedResponses.clear();
-                    mRunnable = null;
                 }
-            };
-            // Post the runnable.
-            mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
+            }
         }
-    }
 
-    /**
-     * Creates a cache key for use with the L1 cache.
-     *
-     * @param url       The URL of the request.
-     * @param maxWidth  The max-width of the output.
-     * @param maxHeight The max-height of the output.
-     * @param scaleType The scaleType of the imageView.
-     */
-    private static String getCacheKey(
-            String url, int maxWidth, int maxHeight, ScaleType scaleType) {
-        return new StringBuilder(url.length() + 12)
-                .append("#W")
-                .append(maxWidth)
-                .append("#H")
-                .append(maxHeight)
-                .append("#S")
-                .append(scaleType.ordinal())
-                .append(url)
-                .toString();
+        /**
+         * Returns the bitmap associated with the request URL if it has been loaded, null otherwise.
+         */
+        public Bitmap getBitmap() {
+            return mBitmap;
+        }
+
+        /**
+         * Returns the requested URL for this container.
+         */
+        public String getRequestUrl() {
+            return mRequestUrl;
+        }
     }
 }

+ 35 - 43
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/ImageRequest.java

@@ -19,9 +19,6 @@ package cn.yyxx.support.volley.source.toolbox;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
-import android.support.annotation.GuardedBy;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
 import android.widget.ImageView.ScaleType;
 
 import cn.yyxx.support.volley.source.DefaultRetryPolicy;
@@ -49,25 +46,20 @@ public class ImageRequest extends Request<Bitmap> {
      * Default backoff multiplier for image requests
      */
     public static final float DEFAULT_IMAGE_BACKOFF_MULT = 2f;
-
+    /**
+     * Decoding lock so that we don't decode more than one image at a time (to avoid OOM's)
+     */
+    private static final Object sDecodeLock = new Object();
     /**
      * Lock to guard mListener as it is cleared on cancel() and read on delivery.
      */
     private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    @Nullable
-    private Response.Listener<Bitmap> mListener;
-
     private final Config mDecodeConfig;
     private final int mMaxWidth;
     private final int mMaxHeight;
     private final ScaleType mScaleType;
-
-    /**
-     * Decoding lock so that we don't decode more than one image at a time (to avoid OOM's)
-     */
-    private static final Object sDecodeLock = new Object();
+    // @GuardedBy("mLock")
+    private Response.Listener<Bitmap> mListener;
 
     /**
      * Creates a new image request, decoding to a maximum specified width and height. If both width
@@ -91,7 +83,7 @@ public class ImageRequest extends Request<Bitmap> {
             int maxHeight,
             ScaleType scaleType,
             Config decodeConfig,
-            @Nullable Response.ErrorListener errorListener) {
+            Response.ErrorListener errorListener) {
         super(Method.GET, url, errorListener);
         setRetryPolicy(
                 new DefaultRetryPolicy(
@@ -127,11 +119,6 @@ public class ImageRequest extends Request<Bitmap> {
                 errorListener);
     }
 
-    @Override
-    public Priority getPriority() {
-        return Priority.LOW;
-    }
-
     /**
      * Scales one side of a rectangle to fit aspect ratio.
      *
@@ -190,6 +177,34 @@ public class ImageRequest extends Request<Bitmap> {
         return resized;
     }
 
+    /**
+     * Returns the largest power-of-two divisor for use in downscaling a bitmap that will not result
+     * in the scaling past the desired dimensions.
+     *
+     * @param actualWidth   Actual width of the bitmap
+     * @param actualHeight  Actual height of the bitmap
+     * @param desiredWidth  Desired width of the bitmap
+     * @param desiredHeight Desired height of the bitmap
+     */
+    // Visible for testing.
+    static int findBestSampleSize(
+            int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
+        double wr = (double) actualWidth / desiredWidth;
+        double hr = (double) actualHeight / desiredHeight;
+        double ratio = Math.min(wr, hr);
+        float n = 1.0f;
+        while ((n * 2) <= ratio) {
+            n *= 2;
+        }
+
+        return (int) n;
+    }
+
+    @Override
+    public Priority getPriority() {
+        return Priority.LOW;
+    }
+
     @Override
     protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
         // Serialize all decode on a global lock to reduce concurrent heap usage.
@@ -272,27 +287,4 @@ public class ImageRequest extends Request<Bitmap> {
             listener.onResponse(response);
         }
     }
-
-    /**
-     * Returns the largest power-of-two divisor for use in downscaling a bitmap that will not result
-     * in the scaling past the desired dimensions.
-     *
-     * @param actualWidth   Actual width of the bitmap
-     * @param actualHeight  Actual height of the bitmap
-     * @param desiredWidth  Desired width of the bitmap
-     * @param desiredHeight Desired height of the bitmap
-     */
-    @VisibleForTesting
-    static int findBestSampleSize(
-            int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
-        double wr = (double) actualWidth / desiredWidth;
-        double hr = (double) actualHeight / desiredHeight;
-        double ratio = Math.min(wr, hr);
-        float n = 1.0f;
-        while ((n * 2) <= ratio) {
-            n *= 2;
-        }
-
-        return (int) n;
-    }
 }

+ 15 - 13
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/JsonArrayRequest.java

@@ -16,7 +16,10 @@
 
 package cn.yyxx.support.volley.source.toolbox;
 
-import android.support.annotation.Nullable;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import java.io.UnsupportedEncodingException;
 
 import cn.yyxx.support.volley.source.NetworkResponse;
 import cn.yyxx.support.volley.source.ParseError;
@@ -24,11 +27,6 @@ import cn.yyxx.support.volley.source.Response;
 import cn.yyxx.support.volley.source.Response.ErrorListener;
 import cn.yyxx.support.volley.source.Response.Listener;
 
-import org.json.JSONArray;
-import org.json.JSONException;
-
-import java.io.UnsupportedEncodingException;
-
 /**
  * A request for retrieving a {@link JSONArray} response body at a given URL.
  */
@@ -41,7 +39,7 @@ public class JsonArrayRequest extends JsonRequest<JSONArray> {
      * @param listener      Listener to receive the JSON response
      * @param errorListener Error listener, or null to ignore errors.
      */
-    public JsonArrayRequest(String url, Listener<JSONArray> listener, @Nullable ErrorListener errorListener) {
+    public JsonArrayRequest(String url, Listener<JSONArray> listener, ErrorListener errorListener) {
         super(Method.GET, url, null, listener, errorListener);
     }
 
@@ -50,17 +48,17 @@ public class JsonArrayRequest extends JsonRequest<JSONArray> {
      *
      * @param method        the HTTP method to use
      * @param url           URL to fetch the JSON from
-     * @param jsonRequest   A {@link JSONArray} to post with the request. Null indicates no parameters
-     *                      will be posted along with request.
+     * @param jsonRequest   A {@link JSONArray} to post with the request. Null is allowed and
+     *                      indicates no parameters will be posted along with request.
      * @param listener      Listener to receive the JSON response
      * @param errorListener Error listener, or null to ignore errors.
      */
     public JsonArrayRequest(
             int method,
             String url,
-            @Nullable JSONArray jsonRequest,
+            JSONArray jsonRequest,
             Listener<JSONArray> listener,
-            @Nullable ErrorListener errorListener) {
+            ErrorListener errorListener) {
         super(
                 method,
                 url,
@@ -72,8 +70,12 @@ public class JsonArrayRequest extends JsonRequest<JSONArray> {
     @Override
     protected Response<JSONArray> parseNetworkResponse(NetworkResponse response) {
         try {
-            String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
-            return Response.success(new JSONArray(jsonString), HttpHeaderParser.parseCacheHeaders(response));
+            String jsonString =
+                    new String(
+                            response.data,
+                            HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
+            return Response.success(
+                    new JSONArray(jsonString), HttpHeaderParser.parseCacheHeaders(response));
         } catch (UnsupportedEncodingException e) {
             return Response.error(new ParseError(e));
         } catch (JSONException je) {

+ 10 - 12
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/JsonObjectRequest.java

@@ -16,7 +16,10 @@
 
 package cn.yyxx.support.volley.source.toolbox;
 
-import android.support.annotation.Nullable;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
 
 import cn.yyxx.support.volley.source.NetworkResponse;
 import cn.yyxx.support.volley.source.ParseError;
@@ -24,11 +27,6 @@ import cn.yyxx.support.volley.source.Response;
 import cn.yyxx.support.volley.source.Response.ErrorListener;
 import cn.yyxx.support.volley.source.Response.Listener;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.UnsupportedEncodingException;
-
 /**
  * A request for retrieving a {@link JSONObject} response body at a given URL, allowing for an
  * optional {@link JSONObject} to be passed in as part of the request body.
@@ -40,17 +38,17 @@ public class JsonObjectRequest extends JsonRequest<JSONObject> {
      *
      * @param method        the HTTP method to use
      * @param url           URL to fetch the JSON from
-     * @param jsonRequest   A {@link JSONObject} to post with the request. Null indicates no
-     *                      parameters will be posted along with request.
+     * @param jsonRequest   A {@link JSONObject} to post with the request. Null is allowed and
+     *                      indicates no parameters will be posted along with request.
      * @param listener      Listener to receive the JSON response
      * @param errorListener Error listener, or null to ignore errors.
      */
     public JsonObjectRequest(
             int method,
             String url,
-            @Nullable JSONObject jsonRequest,
+            JSONObject jsonRequest,
             Listener<JSONObject> listener,
-            @Nullable ErrorListener errorListener) {
+            ErrorListener errorListener) {
         super(
                 method,
                 url,
@@ -67,9 +65,9 @@ public class JsonObjectRequest extends JsonRequest<JSONObject> {
      */
     public JsonObjectRequest(
             String url,
-            @Nullable JSONObject jsonRequest,
+            JSONObject jsonRequest,
             Listener<JSONObject> listener,
-            @Nullable ErrorListener errorListener) {
+            ErrorListener errorListener) {
         this(
                 jsonRequest == null ? Method.GET : Method.POST,
                 url,

+ 20 - 16
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/JsonRequest.java

@@ -16,8 +16,7 @@
 
 package cn.yyxx.support.volley.source.toolbox;
 
-import android.support.annotation.GuardedBy;
-import android.support.annotation.Nullable;
+import java.io.UnsupportedEncodingException;
 
 import cn.yyxx.support.volley.source.NetworkResponse;
 import cn.yyxx.support.volley.source.Request;
@@ -26,8 +25,6 @@ import cn.yyxx.support.volley.source.Response.ErrorListener;
 import cn.yyxx.support.volley.source.Response.Listener;
 import cn.yyxx.support.volley.source.VolleyLog;
 
-import java.io.UnsupportedEncodingException;
-
 /**
  * A request for retrieving a T type response body at a given URL that also optionally sends along a
  * JSON body in the request specified.
@@ -35,22 +32,25 @@ import java.io.UnsupportedEncodingException;
  * @param <T> JSON type of response expected
  */
 public abstract class JsonRequest<T> extends Request<T> {
-    /** Default charset for JSON request. */
+    /**
+     * Default charset for JSON request.
+     */
     protected static final String PROTOCOL_CHARSET = "utf-8";
 
-    /** Content type for request. */
+    /**
+     * Content type for request.
+     */
     private static final String PROTOCOL_CONTENT_TYPE =
             String.format("application/json; charset=%s", PROTOCOL_CHARSET);
 
-    /** Lock to guard mListener as it is cleared on cancel() and read on delivery. */
+    /**
+     * Lock to guard mListener as it is cleared on cancel() and read on delivery.
+     */
     private final Object mLock = new Object();
-
-    @Nullable
-    @GuardedBy("mLock")
+    private final String mRequestBody;
+    // @GuardedBy("mLock")
     private Listener<T> mListener;
 
-    @Nullable private final String mRequestBody;
-
     /**
      * Deprecated constructor for a JsonRequest which defaults to GET unless {@link #getPostBody()}
      * or {@link #getPostParams()} is overridden (which defaults to POST).
@@ -66,9 +66,9 @@ public abstract class JsonRequest<T> extends Request<T> {
     public JsonRequest(
             int method,
             String url,
-            @Nullable String requestBody,
+            String requestBody,
             Listener<T> listener,
-            @Nullable ErrorListener errorListener) {
+            ErrorListener errorListener) {
         super(method, url, errorListener);
         mListener = listener;
         mRequestBody = requestBody;
@@ -96,14 +96,18 @@ public abstract class JsonRequest<T> extends Request<T> {
     @Override
     protected abstract Response<T> parseNetworkResponse(NetworkResponse response);
 
-    /** @deprecated Use {@link #getBodyContentType()}. */
+    /**
+     * @deprecated Use {@link #getBodyContentType()}.
+     */
     @Deprecated
     @Override
     public String getPostBodyContentType() {
         return getBodyContentType();
     }
 
-    /** @deprecated Use {@link #getBody()}. */
+    /**
+     * @deprecated Use {@link #getBody()}.
+     */
     @Deprecated
     @Override
     public byte[] getPostBody() {

+ 7 - 66
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/NetworkImageView.java

@@ -14,9 +14,6 @@
 package cn.yyxx.support.volley.source.toolbox;
 
 import android.content.Context;
-import android.graphics.Bitmap;
-import android.support.annotation.MainThread;
-import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.ViewGroup.LayoutParams;
@@ -36,31 +33,15 @@ public class NetworkImageView extends ImageView {
     private String mUrl;
 
     /**
-     * Resource ID of the image to be used as a placeholder until the network image is loaded. Won't
-     * be set at the same time as mDefaultImageBitmap.
+     * Resource ID of the image to be used as a placeholder until the network image is loaded.
      */
     private int mDefaultImageId;
 
     /**
-     * Bitmap of the image to be used as a placeholder until the network image is loaded. Won't be
-     * set at the same time as mDefaultImageId.
-     */
-    @Nullable
-    Bitmap mDefaultImageBitmap;
-
-    /**
-     * Resource ID of the image to be used if the network response fails. Won't be set at the same
-     * time as mErrorImageBitmap.
+     * Resource ID of the image to be used if the network response fails.
      */
     private int mErrorImageId;
 
-    /**
-     * Bitmap of the image to be used if the network response fails. Won't be set at the same time
-     * as mErrorImageId.
-     */
-    @Nullable
-    private Bitmap mErrorImageBitmap;
-
     /**
      * Local copy of the ImageLoader.
      */
@@ -88,69 +69,35 @@ public class NetworkImageView extends ImageView {
      * immediately either set the cached image (if available) or the default image specified by
      * {@link NetworkImageView#setDefaultImageResId(int)} on the view.
      *
-     * <p>NOTE: If applicable, {@link NetworkImageView#setDefaultImageResId(int)} or {@link
-     * NetworkImageView#setDefaultImageBitmap} and {@link NetworkImageView#setErrorImageResId(int)}
-     * or {@link NetworkImageView#setErrorImageBitmap(Bitmap)} should be called prior to calling
-     * this function.
-     *
-     * <p>Must be called from the main thread.
+     * <p>NOTE: If applicable, {@link NetworkImageView#setDefaultImageResId(int)} and {@link
+     * NetworkImageView#setErrorImageResId(int)} should be called prior to calling this function.
      *
      * @param url         The URL that should be loaded into this ImageView.
      * @param imageLoader ImageLoader that will be used to make the request.
      */
-    @MainThread
     public void setImageUrl(String url, ImageLoader imageLoader) {
-        Threads.throwIfNotOnMainThread();
         mUrl = url;
         mImageLoader = imageLoader;
         // The URL has potentially changed. See if we need to load it.
-        loadImageIfNecessary(/* isInLayoutPass= */ false);
+        loadImageIfNecessary(false);
     }
 
     /**
      * Sets the default image resource ID to be used for this view until the attempt to load it
      * completes.
-     *
-     * <p>This will clear anything set by {@link NetworkImageView#setDefaultImageBitmap}.
      */
     public void setDefaultImageResId(int defaultImage) {
-        mDefaultImageBitmap = null;
         mDefaultImageId = defaultImage;
     }
 
-    /**
-     * Sets the default image bitmap to be used for this view until the attempt to load it
-     * completes.
-     *
-     * <p>This will clear anything set by {@link NetworkImageView#setDefaultImageResId}.
-     */
-    public void setDefaultImageBitmap(Bitmap defaultImage) {
-        mDefaultImageId = 0;
-        mDefaultImageBitmap = defaultImage;
-    }
-
     /**
      * Sets the error image resource ID to be used for this view in the event that the image
      * requested fails to load.
-     *
-     * <p>This will clear anything set by {@link NetworkImageView#setErrorImageBitmap}.
      */
     public void setErrorImageResId(int errorImage) {
-        mErrorImageBitmap = null;
         mErrorImageId = errorImage;
     }
 
-    /**
-     * Sets the error image bitmap to be used for this view in the event that the image requested
-     * fails to load.
-     *
-     * <p>This will clear anything set by {@link NetworkImageView#setErrorImageResId}.
-     */
-    public void setErrorImageBitmap(Bitmap errorImage) {
-        mErrorImageId = 0;
-        mErrorImageBitmap = errorImage;
-    }
-
     /**
      * Loads the image for the view if it isn't already loaded.
      *
@@ -213,8 +160,6 @@ public class NetworkImageView extends ImageView {
                             public void onErrorResponse(VolleyError error) {
                                 if (mErrorImageId != 0) {
                                     setImageResource(mErrorImageId);
-                                } else if (mErrorImageBitmap != null) {
-                                    setImageBitmap(mErrorImageBitmap);
                                 }
                             }
 
@@ -233,7 +178,7 @@ public class NetworkImageView extends ImageView {
                                             new Runnable() {
                                                 @Override
                                                 public void run() {
-                                                    onResponse(response, /* isImmediate= */ false);
+                                                    onResponse(response, false);
                                                 }
                                             });
                                     return;
@@ -243,8 +188,6 @@ public class NetworkImageView extends ImageView {
                                     setImageBitmap(response.getBitmap());
                                 } else if (mDefaultImageId != 0) {
                                     setImageResource(mDefaultImageId);
-                                } else if (mDefaultImageBitmap != null) {
-                                    setImageBitmap(mDefaultImageBitmap);
                                 }
                             }
                         },
@@ -256,8 +199,6 @@ public class NetworkImageView extends ImageView {
     private void setDefaultImageOrNull() {
         if (mDefaultImageId != 0) {
             setImageResource(mDefaultImageId);
-        } else if (mDefaultImageBitmap != null) {
-            setImageBitmap(mDefaultImageBitmap);
         } else {
             setImageBitmap(null);
         }
@@ -266,7 +207,7 @@ public class NetworkImageView extends ImageView {
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
-        loadImageIfNecessary(/* isInLayoutPass= */ true);
+        loadImageIfNecessary(true);
     }
 
     @Override

+ 8 - 8
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/RequestFuture.java

@@ -18,15 +18,15 @@ package cn.yyxx.support.volley.source.toolbox;
 
 import android.os.SystemClock;
 
-import cn.yyxx.support.volley.source.Request;
-import cn.yyxx.support.volley.source.Response;
-import cn.yyxx.support.volley.source.VolleyError;
-
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+import cn.yyxx.support.volley.source.Request;
+import cn.yyxx.support.volley.source.Response;
+import cn.yyxx.support.volley.source.VolleyError;
+
 /**
  * A Future that represents a Volley request.
  *
@@ -60,11 +60,11 @@ public class RequestFuture<T> implements Future<T>, Response.Listener<T>, Respon
     private T mResult;
     private VolleyError mException;
 
-    public static <E> RequestFuture<E> newFuture() {
-        return new RequestFuture<>();
+    private RequestFuture() {
     }
 
-    private RequestFuture() {
+    public static <E> RequestFuture<E> newFuture() {
+        return new RequestFuture<E>();
     }
 
     public void setRequest(Request<?> request) {
@@ -88,7 +88,7 @@ public class RequestFuture<T> implements Future<T>, Response.Listener<T>, Respon
     @Override
     public T get() throws InterruptedException, ExecutionException {
         try {
-            return doGet(/* timeoutMs= */ null);
+            return doGet(null);
         } catch (TimeoutException e) {
             throw new AssertionError(e);
         }

+ 4 - 12
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/StringRequest.java

@@ -16,8 +16,7 @@
 
 package cn.yyxx.support.volley.source.toolbox;
 
-import android.support.annotation.GuardedBy;
-import android.support.annotation.Nullable;
+import java.io.UnsupportedEncodingException;
 
 import cn.yyxx.support.volley.source.NetworkResponse;
 import cn.yyxx.support.volley.source.Request;
@@ -25,8 +24,6 @@ import cn.yyxx.support.volley.source.Response;
 import cn.yyxx.support.volley.source.Response.ErrorListener;
 import cn.yyxx.support.volley.source.Response.Listener;
 
-import java.io.UnsupportedEncodingException;
-
 /**
  * A canned request for retrieving the response body at a given URL as a String.
  */
@@ -37,8 +34,7 @@ public class StringRequest extends Request<String> {
      */
     private final Object mLock = new Object();
 
-    @Nullable
-    @GuardedBy("mLock")
+    // @GuardedBy("mLock")
     private Listener<String> mListener;
 
     /**
@@ -50,10 +46,7 @@ public class StringRequest extends Request<String> {
      * @param errorListener Error listener, or null to ignore errors
      */
     public StringRequest(
-            int method,
-            String url,
-            Listener<String> listener,
-            @Nullable ErrorListener errorListener) {
+            int method, String url, Listener<String> listener, ErrorListener errorListener) {
         super(method, url, errorListener);
         mListener = listener;
     }
@@ -65,8 +58,7 @@ public class StringRequest extends Request<String> {
      * @param listener      Listener to receive the String response
      * @param errorListener Error listener, or null to ignore errors
      */
-    public StringRequest(
-            String url, Listener<String> listener, @Nullable ErrorListener errorListener) {
+    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
         this(Method.GET, url, listener, errorListener);
     }
 

+ 0 - 14
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/Threads.java

@@ -1,14 +0,0 @@
-package cn.yyxx.support.volley.source.toolbox;
-
-import android.os.Looper;
-
-final class Threads {
-    private Threads() {
-    }
-
-    static void throwIfNotOnMainThread() {
-        if (Looper.myLooper() != Looper.getMainLooper()) {
-            throw new IllegalStateException("Must be invoked from the main thread.");
-        }
-    }
-}

+ 6 - 5
library_volley/src/main/java/cn/yyxx/support/volley/source/toolbox/Volley.java

@@ -22,11 +22,11 @@ import android.content.pm.PackageManager.NameNotFoundException;
 import android.net.http.AndroidHttpClient;
 import android.os.Build;
 
+import java.io.File;
+
 import cn.yyxx.support.volley.source.Network;
 import cn.yyxx.support.volley.source.RequestQueue;
 
-import java.io.File;
-
 public class Volley {
 
     /**
@@ -54,13 +54,14 @@ public class Volley {
                 String userAgent = "volley/0";
                 try {
                     String packageName = context.getPackageName();
-                    PackageInfo info = context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
+                    PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
                     userAgent = packageName + "/" + info.versionCode;
                 } catch (NameNotFoundException e) {
-                    e.printStackTrace();
                 }
 
-                network = new BasicNetwork(new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
+                network =
+                        new BasicNetwork(
+                                new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
             }
         } else {
             network = new BasicNetwork(stack);

+ 1 - 11
library_volleyx/build.gradle

@@ -19,23 +19,13 @@ android {
     }
 
     buildFeatures {
-        buildConfig = false
+        buildConfig false
     }
 
     lintOptions {
         abortOnError false
     }
 
-    repositories {
-        flatDir {
-            dirs 'libs'
-        }
-    }
-
-    dexOptions {
-        preDexLibraries = false
-    }
-
     //api23以上使用 httpClient
     useLibrary 'org.apache.http.legacy'
 }

BIN
libs/yyxx_support_volley_1.0.0.jar


+ 17 - 1
settings.gradle

@@ -1,6 +1,22 @@
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+        jcenter()
+        maven {
+            allowInsecureProtocol = true
+            url 'https://maven.aliyun.com/repository/public'
+        }
+        maven {
+            allowInsecureProtocol = true
+            url 'https://jitpack.io'
+        }
+    }
+}
+rootProject.name = 'YYXXSupportSdk'
 include ':demo'
 include ':library_support'
 include ':library_volleyx'
 include ':library_volley'
-rootProject.name = 'YYXXSupportSdk'