Voleybolla senkron istek yapabilir miyim?


133

Zaten bir arka plan iş parçacığı olan bir Hizmette olduğumu hayal edin. Geri aramaların eşzamanlı olarak gerçekleşmesi için aynı iş parçacığında voleybolu kullanarak bir istek yapabilir miyim?

Bunun 2 nedeni var: - Birincisi, başka bir iş parçacığına ihtiyacım yok ve onu yaratmak israf olur. - İkinci olarak, bir ServiceIntent içindeysem, iş parçacığının yürütülmesi geri aramadan önce bitecek ve bu nedenle Volley'den yanıt almayacağım. Kontrol edebileceğim bir runloop içeren bir iş parçacığına sahip kendi Hizmetimi oluşturabileceğimi biliyorum, ancak bu işlevselliğin voleybolda olması arzu edilirdi.

Teşekkür ederim!


5
@ Blundell'in yanı sıra yüksek oy alan (ve çok faydalı) yanıtı okuduğunuzdan emin olun.
Jedidja

Yanıtlar:


183

Volley'in dersiyle mümkün gibi görünüyor RequestFuture. Örneğin, senkronize bir JSON HTTP GET isteği oluşturmak için aşağıdakileri yapabilirsiniz:

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 Güncellendi. Bu yapıcıyı kullanır JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener, ErrorListener errorlistener). RequestFuture<JSONObject>uygular hem Listener<JSONObject>ve ErrorListenerarabirimler, bu nedenle son iki parametre olarak kullanılabilir.
Matt

21
sonsuza kadar engelliyor!
Muhammed Subhi Şeyh Quroush

9
İpucu: İsteği istek kuyruğuna eklemeden ÖNCE future.get () öğesini çağırırsanız sonsuza kadar engelliyor olabilir.
datayeah

3
Bağlantı hatası olabileceğinden sonsuza kadar engellenecek Blundell Cevap
Mina Gabriel

4
Bunu ana başlıkta yapmamanız gerektiğini söylemelisiniz. Bu benim için net değildi. Ana iş parçacığı tarafından engellenirse future.get(), uygulama durur veya ayarlanırsa kesin olarak zaman aşımına uğrar.
r00tandy

125

Not @ Matthews yanıtı doğrudur AMA başka bir iş parçacığında iseniz ve internetiniz olmadığında bir voleybol araması yaparsanız, hata geri aramanız ana iş parçacığında çağrılacaktır, ancak üzerinde bulunduğunuz iş parçacığı sonsuza kadar engellenecektir. (Bu nedenle, bu iş parçacığı bir IntentService ise, ona hiçbir zaman başka bir mesaj gönderemezsiniz ve hizmetiniz temelde ölür).

get()Zaman aşımı olan sürümünü kullanın ve future.get(30, TimeUnit.SECONDS)iş parçacığınızdan çıkmak için hatayı yakalayın.

@Mathews cevabıyla eşleşmek için:

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

Aşağıda bir yöntemle paketledim ve farklı bir istek kullanıyorum:

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

Bu oldukça önemli bir nokta! Bu yanıtın neden daha fazla olumlu oy almadığından emin değilim.
Jedidja

1
Ayrıca, ExecutionException'ı isteğe aktardığınız aynı dinleyiciye iletirseniz, istisnayı iki kez işleyeceğinizdir. Bu istisna, voleybolun sizin için errorListener'a geçeceği istek sırasında bir istisna meydana geldiğinde oluşur.
Stimsoni

@Blundell Cevabınızı anlamıyorum. Dinleyici, UI iş parçacığında çalıştırılırsa, beklemede olan bir arka plan iş parçacığına ve notifyAll () 'ı çağıran UI iş parçacığına sahip olursunuz. Teslimat, gelecekteki get () ile engellendiğiniz aynı iş parçacığı üzerinde yapılırsa bir kilitlenme meydana gelebilir. Yani cevabınız hiçbir anlam ifade etmiyor.
greywolf82

1
@ greywolf82 IntentService, tek bir iş parçacığının iş parçacığı havuzu yürütücüsüdür, bu nedenle IntentService bir döngüde oturduğu için sonsuza kadar engellenecektir
Blundell

@Blundell Anlamıyorum. Bir bildirim çağrılana kadar oturur ve UI iş parçacığından çağrılır. İki farklı iş parçacığına sahip olana kadar çıkmaza
giremiyorum

9

Muhtemelen Vadeli İşlemleri kullanmanız önerilir, ancak herhangi bir nedenle istemiyorsanız, kendi senkronize engelleme şeyinizi pişirmek yerine a java.util.concurrent.CountDownLatch. Böylece bu böyle çalışır ..

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

İnsanlar bunu gerçekten yapmaya çalışıyor gibi göründüklerinden ve bazı sorunlarla karşılaştıklarından, kullanımda bunun "gerçek hayattan" çalışan bir örneğini sunmaya karar verdim. İşte https://github.com/timolehto/SynchronousVolleySample

Şimdi çözüm işe yarasa da bazı sınırlamaları var. En önemlisi, onu ana UI iş parçacığında arayamazsınız. Volley, istekleri arka planda yürütür, ancak Volley varsayılan Looperolarak yanıtları göndermek için uygulamanın ana bölümünü kullanır . Bu, ana UI iş parçacığı yanıtı beklediğinden, ancak teslimatı işlemeden önce bitmesini Looperbeklediğinden kilitlenmeye neden olur onCreate. Eğer gerçekten bunu yapmak istiyorsanız, statik yardımcı yöntemler yerine , ana UI iş parçacığından farklı bir iş parçacığına bağlı olan a kullanarak RequestQueuekendi ExecutorDeliverybağladığınız kendi geçirmenizi başlatabilirsiniz .HandlerLooper


