Tôi có thể thực hiện một yêu cầu đồng bộ với bóng chuyền không?


133

Hãy tưởng tượng tôi đang ở trong một Dịch vụ đã có chủ đề nền. Tôi có thể thực hiện một yêu cầu bằng cách sử dụng bóng chuyền trong cùng một chuỗi để cuộc gọi lại diễn ra đồng bộ không?

Có 2 lý do cho việc này: - Đầu tiên, tôi không cần một chủ đề khác và sẽ thật lãng phí khi tạo ra nó. - Thứ hai, nếu tôi đang ở trong ServiceIntent, việc thực hiện chuỗi sẽ kết thúc trước khi gọi lại và do đó tôi sẽ không có phản hồi từ Volley. Tôi biết tôi có thể tạo Dịch vụ của riêng mình có một số luồng với runloop tôi có thể kiểm soát, nhưng sẽ rất mong muốn có chức năng này trong bóng chuyền.

Cảm ơn bạn!


5
Hãy chắc chắn đọc phản hồi của @ Blundell cũng như câu trả lời được đánh giá cao (và rất hữu ích).
Jedidja

Câu trả lời:


183

Có vẻ như điều đó là có thể với RequestFuturelớp học của Volley . Ví dụ: để tạo một yêu cầu JSON HTTP GET đồng bộ, bạn có thể làm như sau:

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 Cập nhật. Điều này sử dụng các nhà JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener, ErrorListener errorlistener)xây dựng. RequestFuture<JSONObject>thực hiện cả giao diện Listener<JSONObject>ErrorListenergiao diện, vì vậy nó có thể được sử dụng làm hai tham số cuối cùng.
Matt

21
nó đang chặn mãi mãi!
Mohammed Subhi Sheikh Quroush

9
Gợi ý: nó có thể bị chặn mãi mãi nếu bạn gọi tương lai.get () TRƯỚC KHI bạn thêm yêu cầu vào hàng yêu cầu.
datayeah

3
nó sẽ bị chặn
Mina Gabriel

4
Bạn nên nói rằng bạn không nên làm điều này trên chủ đề chính. Điều đó không rõ ràng với tôi. Nguyên nhân là nếu luồng chính bị chặn future.get()thì ứng dụng sẽ bị đình trệ hoặc hết thời gian chờ nếu chắc chắn được đặt như vậy.
r00tandy

125

Lưu ý @Matthews câu trả lời là NHƯNG nếu bạn đang ở một chủ đề khác và bạn thực hiện một cuộc gọi bóng chuyền khi bạn không có internet, cuộc gọi lại lỗi của bạn sẽ được gọi trên chủ đề chính, nhưng chủ đề bạn đang bật sẽ bị chặn TUYỆT VỜI. (Do đó, nếu chủ đề đó là một IntentService, bạn sẽ không bao giờ có thể gửi một tin nhắn khác đến nó và dịch vụ của bạn về cơ bản sẽ bị chết).

Sử dụng phiên bản get()có thời gian chờfuture.get(30, TimeUnit.SECONDS) và bắt lỗi để thoát chuỗi của bạn.

