Autore Topic: AsyncTask & server remoto  (Letto 888 volte)

Offline wlf

  • Utente normale
  • ***
  • Post: 315
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Xperia
AsyncTask & server remoto
« il: 12 Settembre 2014, 10:35:28 CEST »
0
Salve,
avrei la necessità di realizzare un App che funga da display di un dato presente su un server remoto che posso contattare tramite http POST. Il dato non è statico, quindi dopo una prima connessione dovrei continuare ogni tot (pochi secondi) a contattare lo stesso server per richiedere nuovamente una response, finché non mi voglio disconnettere e terminare di visualizzare tale dato.

Praticamente ho un server remoto che posso chiamare mandando 3 tipi di post; CONNECT, POLL, DISCONNECT. Ho quindi realizzato un AsyncTask (OnlineTask) che prende come parametro il tipo di POST e riesco tranquillamente a leggermi la Response ed ottenere il dato richiesto. Il problema è realizzare un ciclo che lanci l'OnlineTask ne attenda la fine e lanci successivamente lo stesso task per il POLL e poi per la DISCONNECT quando voglio terminare il tutto. :(
Se metto in sequenza i 3 lanci ho un errore di questo tipo:

Codice: [Seleziona]
09-12 10:13:57.089: E/AndroidRuntime(2516): java.lang.IllegalStateException: Cannot execute task: the task is already running.

Ho quindi pensato di realizzare un altro AsyncTask (ComunicaTask) per lanciare il precedente:

Codice: [Seleziona]
        private class ComunicaTask extends AsyncTask<Void, Void, Boolean> {

                @Override
                protected void onPreExecute() {
                        Log.d("ComunicaTask", "Entrata ComunicaTask");
                        isRunning = true;
                }

                @Override
                protected Boolean doInBackground(Void... params) {
                        OnlineTask task = new OnlineTask();
                        task.execute("Connect");
                        return true;
                }
               
                @Override
                protected void onPostExecute(Boolean result) {
                        if (result) {
                                OnlineTask task = new OnlineTask();
                                try {
                                        while (isRunning) {
                                                try {
                                                        Thread.sleep(20000);
                                                } catch (InterruptedException e) {
                                    Log.d("ComunicaTask", "Sleep exception " + e);
                                }
                            Log.d("ComunicaTask", "Ciclo while");
                                                Log.d("ComunicaTask", "Ciclo while poll");
                                                task.execute("Poll");                                                               
                                        }                                       
                                } catch (Exception e) {
                                        Log.d("ComunicaTask", "Try exception " + e);
                                } finally {
                                        Log.d("ComunicaTask", "Uscita ComunicaTask");
                                        task.execute("Disconnect");
                                }                                                               
                        }
                }       
        }

