138

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

这有两个原因:

  • 首先,我不需要另一个线程,创建它会很浪费。
  • 其次,如果我在 ServiceIntent 中,线程的执行将在回调之前完成,因此我将没有来自 Volley 的响应。我知道我可以创建自己的服务,该服务具有一些我可以控制的运行循环的线程,但最好在 volley 中具有此功能。
4

8 回答 8

192

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
}
于 2013-06-11T01:55:00.157 回答
130

注意@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;
    }
于 2014-05-22T13:52:26.420 回答
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应用程序的 main 来调度响应。这会导致死锁,因为主 UI 线程正在等待响应,但在处理交付之前Looper正在等待完成。onCreate如果您真的想这样做,您可以实例化您自己的实例,而不是静态辅助方法,将您自己的方法RequestQueue与using aExecutorDelivery绑定,该Handlerusing aLooper与主 UI 线程绑定到不同的线程。

于 2016-06-30T08:00:04.703 回答
2

作为对@Blundells 和@Mathews 答案的补充观察,我不确定除了Volley 主线程之外的任何呼叫。

来源

看一下RequestQueue实现,似乎RequestQueue使用 aNetworkDispatcher来执行请求并使用 aResponseDelivery来传递结果(ResponseDelivery被注入到 中NetworkDispatcher)。ResponseDelivery反过来,它是通过主线程的衍生创建的(HandlerRequestQueue实现中的第 112 行附近)。

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

基本原理

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

建议的解决方案

一种方法是覆盖RequestQueue创建a 的默认方式,其中使用替代构造函数,注入ResponseDelivery当前线程而不是主线程产生的 a 。然而,我还没有调查这件事的影响。

于 2015-01-15T12:01:42.417 回答
2

您可以使用 kotlin Coroutines 实现此目的

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
private suspend fun request(context: Context, link : String) : String{
   return suspendCancellableCoroutine { continuation ->
      val queue = Volley.newRequestQueue(context)
      val stringRequest = StringRequest(Request.Method.GET, link,
         { response ->
            continuation.resumeWith(Result.success(response))
         },
          {
            continuation.cancel(Exception("Volley Error"))
         })

      queue.add(stringRequest)
   }
}

并打电话给

CoroutineScope(Dispatchers.IO).launch {
    val response = request(CONTEXT, "https://www.google.com")
    withContext(Dispatchers.Main) {
       Toast.makeText(CONTEXT, response,Toast.LENGTH_SHORT).show()
   }
}
于 2020-08-20T12:13:04.350 回答
2

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

根据我在浏览库后的理解,RequestQueue在它的方法中发送请求start()

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

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

虽然你的第二个目的已经实现,但你的第一个目的不是因为总是产生一个新线程,无论你从哪个线程执行RequestFuture

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

于 2016-12-17T06:42:08.687 回答
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();
            }
        }
于 2014-11-06T17:21:20.560 回答
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();
于 2019-05-25T00:37:57.313 回答