我可以用截击进行同步请求吗?

时间:2022-10-22 19:45:47

Imagine I'm in a Service that already has a background thread. Can I do a request using volley in that same thread, so that callbacks happen synchronously?

假设我在一个已经有后台线程的服务中。我可以在相同的线程中使用volley来做一个请求,这样回调会同步发生吗?

There are 2 reasons for this: - First, I do not need another thread and it would be a waste to create it. - Second, if I'm in a ServiceIntent, the execution of the thread will finish before the callback, and therefor I will have no response from Volley. I know I can create my own Service that has some thread with a runloop I can control, but it would be desirable having this functionality in volley.

有两个原因:-首先,我不需要另一个线程,创建它将是一种浪费。第二,如果我在ServiceIntent中,线程的执行将在回调之前完成,因此我不会收到来自Volley的响应。我知道我可以创建我自己的服务,它有一个我可以控制的runloop的线程,但是在volley中有这个功能是可取的。

Thank you!

谢谢你!

6 个解决方案

#1


168  

It looks like it is possible with Volley's RequestFuture class. For example, to create a synchronous JSON HTTP GET request, you can do the following:

看来Volley的RequestFuture类是可行的。例如,要创建一个同步JSON HTTP GET请求,可以执行以下操作:

RequestFuture<JSONObject> future = RequestFuture.newFuture();
JsonObjectRequest request = new JsonObjectRequest(URL, new JSONObject(), future, future);
requestQueue.add(request);

try {
  JSONObject response = future.get(); // this will block
} catch (InterruptedException e) {
  // exception handling
} catch (ExecutionException e) {
  // exception handling
}

#2


104  

Note @Matthews answer is correct BUT if you are on another thread and you do a volley call when you have no internet, your error callback will be called on the main thread, but the thread you are on will be blocked FOREVER. (Therefore if that thread is an IntentService, you will never be able to send another message to it and your service will be basically dead).

注意@Matthews的回答是正确的,但是如果您在另一个线程上,并且在没有internet时进行截击调用,您的错误回调将在主线程上被调用,但是您所处的线程将永远被阻塞。(因此,如果该线程是IntentService,那么您将永远无法向它发送另一条消息,您的服务将基本上处于死状态)。

Use the version of get() that has a timeout future.get(30, TimeUnit.SECONDS) and catch the error to exit your thread.

使用具有超时未来的get()版本。获取(30,TimeUnit.SECONDS)并捕获错误以退出线程。

To match @Mathews answer:

匹配@Mathews回答:

        try {
            return future.get(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            // exception handling
        } catch (ExecutionException e) {
            // exception handling
        } catch (TimeoutException e) {
            // exception handling
        }

Below I wrapped it in a method & use a different request:

下面我用一种方法包装它&使用另一种请求:

   /**
     * Runs a blocking Volley request
     *
     * @param method        get/put/post etc
     * @param url           endpoint
     * @param errorListener handles errors
     * @return the input stream result or exception: NOTE returns null once the onErrorResponse listener has been called
     */
    public InputStream runInputStreamRequest(int method, String url, Response.ErrorListener errorListener) {
        RequestFuture<InputStream> future = RequestFuture.newFuture();
        InputStreamRequest request = new InputStreamRequest(method, url, future, errorListener);
        getQueue().add(request);
        try {
            return future.get(REQUEST_TIMEOUT, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Log.e("Retrieve cards api call interrupted.", e);
            errorListener.onErrorResponse(new VolleyError(e));
        } catch (ExecutionException e) {
            Log.e("Retrieve cards api call failed.", e);
            errorListener.onErrorResponse(new VolleyError(e));
        } catch (TimeoutException e) {
            Log.e("Retrieve cards api call timed out.", e);
            errorListener.onErrorResponse(new VolleyError(e));
        }
        return null;
    }

#3


4  

It is probably recommended to use the Futures, but if for whatever reason you don't want to, instead of cooking your own synchronized blocking thing you should use a java.util.concurrent.CountDownLatch. So that would work like this..

可能建议使用期货,但如果出于某种原因您不想使用,那么应该使用java.util.concurrent.CountDownLatch来代替您自己的同步阻塞。这样就可以了。

//I'm running this in an instrumentation test, in real life you'd ofc obtain the context differently...
final Context context = InstrumentationRegistry.getTargetContext();
final RequestQueue queue = Volley.newRequestQueue(context);
final CountDownLatch countDownLatch = new CountDownLatch(1);
final Object[] responseHolder = new Object[1];

final StringRequest stringRequest = new StringRequest(Request.Method.GET, "http://google.com", new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        responseHolder[0] = response;
        countDownLatch.countDown();
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        responseHolder[0] = error;
        countDownLatch.countDown();
    }
});
queue.add(stringRequest);
try {
    countDownLatch.await();
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}
if (responseHolder[0] instanceof VolleyError) {
    final VolleyError volleyError = (VolleyError) responseHolder[0];
    //TODO: Handle error...
} else {
    final String response = (String) responseHolder[0];
    //TODO: Handle response...
}