Inoltre mettendo il ciclo "While" ho la UI che diventa nera e non risponde più! :(

Sapreste dirmi come posso fare a lanciare degli AsyncTask attendendo la risposta degli stessi? Idee in merito?
Grazie.

Offline arlabs

  • Utente normale
  • ***
  • Post: 430
  • Respect: +49
    • Mostra profilo
  • Dispositivo Android:
    GalaxyS6, Nexus5
  • Play Store ID:
    AR Labs
  • Sistema operativo:
    Windows 10
Re:AsyncTask & server remoto
« Risposta #1 il: 12 Settembre 2014, 14:46:39 CEST »
0
Solo il codice che sta in "doInBackground" viene effettivamente eseguito in un thread a parte.

la OnPre e OnPost vengono eseguite nel main thread, quello che gestisce anche l'interfaccia utente, che non dovrebbe mai essere impegnato pesantemente in cicli.

In questo caso nella OnPost tu hai, non solo un ciclo, ma addirittura una Sleep all'interno del ciclo. Cioé mandi in "sleep" il thread principale e ciò ti blocca l'interfaccia.

Anche spostare quel ciclo nella doInBackground" non è la soluzione.
Per 2 emotivi: Primo perché un AsynchTask deve essere lanciato dal main thread.
Secondo perché l'AsynchTask non è fatto per operare in cicli continui, ma per brevi operazioni (diciamo da 100ms a qualche sec.). E' giusto che tu lo usi per interrogare il server con l'OnlineTask.
Non è giusto che lo usi per iterare l'interrogazione.

Da Honeycomb tutti gli AynchTask lanciati con "execute" girano su un unico thread. Quindi usare l'AsynchTask per operazioni molto lunghe non permetterebbe a nessun altro AsynchTask di girare.

La soluzione più semplice e di lanciare il primo OnlineTask e "pianificare" un successivo lancio ogni volta che questo termina con un PostDelayed.

Ciao.

Offline wlf

  • Utente normale
  • ***
  • Post: 315
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Xperia
Re:AsyncTask & server remoto
« Risposta #2 il: 12 Settembre 2014, 16:50:09 CEST »
0
Solo il codice che sta in "doInBackground" viene effettivamente eseguito in un thread a parte.

la OnPre e OnPost vengono eseguite nel main thread, quello che gestisce anche l'interfaccia utente, che non dovrebbe mai essere impegnato pesantemente in cicli.

In questo caso nella OnPost tu hai, non solo un ciclo, ma addirittura una Sleep all'interno del ciclo. Cioé mandi in "sleep" il thread principale e ciò ti blocca l'interfaccia.

Ineccepibile, prima avevo messo tutto nella doInBackground e non funzionava comunque ... allora ho spostato tutto nella OnPost ...
Ti confesso cho ho fatto anche tutta una serie di tentativi infruttuosi con la task.execute("Connect").get(), con task.isCancelled(), leggendo lo stato con task.getStatus(), etc ...

Citazione
Anche spostare quel ciclo nella doInBackground" non è la soluzione.
Per 2 emotivi: Primo perché un AsynchTask deve essere lanciato dal main thread.

Infatti lo spostamento nella OnPost era appunto perché nella doInBackground non partiva appunto la OnlineTask! ;)

Citazione
Secondo perché l'AsynchTask non è fatto per operare in cicli continui, ma per brevi operazioni (diciamo da 100ms a qualche sec.). E' giusto che tu lo usi per interrogare il server con l'OnlineTask.
Non è giusto che lo usi per iterare l'interrogazione.

Da Honeycomb tutti gli AynchTask lanciati con "execute" girano su un unico thread. Quindi usare l'AsynchTask per operazioni molto lunghe non permetterebbe a nessun altro AsynchTask di girare.

Io però vorrei itereare l'operazione ogni 2 secondi circa dopo che il precedente AsyncTask è finito. Sarebbero quindi  singoli AsyncTask eseguiti con un intervallo, seppur minimo, di tempo, non voglio task contemporanei!


Citazione
La soluzione più semplice e di lanciare il primo OnlineTask e "pianificare" un successivo lancio ogni volta che questo termina con un PostDelayed.

Come fai a capire quando termina il primo OnlineTask? Stavo cercando di far funzionare il PostDelayed ma ancora non sono riuscito ...

Codice: [Seleziona]
        final Handler mHandler = new Handler();
               
            mHandler.postDelayed(new Runnable() {

                public void run() {
                    new OnlineTask().execute("Poll");
                }
            }, 2000);

