Concorrenza su Android con servizio

In questo tutorial esploreremo il Servizio componente e la sua superclasse, il IntentService. Imparerai quando e come utilizzare questo componente per creare grandi soluzioni di concorrenza per operazioni di background di lunga durata. Daremo anche uno sguardo veloce a IPC (Inter Process Communication), per imparare come comunicare con i servizi in esecuzione su diversi processi.

Per seguire questo tutorial avrai bisogno di conoscere la concorrenza su Android. Se non ne sai molto, potresti prima leggere alcuni dei nostri altri articoli sull'argomento.

1. La componente di servizio

Il Servizio componente è una parte molto importante del framework di concorrenza di Android. Soddisfa la necessità di eseguire un'operazione di lunga durata all'interno di un'applicazione o fornisce alcune funzionalità per altre applicazioni. In questo tutorial ci concentreremo esclusivamente su ServizioLa capacità di svolgere attività a lungo termine e come utilizzare questo potere per migliorare la concorrenza.

Che cos'è un servizio?

UN Servizio è un componente semplice che viene istanziato dal sistema per eseguire un lavoro di lunga durata che non dipende necessariamente dall'interazione dell'utente. Può essere indipendente dal ciclo di vita dell'attività e può anche essere eseguito su un processo completamente diverso.

Prima di immergerti in una discussione su cosa Servizio rappresenta, è importante sottolineare che anche se i servizi sono comunemente utilizzati per operazioni in background di lunga durata e per eseguire attività su processi diversi, un Servizio non rappresenta a Filo o un processo. Funzionerà solo in un thread in background o in un processo diverso se è esplicitamente richiesto di farlo.

UN Servizio ha due caratteristiche principali:

  • Una funzionalità per l'applicazione per dire al sistema qualcosa che vuole fare in background.
  • Una possibilità per un'applicazione di esporre alcune delle sue funzionalità ad altre applicazioni.

Servizi e discussioni

C'è molta confusione su servizi e thread. Quando un Servizio è dichiarato, non contiene a Filo. Di fatto, per impostazione predefinita viene eseguito direttamente sul thread principale e qualsiasi lavoro svolto su di esso può potenzialmente bloccare un'applicazione. (A meno che non sia un IntentService, un Servizio sottoclasse che già viene fornito con un thread di lavoro configurato).

Quindi, in che modo i servizi offrono una soluzione di concorrenza? Bene, a Servizio non contiene un thread per impostazione predefinita, ma può essere facilmente configurato per funzionare con il proprio thread o con un pool di thread. Ne vedremo di più qui sotto.

Ignorando la mancanza di un thread incorporato, a Servizio è una soluzione eccellente per i problemi di concorrenza in determinate situazioni. Le ragioni principali per scegliere a Servizio su altre soluzioni di concorrenza come AsyncTask o il framework HaMeR sono:

  • UN Servizio può essere indipendente dai cicli di vita delle attività.
  • UN Servizio è appropriato per l'esecuzione di operazioni lunghe.
  • I servizi non dipendono dall'interazione dell'utente.
  • Quando si eseguono processi diversi, Android può provare a mantenere attivi i servizi anche quando il sistema non ha risorse sufficienti.
  • UN Servizio può essere riavviato per riprendere il suo lavoro.

Tipi di servizio

Ci sono due tipi di Servizio, iniziato e limite.

UN iniziato il servizio è lanciato via Context.startService (). Generalmente esegue solo una operazione e funzionerà indefinitamente fino al termine dell'operazione, quindi si spegnerà automaticamente. In genere, non restituisce alcun risultato all'interfaccia utente.

Il servizio vincolato viene lanciato via Context.bindService (), e consente una comunicazione bidirezionale tra client e Servizio. Può anche connettersi con più client. Distrugge se stesso quando non ci sono client collegati ad esso.

