diff --git a/src/main/java/com/android/volley/AsyncNetwork.java b/src/main/java/com/android/volley/AsyncNetwork.java index f9cdd589..c6ab7920 100644 --- a/src/main/java/com/android/volley/AsyncNetwork.java +++ b/src/main/java/com/android/volley/AsyncNetwork.java @@ -20,6 +20,7 @@ import com.android.volley.toolbox.AsyncHttpStack; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.atomic.AtomicReference; /** An asynchronous implementation of {@link Network} to perform requests. */ @@ -27,6 +28,7 @@ public abstract class AsyncNetwork implements Network { private final AsyncHttpStack mAsyncStack; private ExecutorService mBlockingExecutor; private ExecutorService mNonBlockingExecutor; + private ScheduledThreadPoolExecutor mScheduledExecutor; protected AsyncNetwork(AsyncHttpStack stack) { mAsyncStack = stack; @@ -113,6 +115,15 @@ public void setBlockingExecutor(ExecutorService executor) { mAsyncStack.setBlockingExecutor(executor); } + /** + * This method sets the scheduled executor to be used by the network and stack for tasks to be + * scheduled. This method must be called before performing any requests. + */ + @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) + public void setScheduledExecutor(ScheduledThreadPoolExecutor executor) { + mScheduledExecutor = executor; + } + /** Gets blocking executor to perform any potentially blocking tasks. */ protected ExecutorService getBlockingExecutor() { return mBlockingExecutor; @@ -123,6 +134,11 @@ protected ExecutorService getNonBlockingExecutor() { return mNonBlockingExecutor; } + /** Gets scheduled executor to perform any tasks that need to be scheduled. */ + protected ScheduledThreadPoolExecutor getScheduledExecutor() { + return mScheduledExecutor; + } + /** Gets the {@link AsyncHttpStack} to be used by the network. */ protected AsyncHttpStack getHttpStack() { return mAsyncStack; diff --git a/src/main/java/com/android/volley/AsyncRequestQueue.java b/src/main/java/com/android/volley/AsyncRequestQueue.java index bbbcf927..f8e16669 100644 --- a/src/main/java/com/android/volley/AsyncRequestQueue.java +++ b/src/main/java/com/android/volley/AsyncRequestQueue.java @@ -32,6 +32,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -67,6 +68,19 @@ public class AsyncRequestQueue extends RequestQueue { */ private ExecutorService mBlockingExecutor; + /** Executor to be used for tasks that need to be scheduled. */ + private ScheduledThreadPoolExecutor mScheduledExecutor = + new ScheduledThreadPoolExecutor( + /* corePoolSize= */ 0, + new ThreadFactory() { + @Override + public Thread newThread(@NonNull Runnable runnable) { + Thread t = Executors.defaultThreadFactory().newThread(runnable); + t.setName("Volley-ScheduledExecutor"); + return t; + } + }); + /** * This interface may be used by advanced applications to provide custom executors according to * their needs. Apps must create ExecutorServices dynamically given a blocking queue rather than @@ -112,6 +126,7 @@ public void start() { mBlockingExecutor = mExecutorFactory.createBlockingExecutor(getBlockingQueue()); mNetwork.setBlockingExecutor(mBlockingExecutor); mNetwork.setNonBlockingExecutor(mNonBlockingExecutor); + mNetwork.setScheduledExecutor(mScheduledExecutor); mNonBlockingExecutor.execute( new Runnable() { diff --git a/src/main/java/com/android/volley/DefaultTimeoutRetryPolicy.java b/src/main/java/com/android/volley/DefaultTimeoutRetryPolicy.java new file mode 100644 index 00000000..d63c1347 --- /dev/null +++ b/src/main/java/com/android/volley/DefaultTimeoutRetryPolicy.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.volley; + +/** Default retry policy for requests that want a timeout before retrying an error. */ +public class DefaultTimeoutRetryPolicy extends DefaultRetryPolicy implements TimeoutRetryPolicy { + /** The timeout before retrying a failed request in milliseconds. */ + int mTimeoutBeforeRetryMs; + + /** + * Constructs a new retry policy + * + * @param timeoutBeforeRetryMs is the timeout before retrying a failed request in milliseconds. + */ + public DefaultTimeoutRetryPolicy(int timeoutBeforeRetryMs) { + super(); + if (timeoutBeforeRetryMs < 0) { + throw new IllegalArgumentException("timeout cannot be negative"); + } + mTimeoutBeforeRetryMs = timeoutBeforeRetryMs; + } + + /** + * Constructs a new retry policy. + * + * @param initialTimeoutMs The initial timeout for the policy. + * @param maxNumRetries The maximum number of retries. + * @param backoffMultiplier Backoff multiplier for the policy. + * @param timeoutBeforeRetryMs Timeout before retrying a failed request in milliseconds. + */ + public DefaultTimeoutRetryPolicy( + int initialTimeoutMs, + int maxNumRetries, + float backoffMultiplier, + int timeoutBeforeRetryMs) { + super(initialTimeoutMs, maxNumRetries, backoffMultiplier); + mTimeoutBeforeRetryMs = timeoutBeforeRetryMs; + } + + /** Returns the timeout before attempting to retry the request in ms. */ + @Override + public int getTimeoutBeforeRetryMs() { + return mTimeoutBeforeRetryMs; + } +} diff --git a/src/main/java/com/android/volley/TimeoutRetryPolicy.java b/src/main/java/com/android/volley/TimeoutRetryPolicy.java new file mode 100644 index 00000000..56d2c1d5 --- /dev/null +++ b/src/main/java/com/android/volley/TimeoutRetryPolicy.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.volley; + +/** + * Retry policy for a request. + * + *

Same as {@link RetryPolicy}, except instead of triggering the retry attempt instantly, the + * user can set a timeout in milliseconds to wait before retrying the request. + */ +public interface TimeoutRetryPolicy extends RetryPolicy { + /** Returns the timeout before attempting to retry the request in ms. */ + int getTimeoutBeforeRetryMs(); +} diff --git a/src/main/java/com/android/volley/toolbox/BasicAsyncNetwork.java b/src/main/java/com/android/volley/toolbox/BasicAsyncNetwork.java index cbc3c843..a2eb30f4 100644 --- a/src/main/java/com/android/volley/toolbox/BasicAsyncNetwork.java +++ b/src/main/java/com/android/volley/toolbox/BasicAsyncNetwork.java @@ -27,12 +27,16 @@ import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.RequestTask; +import com.android.volley.RetryPolicy; +import com.android.volley.TimeoutRetryPolicy; import com.android.volley.VolleyError; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.util.List; import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; /** A network performing Volley requests over an {@link HttpStack}. */ public class BasicAsyncNetwork extends AsyncNetwork { @@ -102,8 +106,8 @@ private void onRequestSucceeded( /* Method to be called after a failed network request */ private void onRequestFailed( - Request request, - OnRequestComplete callback, + final Request request, + final OnRequestComplete callback, IOException exception, long requestStartMs, @Nullable HttpResponse httpResponse, @@ -115,7 +119,26 @@ private void onRequestFailed( callback.onError(volleyError); return; } - performRequest(request, callback); + + RetryPolicy policy = request.getRetryPolicy(); + // If the retry policy is not an instance of TimeoutRetryPolicy OR no scheduling executor + // was set, then just perform the request immediately. + if (!(policy instanceof TimeoutRetryPolicy) || getScheduledExecutor() == null) { + performRequest(request, callback); + } else { + // Otherwise. schedule the request on the ScheduledExecutor for the appropriate time. + ScheduledFuture retry = + getScheduledExecutor() + .schedule( + new Runnable() { + @Override + public void run() { + performRequest(request, callback); + } + }, + ((TimeoutRetryPolicy) policy).getTimeoutBeforeRetryMs(), + TimeUnit.MILLISECONDS); + } } @Override