12

Su Android, ho un Activity chiamato FirstActivity che avvia un Service denominato MyService per fare roba di rete in background. Il Activity e lo Service comunicano tra loro continuamente chiamando metodi.Attività con servizio di lunga durata in background che non verrà ucciso

Ora, quando l'utente si sposta FirstActivity-SecondActivity, il servizio in background dovrebbe non essere uccisi o ri-creato, ma tenuto in vita e passato a SecondActivity che ora sarà quello di comunicare con il servizio.

In altre parole, il Service deve continuare a funzionare fino a quando una delle due Activity s è in esecuzione, e non dovrebbe fermarsi mentre l'utente naviga tra i due Activity s.

Uno dei Activity s sarà sempre in primo piano e durante questo periodo, il servizio dovrebbe (in modo ottimale) non essere mai ucciso. Penso che questo non dovrebbe essere un problema perché uno di questi due Activity s è sempre attivo e quindi Android sa che il servizio è importante e non qualcosa che deve essere ucciso.

(Se non ci fosse alcun modo per impedire Android di uccidere e ri-creazione del servizio di tanto in tanto, avrei bisogno di un modo per ripristinare la piena stato del servizio con grazia.)

Per riassumere , lo Service dovrebbe avere la stessa durata dei due "Activity s" combinati ". Dovrebbe iniziare con il primo di essi e fermarsi non prima che entrambi siano stati distrutti.

Quindi il seguente codice è corretto per quella configurazione e gli obiettivi?

public class MyService extends Service { 

    public class LocalBinder extends Binder { 
     public MyService getService() { 
      return MyService.this; 
     } 
    } 

    ... 

} 

public class FirstActivity extends Activity { 

    private MyService mMyService; 

    private ServiceConnection mMainServiceConnection = new ServiceConnection() { 

     @Override 
     public void onServiceConnected(ComponentName className, IBinder service) { 
      MyService mainService = ((LocalBinder) service).getService(); 
      mMyService = mainService; 
      mMyService.setCallback(FirstActivity.this); 
     } 

     @Override 
     public void onServiceDisconnected(ComponentName className) { 
      mMyService = null; 
     } 
    }; 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     ... 
     startService(new Intent(FirstActivity.this, MyService.class)); 
    } 

    @Override 
    protected void onResume() { 
     super.onResume(); 
     bindService(new Intent(FirstActivity.this, MyService.class), mMainServiceConnection, Context.BIND_AUTO_CREATE); 
    } 

    @Override 
    protected void onPause() { 
     super.onPause(); 
     if (mMainServiceConnection != null) { 
      unbindService(mMainServiceConnection); 
     } 

     if (mMyService != null) { 
      mMyService.setCallback(null); 
     } 

     if (!isUserMovingToSecondActivity) { 
      stopService(new Intent(FirstActivity.this, MyService.class)); 
     } 
    } 

    @Override 
    public void onBackPressed() { 
     stopService(new Intent(FirstActivity.this, MyService.class)); 
     super.onBackPressed(); 
    } 

    ... 

} 

public class SecondActivity extends Activity { 

    private MyService mMyService; 

    private ServiceConnection mMainServiceConnection = new ServiceConnection() { 

     @Override 
     public void onServiceConnected(ComponentName className, IBinder service) { 
      MyService mainService = ((LocalBinder) service).getService(); 
      mMyService = mainService; 
      mMyService.setCallback(SecondActivity.this); 
     } 

     @Override 
     public void onServiceDisconnected(ComponentName className) { 
      mMyService = null; 
     } 
    }; 

    @Override 
    protected void onResume() { 
     super.onResume(); 
     bindService(new Intent(SecondActivity.this, MyService.class), mMainServiceConnection, Context.BIND_AUTO_CREATE); 
    } 

    @Override 
    protected void onPause() { 
     super.onPause(); 
     if (mMainServiceConnection != null) { 
      unbindService(mMainServiceConnection); 
     } 
    } 

    @Override 
    protected void onDestroy() { 
     ... 
     stopService(new Intent(SecondActivity.this, MyService.class)); 
    } 

    ... 

} 

È questo il modo migliore per garantire una lunga durata di servizio in background delle Activity s che non saranno uccisi o ricreato?

Che dire di Context.BIND_AUTO_CREATE? È corretto avere questo flag impostato qui? Che dire di Context.BIND_ADJUST_WITH_ACTIVITY e Context.BIND_WAIVE_PRIORITY - ho bisogno di questi?

+0

Hai provato quel codice o stai chiedendo prima di provare? Se ci hai provato, hai ricevuto degli errori? Non si è comportato come ti aspettavi? Avremo una discussione molto più produttiva e una risposta più utile per la comunità e per te stesso se effettivamente la proverai e poi ci chiedi la soluzione un problema concreto che hai – DallaRosa

+0

@DallaRosa Sì, certo, ho provato questo.Questa è una domanda generale su come progettare una classe di servizio e le corrispondenti classi di attività, in modo che il servizio duri il più a lungo possibile e non venga ucciso. Gli utenti hanno segnalato che le funzionalità fornite da questo servizio smettono di essere disponibili in una delle classi 'Activity' di volta in volta. E io (a) trovo difficile eseguire il debug quando il sistema potrebbe uccidere questo servizio e (b) vorrebbe conoscere alcune best practice o miglioramenti che dovrebbero essere applicati a questo servizio per gli obiettivi e i requisiti ben definiti. – caw

