발리와 동기식 요청을 할 수 있습니까?


133

이미 백그라운드 스레드가있는 서비스에 있다고 가정하십시오. 콜백이 동 기적으로 발생하도록 동일한 스레드에서 발리를 사용하여 요청을 할 수 있습니까?

여기에는 두 가지 이유가 있습니다.-먼저 다른 스레드가 필요하지 않으며 스레드를 생성하는 데 낭비가됩니다. -둘째, ServiceIntent에 있으면 콜백 전에 스레드 실행이 완료되므로 Volley의 응답이 없습니다. 내가 제어 할 수있는 runloop가있는 스레드가있는 자체 서비스를 만들 수 있다는 것을 알고 있지만이 기능을 발리에서 사용하는 것이 바람직합니다.

감사합니다!


5
@Blundell의 답변과 고도로 찬성 (그리고 매우 유용한) 답변을 읽으십시오.
Jedidja

답변:


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
Matt

21
그것은 영원히 차단됩니다!
Mohammed Subhi 셰이크 Quroush

9
힌트 : 요청을 요청 대기열에 추가하기 전에 future.get ()을 호출하면 영원히 차단 될 수 있습니다.
datayeah

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

그것은 매우 중요한 포인트입니다! 왜이 답변이 더 많은지지를 얻지 못한 지 확실하지 않습니다.
Jedidja

1
또한 요청에 전달한 동일한 리스너에 ExecutionException을 전달하면 예외를 두 번 처리하게됩니다. 발리가 요청을 통해 errorListener로 전달되는 동안 예외가 발생하면이 예외가 발생합니다.
Stimsoni

@Blundell 답장을 이해하지 못합니다. 리스너가 UI 스레드에서 실행되면 대기중인 백그라운드 스레드와 notifyAll ()을 호출하는 UI 스레드가 있으므로 괜찮습니다. 향후 get ()로 차단 된 동일한 스레드에서 전달이 완료되면 교착 상태가 발생할 수 있습니다. 그래서 당신의 응답은 의미가없는 것 같습니다.
greywolf82

1
@ greywolf82 IntentService는 단일 스레드의 스레드 풀 실행기이므로 IntentService가 루프에있을 때 영원히 차단됩니다.
Blundell

@Blundell 이해가되지 않습니다. 통지가 호출되고 UI 스레드에서 호출 될 때까지 앉아 있습니다. 두 개의 다른 실을 가질 때까지 교착 상태를 볼 수 없습니다
greywolf82

9

선물을 사용하는 것이 좋을 수도 있지만, 어떤 이유로 든 원하지 않는 경우 동기화 된 차단 요리를 요리하는 대신을 사용해야합니다 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 스레드에서 호출 할 수 없다는 것입니다. 발리는 백그라운드에서 요청을 실행하지만 기본적으로 발리는Looper 응용 프로그램 을 하여 응답을 발송합니다. 기본 UI 스레드가 응답을 대기하고 있지만 전달을 처리하기 전에 완료 Looper대기 중이므로 교착 상태가 발생합니다 onCreate. 당신이 정말 정말 대신 정적 도우미 방법이 당신이 수를하고 싶은 경우에, 당신의 자신의 인스턴스를 RequestQueue통과 당신의 자신 ExecutorDelivery에 연결을 Handler사용하여 Looper메인 UI 스레드에서 다른 스레드에 연결된다.


이 솔루션은 내 스레드를 영원히 차단하고 countDownLatch 대신 Thread.sleep을 변경했으며 문제가 해결되었습니다.
snersesyan

이 방법으로 실패한 코드의 완전한 샘플을 제공 할 수 있다면 문제가 무엇인지 알아낼 수 있습니다. 카운트 다운 래치와 함께 수면이 어떻게 의미가 있는지 알 수 없습니다.
Timo

좋아요 @VinojVetha 상황을 명확히하기 위해 답변을 약간 업데이트하고 코드를 쉽게 복제하고 시도 할 수있는 GitHub 저장소를 제공했습니다. 더 많은 문제가있는 경우 참조로 문제를 보여주는 샘플 저장소 포크를 제공하십시오.
Timo

