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

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

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

감사합니다!



답변

발리의 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
}


답변

참고 @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;
    }


답변

선물을 사용하는 것이 좋을 수도 있지만, 어떤 이유로 든 원하지 않는 경우 동기화 된 차단 요리를 요리하는 대신을 사용해야합니다 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 스레드에서 다른 스레드에 연결된다.


답변

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

소스

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

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

이론적 해석

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

제안 된 솔루션

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


답변

나는 그 효과를 달성하기 위해 자물쇠를 사용한다. 이제 누군가 내 의견이 올바른지 궁금해 하는가?

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


답변

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

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

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

이제 모두 CacheDispatcherNetworkDispatcher 클래스 스레드를 확장합니다. 따라서 효과적으로 요청 큐를 대기시키기 위해 새로운 작업자 스레드가 생성되고 응답은 내부적으로 구현 된 성공 및 오류 리스너로 리턴됩니다.RequestFuture .

두 번째 목적은 달성되었지만 실행하는 스레드에 관계없이 항상 새 스레드가 생성되기 때문에 첫 번째 목적은 아닙니다 RequestFuture .

즉, 기본 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 thread = new Thread(new Runnable() {
                                    @Override
                                    public void run() {

                                        String response = syncCall();

                                    }
                                });
                                thread.start();