Per scegliere tra questi due tipi, il  Servizio deve implementare alcune callback: onStartCommand () da eseguire come servizio avviato, e onBind () per funzionare come servizio associato. UN Servizio può scegliere di implementare solo uno di questi tipi, ma può anche adottare entrambi allo stesso tempo senza problemi. 

2. Implementazione del servizio

Per utilizzare un servizio, estendere il Servizio classificare e sovrascrivere i suoi metodi di callback, in base al tipo di Servizio . Come accennato prima, per i servizi avviati il onStartCommand () il metodo deve essere implementato e per i servizi associati, il onBind () metodo. In realtà, il onBind ()il metodo deve essere dichiarato per entrambi i tipi di servizio, ma può restituire null per i servizi avviati.

public class CustomService estende il servizio @ Override public int onStartCommand (Intent intent, int flags, int startId) // Esegui le tue operazioni // Il servizio non verrà terminato automaticamente restituirà Service.START_NOT_STICKY;  @Nullable @Override public IBinder onBind (Intent intent) // Crea una connessione con un client // utilizzando un'interfaccia implementata su IBinder return null; 
  • onStartCommand (): lanciato da Context.startService (). Questo è solitamente chiamato da un'attività. Una volta chiamato, il servizio può essere eseguito indefinitamente e spetta a te interromperlo, chiamando stopSelf () o StopService ().
  • onBind (): chiamato quando un componente vuole connettersi al servizio. Chiamato sul sistema da Context.bindService (). Restituisce un IBinder che fornisce un'interfaccia per comunicare con il cliente.

Anche il ciclo di vita del servizio è importante da prendere in considerazione. Il onCreate ()  e OnDestroy () i metodi devono essere implementati per inizializzare e arrestare tutte le risorse o le operazioni del servizio.

Dichiarazione di un servizio sul manifesto

Il Servizio componente deve essere dichiarato sul manifest con il  elemento. In questa dichiarazione è anche possibile, ma non obbligatorio, impostare un diverso processo per il Servizio correre dentro.

...  ...  

2.2. Lavorare con i servizi avviati

Per avviare un servizio iniziato è necessario chiamare Context.startService () metodo. Il Intento deve essere creato con il Contesto e il Servizio classe. In questo caso dovrebbero essere trasmesse anche tutte le informazioni o i dati rilevanti Intento.

Intent serviceIntent = new Intent (this, CustomService.class); // Passa i dati da elaborare sui dati del pacchetto di servizi = nuovo pacchetto (); data.putInt ("OperationType", 99); data.putString ("DownloadURL", "http://mydownloadurl.com"); serviceIntent.putExtras (dati); // Avvio del servizio startService (serviceIntent);

Nel tuo Servizio classe, il metodo di cui dovresti preoccuparti è il onStartCommand (). È su questo metodo che devi chiamare qualsiasi operazione che vuoi eseguire sul servizio avviato. Elaborerai il Intento per acquisire le informazioni inviate dal cliente. Il startID rappresenta un ID univoco, creato automaticamente per questa specifica richiesta e il bandiere può anche contenere informazioni aggiuntive su di esso.

 @Override public int onStartCommand (Intent intent, int flags, int startId) Bundle data = intent.getExtras (); if (data! = null) int operation = data.getInt (KEY_OPERATION); // Controlla quale operazione eseguire e invia un messaggio se (operazione == OP_DOWNLOAD) // fai un download torna START_STICKY; 

Il onStartCommand () restituisce una costante int che controlla il comportamento:

  • Service.START_STICKY: Il servizio viene riavviato se viene interrotto.
  • Service.START_NOT_STICKY: Il servizio non è stato riavviato.
  • Service.START_REDELIVER_INTENT: Il servizio viene riavviato dopo un arresto anomalo e gli intenti che verranno elaborati verranno riconsegnati.