Để khớp với câu trả lời @Mathews:

        try {
            return future.get(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            // exception handling
        } catch (ExecutionException e) {
            // exception handling
        } catch (TimeoutException e) {
            // exception handling
        }

Dưới đây tôi gói nó trong một phương thức và sử dụng một yêu cầu khác:

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

Đó là một điểm khá quan trọng! Không chắc chắn tại sao câu trả lời này đã không nhận được nhiều sự ủng hộ hơn.
Jedidja

1
Cũng đáng chú ý là nếu bạn chuyển ExecutException cho cùng một người nghe mà bạn đã gửi vào yêu cầu, bạn sẽ xử lý ngoại lệ hai lần. Ngoại lệ này xảy ra khi một ngoại lệ đã xảy ra trong yêu cầu bóng chuyền sẽ chuyển qua errorListener cho bạn.
Stimsoni

@Blundell Tôi không hiểu câu trả lời của bạn. Nếu trình nghe được thực thi trên luồng UI, bạn có một luồng nền đang chờ và luồng UI gọi notifyAll () vì vậy nó ổn. Bế tắc có thể xảy ra nếu việc phân phối được thực hiện trên cùng một chuỗi mà bạn bị chặn với get () trong tương lai. Vì vậy, phản ứng của bạn dường như không có ý nghĩa.
greywolf82

1
@ greywolf82 IntentServicelà người thực thi nhóm luồng của một luồng, do đó IntentService sẽ bị chặn vĩnh viễn, nó nằm trong một vòng lặp
Blundell

@Blundell Tôi không hiểu. Nó nằm cho đến khi một thông báo sẽ được gọi và nó sẽ được gọi từ luồng UI. Cho đến khi bạn có hai luồng khác nhau, tôi không thể thấy bế tắc
greywolf82

9

Có thể nên sử dụng Tương lai, nhưng nếu vì bất kỳ lý do gì bạn không muốn, thay vì nấu thứ chặn đồng bộ của riêng bạn, bạn nên sử dụng a java.util.concurrent.CountDownLatch. Vì vậy, nó sẽ làm việc như thế này ..

//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...
}

Vì mọi người dường như thực sự cố gắng làm điều này và gặp một số rắc rối, tôi quyết định tôi thực sự cung cấp một mẫu làm việc "thực tế" trong việc này đang được sử dụng. Đây là https://github.com/timolehto/SynyncousVolleySample

Bây giờ mặc dù giải pháp hoạt động, nó có một số hạn chế. Quan trọng nhất, bạn không thể gọi nó trên chuỗi UI chính. Volley thực hiện các yêu cầu trên nền, nhưng theo mặc định, Volley sử dụng chính Looperứng dụng để gửi phản hồi. Điều này gây ra bế tắc vì luồng UI chính đang chờ phản hồi, nhưng Looperchờ đợi onCreateđể kết thúc trước khi xử lý việc phân phối. Nếu bạn thực sự thực sự muốn làm điều này bạn có thể, thay vì các phương pháp tĩnh helper, nhanh chóng của riêng bạn RequestQueueđi qua nó của riêng bạn ExecutorDeliverygắn với một Handlersử dụng một Loopertrong đó được gắn với chủ đề khác nhau từ thread UI chính.


Giải pháp này chặn chủ đề của tôi mãi mãi, thay đổi Thread.s ngủ thay vì CountDownLatch và vấn đề đã được giải quyết
snersesyan

Nếu bạn có thể cung cấp một mẫu hoàn chỉnh của mã bị lỗi theo cách này, có lẽ chúng ta có thể tìm ra vấn đề là gì. Tôi không thấy việc ngủ kết hợp với chốt đếm ngược có ý nghĩa như thế nào.
Timo

Được rồi @VinojVetha Tôi đã cập nhật câu trả lời một chút để làm rõ tình huống và cung cấp một repo GitHub mà bạn có thể dễ dàng sao chép và thử mã trong thực tế. Nếu bạn có nhiều vấn đề hơn, vui lòng cung cấp một nhánh của repo mẫu thể hiện vấn đề của bạn như một tài liệu tham khảo.
Timo

Đây là một giải pháp tuyệt vời cho yêu cầu đồng bộ cho đến nay.
bikram

2

Là một quan sát bổ sung cho cả hai câu trả lời @Blundells và @Mathews, tôi không chắc có bất kỳ cuộc gọi nào được gửi đến bất cứ điều gì ngoài chủ đề chính của Volley.

Nguồn

Nhìn vào việc RequestQueuethực hiện , có vẻ như họ RequestQueueđang sử dụng a NetworkDispatcherđể thực hiện yêu cầu và a ResponseDeliveryđể đưa ra kết quả ( ResponseDeliveryđược đưa vào NetworkDispatcher). Lần ResponseDeliverylượt được tạo với một Handlersinh sản từ luồng chính (đâu đó xung quanh dòng 112 trongRequestQueue thực hiện).

Ở đâu đó về dòng 135 trong quá trình NetworkDispatcherthực hiện , có vẻ như các kết quả thành công cũng được phân phối giống ResponseDeliverynhư bất kỳ lỗi nào. Lần nữa; một ResponseDeliverydựa trên mộtHandler sinh sản từ chủ đề chính.

