我可以用截击做一个同步请求吗?

假设我在一个已经有后台线程的服务中。我是否可以在同一个线程中使用截击来执行请求,以便同步发生回调?

这有两个原因:

  • 首先,我不需要另一个线程,创建它将是一种浪费。
  • 其次,如果我在 ServiceInent 中,线程的执行将在回调之前完成,因此我将没有来自 Volley 的响应。我知道我可以创建我自己的服务,它有一些线程和一个我可以控制的运行循环,但是我希望在截击中拥有这个功能。
76666 次浏览

看起来用沃利的 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
}

Note@Matthews 的回答是正确的,但是如果你在另一个线程上,当你没有互联网的时候,你做了一个抽射调用,您的错误回调将在主线程上调用,但是您所在的线程将被永远阻塞。(因此,如果那个线程是一个意向服务,你将永远不能向它发送另一条消息,你的服务将基本上死亡)。

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

与“ 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;
}

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

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

作为对@Blundells 和@Mathews 回答的补充观察,我不确定 任何调用是否通过 Volley 向任何 但是主线传递。

源头

看看 RequestQueue实现,似乎 RequestQueue使用 NetworkDispatcher来执行请求,使用 ResponseDelivery来交付结果(ResponseDelivery被注入到 NetworkDispatcher中)。ResponseDelivery依次由主线程(在 RequestQueue实现中的第112行左右)产生的 Handler创建。

NetworkDispatcher实现的第135行的某个地方,似乎成功的结果也是通过与任何错误相同的 ResponseDelivery传递的。同样,一个 ResponseDelivery基于从主线程产生的 Handler

理由

对于从 IntentService发出请求的用例来说,假设服务的线程应该阻塞,直到我们从 Volley 得到响应(以保证处理结果的活动运行时作用域)是公平的。

建议的解决方案

一种方法是重写 创建 RequestQueue的默认方式,在这种方式中使用替代构造函数,注入一个从 目前线程而不是主线程产生的 ResponseDelivery。不过,我还没有调查这件事的影响。

可能建议使用 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绑定到一个 Handler使用一个 Looper绑定到不同的线程从主 UI 线程。

我想补充一下马修已经接受的答案。虽然 RequestFuture似乎是从您创建的线程进行同步调用,但它并不是。相反,调用是在后台线程上执行的。

根据我通过图书馆了解到的情况,RequestQueue中的请求是用它的 start()方法发送的:

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

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

虽然达到了第二个目的,但是第一个目的没有达到,因为无论从哪个线程执行 RequestFuture,总是会产生一个新线程。

简而言之,如果我错了,请纠正我。

你可以用截击来同步请求,但是你必须在不同的线程中调用这个方法,否则你运行的应用程序会阻塞,应该是这样的:

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

你可以用 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()
}
}