{"id":12681,"date":"2017-09-01T10:27:05","date_gmt":"2017-09-01T08:27:05","guid":{"rendered":"http:\/\/www.devapp.it\/wordpress\/?p=12681"},"modified":"2017-09-01T10:27:05","modified_gmt":"2017-09-01T08:27:05","slug":"android-avviare-attivita-in-background-con-jobscheduler","status":"publish","type":"post","link":"https:\/\/www.devapp.it\/wordpress\/android-avviare-attivita-in-background-con-jobscheduler\/","title":{"rendered":"Android: avviare attivit\u00e0 in background con JobScheduler"},"content":{"rendered":"<p>Capita spesso che in un&#8217;applicazione Android dalle finalit\u00e0 non banali sia necessario attivare delle funzionalit\u00e0 da svolgere in background, non richiedendo &#8211; in pratica &#8211; l&#8217;interazione con l&#8217;utente. Casi comuni di questo tipo possono riguardare elaborazioni prolungate, scaricamento di dati, salvataggi di informazioni presso server remoti:\u00a0la componente appositamente dedicata a queste circostanze \u00e8 il Service. Per conferire pi\u00f9 flessibilit\u00e0 a questo meccanismo Android, nella versione Lollipop (API 21), ha introdotto il <strong>JobScheduler<\/strong>, un servizio di sistema cui possiamo affidare dei &#8220;lavori da eseguire&#8221; e specificare in quali condizioni essi dovranno avere luogo.<\/p>\n<p>L&#8217;unit\u00e0 che definir\u00e0 il compito da svolgere \u00e8 rappresentato dalla classe <strong>JobInfo<\/strong> cui forniremo, in primis, il componente che si incaricher\u00e0 di eseguire\u00a0l&#8217;attivit\u00e0 asincrona, implementazione della classe <strong>JobSchedulerService<\/strong> (a sua volta, discendente\u00a0del \u00a0Service) e, successivamente, altre specifiche\u00a0come\u00a0l&#8217;impostazione di eventuali tempistiche (ad esempio, esecuzione periodica ad intervalli di tempo regolari), tipologia di rete da utilizzare (attivit\u00e0 da eseguire solo se connessi a WiFi), condizioni del dispositivo (un task pu\u00f2 essere eseguito solo se il dispositivo \u00e8 in carica)\u00a0o altre tra quelle previste.<\/p>\n<p>Fondamentale ricordare che \u00e8 <strong>impossibile utilizzare la classe JobSchedulerService senza specificare condizioni<\/strong>, cosa che tra l&#8217;altro non ne giustificherebbe la preferenza rispetto ad un normale Service.<\/p>\n<h2>JobSchedulerService: come funziona<\/h2>\n<p>Importante ricordare che il JobSchedulerService lavora in modalit\u00e0 sincrona quindi per metterlo in condizione di svolgere lavori asincroni \u00e8 necessario attivare un thread secondario con uno dei vari metodi che conosciamo: API della concorrenza Java, AsyncTask o altro ancora. All&#8217;avvio del JobSchedulerService, verr\u00e0 invocato il suo metodo <strong>onStartJob<\/strong> che restituisce un <em>boolean<\/em>: questo per lavori protratti nel tempo deve essere <em>true<\/em>. Proprio nel metodo <em>onStartJob<\/em>, all&#8217;occorrenza, conviene avviare il thread secondario. Da ricordare che\u00a0in casi simili sar\u00e0 necessario trattare adeguatamente la comunicazione tra thread per passare i risultati ottenuti nell&#8217;elaborazione asincrona verso il main thread. Anche per questo scopo si potr\u00e0 far uso di vari metodi come un Handler o le forme di comunicazione instaurate da AsyncTask.<\/p>\n<p>Infine, si potr\u00e0 richiedere l&#8217;interruzione del JobSchedulerService invocando\u00a0<em>jobFinished<\/em> che attiver\u00e0 il metodo di callback <strong>onStopJob<\/strong>.<\/p>\n<h2>L&#8217;esempio<\/h2>\n<p>L&#8217;esempio seguente mostrer\u00e0 come attivare un Job \u00a0solo nel caso in cui il dispositivo sia collegato a rete WiFi. Il Service\u00a0verr\u00e0 &#8220;schedulato&#8221;\u00a0al click di un Floating Action Button<\/p>\n<p><a href=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2016\/12\/android-jobscheduler_service_01.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-12686\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2016\/12\/android-jobscheduler_service_01-174x300.jpg\" alt=\"\" width=\"174\" height=\"300\" srcset=\"https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2016\/12\/android-jobscheduler_service_01-174x300.jpg 174w, https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2016\/12\/android-jobscheduler_service_01.jpg 261w\" sizes=\"auto, (max-width: 174px) 100vw, 174px\" \/><\/a><\/p>\n<p>e se il suo accodamento alle attivit\u00e0 del JobScheluder andr\u00e0\u00a0in porto apparir\u00e0 una SnackBar come la seguente:<\/p>\n<p><a href=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2016\/12\/android-jobscheduler_service_02-1.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-12687\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2016\/12\/android-jobscheduler_service_02-1.jpg\" alt=\"\" width=\"260\" height=\"128\" \/><\/a><\/p>\n<p>Il Service non svolger\u00e0 alcuna vera attivit\u00e0 ma segnaler\u00e0 la sua attivazione con una notifica nella status bar:<\/p>\n<p><a href=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2016\/12\/android-jobscheduler_service_03.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-12684\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2016\/12\/android-jobscheduler_service_03-300x132.jpg\" alt=\"\" width=\"300\" height=\"132\" \/><\/a><\/p>\n<p>Tutto ci\u00f2 sar\u00e0 predisposto per funzionare\u00a0solo se il dispositivo \u00e8 collegato al WiFi pertanto potremo fare pi\u00f9 prove constatando che in caso di disconnessione da WiFi il Job sar\u00e0 correttamente &#8220;schedulato&#8221; ma verr\u00e0 avviato solo al prossimo ricollegamento ad una rete di questo tipo.<\/p>\n<h2>Schedulazione di un Job<\/h2>\n<p>Il codice che segue \u00e8 quanto viene svolto al <em>click<\/em> del Floating Action Button<\/p>\n<pre class=\"lang:java decode:true\">FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);\r\n        fab.setOnClickListener(new View.OnClickListener() {\r\n            @Override\r\n            public void onClick(View view) {\r\n                \/\/ 1. definizione del JobInfo\r\n                JobInfo.Builder builder=new JobInfo.Builder(1,\r\n                        new ComponentName(getPackageName(), UpdateJobService.class.getName()));\r\n                \/\/ funziona solo con reti WiFi\r\n                builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);\r\n                JobInfo job=builder.build();\r\n\r\n                \/\/ 2. otteniamo un riferimento al JobScheduler\r\n                JobScheduler scheduler= (JobScheduler) MainActivity.this.getSystemService(JOB_SCHEDULER_SERVICE);\r\n             \r\n                \/\/ 3. scheduliamo il Job\r\n                int result=scheduler.schedule(job);\r\n               \r\n                \/\/ 4. verifichiamo se schedulazione andata in porto\r\n                if (result==JobScheduler.RESULT_SUCCESS)\r\n                    Snackbar.make(view, \"Job attivato\", Snackbar.LENGTH_LONG).show();\r\n                else\r\n                    Snackbar.make(view, \"Errore: Job non attivato\", Snackbar.LENGTH_LONG).show();\r\n            }\r\n        });<\/pre>\n<p>Per prima cosa, definiremo un oggetto JobInfo che specificher\u00e0 Service da attivare e condizioni di avvio. Successivamente, passeremo alla schedulazione vera e propria tramite il metodo <em>schedule<\/em> del JobScheduler verificandone il risultato.<\/p>\n<p>Notiamo che\u00a0<strong>non saremo noi a far partire il Job<\/strong>: lo affideremo semplicemente al JobScheduler che valuter\u00e0 il momento migliore per avviarlo.<\/p>\n<p>Quello che segue \u00e8 il JobService:<\/p>\n<pre class=\"lang:java decode:true\">public class UpdateJobService extends JobService {\r\n\r\n    private static final int NOTIF_ID=100;\r\n\r\n    @Override\r\n    public boolean onStartJob(JobParameters params) {\r\n        Calendar cal= Calendar.getInstance();\r\n        String when=String.format(\"%02d:%02d:%02d\", cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE),cal.get(Calendar.SECOND));\r\n       \r\n        Notification notification = new NotificationCompat.Builder(this)\r\n                .setSmallIcon(android.R.drawable.ic_dialog_alert)\r\n                .setContentTitle(\"Job eseguito\")\r\n                .setContentText(\"Scattato alle \"+when)\r\n                .build();\r\n\r\n        NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);\r\n        notificationManager.notify(NOTIF_ID, notification);\r\n\r\n        return true;\r\n    }\r\n\r\n    @Override\r\n    public boolean onStopJob(JobParameters jobParameters) {\r\n        return true;\r\n    }\r\n}<\/pre>\n<p>All&#8217;avvio del Job viene invocato <em>onStartJob<\/em>. Come gi\u00e0 detto, questo metodo lavora sul thread principale. Visto che dobbiamo solo inviare una notifica evitiamo di aprire un thread secondario ma ci\u00f2 sar\u00e0 necessario per attivit\u00e0 prolungate.<\/p>\n<h2>Conclusioni<\/h2>\n<p>Lo JobScheduler\u00a0pone soluzioni a molte casistiche applicative in cui si sentiva la necessit\u00e0 di utilizzare un Service per svolgere attivit\u00e0 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&#8217;ambito di attivit\u00e0 ed il sistema si occupa di gestirlo al meglio.<\/p>\n<p>L&#8217;argomento \u00e8 molto interessante e si presta ad interessanti considerazioni architetturali. Gli approfondimenti\u00a0che merita verranno affrontati nei prossimi articoli.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Capita spesso che in un&#8217;applicazione Android dalle finalit\u00e0 non banali sia necessario attivare delle funzionalit\u00e0 da svolgere&#8230;<\/p>\n","protected":false},"author":561,"featured_media":13222,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1682,1],"tags":[1278,1384,1832,1833,1831],"class_list":["post-12681","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-android","category-tutorial-pratici","tag-android","tag-background","tag-jobscheduler","tag-jobservice","tag-service"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts\/12681","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/users\/561"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/comments?post=12681"}],"version-history":[{"count":6,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts\/12681\/revisions"}],"predecessor-version":[{"id":12707,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts\/12681\/revisions\/12707"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/media\/13222"}],"wp:attachment":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/media?parent=12681"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/categories?post=12681"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/tags?post=12681"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}