Чи можу я зробити синхронний запит із залпом?


133

Уявіть, що я в службі, яка вже має фонову нитку. Чи можу я зробити запит, використовуючи залп у тій самій нитці, щоб зворотні виклики відбувалися синхронно?

Для цього є дві причини: - По-перше, мені не потрібна ще одна нитка, і було б марно її створити. - По-друге, якщо я перебуваю в ServiceIntent, виконання потоку закінчиться до зворотного виклику, і на це я не матиму відповіді від Воллі. Я знаю, що можу створити власну службу, яка має якусь нитку із запуском, яку я можу контролювати, але хочеться мати цю функціональність у залпі.

Дякую!


5
Не забудьте прочитати відповідь @ Blundell, а також висококваліфіковану (і дуже корисну) відповідь.
Джедіджа

Відповіді:


183

Схоже, це можливо з 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
}

5
@tasomaniac Оновлено. Для цього використовується JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener, ErrorListener errorlistener)конструктор. RequestFuture<JSONObject>реалізує як інтерфейси, так Listener<JSONObject>і ErrorListenerінтерфейси, тому його можна використовувати як два останніх параметра.
Метт

21
це блокує назавжди!
Мухаммед Субхі Шейх Куруш

9
Підказка: воно може блокуватися назавжди, якщо ви зателефонуєте на future.get () ДО того, як ви додасте запит до черги запитів.
datay yes

3
він буде заблокований назавжди, оскільки у вас може виникнути помилка підключення, прочитавши відповідь Blundell
Міна Габріель,

4
Слід сказати, що ви не повинні робити цього на основній темі. Мені це було не ясно. Причиною, якщо основний потік заблоковано, future.get()тоді додаток зупиниться або запуститься в тайм-аут, якщо встановлено так.
r00tandy

125

Зауважте, що відповідь @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;
    }

Це досить важливий момент! Не впевнений, чому ця відповідь не отримала більше відгуків.
Джедіджа

1
Також варто зазначити, що якщо ви передасте ExecutionException тому ж слухачеві, який ви передали в запит, ви оброблятимете виняток двічі. Цей виняток трапляється, коли під час запиту стався виняток, через який залп перейде до вас.
Стімсоні

@Blundell Я не розумію вашої відповіді. Якщо слухач виконується на потоці користувальницького інтерфейсу, у вас є фонова нитка у режимі очікування та потік інтерфейсу, який викликає notifyAll (), тож це нормально. Тупик може трапитися, якщо доставку буде виконано в тій самій потоці, у якій ви заблоковані з майбутнім get (). Тож ваша відповідь здається без сенсу.
greywolf82

1
@ greywolf82 IntentService- виконавець пулу потоків однієї нитки, тому IntentService буде заблоковано, тому він сидить у циклі
Blundell

@Blundell я не розумію. Він сидить, поки не буде викликано повідомлення, і воно буде викликано з потоку інтерфейсу користувача. Поки у вас є дві різні
теми,

9

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

Зараз, хоча рішення працює, воно має деякі обмеження. Найголовніше, що ви не можете його викликати в основному потоці інтерфейсу користувача. Воллі виконує запити на задньому плані, але за замовчуванням Воллі використовує основну Looperпрограму для відправки відповідей. Це спричиняє глухий кут, оскільки основний потік інтерфейсу очікує відповіді, але Looperочікування onCreateзавершиться перед обробкою доставки. Якщо ви дійсно хочете цього зробити, ви могли б замість статичних методів помічників присвоїти власну RequestQueueпередачу її своїм власним ExecutorDeliveryприв’язаним до Handlerвикористання, Looperякий прив’язаний до іншого потоку від основного потоку інтерфейсу.


Це рішення назавжди блокує мою нитку, змінив Thread.sleep замість countDownLatch і вирішила проблему
snersesyan

Якщо ви можете надати повний зразок невдалого коду таким чином, можливо, ми зможемо з'ясувати, у чому проблема. Я не бачу, як має сенс спати разом із засувками зворотного відліку.
Тимо

Добре @VinojVetha Я трохи оновив відповідь, щоб прояснити ситуацію, і надав репо-релігію GitHub, яку можна легко клонувати та спробувати код у дії. Якщо у вас виникло більше питань, будь ласка, надайте вилку зразка репо, демонструючи вашу проблему як орієнтир.
Тимо

Це чудове рішення для синхронного запиту на даний момент.
бікрам

2

Як доповнення до відповідей @Blundells і @Mathews, я не впевнений, що Воллі передає будь-який дзвінок, окрім головної теми.

Джерело

Подивившись на RequestQueueреалізацію, здається, що RequestQueueвикористовується a NetworkDispatcherдля виконання запиту та a ResponseDeliveryдля доставки результату ( ResponseDeliveryвводиться в NetworkDispatcher). У ResponseDeliveryсвою чергу, створюється з Handlerнерестом з головної нитки (десь біля рядка 112 в RequestQueueреалізації).

Десь близько 135-го рядка в NetworkDispatcherреалізації, схоже, що також успішні результати надаються через ті ж самі ResponseDeliveryпомилки. Знову ж таки; на ResponseDeliveryоснові Handlerнересту з основної нитки.

Обґрунтування

У випадку використання, коли запит повинен бути зроблений від IntentServiceсправедливого, слід припустити, що потік служби повинен блокуватися, поки ми не отримаємо відповідь від Воллі (щоб гарантувати живу область виконання часу для обробки результату в).

Пропоновані рішення

Один з підходів будуть перевизначити шлях за замовчуванням RequestQueueстворюється , коли альтернативний конструктор використовується замість інжекції , ResponseDeliveryякий розмножується від поточної нитки , а не в головному потоці. Я, однак, не досліджував наслідків цього.


1
Реалізація користувацької реалізації ResponseDelivery ускладнюється тим, що finish()метод у Requestкласі та RequestQueueкласі є пакетом приватним, окрім використання рефлекторного злому, я не впевнений, що існує спосіб. Що я в кінцевому підсумку робив, щоб не допустити нічого, що працює на головному (UI) потоці, - це встановлення альтернативної нитки Looper (використання Looper.prepareLooper(); Looper.loop()) та передача ExecutorDeliveryекземпляра RequestQueueконструктору за допомогою обробника для цього циклу. У вас є накладні витрати ще одного петляра, але тримайтеся осторонь головної нитки
Стівен Джеймс Рук

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

Я думаю, ви по суті реалізуєте те, що вже пропонує Воллі, коли використовуєте "ф'ючерси".
spaaarky21

1
Я б порекомендував мати catch (InterruptedException e)внутрішню петлю. Інакше нитка не буде чекати, якщо її
перервуть

@jayeffkay Я вже переживаю виняток, якщо ** переривається ексцепція ** виникає в той час, як цикл улов обробляє його.
forcewill

1

Я хочу додати щось до прийнятої відповіді Метью. Хоча RequestFutureможе здатися, що ви робите синхронний дзвінок із створеного ним потоку, це не так. Натомість виклик виконується на фоновому потоці.

З того, що я розумію, переходячи через бібліотеку, запити в the RequestQueueвідправляються у своєму start()методі:

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

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

Хоча ваша друга мета досягнута, але ваша перша мета - це не тому, що нова нитка завжди породжується, незалежно від того, з якої нитки ви виконуєте RequestFuture.

Коротше кажучи, справжній синхронний запит неможливий із бібліотекою Volley за замовчуванням. Виправте мене, якщо я помиляюся.


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();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.