
Capita spesso che in un’applicazione Android dalle finalità non banali sia necessario attivare delle funzionalità da svolgere in background, non richiedendo – in pratica – l’interazione con l’utente. Casi comuni di questo tipo possono riguardare elaborazioni prolungate, scaricamento di dati, salvataggi di informazioni presso server remoti: la componente appositamente dedicata a queste circostanze è il Service. Per conferire più flessibilità a questo meccanismo Android, nella versione Lollipop (API 21), ha introdotto il JobScheduler, un servizio di sistema cui possiamo affidare dei “lavori da eseguire” e specificare in quali condizioni essi dovranno avere luogo.
L’unità che definirà il compito da svolgere è rappresentato dalla classe JobInfo cui forniremo, in primis, il componente che si incaricherà di eseguire l’attività asincrona, implementazione della classe JobSchedulerService (a sua volta, discendente del Service) e, successivamente, altre specifiche come l’impostazione di eventuali tempistiche (ad esempio, esecuzione periodica ad intervalli di tempo regolari), tipologia di rete da utilizzare (attività da eseguire solo se connessi a WiFi), condizioni del dispositivo (un task può essere eseguito solo se il dispositivo è in carica) o altre tra quelle previste.
Fondamentale ricordare che è impossibile utilizzare la classe JobSchedulerService senza specificare condizioni, cosa che tra l’altro non ne giustificherebbe la preferenza rispetto ad un normale Service.
JobSchedulerService: come funziona
Importante ricordare che il JobSchedulerService lavora in modalità sincrona quindi per metterlo in condizione di svolgere lavori asincroni è necessario attivare un thread secondario con uno dei vari metodi che conosciamo: API della concorrenza Java, AsyncTask o altro ancora. All’avvio del JobSchedulerService, verrà invocato il suo metodo onStartJob che restituisce un boolean: questo per lavori protratti nel tempo deve essere true. Proprio nel metodo onStartJob, all’occorrenza, conviene avviare il thread secondario. Da ricordare che in casi simili sarà necessario trattare adeguatamente la comunicazione tra thread per passare i risultati ottenuti nell’elaborazione asincrona verso il main thread. Anche per questo scopo si potrà far uso di vari metodi come un Handler o le forme di comunicazione instaurate da AsyncTask.
Infine, si potrà richiedere l’interruzione del JobSchedulerService invocando jobFinished che attiverà il metodo di callback onStopJob.
L’esempio
L’esempio seguente mostrerà come attivare un Job solo nel caso in cui il dispositivo sia collegato a rete WiFi. Il Service verrà “schedulato” al click di un Floating Action Button
e se il suo accodamento alle attività del JobScheluder andrà in porto apparirà una SnackBar come la seguente:
Il Service non svolgerà alcuna vera attività ma segnalerà la sua attivazione con una notifica nella status bar:
Tutto ciò sarà predisposto per funzionare solo se il dispositivo è collegato al WiFi pertanto potremo fare più prove constatando che in caso di disconnessione da WiFi il Job sarà correttamente “schedulato” ma verrà avviato solo al prossimo ricollegamento ad una rete di questo tipo.
Schedulazione di un Job
Il codice che segue è quanto viene svolto al click del Floating Action Button
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 1. definizione del JobInfo JobInfo.Builder builder=new JobInfo.Builder(1, new ComponentName(getPackageName(), UpdateJobService.class.getName())); // funziona solo con reti WiFi builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); JobInfo job=builder.build(); // 2. otteniamo un riferimento al JobScheduler JobScheduler scheduler= (JobScheduler) MainActivity.this.getSystemService(JOB_SCHEDULER_SERVICE); // 3. scheduliamo il Job int result=scheduler.schedule(job); // 4. verifichiamo se schedulazione andata in porto if (result==JobScheduler.RESULT_SUCCESS) Snackbar.make(view, "Job attivato", Snackbar.LENGTH_LONG).show(); else Snackbar.make(view, "Errore: Job non attivato", Snackbar.LENGTH_LONG).show(); } });
Per prima cosa, definiremo un oggetto JobInfo che specificherà Service da attivare e condizioni di avvio. Successivamente, passeremo alla schedulazione vera e propria tramite il metodo schedule del JobScheduler verificandone il risultato.
Notiamo che non saremo noi a far partire il Job: lo affideremo semplicemente al JobScheduler che valuterà il momento migliore per avviarlo.
Quello che segue è il JobService:
public class UpdateJobService extends JobService { private static final int NOTIF_ID=100; @Override public boolean onStartJob(JobParameters params) { Calendar cal= Calendar.getInstance(); String when=String.format("%02d:%02d:%02d", cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE),cal.get(Calendar.SECOND)); Notification notification = new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.ic_dialog_alert) .setContentTitle("Job eseguito") .setContentText("Scattato alle "+when) .build(); NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager.notify(NOTIF_ID, notification); return true; } @Override public boolean onStopJob(JobParameters jobParameters) { return true; } }
All’avvio del Job viene invocato onStartJob. Come già detto, questo metodo lavora sul thread principale. Visto che dobbiamo solo inviare una notifica evitiamo di aprire un thread secondario ma ciò sarà necessario per attività prolungate.
Conclusioni
Lo JobScheduler pone soluzioni a molte casistiche applicative in cui si sentiva la necessità di utilizzare un Service per svolgere attività in background ma era complicato definirne le condizioni di attivazione. A differenza di un comune Service, il JobSchedulerService non viene avviato, se ne configura l’ambito di attività ed il sistema si occupa di gestirlo al meglio.
L’argomento è molto interessante e si presta ad interessanti considerazioni architetturali. Gli approfondimenti che merita verranno affrontati nei prossimi articoli.
No Responses to “Android: avviare attività in background con JobScheduler”