Come accennato in precedenza, è necessario arrestare un servizio avviato, altrimenti verrà eseguito a tempo indeterminato. Questo può essere fatto dal Servizio chiamata stopSelf () su se stesso o da una chiamata client StopService () su di esso.

void someOperation () // esegue qualche operazione di lunga durata // e interrompe il servizio quando viene eseguito stopSelf (); 

Associazione ai servizi

I componenti possono creare connessioni con i servizi, stabilendo con essi una comunicazione a due vie. Il cliente deve chiamare Context.bindService (), passando un Intento, un ServiceConnection interfaccia e a bandiera come parametri. UN Servizio può essere associato a più client e verrà distrutto una volta che non ci sono client connessi.

void bindWithService () Intent intent = new Intent (this, PlayerService.class); // collega con Service bindService (intent, mConnection, Context.BIND_AUTO_CREATE); 

È possibile inviare Messaggio oggetti ai servizi. Per farlo dovrai creare un Messaggero dal lato client in a ServiceConnection.onServiceConnected implementazione dell'interfaccia e usarlo per inviare Messaggio oggetti al Servizio.

private ServiceConnection mConnection = new ServiceConnection () @ Override pubblico onServiceConnected (ComponenName className, servizio IBinder) // usa l'IBinder ricevuto per creare un Messenger mServiceMessenger = new Messenger (servizio); mBound = true;  @Override public void onServiceDisconnected (ComponentName arg0) mBound = false; mServiceMessenger = null; ;

È anche possibile passare una risposta Messaggero al Servizio per il client di ricevere messaggi. Attenzione però, perché il cliente potrebbe non essere più in giro per ricevere il messaggio del servizio. Puoi anche usare BroadcastReceiver o qualsiasi altra soluzione di trasmissione.

 gestore privato mResponseHandler = new Handler () @Override public void handleMessage (Message msg) // gestisce la risposta dal servizio; Message msgReply = Message.obtain (); msgReply.replyTo = new Messenger (mResponseHandler); prova mServiceMessenger.send (msgReply);  catch (RemoteException e) e.printStackTrace (); 

È importante non associarti al Servizio quando il cliente viene distrutto.

@Override protected void onDestroy () super.onDestroy (); // disconnetti dal servizio if (mBound) unbindService (mConnection); mBound = falso; 

Sul Servizio lato, è necessario implementare il Service.onBind () metodo, fornendo un IBinder fornito da a Messaggero. Questo trasmetterà una risposta handler gestire il Messaggio oggetti ricevuti dal cliente.

 IncomingHandler (PlayerService playerService) mPlayerService = new WeakReference <> (playerService);  @Override public void handleMessage (Message msg) // handle messages public IBinder onBind (Intent intent) // passa un raccoglitore usando il Messenger creato return mMessenger.getBinder ();  Messenger finale mMessenger = new Messenger (new IncomingHandler (this));

3 Concorrenza usando i servizi

Infine, è il momento di parlare di come risolvere i problemi di concorrenza utilizzando i servizi. Come accennato prima, uno standard Servizio non contiene thread aggiuntivi e verrà eseguito sul main Filo per impostazione predefinita. Per superare questo problema devi aggiungere un lavoratore Filo, un pool di thread o eseguire il Servizio su un altro processo. Puoi anche usare una sottoclasse di Servizio chiamato IntentService che contiene già un Filo.

Esecuzione di un servizio su un thread di lavoro

Per rendere il Servizio eseguire su uno sfondo Filo potresti semplicemente creare un extra Filo ed eseguire il lavoro lì. Tuttavia, Android ci offre una soluzione migliore. Un modo per sfruttare al meglio il sistema è implementare il framework HaMeR all'interno di Servizio, ad esempio eseguendo il ciclo a Filo con una coda di messaggi che può elaborare i messaggi indefinitamente.

È importante capire che questa implementazione elaborerà i compiti in modo sequenziale. Se è necessario ricevere ed elaborare più attività contemporaneamente, è necessario utilizzare un pool di thread. L'utilizzo di pool di thread non rientra nell'ambito di questo tutorial e non ne parleremo oggi. 