#4


2  

As a complementary observation to both @Blundells and @Mathews answers, I'm not sure any call is delivered to anything but the main thread by Volley.

作为对@Blundells和@Mathews答案的补充观察,我不确定任何呼叫都是通过截击传递给主线。

The Source

Having a look at the RequestQueue implementation it seems the RequestQueue is using a NetworkDispatcher to execute the request and a ResponseDelivery to deliver the result (the ResponseDelivery is injected into the NetworkDispatcher). The ResponseDelivery is in turn created with a Handler spawn from the main thread (somewhere around line 112 in the RequestQueue implementation).

看一下RequestQueue实现,似乎RequestQueue使用NetworkDispatcher执行请求,使用ResponseDelivery交付结果(ResponseDelivery被注入到NetworkDispatcher中)。ResponseDelivery反过来是由处理程序从主线程衍生而来的(在RequestQueue实现的第112行附近)。

Somewhere about line 135 in the NetworkDispatcher implementation it seems like also successful results are delivered through the same ResponseDelivery as any errors. Again; a ResponseDelivery based on a Handler spawn from the main thread.

在NetworkDispatcher实现的第135行中,似乎成功的结果通过与任何错误相同的ResponseDelivery传递。再一次;基于处理程序的响应交付从主线程派生。

Rationale

基本原理

For the use-case where a request is to be made from an IntentService it's fair to assume that the thread of the service should block until we have a response from Volley (to guarantee a living runtime scope to handle the result in).

对于要从IntentService发出请求的用例,可以合理地假设服务的线程应该被阻塞,直到我们收到来自Volley的响应(以保证有一个活动的运行时范围来处理结果)。

Suggested solutions

建议的解决方案

One approach would be to override the default way a RequestQueue is created, where an alternative constructor is used instead, injecting a ResponseDelivery which spawns from the current thread rather than the main thread. I haven't investigated the implications of this, however.

一种方法是重写RequestQueue的默认方式,在这里使用替代的构造函数,注入一个ResponseDelivery,它从当前线程而不是主线中生成。然而,我还没有研究过这其中的含义。

#5


1  

I use a lock to achieve that effect now im wondering if its correct my way anyone want to comment ?

我用一把锁来达到这个效果,现在我想知道我的方式是否正确,有人想评论吗?

// as a field of the class where i wan't to do the synchronous `volley` call   
Object mLock = new Object();


// need to have the error and success listeners notifyin
final boolean[] finished = {false};
            Response.Listener<ArrayList<Integer>> responseListener = new Response.Listener<ArrayList<Integer>>() {
                @Override
                public void onResponse(ArrayList<Integer> response) {
                    synchronized (mLock) {
                        System.out.println();
                        finished[0] = true;
                        mLock.notify();

                    }


                }
            };

            Response.ErrorListener errorListener = new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    synchronized (mLock) {
                        System.out.println();
                        finished[0] = true;
                        System.out.println();
                        mLock.notify();
                    }
                }
            };

