Answers:
看起来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
}
JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener, ErrorListener errorlistener)
构造函数。RequestFuture<JSONObject>
同时实现Listener<JSONObject>
和ErrorListener
接口,因此可以用作最后两个参数。
future.get()
则导致该应用停止运行或超时(如果已设置)。
注意@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;
}
IntentService
是单个线程的线程池执行程序,因此IntentService将永远处于阻塞状态,因为它处于循环状态
可能建议使用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线程的线程不同。Handler
Looper
作为一种补充的观察到两个@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。线程而不是主线程。但是,我尚未对此进行调查。
finish()
方法都是私有程序包,因此实现自定义ResponseDelivery实现非常复杂,除了使用反射黑客外,我不确定是否有解决办法。为了防止在主(UI)线程上运行任何东西,我最终要做的是设置一个备用Looper线程(使用),并使用该Looper 的处理程序将实例传递给构造函数。您有另一个弯针的开销,但要远离主线程Request
RequestQueue
Looper.prepareLooper(); Looper.loop()
ExecutorDelivery
RequestQueue
我现在使用锁来实现这种效果,我想知道是否有人想评论它是否正确?
// 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();
}
}
catch (InterruptedException e)
在while循环内部。否则,如果由于某种原因被中断,线程将无法等待
我想在马修接受的答案中添加一些内容。而RequestFuture
似乎从您创建的线程中进行了同步调用,但事实并非如此。而是在后台线程上执行该调用。
根据我对图书馆的了解,在中的请求RequestQueue
是通过其start()
方法分派的:
public void start() {
....
mCacheDispatcher = new CacheDispatcher(...);
mCacheDispatcher.start();
....
NetworkDispatcher networkDispatcher = new NetworkDispatcher(...);
networkDispatcher.start();
....
}
现在,CacheDispatcher
和NetworkDispatcher
类都扩展了线程。因此,有效地产生了一个新的工作线程来使请求队列出队,并将响应返回给由内部实现的成功和错误侦听器RequestFuture
。
尽管您的第二个目的已经实现,但您的第一个目的却不是,因为无论从哪个线程执行,总是会产生一个新线程 RequestFuture
。
简而言之,默认的Volley库无法实现真正的同步请求。如果我错了,请纠正我。
您可以使用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();