我可以与排球进行同步请求吗?


133

想象我在一个已经有后台线程的服务中。我可以在同一线程中使用Volley发出请求,以便回调同步发生吗?

这样做有两个原因:-首先,我不需要另一个线程,创建它会很浪费。-其次,如果我处于ServiceIntent中,则线程的执行将在回调之前完成,因此,我不会收到Volley的响应。我知道我可以创建自己的Service,该Service具有可以控制的Runloop线程,但是最好在volley中具有此功能。

谢谢!


5
请务必阅读@Blundell的回复以及获得高度评价(非常有用)的答案。
Jedidja 2014年

Answers:


183

看起来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
}

5
@tasomaniac更新。这使用了JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener, ErrorListener errorlistener)构造函数。RequestFuture<JSONObject>同时实现Listener<JSONObject>ErrorListener接口,因此可以用作最后两个参数。
马特

21
它永远阻塞!
Mohammed Subhi Sheikh Quroush 2014年

9
提示:如果在将请求添加到请求队列之前调用future.get(),它可能会永远阻塞。
datayeah 2014年

3
它将永远被阻止,因为您可能会遇到连接错误,请阅读Blundell解答
Mina Gabriel

4
应该说您不应该在主线程上执行此操作。我不清楚。如果主线程被阻止,future.get()则导致该应用停止运行或超时(如果已设置)。
r00tandy

125

注意@Matthews答案是正确的,但是如果您在另一个线程上并且在没有互联网的情况下进行凌空呼叫,则错误回调将在主线程上被调用,但是您所在的线程将被永远阻止。(因此,如果该线程是IntentService,您将永远无法向其发送另一条消息,并且您的服务将基本失效)。

使用get()存在超时的版本future.get(30, TimeUnit.SECONDS)并捕获错误以退出线程。

匹配@Mathews答案:

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

下面我将其包装在一个方法中并使用其他请求:

   /**
     * 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;
    }

这是相当重要的一点!不知道为什么这个答案没有得到更多的赞誉。
Jedidja 2014年

1
还要注意的是,如果将ExecutionException传递给传递给请求的同一个侦听器,则将处理两次该异常。当请求期间发生异常时,会发生此异常,凌空将为您传递到errorListener。
Stimsoni,2015年

@Blundell我不明白您的答复。如果侦听器是在UI线程上执行的,则有一个等待中的后台线程,并且有UI线程调用notifyAll(),这样就可以了。如果传递是在与将来的get()阻塞的同一线程上完成的,则可能发生死锁。因此,您的回应似乎毫无意义。
greywolf82 2015年

1
@ greywolf82 IntentService是单个线程的线程池执行程序,因此IntentService将永远处于阻塞状态,因为它处于循环状态
Blundell

@Blundell我不明白。它一直坐着直到通知被调用并且将从UI线程调用它为止。除非您有两个不同的线程,否则我看不到僵局
greywolf82 2015年

9

可能建议使用Futures,但是如果出于任何原因,您不想使用自己的同步阻塞对象,则应该使用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...
}

由于人们似乎实际上试图这样做并遇到了一些麻烦,因此我决定实际上要提供一个正在使用的“现实生活”工作样本。这里是 https://github.com/timolehto/SynchronousVolleySample

现在,即使该解决方案有效,它也有一些局限性。最重要的是,您不能在主UI线程上调用它。Volley确实在后台执行请求,但是默认情况下Volley使用Looper应用程序的主体来分派响应。这会导致死锁,因为主UI线程正在等待响应,但是在处理传递之前Looper正在等待onCreate完成。如果您确实确实想执行此操作,则可以使用RequestQueue自己的实例化方法(而不是静态帮助器方法)来实例化自己,将其传递ExecutorDelivery给与a绑定的对象,后者与主UI线程的线程不同。HandlerLooper


此解决方案永远阻止了我的线程,更改了Thread.sleep而不是countDownLatch并解决了问题
snersesyan

如果您可以提供以这种方式失败的代码的完整样本,也许我们可以找出问题所在。我看不到与倒计时闩锁一起睡眠是如何有意义的。
蒂莫

好吧@VinojVetha我已经对答案进行了一些更新,以澄清这种情况,并提供了一个GitHub存储库,您可以轻松地克隆它并尝试运行代码。如果您还有其他问题,请提供示例存储库的分叉,以演示您的问题作为参考。
Timo

到目前为止,这是同步请求的绝佳解决方案。
bikram

2

作为一种补充的观察到两个@Blundells和@Mathews答案,我不知道任何呼叫传送到任何东西,但通过排球主线程。

来源

看一下RequestQueue实现,看来似乎RequestQueue正在使用a NetworkDispatcher执行请求并ResponseDelivery传递a 结果(将ResponseDelivery其注入NetworkDispatcher)。的ResponseDelivery是在与创建转Handler从主线程产卵(周围某处中的行112 RequestQueue实现)。

NetworkDispatcher实现中第135行的某个位置,似乎也通过与ResponseDelivery任何错误相同的方式传递了成功的结果。再次; 一个ResponseDelivery基于Handler主线程生成的对象。

基本原理

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

建议的解决方案

一种方法是重写a的默认RequestQueue创建方式,在该方法中使用替代构造函数,注入ResponseDelivery当前生成的a。线程而不是主线程。但是,我尚未对此进行调查。


1
由于自定义类和类中的finish()方法都是私有程序包,因此实现自定义ResponseDelivery实现非常复杂,除了使用反射黑客外,我不确定是否有解决办法。为了防止在主(UI)线程上运行任何东西,我最终要做的是设置一个备用Looper线程(使用),并使用该Looper 的处理程序将实例传递给构造函数。您有另一个弯针的开销,但要远离主线程RequestRequestQueueLooper.prepareLooper(); Looper.loop()ExecutorDeliveryRequestQueue
Stephen James Hand

1

我现在使用锁来实现这种效果,我想知道是否有人想评论它是否正确?

// 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();
            }
        }

我认为您实质上是在使用“期货”时实现Volley已经提供的功能。
spaaarky21

1
我建议catch (InterruptedException e)在while循环内部。否则,如果由于某种原因被中断,线程将无法等待
jayeffkay 2015年

@jayeffkay如果在while循环中发生** InterruptedException **,则catch已经处理了该异常,我已经在捕获该异常。
forcewill 2015年

1

我想在马修接受的答案中添加一些内容。而RequestFuture似乎从您创建的线程中进行了同步调用,但事实并非如此。而是在后台线程上执行该调用。

根据我对图书馆的了解,在中的请求RequestQueue是通过其start()方法分派的:

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

现在,CacheDispatcherNetworkDispatcher类都扩展了线程。因此,有效地产生了一个新的工作线程来使请求队列出队,并将响应返回给由内部实现的成功和错误侦听器RequestFuture

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

简而言之,默认的Volley库无法实现真正​​的同步请求。如果我错了,请纠正我。


0

您可以使用volley进行同步请求,但必须在不同的线程中调用该方法,否则正在运行的应用程序将被阻止,它应该像这样:

public String syncCall(){

    String URL = "http://192.168.1.35:8092/rest";
    String response = new String();



    RequestQueue requestQueue = Volley.newRequestQueue(this.getContext());

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

    try {
        response = future.get().toString();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (JSONException e) {
        e.printStackTrace();
    }

    return response;


}

之后,您可以在线程中调用该方法:

 Thread thread = new Thread(new Runnable() {
                                    @Override
                                    public void run() {

                                        String response = syncCall();

                                    }
                                });
                                thread.start();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.