首页 文章

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

提问于
浏览
123

想象一下,我在一个已经有后台线程的服务中 . 我可以在同一个线程中使用齐射进行请求,以便回调同步发生吗?

这有两个原因: - 首先,我不需要另一个线程,创建它是一种浪费 . - 其次,如果我在ServiceIntent中,线程的执行将在回调之前完成,因此我将没有来自Volley的响应 . 我知道我可以创建自己的服务,其中包含一些我可以控制的runloop的线程,但是需要在volley中使用此功能 .

谢谢!

6 回答

  • 175

    看起来有可能与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
    }
    
  • 111

    注意@Matthews答案是正确的但是如果你在另一个线程并且在没有互联网时你进行了一次凌空调用, your error callback will be called on the main thread, but the thread you are on will be blocked FOREVER. (因此如果该线程是一个IntentService,你将永远无法向它发送另一条消息,你的服务将会基本上死了) .

    使用具有超时 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;
        }
    
  • 6

    可能建议使用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...
    }
    

    由于人们似乎真的试图这样做并遇到一些麻烦我决定我实际上提供了一个正在使用的"real life"工作样本 . 这是https://github.com/timolehto/SynchronousVolleySample

    现在即使解决方案有效,它也有一些局限性 . 最重要的是,您无法在主UI线程上调用它 . Volley确实在后台执行请求,但默认情况下,Volley使用应用程序的主 Looper 来发送响应 . 当主UI线程正在等待响应时,这会导致死锁,但 Looper 在处理传递之前正在等待 onCreate 完成 . 如果你真的想要这样做,你可以,而不是静态帮助器方法,实例化你自己的 RequestQueue 传递它自己的 ExecutorDelivery 绑定到 Handler 使用 Looper 绑定到主UI线程的不同线程 .

  • 2

    作为对@Blundells和@Mathews答案的补充观察,我不确定除了Volley的主要线程之外还有任何电话 .

    The Source

    看看RequestQueue implementation似乎 RequestQueue 正在使用 NetworkDispatcher 执行请求而 ResponseDelivery 正在传递结果( ResponseDelivery 被注入 NetworkDispatcher ) . ResponseDelivery 依次使用主线程中的 Handler spawn创建(在 RequestQueue 实现中的第112行附近) .

    NetworkDispatcher implementation中关于第135行的某处,似乎也成功的结果通过与任何错误相同的 ResponseDelivery 传递 . 再次; a ResponseDelivery 基于主线程的 Handler spawn .

    Rationale

    对于从 IntentService 发出请求的用例,可以假设服务的线程应该阻塞,直到我们得到Volley的响应(以保证生存的运行时范围来处理结果) .

    Suggested solutions

    一种方法是覆盖默认方式RequestQueue is created,其中使用替代构造函数,注入 ResponseDelivery ,它从当前线程而不是主线程生成 . 但是,我没有调查过这个问题 .

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

    我想在Matthew接受的答案中添加一些内容 . 虽然 RequestFuture 似乎可以从您创建它的线程进行同步调用,但事实并非如此 . 而是在后台线程上执行调用 .

    根据我在浏览库后的理解, RequestQueue 中的请求将以 start() 方式调度:

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

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

    虽然你的第二个目的已经实现,但你的第一个目的不是因为总是产生一个新的线程,无论你从哪个线程执行 RequestFuture .

    简而言之, true synchronous request is not possible with default Volley library. 如果我错了,请纠正我 .

相关问题