// after adding the Request to the volley queue
synchronized (mLock) {
            try {
                while(!finished[0]) {
                    mLock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

#6


1  

I want to add something to Matthew's accepted answer. While RequestFuture might seem to make a synchronous call from the thread you created it, it does not. Instead, the call is executed on a background thread.

我想补充一些东西给马修已经接受的答案。虽然RequestFuture可能会从您创建的线程中发出同步调用,但它不会。相反,调用是在后台线程上执行的。

From what I understand after going through the library, requests in the RequestQueue are dispatched in its start() method:

根据我的理解,在经过库之后,RequestQueue中的请求将以其start()方法分派:

    public void start() {
        ....
        mCacheDispatcher = new CacheDispatcher(...);
        mCacheDispatcher.start();
        ....
           NetworkDispatcher networkDispatcher = new NetworkDispatcher(...);
           networkDispatcher.start();
        ....
    }

Now both CacheDispatcher and NetworkDispatcher classes extend thread. So effectively a new worker thread is spawned for dequeuing the request queue and the response is returned to the success and error listeners implemented internally by RequestFuture.

现在CacheDispatcher类和NetworkDispatcher类都扩展了线程。因此,产生了一个新的工作线程,用于将请求队列排入队列,并将响应返回给由RequestFuture在内部实现的成功和错误监听器。

Although your second purpose is attained but you first purpose is not since a new thread is always spawned, no matter from which thread you execute RequestFuture.

尽管您的第二个目的已经实现,但是您的第一个目的并不是因为总是生成一个新线程,无论您从哪个线程执行RequestFuture。

In short, true synchronous request is not possible with default Volley library. Correct me if I am wrong.

简而言之,在默认的Volley库中不可能有真正的同步请求。如果我错了请纠正我。

#1


168  

It looks like it is possible with Volley's RequestFuture class. For example, to create a synchronous JSON HTTP GET request, you can do the following:

看来Volley的RequestFuture类是可行的。例如,要创建一个同步JSON HTTP GET请求,可以执行以下操作:

RequestFuture<JSONObject> future = RequestFuture.newFuture();
JsonObjectRequest request = new JsonObjectRequest(URL, new JSONObject(), future, future);
requestQueue.add(request);

try {
  JSONObject response = future.get(); // this will block
} catch (InterruptedException e) {
  // exception handling
} catch (ExecutionException e) {
  // exception handling
}

#2


104  

Note @Matthews answer is correct BUT if you are on another thread and you do a volley call when you have no internet, your error callback will be called on the main thread, but the thread you are on will be blocked FOREVER. (Therefore if that thread is an IntentService, you will never be able to send another message to it and your service will be basically dead).

注意@Matthews的回答是正确的,但是如果您在另一个线程上,并且在没有internet时进行截击调用,您的错误回调将在主线程上被调用,但是您所处的线程将永远被阻塞。(因此,如果该线程是IntentService,那么您将永远无法向它发送另一条消息,您的服务将基本上处于死状态)。

Use the version of get() that has a timeout future.get(30, TimeUnit.SECONDS) and catch the error to exit your thread.

使用具有超时未来的get()版本。获取(30,TimeUnit.SECONDS)并捕获错误以退出线程。

To match @Mathews answer:

匹配@Mathews回答:

        try {
            return future.get(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            // exception handling
        } catch (ExecutionException e) {
            // exception handling
        } catch (TimeoutException e) {
            // exception handling
        }

Below I wrapped it in a method & use a different request:

下面我用一种方法包装它&使用另一种请求:

   /**
     * Runs a blocking Volley request
     *
     * @param method        get/put/post etc
     * @param url           endpoint
     * @param errorListener handles errors
     * @return the input stream result or exception: NOTE returns null once the onErrorResponse listener has been called
     */
    public InputStream runInputStreamRequest(int method, String url, Response.ErrorListener errorListener) {
        RequestFuture<InputStream> future = RequestFuture.newFuture();
        InputStreamRequest request = new InputStreamRequest(method, url, future, errorListener);
        getQueue().add(request);
        try {
            return future.get(REQUEST_TIMEOUT, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Log.e("Retrieve cards api call interrupted.", e);
            errorListener.onErrorResponse(new VolleyError(e));
        } catch (ExecutionException e) {
            Log.e("Retrieve cards api call failed.", e);
            errorListener.onErrorResponse(new VolleyError(e));
        } catch (TimeoutException e) {
            Log.e("Retrieve cards api call timed out.", e);
            errorListener.onErrorResponse(new VolleyError(e));
        }
        return null;
    }

#3


4  

It is probably recommended to use the Futures, but if for whatever reason you don't want to, instead of cooking your own synchronized blocking thing you should use a java.util.concurrent.CountDownLatch. So that would work like this..

可能建议使用期货,但如果出于某种原因您不想使用,那么应该使用java.util.concurrent.CountDownLatch来代替您自己的同步阻塞。这样就可以了。

//I'm running this in an instrumentation test, in real life you'd ofc obtain the context differently...
final Context context = InstrumentationRegistry.getTargetContext();
final RequestQueue queue = Volley.newRequestQueue(context);
final CountDownLatch countDownLatch = new CountDownLatch(1);
final Object[] responseHolder = new Object[1];

final StringRequest stringRequest = new StringRequest(Request.Method.GET, "http://google.com", new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        responseHolder[0] = response;
        countDownLatch.countDown();
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        responseHolder[0] = error;
        countDownLatch.countDown();
    }
});
queue.add(stringRequest);
try {
    countDownLatch.await();
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}
if (responseHolder[0] instanceof VolleyError) {
    final VolleyError volleyError = (VolleyError) responseHolder[0];
    //TODO: Handle error...
} else {
    final String response = (String) responseHolder[0];
    //TODO: Handle response...
}

#4


2  

As a complementary observation to both @Blundells and @Mathews answers, I'm not sure any call is delivered to anything but the main thread by Volley.

作为对@Blundells和@Mathews答案的补充观察,我不确定任何呼叫都是通过截击传递给主线。

The Source

Having a look at the RequestQueue implementation it seems the RequestQueue is using a NetworkDispatcher to execute the request and a ResponseDelivery to deliver the result (the ResponseDelivery is injected into the NetworkDispatcher). The ResponseDelivery is in turn created with a Handler spawn from the main thread (somewhere around line 112 in the RequestQueue implementation).

看一下RequestQueue实现,似乎RequestQueue使用NetworkDispatcher执行请求,使用ResponseDelivery交付结果(ResponseDelivery被注入到NetworkDispatcher中)。ResponseDelivery反过来是由处理程序从主线程衍生而来的(在RequestQueue实现的第112行附近)。

Somewhere about line 135 in the NetworkDispatcher implementation it seems like also successful results are delivered through the same ResponseDelivery as any errors. Again; a ResponseDelivery based on a Handler spawn from the main thread.

在NetworkDispatcher实现的第135行中,似乎成功的结果通过与任何错误相同的ResponseDelivery传递。再一次;基于处理程序的响应交付从主线程派生。

Rationale

基本原理

For the use-case where a request is to be made from an IntentService it's fair to assume that the thread of the service should block until we have a response from Volley (to guarantee a living runtime scope to handle the result in).

对于要从IntentService发出请求的用例,可以合理地假设服务的线程应该被阻塞,直到我们收到来自Volley的响应(以保证有一个活动的运行时范围来处理结果)。

Suggested solutions

建议的解决方案

One approach would be to override the default way a RequestQueue is created, where an alternative constructor is used instead, injecting a ResponseDelivery which spawns from the current thread rather than the main thread. I haven't investigated the implications of this, however.

一种方法是重写RequestQueue的默认方式,在这里使用替代的构造函数,注入一个ResponseDelivery,它从当前线程而不是主线中生成。然而,我还没有研究过这其中的含义。

#5


1  

I use a lock to achieve that effect now im wondering if its correct my way anyone want to comment ?

我用一把锁来达到这个效果,现在我想知道我的方式是否正确,有人想评论吗?

// as a field of the class where i wan't to do the synchronous `volley` call   
Object mLock = new Object();


// need to have the error and success listeners notifyin
final boolean[] finished = {false};
            Response.Listener<ArrayList<Integer>> responseListener = new Response.Listener<ArrayList<Integer>>() {
                @Override
                public void onResponse(ArrayList<Integer> response) {
                    synchronized (mLock) {
                        System.out.println();
                        finished[0] = true;
                        mLock.notify();

                    }


                }
            };

            Response.ErrorListener errorListener = new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    synchronized (mLock) {
                        System.out.println();
                        finished[0] = true;
                        System.out.println();
                        mLock.notify();
                    }
                }
            };

// after adding the Request to the volley queue
synchronized (mLock) {
            try {
                while(!finished[0]) {
                    mLock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

#6


1  

I want to add something to Matthew's accepted answer. While RequestFuture might seem to make a synchronous call from the thread you created it, it does not. Instead, the call is executed on a background thread.

我想补充一些东西给马修已经接受的答案。虽然RequestFuture可能会从您创建的线程中发出同步调用,但它不会。相反,调用是在后台线程上执行的。

From what I understand after going through the library, requests in the RequestQueue are dispatched in its start() method:

根据我的理解,在经过库之后,RequestQueue中的请求将以其start()方法分派:

    public void start() {
        ....
        mCacheDispatcher = new CacheDispatcher(...);
        mCacheDispatcher.start();
        ....
           NetworkDispatcher networkDispatcher = new NetworkDispatcher(...);
           networkDispatcher.start();
        ....
    }

Now both CacheDispatcher and NetworkDispatcher classes extend thread. So effectively a new worker thread is spawned for dequeuing the request queue and the response is returned to the success and error listeners implemented internally by RequestFuture.

现在CacheDispatcher类和NetworkDispatcher类都扩展了线程。因此,产生了一个新的工作线程,用于将请求队列排入队列,并将响应返回给由RequestFuture在内部实现的成功和错误监听器。

Although your second purpose is attained but you first purpose is not since a new thread is always spawned, no matter from which thread you execute RequestFuture.

尽管您的第二个目的已经实现,但是您的第一个目的并不是因为总是生成一个新线程,无论您从哪个线程执行RequestFuture。

In short, true synchronous request is not possible with default Volley library. Correct me if I am wrong.

简而言之,在默认的Volley库中不可能有真正的同步请求。如果我错了请纠正我。