假设我已经在一个有后台线程的服务中,我能否在同一线程中使用Volley进行请求,以便回调同步发生?
这样做有两个原因:
- 首先,我不需要另一个线程,创建它将是浪费。
- 其次,如果我在ServiceIntent中,线程的执行将在回调之前完成,因此我将无法从Volley获得响应。我知道我可以创建自己的Service,并拥有一些带有可控制的运行循环的线程,但希望在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
}
JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener, ErrorListener errorlistener)
构造函数。 RequestFuture<JSONObject>
实现了Listener<JSONObject>
和ErrorListener
接口,因此可以将其用作最后两个参数。 - Matthewfuture.get()
阻塞,那么如果设置了超时时间,应用程序肯定会停止或超时。 - r00tandy注意:@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 将永远被阻塞。 - Blundellget()
会阻塞线程,如果它是后台线程,那么就会阻塞该线程。 - Blundell虽然建议使用 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...
}
Looper
来分派响应。这会导致死锁,因为主 UI 线程正在等待响应,但Looper
正在等待onCreate
完成以处理传递。如果您真的非常想这么做,可以使用自己的RequestQueue
实例化静态辅助方法,将其传递给与主 UI 线程不同线程相关联的Looper
的Handler
,然后再将其绑定到自己的ExecutorDelivery
上。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()
}
}
Request
类和RequestQueue
类中的finish()
方法是包私有的,除了使用反射技巧,我不确定是否有其他方法可以解决这个问题。为了防止任何东西在主(UI)线程上运行,我最终设置了一个替代的Looper线程(使用Looper.prepareLooper(); Looper.loop()
),并将一个带有该looper处理程序的ExecutorDelivery
实例传递给RequestQueue
构造函数。你会有另一个looper的开销,但可以避免主线程。 - Stephen James HandI want to add something to Matthew's accepted answer. While RequestFuture
might seem to make a synchronous call from the thread you created it, it does not. Instead, the call is executed on a background thread.
From what I understand after going through the library, requests in the RequestQueue
are dispatched in its start()
method:
public void start() {
....
mCacheDispatcher = new CacheDispatcher(...);
mCacheDispatcher.start();
....
NetworkDispatcher networkDispatcher = new NetworkDispatcher(...);
networkDispatcher.start();
....
}
Now both CacheDispatcher
and NetworkDispatcher
classes extend thread. So effectively a new worker thread is spawned for dequeuing the request queue and the response is returned to the success and error listeners implemented internally by RequestFuture
.
Although your second purpose is attained but you first purpose is not since a new thread is always spawned, no matter from which thread you execute RequestFuture
.
In short, true synchronous request is not possible with default Volley library. Correct me if I am wrong.
我现在使用锁来实现这种效果,我想知道我的方式是否正确。有人想评论吗?
// 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 循环内部。否则,如果线程因某种原因被中断,则无法等待。 - jayeffkay你可以使用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();