+0

Posso vedere molti problemi nella gestione del 'Servizio'. Devi fermarlo solo quando il primo 'Activity' viene distrutto, o dipende dal binding e gestirlo correttamente. Ci sono alcune istruzioni generali su questo nei libri Android iniziali: https://books.google.com.pk/books?id=mRGrCQbqHkoC&pg=PA399 – corsair992

risposta

7

(Molte grazie a @ corsair992 per i suoi utili indicazioni!)


Se le attività sono sempre chiamati in questo ordine (cioè FirstActivity comincia SecondActivity, e mai il contrario, allora . si dovrebbe, in fondo, il tentativo di "cravatta" ciclo di vita del servizio di ciclo di vita FirstActivity s'

In generale (vedi avvertenze successive), questo significa:

  • Chiama startService() in FirstActivity.onCreate().
  • Chiama stopService() in FirstActivity.onDestroy().
  • Chiama bindService()/unbindService() nei metodi onStart()/onStop() di entrambe le Attività (per ottenere l'accesso all'oggetto Raccoglitore ed essere in grado di richiamare i metodi su di esso).

Un servizio iniziato in questo modo sarà in vita fino stopService() si chiama e ogni cliente scioglie, vedi Managing the Lifecycle of a Service:

Questi due percorsi non sono completamente separati. Cioè, è possibile associare a un servizio già avviato con startService(). (...) In casi come questo, stopService() o stopSelf() in realtà non interrompe il servizio fino a quando tutti i client non si distaccano .

e:

Quando l'ultimo cliente slega dal servizio, il sistema distrugge il servizio (a meno che il servizio è stato avviato anche da StartService()).

Con questa strategia di base, il Servizio vivrà finchè FirstActivity è intorno (ovvero non viene distrutto). Tuttavia, rimane ancora un punto importante: in caso di una modifica della configurazione (ad esempio una rotazione dello schermo) che non viene gestita in modo esplicito causerà il riavvio dell'attività stessa e il servizio verrà distrutto (poiché stiamo chiamando stopService() in onDestroy()) .

Per evitare ciò, è possibile controllare isChangingConfigurations() prima realmente arrestare il servizio (in quanto un onDestroy() callback si verificano a causa di questo significa che anche se questo caso particolare della attività viene distrutto, verrà ricreato in seguito.

quindi, la soluzione completa sarebbe qualcosa come:

public class FirstActivity extends Activity 
{ 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     startService(new Intent(this, MyService.class)); 
    } 

    private ServiceConnection mServiceConnection = new ServiceConnection() { ... } 

    @Override 
    protected void onStart() { 
     super.onStart(); 
     bindService(new Intent(this, MyService.class), mServiceConnection, Context.BIND_AUTO_CREATE); 
    } 

    @Override 
    protected void onStop() { 
     unbindService(mServiceConnection); 
     super.onStop(); 
    } 

    @Override 
    protected void onDestroy() { 
     if (!isChangingConfigurations()) 
      stopService(new Intent(this, MyService.class)); 

     super.onDestroy(); 
    } 

Mentre SecondActivity sarebbe solo attuare le onStart()/onStop() metodi (nello stesso modo).


Un paio di note sul vostro particolare implementazione:

  • Non è necessario eseguire l'override onBackPressed(), dal momento che se l'attività viene distrutto i metodi del ciclo di vita necessarie saranno chiamati (più, potrebbe essere terminato senza premendo il pulsante Indietro, ad esempio se si chiama finish() su di esso).
  • Arrestare il servizio in onDestroy() anziché onPause() ti evita di dover controllare isUserMovingToSecondActivity.
+0

Grazie! (1) Per il nostro scopo, puoi usare 'onStart()'/'onStop()' e 'onResume()'/'onPause()' in modo intercambiabile, no? Altrimenti, il secondo paio di metodi sembra superiore perché è * garantito * per essere chiamato, in contrasto con la prima coppia, giusto? (2) Il controllo di 'isChangingConfigurations()' fa davvero la differenza? Se 'FirstActivity' viene distrutto, perdiamo comunque la variabile membro' mMyService', e 'onCreate (...)' e 'onStart()' vengono richiamati (il che significa anche che è possibile eseguire il binding due volte). E in 'SecondActivity', perdiamo la connessione in ogni caso. – caw

+1

@MarcoW. (1) 'onStop()' potrebbe non arrivare, ma AFAIK è solo se l'app viene uccisa, nel qual caso anche il servizio. (2) Perderai l'associazione locale, ma il servizio non viene effettivamente distrutto e quindi ricreato quando ti leghi di nuovo (quindi, se hai qualche processo in corso, sarà comunque "essere lì"). Tuttavia, lo sarà se chiami 'stopService()'. Supponevo che volessi mantenere lo stato del servizio a rotazione. – matiash

+0

Riguardo a (2), quando 'onStop()' è chiamato in 'SecondActivity', il' Servizio' è probabilmente ucciso (perché nessun 'Activity' si lega più a esso e tutti gli 'Activity's che hanno chiamato' startService (...) 'hanno chiamato' stopService (...) ') prima di poter re-associare in' onStart() 'dopo la modifica della configurazione. Ho sbagliato? – caw