Подтвердить что ты не робот

Могу ли я сделать синхронный запрос с волейболом?

Представьте, что я нахожусь в службе, у которой уже есть фоновый поток. Могу ли я сделать запрос, используя залп в том же потоке, чтобы обратные вызовы происходили синхронно?

Для этого есть две причины: - Во-первых, мне не нужна другая нить, и было бы бесполезно создавать ее. - Во-вторых, если я нахожусь в ServiceIntent, выполнение потока завершится до обратного вызова, и поэтому у меня не будет ответа от Volley. Я знаю, что могу создать свой собственный Сервис, у которого есть поток с помощью runloop, которым я могу управлять, но хотелось бы иметь эту функциональность в залпе.

Спасибо!

4b9b3361

Ответ 1

Похоже, это возможно с классом Volley RequestFuture. Например, чтобы создать синхронный запрос GET 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
}

Ответ 2

Примечание. Ответ @Matthews правильный, но если вы находитесь в другом потоке, и вы делаете вызов волейбола, когда у вас нет интернета, ваш обратный вызов будет вызываться в основном потоке, но поток, на котором вы находитесь, будет заблокирован FOREVER. (Поэтому, если этот поток является 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;
    }

Ответ 3

Вероятно, рекомендуется использовать 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

Теперь, хотя решение работает, оно имеет некоторые ограничения. Самое главное, вы не можете вызвать его в главном потоке пользовательского интерфейса. Volley выполняет запросы в фоновом режиме, но по умолчанию Volley использует основной Looper приложения для отправки ответов. Это вызывает взаимоблокировку, поскольку основной поток пользовательского интерфейса ожидает ответа, но Looper ожидает onCreate перед обработкой доставки. Если вы действительно хотите это сделать, вы можете вместо статических вспомогательных методов создать свой собственный RequestQueue передав ему собственный ExecutorDelivery связанный с Handler используя Looper который связан с другим потоком из основного потока пользовательского интерфейса.

Ответ 4

В качестве дополнительного наблюдения как для ответов @Blundells, так и для @Mathews я не уверен, что любой вызов доставляется во что-либо, кроме основного потока Volley.

Источник

Взглянув на RequestQueue реализацию, кажется, что RequestQueue использует NetworkDispatcher для выполнения запроса и ResponseDelivery для доставки результата (ResponseDelivery вводится в NetworkDispatcher). ResponseDelivery, в свою очередь, создается с появлением Handler из основного потока (где-то вокруг строки 112 в реализации RequestQueue).

Где-то около строки 135 в NetworkDispatcher реализация кажется, что и успешные результаты доставляются через те же ResponseDelivery, что и любые ошибки. Еще раз; a ResponseDelivery на основе Handler икры из основного потока.

Обоснование

В прецедентном случае, когда запрос должен быть сделан из IntentService, справедливо предположить, что поток службы должен блокироваться до тех пор, пока у нас не будет ответа от Volley (чтобы гарантировать живую среду выполнения для обработки результата в).

Предлагаемые решения

Один из подходов состоит в том, чтобы переопределить способ по умолчанию, созданный RequestQueue, где вместо этого используется альтернативный конструктор, вводя ResponseDelivery который появляется из текущего потока, а не основного потока. Однако я не исследовал последствия этого.

Ответ 5

Я использую блокировку для достижения этого эффекта, теперь я задаюсь вопросом, правильно ли он мой путь кто-нибудь хочет прокомментировать?

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

Ответ 6

Я хочу добавить что-то к принятому ответом Мэтью. Хотя RequestFuture может показаться синхронным вызовом из потока, который вы создали, он этого не делает. Вместо этого вызов выполняется в фоновом потоке.

Из того, что я понимаю после прохождения библиотеки, запросы в RequestQueue отправляются в методе start():

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

Теперь оба класса CacheDispatcher и NetworkDispatcher расширяют поток. Таким образом, создается новый рабочий поток для отбрасывания очереди запросов, и ответ возвращается получателям успеха и ошибок, реализованным внутри RequestFuture.

Несмотря на то, что ваша вторая цель достигнута, но ваша первая цель заключается не в том, что новый поток всегда порожден, независимо от того, из какого потока вы выполняете RequestFuture.

Короче говоря, истинный синхронный запрос невозможен с помощью библиотеки волейбола по умолчанию. Исправьте меня, если я ошибаюсь.

Ответ 7

Вы можете сделать запрос синхронизации с 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();