想象一下,我在一个已经有后台线程的服务中。我可以在同一个线程中使用齐射进行请求,以便回调同步发生吗?
这有两个原因: - 首先,我不需要另一个线程,创建它是一种浪费。 - 其次,如果我在ServiceIntent中,线程的执行将在回调之前完成,因此我将没有来自Volley的响应。我知道我可以创建自己的服务,其中包含一些我可以控制的runloop的线程,但是最好有这个功能。
谢谢!
答案 0 :(得分:177)
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
}
答案 1 :(得分:119)
注意@Matthews答案是正确的但是如果你在另一个线程并且在没有互联网的情况下进行一次凌空调用,你的错误回调将在主线程上被调用,但是你所在的线程将是阻止FOREVER。(因此,如果该线程是IntentService,您将永远无法向其发送另一条消息,您的服务将基本上死亡。)
使用具有超时get()
的{{1}}版本并捕获错误以退出您的主题。
匹配@Mathews答案:
future.get(30, TimeUnit.SECONDS)
下面我把它包裹在一个方法&amp;使用不同的请求:
try {
return future.get(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// exception handling
} catch (ExecutionException e) {
// exception handling
} catch (TimeoutException e) {
// exception handling
}
答案 2 :(得分:7)
可能建议使用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...
}
由于人们似乎真的试图这样做并遇到一些麻烦我决定我实际上提供了一个真实的生活&#34;正在使用的工作样本。这是https://github.com/timolehto/SynchronousVolleySample
现在即使解决方案有效,它也有一些局限性。最重要的是,您无法在主UI线程上调用它。 Volley确实在后台执行请求,但默认情况下,Volley使用应用程序的主Looper
来发送响应。这会导致死锁,因为主UI线程正在等待响应,但Looper
在处理传递之前等待onCreate
完成。如果您真的想要这样做,您可以使用RequestQueue
实例化您自己的ExecutorDelivery
,而不是静态帮助方法,将您自己的Handler
传递给Looper
。它与主UI线程中的不同线程绑定。
答案 3 :(得分:2)
作为对@Blundells和@Mathews答案的补充观察,我不确定任何调用是否被传递给任何,而是Volley的主线程。
来源
看一下RequestQueue
implementation似乎RequestQueue
使用NetworkDispatcher
执行请求,ResponseDelivery
传递结果ResponseDelivery
注入NetworkDispatcher
)。反过来使用主线程中的ResponseDelivery
spawn创建Handler
(RequestQueue
实现中第112行的某处)。
在NetworkDispatcher
implementation的第135行的某处,似乎成功的结果通过与任何错误相同的ResponseDelivery
传递。再次; ResponseDelivery
基于主线程中的Handler
生成。
<强>原理强>
对于要求IntentService
公平的请求的用例,假设服务的线程应该阻止,直到我们得到Volley的回复(为了保证生计)运行时范围来处理结果)。
建议的解决方案
一种方法是覆盖默认方式RequestQueue
is created,其中使用替代构造函数,注入{em}来自当前线程的ResponseDelivery
而不是主线程。然而,我还没有调查过这个问题。
答案 4 :(得分: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();
}
}
答案 5 :(得分:1)
我想在Matthew接受的答案中加入一些内容。虽然RequestFuture
似乎可以从您创建它的线程进行同步调用,但事实并非如此。而是在后台线程上执行调用。
根据我在浏览图书馆时的理解,RequestQueue
中的请求会以start()
方式发送:
public void start() {
....
mCacheDispatcher = new CacheDispatcher(...);
mCacheDispatcher.start();
....
NetworkDispatcher networkDispatcher = new NetworkDispatcher(...);
networkDispatcher.start();
....
}
现在,CacheDispatcher
和NetworkDispatcher
类都扩展了线程。因此,有效地生成了一个新的工作线程,用于使请求队列出列,并将响应返回给RequestFuture
内部实现的成功和错误侦听器。
虽然你的第二个目的已经达到,但你的第一个目的不是因为总是产生一个新的线程,无论你从哪个线程执行RequestFuture
。
简而言之,默认的Volley库无法使用真正的同步请求。如果我错了,请纠正我。
答案 6 :(得分:0)
您可以通过截击进行同步请求,但必须在不同的线程中调用该方法,否则您正在运行的应用程序将被阻止,
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();
答案 7 :(得分:0)
您可以通过kotlin协程实现这一目标
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()
}
}