Cơ sở lý luận

Đối với trường hợp sử dụng trong đó yêu cầu được thực hiện từ một IntentService công bằng, giả sử rằng luồng của dịch vụ sẽ chặn cho đến khi chúng tôi có phản hồi từ Volley (để đảm bảo phạm vi thời gian chạy trực tiếp để xử lý kết quả).

Đề xuất các giải pháp

Một cách tiếp cận sẽ là ghi đè cách mặc định a RequestQueueđược tạo , trong đó một hàm tạo thay thế được sử dụng thay thế, tiêm một ResponseDeliverycái sinh ra từ hiện tại luồng thay vì luồng chính. Tôi đã không điều tra những tác động của điều này, tuy nhiên.


1
Việc triển khai một triển khai Phản hồi giao hàng tùy chỉnh rất phức tạp bởi thực tế là finish()phương thức trong Requestlớp và RequestQueuelớp là gói riêng tư, ngoài việc sử dụng hack phản chiếu tôi không chắc có cách nào khác. Điều cuối cùng tôi đã làm để ngăn chặn mọi thứ chạy trên luồng chính (UI) là thiết lập một luồng Looper thay thế (sử dụng Looper.prepareLooper(); Looper.loop()) và chuyển một ExecutorDeliverycá thể cho hàm RequestQueuetạo với trình xử lý cho trình xử lý đó. Bạn có chi phí hoạt động của một kẻ lừa đảo khác nhưng tránh xa chủ đề chính
Stephen James Hand

1

Tôi sử dụng một khóa để đạt được hiệu ứng đó bây giờ tôi tự hỏi nếu nó đúng theo cách của tôi bất cứ ai muốn bình luận?

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

Tôi nghĩ rằng về cơ bản bạn đang thực hiện những gì Volley đã cung cấp khi bạn sử dụng "tương lai".
spaaarky21

1
Tôi khuyên bạn nên có catch (InterruptedException e)vòng lặp while. Nếu không, chủ đề sẽ không chờ nếu bị gián đoạn vì một số lý do
jayeffkay

@jayeffkay Tôi đã bắt được ngoại lệ nếu một ** Interrupttedception ** xảy ra trong vòng lặp while, lệnh bắt xử lý nó.
lực lượng

1

Tôi muốn thêm một cái gì đó vào câu trả lời được chấp nhận của Matthew. Mặc dù RequestFuturecó thể thực hiện một cuộc gọi đồng bộ từ chuỗi mà bạn đã tạo nhưng nó không thực hiện. Thay vào đó, cuộc gọi được thực hiện trên một luồng nền.

Từ những gì tôi hiểu sau khi đi qua thư viện, các yêu cầu trong RequestQueueđược gửi theo start()phương thức của nó :

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

Bây giờ cả hai CacheDispatcherNetworkDispatchercác lớp mở rộng chủ đề. Vì vậy, một cách hiệu quả một luồng công nhân mới được sinh ra để loại bỏ hàng đợi yêu cầu và phản hồi được trả về cho người nghe thành công và lỗi được thực hiện trong nội bộ RequestFuture.

Mặc dù mục đích thứ hai của bạn đã đạt được nhưng mục đích đầu tiên của bạn không phải là vì một luồng mới luôn được sinh ra, bất kể bạn thực hiện từ luồng nào RequestFuture .

Nói tóm lại, yêu cầu đồng bộ thực sự là không thể với thư viện Volley mặc định. Đúng nếu tôi đã sai lầm.


0

Bạn có thể thực hiện yêu cầu đồng bộ hóa với bóng chuyền nhưng bạn phải gọi phương thức trong luồng khác hoặc ứng dụng đang chạy của bạn sẽ chặn, nó sẽ như thế này:

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;


}

sau đó bạn có thể gọi phương thức trong luồng:

 Thread thread = new Thread(new Runnable() {
                                    @Override
                                    public void run() {

                                        String response = syncCall();

                                    }
                                });
                                thread.start();
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.