이것은 지금까지 동기 요청에 대한 훌륭한 솔루션입니다.
bikram

2

@Blundells와 @Mathews 답변 모두에 대한 보완적인 관찰로, 어떤 전화도 어떤 것으로 전달 되는지 확실 하지 않습니다 . 하지만, 발리하여 메인 스레드.

소스

상기보고 갖는 RequestQueue구현 가 보이는 RequestQueueA를 사용 NetworkDispatcher하여 요청을 실행하고이 ResponseDelivery결과를 전달합니다 (는 ResponseDelivery주입된다 NetworkDispatcher). 이 ResponseDelivery차례로 만들어집니다Handler 메인 쓰레드로부터 스폰 (곳 주위에서 112 라인RequestQueue 구현).

NetworkDispatcher구현의 135 줄 어딘가에서 성공적인 결과가 ResponseDelivery모든 오류 와 동일한 방식 으로 전달되는 것처럼 보입니다 . 다시; ㅏResponseDeliveryA의 기반Handler 메인 스레드에서 산란.

이론적 해석

요청이있는 유스 케이스의 경우 IntentService Volley의 응답이있을 때까지 서비스 스레드가 차단되어야한다고 가정하는 것이 좋습니다 (결과를 처리 할 수있는 살아있는 런타임 범위를 보장하기 위해).

제안 된 솔루션

한 가지 방법은 a RequestQueue가 생성 되는 기본 방식을 재정의하는 것입니다 . 여기서 대체 생성자가 대신 사용 되며 기본 스레드가 아닌 현재 스레드 ResponseDelivery에서 생성되는 것을 생성합니다 . 그러나 이것의 의미는 조사하지 않았습니다.


1
사용자 정의 ResponseDelivery 구현을 구현하는 것은 리플렉션 해킹을 사용하는 것 외에 클래스 및 클래스 의 finish()메소드 가 패키지 전용 이라는 사실로 인해 복잡 합니다. 메인 (UI) 스레드에서 실행중인 것을 방지하기 위해 끝내는 것은 대체 루퍼 스레드를 사용 하고 (을 사용하여 ) 해당 루퍼에 대한 핸들러 를 사용하여 생성자에 인스턴스를 전달하는 것이 었습니다 . 다른 루퍼의 오버 헤드가 있지만 메인 스레드 RequestRequestQueueLooper.prepareLooper(); Looper.loop()ExecutorDeliveryRequestQueue
Stephen James Hand

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

"미래"를 사용할 때 Volley가 이미 제공 한 것을 본질적으로 구현하고 있다고 생각합니다.
spaaarky21

1
catch (InterruptedException e)while 루프 를 사용하는 것이 좋습니다 . 그렇지 않으면 어떤 이유로 중단되면 스레드가 기다리지 못합니다
jayeffkay

@jayeffkay while 루프에서 ** InterruptedException **이 발생하면 캐치에서 처리하는 경우 이미 예외를 포착하고 있습니다.
forcewill

1

Matthew의 대답에 무언가를 추가하고 싶습니다. 동안RequestFuture 당신이 그것을 만든 스레드에서 동기 호출을 만들 보일 수도, 그렇지 않습니다. 대신, 백그라운드 스레드에서 호출이 실행됩니다.

라이브러리를 거친 후에 내가 이해 한 것으로부터의 요청 RequestQueuestart()메소드 로 전달됩니다 .

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

이제 모두 CacheDispatcherNetworkDispatcher 클래스 스레드를 확장합니다. 따라서 효과적으로 요청 큐를 대기시키기 위해 새로운 작업자 스레드가 생성되고 응답은 내부적으로 구현 된 성공 및 오류 리스너로 리턴됩니다.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 thread = new Thread(new Runnable() {
                                    @Override
                                    public void run() {

                                        String response = syncCall();

                                    }
                                });
                                thread.start();
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.