Testandolo ho appurato che la postDelayed ritarda solo la partenza di quanto definito, ma non attende in alcun modo la fine del comando precedente mHandler per lanciare eventualmente il successivo; praticamente se ne metto 3 in fila partono quasi tutti contemporaneamente facendo la solita frittata. :(
Se itero un ciclo controllando una variabile che vado a modificare alla fine della OnPost mi si blocca la UI ... :(

Mi sembra di essere in un circolo vizioso da cui non riesco a venirne a capo ...

Posso eventualmente fare 2 AsyncTask come in origine, ComunicaTask e OnlineTask, itero dentro la doInBackground del primo e per far partire l'OnlineTask utilizzo la progressUpdate? Funzionerebbe?
« Ultima modifica: 12 Settembre 2014, 17:55:54 CEST da wlf »

Offline arlabs

  • Utente normale
  • ***
  • Post: 430
  • Respect: +49
    • Mostra profilo
  • Dispositivo Android:
    GalaxyS6, Nexus5
  • Play Store ID:
    AR Labs
  • Sistema operativo:
    Windows 10
Re:AsyncTask & server remoto
« Risposta #3 il: 12 Settembre 2014, 19:02:58 CEST »
+1
No, la postDelayed attende solo il tempo che le dici... io dicevo di chiamarla dalla onPostExecute dell'OnlineTask (ed eventualmente nella onCanceled).
Cioé alla fine di ogni interrogazione al serve, programmi quella successiva.

Io l'ho usato spesso questo tipo di pianificazione.

Cmq, se vuoi avere un thread a parte che gra di continue e itera ogni 2 secondi, fai pure, ...ma:
 - 1 Dovresti usare la classe Thread, e non la classe AsynchTask.
 - 2 Devi comunque lanciare l'OnlineTask dal main thread (ad es. all'interno del tuo task chiamando runOnUiThread)
 - 3 Se la connessione è lenta o il server occupato, rischi che si accavallino richieste successive.

Ciao.

P.S.
Citazione
Posso eventualmente fare 2 AsyncTask come in origine, ComunicaTask e OnlineTask, itero dentro la doInBackground del primo e per far partire l'OnlineTask utilizzo la progressUpdate? Funzionerebbe?
Non funzionerebbe, perché finché il ComunicaTask è dentro la doInBackground, non ci potrebbe mai entrare l'OnlineTask.
A meno che tu lanciassi i Task con

Codice (Java): [Seleziona]
            asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);anziché
Codice (Java): [Seleziona]
            asyncTask.execute(params);
Ma andresti comunque a snaturare l'utilizzo dell'AsynchTask. Un Thread al posto del ComunicaTask è anche pià semplice da scrivere:

Codice (Java): [Seleziona]
                        new Thread( new Runnable() {
                            @Override
                            public void run() {
                                // Ciclo
                            }
                        }).start();

Sebbene io continui a sconsigliartelo.
« Ultima modifica: 12 Settembre 2014, 19:09:07 CEST da arlabs »

Offline bradipao

  • Moderatore globale
  • Utente storico
  • *****
  • Post: 4043
  • keep it simple
  • Respect: +567
    • Github
    • Google+
    • bradipao
    • Mostra profilo
  • Dispositivo Android:
    Nexus 5
  • Play Store ID:
    Bradipao
  • Sistema operativo:
    W7
Re:AsyncTask & server remoto
« Risposta #4 il: 12 Settembre 2014, 19:19:51 CEST »
0
Appoggio l'ultimo post di arlabs perchè per un piccolo progettino personale ho fatto come lui (un service era overkill nel mio caso).

Aggiungo solamente un'osservazione pratica, derivante da prove reali durante il progettino di cui sopra. Due secondi di intervallo tra le richieste http potrebbero essere drammaticamente pochi. Il punto è che dipende molto dal server e dalla rete tra server e client: potrebbe essere necessario impostare un timeout di 5 secondi per la richiesta di rete, al che ha poco senso avere ripetizioni a meno di 5 secondi. Insomma da valutare in base al caso.
NON rispondo a domande nei messaggi privati
Bradipao @ Play Store

