Le applicazioni Android sono composte da una serie di componenti e
tra queste ci sono i Service, parenti stretti delle Activity ma privi di interfaccia utente.
Sono elementi centralissimi in quanto permettono di svolgere attività in background, ossia operazioni più lunghe che non vanno ad impattare sulla user experience. Affinché sia preservata l'efficienza
dell'applicazione, tali attività in background devono essere svolte in modalità asincrona, ovvero su un thread secondario.
Tale ambito è stato rivoluzionato dall'introduzione nelle API 21 del JobScheduler. Intendiamo con questo termine un meccanismo, dedicato anch'esso alle operazioni in background ma
con una maggiore attenzione alla gestione della batteria.
Questo fattore è sempre più cruciale considerando che Android è un sistema deputato all'impiego su dispositivi mobile che pertanto devono preservare il più a lungo possibile le proprie
risorse energetiche.
Chi gestisce tutti i dati relativi all'attività da svolgere mediante il JobScheduler è un oggetto JobInfo in cui sono specificate tutte le condizioni che determinano l'avvio del
task. I passi che permettono di attivare un'attività asincrona sono:
- definizione di una classe derivata da JobService in cui verranno specificate le attività da svolgere in maniera asincrona;
- creazione di un oggetto JobInfo cui sarà collegato il JobService del punto precedente;
- inserimento del JobInfo nello JobScheduler mediante l'invocazione del metodo schedule;
- attivazione del task asincrono non appena i criteri specificati nel JobInfo saranno soddisfatti.
Dal prossimo paragrafo entriamo nei dettagli del codice per vedere, in pratica, l'applicazione di tutti questi punti.
Preparazione del JobInfo
Alla base di un JobInfo, si trova un oggetto di tipo JobService che deriva per ereditarietà dalla stessa classe Service. Al suo interno
viene gestito l'avvio e la dismissione della vera e propria attività asincrona:
public class MyTask extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
// attività da svolgere all'avvio del service
return true;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
// attività da svolgere al termine del Job
return true;
}
}
Come si vede le caratteristiche di questa classe sono:
- l'ereditarietà da JobService. Noi abbiamo chiamato la classe MyTask a titolo esemplificativo ma qui verrà scelto il nome che
si riterrà più adatto; - l'implementazione del metodo onStartJob. Questo metodo viene invocato per indicare che il job è stato avviato. Al suo interno possiamo
avviare attività o eseguire notifiche ma dobbiamo sempre tenere a mente che esso lavora sul thread main, lo stesso dell'interfaccia utente quindi
qui non siamo ancora in modalità asincrona: abbiamo bisogno di attivare un thread secondario. Il codice riportato in precedenza va completato con le operazioni
di nostro interesse ma si noti che il metodo termina con unreturn true
, questo serve ad avvisare l'app che anche dopo la fine di
onStartJob il servizio dovrà completare la sua esecuzione finchè non verrà invocato il metodo jobFinished. Durante l'esecuzione, quindi a
partire da onStartJob fino al suo termine, viene stabilito un wavelock che eviterà la sospensione delle capacità di elaborazione del dispositivo; - l'implementazione del metodo onStopJob. Viene invocato nel momento in cui il JobScheduler si rende conto che l'esecuzione del task è finita. Questo è il
punto in cui possiamo dismettere tutte le risorse che abbiamo avviato.
Il service che definiamo va comunque dichiarato nel Manifest e, considerando che il JobService è sottoposto alla permission BIND_JOB_SERVICE, vi si inserisce il seguente nodo:
<service android:name="MyTask"
android:permission="android.permission.BIND_JOB_SERVICE" >
Abbiamo indicato come valore della proprietàandroid:name in MyTask ma ciò, come immaginabile, deve essere modificato con il nome che abbiamo
attribuito alla classe.
Creazione del JobInfo
Una volta definito il JobService possiamo creare l'oggetto JobInfo. Come il service rappresenta il contesto operativo in cui l'attività asincrona
sarà eseguita così il JobInfo tratteggerà tutte le condizioni e le modalità in cui esso dovrà essere avviato. Eccone un esempio:
// il Builder richiede la componente relativa al Service
JobInfo.Builder builder=new JobInfo.Builder(1,
new ComponentName(getPackageName(), MyTask.class.getName()));
// impostazione delle condizioni
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
// creazione del JobInfo
JobInfo jobinfo=builder.build();
Il pattern con cui viene creato è quello del Builder: creeremo prima un Builder, su questo saranno invocati diversi metodi che imposteranno
le proprietà dell'oggetto che sta per nascere, infine invocheremo il metodo build per completare la fase di costruzione. Il costruttore
del Builder riceve come input un identificativo numerico e la componente che indica il nostro servizio di classe MyTask come anima operativa del
job. In questo esempio, invochiamo poi setRequiredNetworkType per specificare il tipo di connessione in rete che deve essere disponibile.
A questo punto abbiamo solo definito il necessario ma niente è stato avviato: manca il lancio del task.
Creazione del JobInfo
Ciò che serve adesso è creare l'oggetto JobScheduler che viene richiesto come un System Service mediante il metodo getSystemService
chiamato sul Context. Con il metodo schedule l'oggetto jobinfo che è stato creato poco fa viene indicato per l'avvio. Non eseguiamo noi il
vero e proprio "start" dell'attività ma sarà il JobScheduler a individuare il momento in cui le varie circostanze che ne rendono possibile il
funzionamento si saranno verificate.
// creazione del JobScheduler
JobScheduler scheduler= (JobScheduler) this.getSystemService(JOB_SCHEDULER_SERVICE);
// schedulazione del JobInfo
int res=scheduler.schedule(jobinfo);
// valutazione del risultato
if (res==JobScheduler.RESULT_SUCCESS)
// attività da svolgere in caso di successo
else
// attività da svolgere in caso di errore
Il metodo schedule fornisce un risultato, che qui incapsuliamo nella variabile res, che indica se l'operazione ha avuto successo. Se il suo
risultato, di tipo numerico intero, sarà corrispondente alla costante JobScheduler.RESULT_SUCCESS allora potremo dire che il tutto è andato
a buon fine. Come vediamo è sufficiente un costrutto if per poter emettere notifiche o svolgere operazioni a seconda che si sia avuto successo o meno.