想象一下,我在一个已经有后台线程的服务中。我可以在同一个线程中使用 volley 发出请求,以便同步发生回调吗?
这有两个原因:
- 首先,我不需要另一个线程,创建它会很浪费。
- 其次,如果我在 ServiceIntent 中,线程的执行将在回调之前完成,因此我将没有来自 Volley 的响应。我知道我可以创建自己的服务,该服务具有一些我可以控制的运行循环的线程,但最好在 volley 中具有此功能。
想象一下,我在一个已经有后台线程的服务中。我可以在同一个线程中使用 volley 发出请求,以便同步发生回调吗?
这有两个原因:
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
}
注意@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;
}
可能建议使用 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
绑定,该Handler
using aLooper
与主 UI 线程绑定到不同的线程。
作为对@Blundells 和@Mathews 答案的补充观察,我不确定除了Volley 主线程之外的任何呼叫。
来源
看一下RequestQueue
实现,似乎RequestQueue
使用 aNetworkDispatcher
来执行请求并使用 aResponseDelivery
来传递结果(ResponseDelivery
被注入到 中NetworkDispatcher
)。ResponseDelivery
反过来,它是通过主线程的衍生创建的(Handler
在RequestQueue
实现中的第 112 行附近)。
NetworkDispatcher
在实现中的第 135 行的某个地方,似乎也通过与ResponseDelivery
任何错误相同的方式传递了成功的结果。再次; aResponseDelivery
基于Handler
主线程的衍生。
基本原理
对于从一个请求发出的用例,可以IntentService
公平地假设服务的线程应该阻塞,直到我们从 Volley 得到响应(以保证一个有效的运行时范围来处理结果)。
建议的解决方案
一种方法是覆盖RequestQueue
创建a 的默认方式,其中使用替代构造函数,注入ResponseDelivery
从当前线程而不是主线程产生的 a 。然而,我还没有调查这件事的影响。
您可以使用 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()
}
}
我想在 Matthew 接受的答案中添加一些内容。虽然RequestFuture
似乎从您创建它的线程进行同步调用,但事实并非如此。相反,调用是在后台线程上执行的。
根据我在浏览库后的理解,RequestQueue
在它的方法中发送请求start()
:
public void start() {
....
mCacheDispatcher = new CacheDispatcher(...);
mCacheDispatcher.start();
....
NetworkDispatcher networkDispatcher = new NetworkDispatcher(...);
networkDispatcher.start();
....
}
现在CacheDispatcher
和NetworkDispatcher
类都扩展了线程。因此,有效地产生了一个新的工作线程来使请求队列出队,并将响应返回给内部实现的成功和错误侦听器RequestFuture
。
虽然你的第二个目的已经实现,但你的第一个目的不是因为总是产生一个新线程,无论你从哪个线程执行RequestFuture
。
简而言之,使用默认 Volley 库无法实现真正的同步请求。如果我错了,请纠正我。
我使用锁来实现这种效果现在我想知道它是否正确我的方式有人想发表评论?
// 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 进行同步请求,但您必须在不同的线程中调用该方法,否则您正在运行的应用程序将阻塞,它应该是这样的:
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();