Offline wlf

  • Utente normale
  • ***
  • Post: 315
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Xperia
Re:AsyncTask & server remoto
« Risposta #5 il: 12 Settembre 2014, 20:38:04 CEST »
0
Agganciandomi a quanto mi avete consigliato non potrei, grazie ad un timer di 2 secondi, fare un controllo di un boleano che mi dice se il precedente AsyncTask è finito in modo da discriminare se ripetere l'AsyncTask oppure attendere il prossimo turno? In questo modo non c'è bisogno di alzare l'intervallo a 5 secondi. Sarebbero troppi, l'utente sarebbe meglio che veda il messaggio quasi in tempo reale, se poi consideriamo che a sua volta il server comunica anche con un macchinario remoto si sommano quindi due intervalli ... :(

Offline droid7

  • Nuovo arrivato
  • *
  • Post: 44
  • Respect: +4
    • droidrcc
    • Mostra profilo
    • echlabSoftware
  • Dispositivo Android:
    Lg Optimus One
  • Play Store ID:
    echlab software
  • Sistema operativo:
    Archlinux
Re:AsyncTask & server remoto
« Risposta #6 il: 12 Settembre 2014, 21:44:59 CEST »
0
Io come prima cosa abbandonerei l'utlizzo dell'AsyncTask in favore dell'AsyncTaskLoader. Riguardo la mimica che vuoi implementare potresti descriverla più nel dettaglio ? Vuoi che i dati sul client vengano aggiornati in tempo reale o ad intervalli regolari ?

Android applications: echlabSoftware

Offline Ricky`

  • Amministratore
  • Utente storico
  • *****
  • Post: 3487
  • Respect: +506
    • Github
    • Google+
    • rciovati
    • Mostra profilo
Re:AsyncTask & server remoto
« Risposta #7 il: 13 Settembre 2014, 13:01:57 CEST »
0
Anche io ti consiglio di usare un AsyncTaskLoader invece che un AsyncTask. Ti toglierebbe un po' di grattacapi relativamente alla gestione dell'orientamento e al threading.

Se ti può aiutare ho creato un progettino (che è più un esperimento che altro) dove uso un AsyncTaskLoader in abbinato a retrofit per fare le chiamate http: https://github.com/rciovati/retrofit-loaders-example

Offline wlf

  • Utente normale
  • ***
  • Post: 315
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Xperia
Re:AsyncTask & server remoto
« Risposta #8 il: 15 Settembre 2014, 09:46:15 CEST »
0
Grazie a tutti dei suggerimenti ... ;)

Io come prima cosa abbandonerei l'utlizzo dell'AsyncTask in favore dell'AsyncTaskLoader.

Da quello che ho inteso io l'AsyncTaskLoader è soprattutto meglio per i cambi di orientamento e con le Fagment; nel mio caso ho una Activity con l'orientamento fissato a portrait, quindi, teoricamente se non erro, non dovrebbero esserci significativi vantaggi.   :-\

Citazione
Riguardo la mimica che vuoi implementare potresti descriverla più nel dettaglio ? Vuoi che i dati sul client vengano aggiornati in tempo reale o ad intervalli regolari ?

Ho innanzi tutto la necessità di comunicare al server remoto che mi sono connesso per fare in modo che accenda un dispositivo remoto; ho poi la necessità di leggere lo stato del dispositivo remoto ogni 2 secondi circa ed infine, quando mi voglio disconnettere e far ritornare in pasusa il dispositivo remoto, gli debbo comunicare che mi disconnetto.
Quindi ogni connessione avrà:

- Connessione
- Lettura stato .. (ogni 2 secondi circa)
- Lettura stato .. (ogni 2 secondi circa)
- Lettura stato .. (ogni 2 secondi circa)
- Lettura stato .. (ogni 2 secondi circa)
- Lettura stato .. (ogni 2 secondi circa)
Disconnessione

Se come suggerito, nel main thread aggiungo un timer che ogni 2 secondi cerca di lanciare un asyncTask controllando un booleano se non è già in esecuzione per non avere accavallamenti dite che funziona?

Codice: [Seleziona]
int delay = 1000; // delay 1 sec.
    int period = 2000; // repeat every sec.

    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask()
        {
            public void run()
            {
                //code lancio asyncTask
            }
        }, delay, period);


Offline arlabs

  • Utente normale
  • ***
  • Post: 430
  • Respect: +49
    • Mostra profilo
  • Dispositivo Android:
    GalaxyS6, Nexus5
  • Play Store ID:
    AR Labs
  • Sistema operativo:
    Windows 10
Re:AsyncTask & server remoto
« Risposta #9 il: 15 Settembre 2014, 11:09:51 CEST »
0
Credo che il Timer giri comunque su un Thread (qualcuno con più esperienza mi confermi questa cosa).

Quindi, come nel caso del Thread, dovresti lanciare l'OnlineTask tramite una chiamata ad runOnUiThread.

Per il resto dovrebbe funzionare.

Per sicurezza chiamerei una cancel sull'OnlineTask nella onPause dell'Activity. Per evitare che ti arrivi un risultato mentre stai chiudendo l'App.

Ciao.

Offline wlf

  • Utente normale
  • ***
  • Post: 315
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Xperia
Re:AsyncTask & server remoto
« Risposta #10 il: 15 Settembre 2014, 14:49:22 CEST »
0
Credo che il Timer giri comunque su un Thread (qualcuno con più esperienza mi confermi questa cosa).

Sto testando il Timer e da quello che ho visto continua a girare anche quando lo smartphone va in pausa! :)

Citazione
Quindi, come nel caso del Thread, dovresti lanciare l'OnlineTask tramite una chiamata ad runOnUiThread.

Lo terrò presente ...

Citazione
Per sicurezza chiamerei una cancel sull'OnlineTask nella onPause dell'Activity. Per evitare che ti arrivi un risultato mentre stai chiudendo l'App.

A mio avviso piuttosto che cancellare l'OnlineTask nella onPause dovrò fare la cancel() del timer e chiamare l'OnlineTask con il parametro "Disconnetti" per togliere il collegamento al dispositivo e farlo ritornare in pausa. ;)

Offline wlf

  • Utente normale
  • ***
  • Post: 315
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Xperia
Re:AsyncTask & server remoto
« Risposta #11 il: 16 Settembre 2014, 18:19:27 CEST »
0
Con il timer sono riuscito a farlo funzionare; come ha suggerito correttamente da Arlabs ho dovuto utilizzare il runOnUiThread altrimenti non potevo intervenire sulla UI (Errore "Only the original thread that created a view hierarchy can touch its views."):

Codice: [Seleziona]
        private void prova() {
                int delay = 1000; // delay 1 sec.
            int period = 2000; // repeat every sec.
           

                timerOnline = new Timer();
           
            timerOnline.scheduleAtFixedRate(new TimerTask()
                {
                    public void run()
                    {
                            if (!isConnect) {
                                    onlineAction = "Connect";                                   
                            } else {
                                    onlineAction = "Poll";
                            }
                            if (closeConnection) {
                                    onlineAction = "Disconnect";                                   
                            }
                            if (!isOnline) {                                   
                                    runOnUiThread(new Runnable() {
                                                        public void run() {
                                                    new OnlineTask().execute(onlineAction);                                                                                                   
                                                        }
                                                });                                   
                            }
                    }
                }, delay, period);               
        }

Sfruttando un booleano che mi dice se è ancora online o meno settato nella onPreExecute e nella onPostExecute (isOnline) riesco a discriminare se lanciare un altro AsyncTask o meno. Con un secondo booleano valorizzato esternamente (closeConnection) riesco a disconnettermi e alla fine dell'OnlineTask faccio una timerOnline.cancel() per terminare ogni attività. Così sembra funzionare ...  ;)

Ulteriori suggerimenti?

Grazie a tutti! ;)
« Ultima modifica: 17 Settembre 2014, 12:59:52 CEST da wlf »