Bu çözüm blokları benim iplik sonsuza yerine countDownLatch ait Thread.sleep değiştirdi ve sorun çözüldü
snersesyan

Bu şekilde başarısız olan bir kodun eksiksiz bir örneğini verebilirseniz, sorunun ne olduğunu bulabiliriz. Geri sayım mandallarıyla birlikte uyumanın ne kadar mantıklı olduğunu anlamıyorum.
Timo

Pekala @VinojVetha Durumu açıklığa kavuşturmak için cevabı biraz güncelledim ve kolayca klonlayıp kodu çalışırken deneyebileceğiniz bir GitHub repo sağladım. Daha fazla sorununuz varsa, lütfen sorununuzu referans olarak gösteren örnek depodan bir çatal sağlayın.
Timo

Bu, şimdiye kadar senkronize istek için harika bir çözümdür.
bikram

2

Her iki @Blundells ve @Mathews cevapları için tamamlayıcı bir gözlem olarak emin değilim herhangi çağrı şey teslim edilir fakat Volley tarafından ana iş parçacığı.

Kaynak

Bir göz olması RequestQueueuygulanmasına görünüyor RequestQueuebir kullanıyor NetworkDispatcheristeği yürütmek ve ResponseDeliverysonuç sunmak için ( ResponseDeliveryenjekte edilir NetworkDispatcher). ResponseDeliveryBir oluşturulan sırayla olan Handlerana iş parçacığı gelen spawn (yere etrafında 112 hat RequestQueueuygulaması).

NetworkDispatcherUygulama sırasında 135. satırda bir yerlerde, başarılı sonuçlar ResponseDeliveryherhangi bir hatayla aynı şekilde teslim ediliyor gibi görünüyor . Tekrar; Bir ResponseDeliverysinyale göre Handlerana iş parçacığı gelen spawn.

gerekçe

Bir istekte bulunulacak kullanım durumu için IntentService, hizmetin iş parçacığının Volley'den bir yanıt alana kadar engellemesi gerektiğini varsaymak (sonucu ele almak için yaşayan bir çalışma süresi kapsamını garanti etmek için).

Önerilen çözümler

Bir yaklaşım, a'nın RequestQueueyaratıldığı varsayılan yolu geçersiz kılmak olabilir, bunun yerine alternatif bir kurucu kullanılır ve ana iş parçacığından ziyade mevcut iş parçacığından ResponseDeliveryortaya çıkan a enjekte edilir . Ancak bunun etkilerini araştırmadım.


1
Özel bir ResponseDelivery uygulamasının uygulanması, sınıftaki finish()yöntemin ve Requestsınıftaki yöntemin RequestQueuepakete özel olması nedeniyle karmaşıktır , yansıma saldırısı kullanmak dışında bunun bir yolu olduğundan emin değilim. Ana (UI) iş parçacığında çalışan herhangi bir şeyi önlemek için yaptığım şey, alternatif bir Looper iş parçacığı kurmak (kullanarak Looper.prepareLooper(); Looper.loop()) ve bu döngüleyici için bir işleyiciye sahip bir kurucuya bir ExecutorDeliveryörnek geçirmekti RequestQueue. Başka bir ilmek yapıcının ek yüküne sahipsiniz ama ana iş parçacığından uzak durun
Stephen James Hand

1

Bu etkiyi elde etmek için bir kilit kullanıyorum, şimdi bunun doğru olup olmadığını merak ediyorum.

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

Bence esasen "futures" u kullandığınızda Volley'in zaten sağladığını uyguluyorsunuz.
spaaarky 21

1
catch (InterruptedException e)While döngüsünün içinde olmasını tavsiye ederim . Aksi takdirde ileti dizisi herhangi bir nedenle kesilirse beklemez
jayeffkay

@jayeffkay Yakalama işlemi sırasında bir ** InterruptedException ** meydana gelirse zaten istisnayı yakalıyorum.
forcewill

1

Matthew'un kabul ettiği cevaba bir şeyler eklemek istiyorum. İken RequestFuture, onu oluşturduğunuz parçacağından senkron arama yapmak için görünebilir, öyle değil. Bunun yerine, çağrı bir arka plan iş parçacığında yürütülür.

Kütüphaneden geçtikten sonra anladığım kadarıyla, içindeki istekler RequestQueuekendi start()yönteminde gönderilir :

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

Şimdi hem CacheDispatcherve NetworkDispatchersınıflar iplik uzanır. Böylece, istek kuyruğunu çözmek için yeni bir çalışan iş parçacığı üretilir ve yanıt, dahili olarak uygulanan başarı ve hata dinleyicilerine döndürülür RequestFuture.

İkinci amacınız elde edilmiş olsa da, ilk amacınız, hangi iş parçacığını çalıştırırsanız çalıştırın, her zaman yeni bir iş parçacığı ortaya çıktığı için değildir RequestFuture.

Kısacası, varsayılan Volley kitaplığı ile gerçek zaman uyumlu istek mümkün değildir. Eğer Yanlışsam beni düzelt.


0

Volley ile senkronizasyon talebinde bulunabilirsiniz ancak yöntemi farklı iş parçacığında çağırmalısınız, aksi takdirde çalışan uygulamanız bloke olur, şöyle olmalıdır:

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;


}

bundan sonra yöntemi thread içinde çağırabilirsiniz:

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

                                        String response = syncCall();

                                    }
                                });
                                thread.start();
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.