Per usare HaMeR devi fornire il Servizio con un Looper, un handler e a HandlerThread.

 Looper privato mServiceLooper; servizio privatoHandler mServiceHandler; // Gestore per ricevere messaggi dalla classe finale privata del client ServiceHandler estende Handler ServiceHandler (Looper looper) super (looper);  @Override public void handleMessage (Message msg) super.handleMessage (msg); // gestisce i messaggi // arrestando il servizio usando startId stopSelf (msg.arg1);  @Override public void onCreate () HandlerThread thread = new HandlerThread ("ServiceThread", Process.THREAD_PRIORITY_BACKGROUND); Thread.start (); mServiceLooper = thread.getLooper (); mServiceHandler = new ServiceHandler (mServiceLooper);  

Se la struttura HaMeR non ti è familiare, leggi i nostri tutorial su HaMer per Android.

The IntentService

Se non c'è bisogno per il Servizio per essere tenuto in vita per molto tempo, potresti usarlo IntentService, un Servizio sottoclasse che è pronto per eseguire attività su thread in background. Internamente, IntentService è un Servizio con un'implementazione molto simile a quella sopra proposta. 

Per utilizzare questa classe, tutto ciò che devi fare è estenderlo e implementare il onHandleIntent (), un metodo di aggancio che verrà chiamato ogni volta che un client chiama StartService () su questo Servizio. È importante tenere presente che il IntentService si fermerà non appena il suo lavoro sarà completato.

MyIntentService di classe pubblica estende IntentService public MyIntentService () super ("MyIntentService");  @Override protected void onHandleIntent (Intent intent) // handle Intents send by startService

IPC (Inter Process Communication)

UN Servizio può funzionare su un completamente diverso Processi, indipendentemente da tutte le attività che si verificano nel processo principale. Un processo ha la propria allocazione di memoria, il gruppo di thread e le priorità di elaborazione. Questo approccio può essere davvero utile quando è necessario lavorare indipendentemente dal processo principale.

La comunicazione tra diversi processi si chiama IPC (Inter Process Communication). In un Servizio ci sono due modi principali per fare IPC: usando a Messaggero o implementando un AIDL interfaccia. 

Abbiamo imparato come inviare e ricevere messaggi tra servizi. Tutto ciò che devi fare è usare create a Messaggero usando il IBinder istanza ricevuta durante il processo di connessione e usarla per inviare una risposta Messaggero ritorno al Servizio.

 gestore privato mResponseHandler = new Handler () @Override public void handleMessage (Message msg) // gestisce la risposta dal servizio; private ServiceConnection mConnection = new ServiceConnection () @ Override pubblico onServiceConnected (ComponenName className, servizio IBinder) // usa l'IBinder ricevuto per creare un Messenger mServiceMessenger = new Messenger (servizio); Message msgReply = Message.obtain (); msgReply.replyTo = new Messenger (mResponseHandler); prova mServiceMessenger.send (msgReply);  catch (RemoteException e) e.printStackTrace (); 

Il AIDL l'interfaccia è una soluzione molto potente che consente chiamate dirette Servizio metodi in esecuzione su diversi processi ed è appropriato utilizzare quando il tuo Servizio è davvero complesso però, AIDL è complicato da implementare ed è usato raramente, quindi il suo uso non sarà discusso in questo tutorial.

4. Conclusione

I servizi possono essere semplici o complessi. Dipende dalle esigenze della tua applicazione. Ho cercato di coprire il maggior numero possibile di argomenti su questo tutorial, tuttavia mi sono concentrato solo sull'utilizzo dei servizi per scopi di concorrenza e ci sono più possibilità per questo componente. Voglio studiare di più, dai un'occhiata alla documentazione e alle guide di Android. 

A presto!