SlideShare a Scribd company logo
1 of 31
Android
Lavorare in background
Chi sono
Francesco D’Amico
francesco.damico@eng.it
spawn150@gmail.com
@spawn150
spawn150
� UIThread e Multithreading Android
� AsyncTask
� HandlerThread
� Service e IntentService
Agenda
UI Thread e il multithreading
Threads in Android
Main Thread (o UI Thread)
Processo
Main Thread (UI Thread)
System Events Input Events ApplicationService Alarm UI Drawing
Nostro Codice
Disegno della UI
Processo
Main Thread (UI Thread)
App Events UI Drawing UI DrawingUI Drawing UI Drawing UI Drawing
{
16 ms
{
16 ms
{
16 ms
{
16 ms
{
16 ms
1000 ms / 60 frames = 16,666666667 ms / frame
Disegno della UI - Dropped Frames
Processo
Main Thread (UI Thread)
App Events UI Drawing UI Drawing UI DrawingNostra fantastica funzione!
{
16 ms
{
16 ms
{
16 ms
{
16 ms
{
16 ms
Dropped Frames
Input event
Disegno della UI - Worker Thread
Processo
Main Thread (UI Thread)
App Events UI Drawing UI Drawing UI DrawingNostra fantastica funzione!
Input event
Worker Thread
Nostra fantastica funzione!
UI Drawing UI Drawing
Lavorare in background con un Thread
Main Thread (UI Thread)
Input Event
Worker Thread
Nostra fantastica funzione!
Aggiorno UI
runOnUIThread()
Quali operazioni dovrebbero essere gestite in un
background thread?
� Comunicazione HTTP
� Lettura / Scrittura di un file
� Operazioni su Database
� Lettura / Scrittura nelle SharedPreferences
� Elaborazione di immagini
� Parsing di testo / JSON / XML
� ...tutto ciò che non riguarda la UI ;-)
Quali operazioni gestire in background thread
Cosa ci mette a disposizione Android per
gestire i threads?
AsyncTask
Permette di eseguire il lavoro in background e pubblicare i risultati sul UIThread senza gestire
direttamente i threads
HandlerThread
Permette di eseguire dei tasks in sequenza in una MessageQueue
IntentService
Permette di eseguire Intents fuori dal UIThread
Componenti per gestire i threads
Looper, Handler, Message e MessageQueue
Il cuore della gestione dei Threads in Android
� I componenti core del multithreading nel SDK di Android sono:
� Looper, Handler, Message e MessageQueue
� Lo UIThread è basato su tali componenti
� Tutti i worker threads, creati con i componenti messi a
disposizione dal SDK, sono costruiti sopra i componenti core
� Permette di rendere safe la gestione dei threads
Il core del multithreading in Android
Dettaglio del core del multithreading
Looper MessageQueue MessageHandler
1 1 0…*11…* 1
Come funziona?
MessageQueue
Looper
Handler
Handler
Looper.prepare()
Looper.loop()
Looper.myLooper().quit()
now
Handler handlerA = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.d("HANDLERA", "Message: "+msg.what);
return true;
}
});
Message messageA1 = handlerA.obtainMessage(0);
Message messageA2 = handlerA.obtainMessage(1);
Handler handlerB = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.d("HANDLERB", "Message: "+msg.what);
return true;
}
});
Message messageB1 = handlerB.obtainMessage(2);
Message messageB2 = handlerB.obtainMessage(3);
handlerA.sendMessage(messageA1);
handlerB.sendMessage(messageB1);
handlerA.sendMessage(messageA2);
handlerB.sendMessage(messageB2);
Quiz Time / 1
it.englab.androidcourse.threads D/HANDLERA: Message: 0
it.englab.androidcourse.threads D/HANDLERB: Message: 2
it.englab.androidcourse.threads D/HANDLERA: Message: 1
it.englab.androidcourse.threads D/HANDLERB: Message: 3
Handler handlerA = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.d("HANDLERA", "Message: "+msg.what);
return true;
}
});
Message messageA1 = handlerA.obtainMessage(0);
Message messageA2 = handlerA.obtainMessage(1);
Handler handlerB = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.d("HANDLERB", "Message: "+msg.what);
return true;
}
});
Message messageB1 = handlerB.obtainMessage(2);
Message messageB2 = handlerB.obtainMessage(3);
Message messageWithTarget = Message.obtain();
messageWithTarget.what = 4;
messageWithTarget.setTarget(handlerA);
handlerA.sendMessage(messageA1);
handlerB.sendMessage(messageB1);
handlerB.post(new Runnable() {
@Override
public void run() {
Log.d("RUNNABLE", "Runnable task!");
}
});
messageWithTarget.sendToTarget();
handlerA.sendMessage(messageA2);
handlerB.sendMessage(messageB2);
Quiz Time / 2
it.englab.androidcourse.threads D/HANDLERA: Message: 0
it.englab.androidcourse.threads D/HANDLERB: Message: 2
it.englab.androidcourse.threads D/RUNNABLE: Runnable task!
it.englab.androidcourse.threads D/HANDLERA: Message: 4
it.englab.androidcourse.threads D/HANDLERA: Message: 1
it.englab.androidcourse.threads D/HANDLERB: Message: 3
Gestire il lavoro pesante in background e interagire con la UI
AsyncTask
� Si crea estendendo la classe AsyncTask<Params, Progress,
Result>
� E’ ideale per operazioni non molto lunghe e che aggiornano la UI
� Di default più esecuzioni di un AsyncTask sono eseguite in
sequenza
� E’ possibile interrompere un AsyncTask
� ATTENZIONE! - E’ facile creare dei memory leaks!!!
AsyncTask - Cos’è?
Lavorare in background con un AsyncTask
Main Thread (UI Thread)
onPreExecute()
Worker Thread
doInBackground(){
Nostra fantastica funzione!
}
onPostExecute()onProgressUpdate() onProgressUpdate()
AsyncTask - Esempio
private class MyAsyncTask extends AsyncTask<Integer, Integer, String> {
@Override
protected void onPreExecute() {
...
}
@Override
protected void onProgressUpdate(Integer... values) {
...
}
@Override
protected String doInBackground(Integer... params) {
...
}
@Override
protected void onPostExecute(String s) {
...
}
@Override
protected void onCancelled() {
...
}
}
MyAsyncTask myAsyncTask = new MyAsyncTask();
myAsyncTask.execute(10);
MyAsyncTask myAsyncTask = new MyAsyncTask();
myAsyncTask.executeOnExecutor(
AsyncTask.THREAD_POOL_EXECUTOR, 10);
Esecuzione seriale
Esecuzione in parallelo
Postare messaggi in una MessageQueue
HandlerThread
� Si crea estendendo la classe HandlerThread oppure è possibile
utilizzare la classe fornita dal SDK
� E’ ideale per eseguire lunghe operazioni in background
mantenendo il controllo dell’ordine di esecuzione
� ATTENZIONE! - definire la priority. Di default ha la stessa priority
del UIThread (usare la priority di Linux es.:
Process.THREAD_PRIORITY_BACKGROUND)
� ATTENZIONE! - invocare i metodi quit() o quitSafely()
quando si vuole terminare il thread!!!
HandlerThread - Cos’è?
Come funziona?
MessageQueue
Looper
Handler
Handler
new HandlerThread().start()
myHandlerThread.quit()
now
Gestire gli intents senza UI
Service e IntentService
� Si creano estendendo rispettivamente le classi Service e
IntentService
� Devono essere dichiarati nel AndroidManifest.xml con il tag
<service>
� Sono ideali per eseguire lunghe operazioni in background
nascoste all’utente o invocabili da altre applicazioni
� Se il processo è terminato dal sistema operativo hanno la
possibilità di ripartire automaticamente
� ATTENZIONE! - Il Service è eseguito sullo UI Thread
� ATTENZIONE! - L’IntentService estende Service ed è reso
disponibile dal SDK per gestire operazioni su un Worker Thread
Service e IntentService - Cosa sono?
Service - Esempio
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) { /* Unico metodo obbligatorio da implementare */}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
Intent intent = new Intent(context, MyService.class);
context.startService(intent);
Intent intent = new Intent(context, MyService.class);
context.bindService(intent, mConnection,
Context.BIND_AUTO_CREATE);
Start Service
Bind Service
Data la sequenza di metodi invocati: startService(),
startService(), bindService(), stopService(),
unbindService(), stopService()
Quando viene invocato il metodo onDestroy() del Service?
A.Al primo stopService()
B.Dopo il metodo unbindService()
C.Dopo l’esecuzione dei due stopService()
Quiz Time
Grazie!
Domande?
francesco.damico@eng.it
spawn150@gmail.com
@spawn150
spawn150

More Related Content

Similar to 06 Android - lavorare in background

breve introduzione a node.js
breve introduzione a node.jsbreve introduzione a node.js
breve introduzione a node.jsnetmeansnet
 
Reactive programming principles
Reactive programming principlesReactive programming principles
Reactive programming principlesRiccardo Cardin
 
Javascript - 7 | WebMaster & WebDesigner
Javascript - 7 | WebMaster & WebDesignerJavascript - 7 | WebMaster & WebDesigner
Javascript - 7 | WebMaster & WebDesignerMatteo Magni
 
Corso avanzato di Tecnologie WEB - jquery e d3js
Corso avanzato di Tecnologie WEB - jquery e d3jsCorso avanzato di Tecnologie WEB - jquery e d3js
Corso avanzato di Tecnologie WEB - jquery e d3jsStudiabo
 
Sviluppare per Microsoft Band
Sviluppare per Microsoft BandSviluppare per Microsoft Band
Sviluppare per Microsoft BandMassimo Bonanni
 
Async/Await: make it simple!!
Async/Await: make it simple!!Async/Await: make it simple!!
Async/Await: make it simple!!Massimo Bonanni
 
High specialized vm on open stack cloud
High specialized vm on open stack cloudHigh specialized vm on open stack cloud
High specialized vm on open stack cloudGabriele Baldoni
 
Jakarta Struts
Jakarta StrutsJakarta Struts
Jakarta Strutsjgiudici
 
Sviluppare per microsoft band
Sviluppare per microsoft bandSviluppare per microsoft band
Sviluppare per microsoft bandDotNetCampus
 
SVILUPPARE PER MICROSOFT BAND
SVILUPPARE PER MICROSOFT BANDSVILUPPARE PER MICROSOFT BAND
SVILUPPARE PER MICROSOFT BANDDotNetCampus
 
Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...
Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...
Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...Codemotion
 
Twcrashcourse
TwcrashcourseTwcrashcourse
Twcrashcourserik0
 
E suap - tecnologie client
E suap - tecnologie client E suap - tecnologie client
E suap - tecnologie client Sabino Labarile
 
Asp.NET MVC Framework
Asp.NET MVC FrameworkAsp.NET MVC Framework
Asp.NET MVC FrameworkDotNetMarche
 
MongoDB User Group Padova - Overviews iniziale su MongoDB
MongoDB User Group Padova - Overviews iniziale su MongoDBMongoDB User Group Padova - Overviews iniziale su MongoDB
MongoDB User Group Padova - Overviews iniziale su MongoDBStefano Dindo
 
jQuery - 1 | WebMaster & WebDesigner
jQuery - 1 | WebMaster & WebDesignerjQuery - 1 | WebMaster & WebDesigner
jQuery - 1 | WebMaster & WebDesignerMatteo Magni
 
Multithreading, multiprocessing e Asincronia
Multithreading, multiprocessing e AsincroniaMultithreading, multiprocessing e Asincronia
Multithreading, multiprocessing e AsincroniaSebastiano Merlino (eTr)
 

Similar to 06 Android - lavorare in background (20)

breve introduzione a node.js
breve introduzione a node.jsbreve introduzione a node.js
breve introduzione a node.js
 
Rest sdk
Rest sdkRest sdk
Rest sdk
 
Reactive programming principles
Reactive programming principlesReactive programming principles
Reactive programming principles
 
#dd12 Applicazioni a tre voci (Android e Domino)
#dd12 Applicazioni a tre voci (Android e Domino)#dd12 Applicazioni a tre voci (Android e Domino)
#dd12 Applicazioni a tre voci (Android e Domino)
 
ASP.NET Core essentials
ASP.NET Core essentialsASP.NET Core essentials
ASP.NET Core essentials
 
Javascript - 7 | WebMaster & WebDesigner
Javascript - 7 | WebMaster & WebDesignerJavascript - 7 | WebMaster & WebDesigner
Javascript - 7 | WebMaster & WebDesigner
 
Corso avanzato di Tecnologie WEB - jquery e d3js
Corso avanzato di Tecnologie WEB - jquery e d3jsCorso avanzato di Tecnologie WEB - jquery e d3js
Corso avanzato di Tecnologie WEB - jquery e d3js
 
Sviluppare per Microsoft Band
Sviluppare per Microsoft BandSviluppare per Microsoft Band
Sviluppare per Microsoft Band
 
Async/Await: make it simple!!
Async/Await: make it simple!!Async/Await: make it simple!!
Async/Await: make it simple!!
 
High specialized vm on open stack cloud
High specialized vm on open stack cloudHigh specialized vm on open stack cloud
High specialized vm on open stack cloud
 
Jakarta Struts
Jakarta StrutsJakarta Struts
Jakarta Struts
 
Sviluppare per microsoft band
Sviluppare per microsoft bandSviluppare per microsoft band
Sviluppare per microsoft band
 
SVILUPPARE PER MICROSOFT BAND
SVILUPPARE PER MICROSOFT BANDSVILUPPARE PER MICROSOFT BAND
SVILUPPARE PER MICROSOFT BAND
 
Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...
Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...
Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...
 
Twcrashcourse
TwcrashcourseTwcrashcourse
Twcrashcourse
 
E suap - tecnologie client
E suap - tecnologie client E suap - tecnologie client
E suap - tecnologie client
 
Asp.NET MVC Framework
Asp.NET MVC FrameworkAsp.NET MVC Framework
Asp.NET MVC Framework
 
MongoDB User Group Padova - Overviews iniziale su MongoDB
MongoDB User Group Padova - Overviews iniziale su MongoDBMongoDB User Group Padova - Overviews iniziale su MongoDB
MongoDB User Group Padova - Overviews iniziale su MongoDB
 
jQuery - 1 | WebMaster & WebDesigner
jQuery - 1 | WebMaster & WebDesignerjQuery - 1 | WebMaster & WebDesigner
jQuery - 1 | WebMaster & WebDesigner
 
Multithreading, multiprocessing e Asincronia
Multithreading, multiprocessing e AsincroniaMultithreading, multiprocessing e Asincronia
Multithreading, multiprocessing e Asincronia
 

06 Android - lavorare in background

  • 3. � UIThread e Multithreading Android � AsyncTask � HandlerThread � Service e IntentService Agenda
  • 4. UI Thread e il multithreading Threads in Android
  • 5. Main Thread (o UI Thread) Processo Main Thread (UI Thread) System Events Input Events ApplicationService Alarm UI Drawing Nostro Codice
  • 6. Disegno della UI Processo Main Thread (UI Thread) App Events UI Drawing UI DrawingUI Drawing UI Drawing UI Drawing { 16 ms { 16 ms { 16 ms { 16 ms { 16 ms 1000 ms / 60 frames = 16,666666667 ms / frame
  • 7. Disegno della UI - Dropped Frames Processo Main Thread (UI Thread) App Events UI Drawing UI Drawing UI DrawingNostra fantastica funzione! { 16 ms { 16 ms { 16 ms { 16 ms { 16 ms Dropped Frames Input event
  • 8. Disegno della UI - Worker Thread Processo Main Thread (UI Thread) App Events UI Drawing UI Drawing UI DrawingNostra fantastica funzione! Input event Worker Thread Nostra fantastica funzione! UI Drawing UI Drawing
  • 9. Lavorare in background con un Thread Main Thread (UI Thread) Input Event Worker Thread Nostra fantastica funzione! Aggiorno UI runOnUIThread()
  • 10. Quali operazioni dovrebbero essere gestite in un background thread?
  • 11. � Comunicazione HTTP � Lettura / Scrittura di un file � Operazioni su Database � Lettura / Scrittura nelle SharedPreferences � Elaborazione di immagini � Parsing di testo / JSON / XML � ...tutto ciò che non riguarda la UI ;-) Quali operazioni gestire in background thread
  • 12. Cosa ci mette a disposizione Android per gestire i threads?
  • 13. AsyncTask Permette di eseguire il lavoro in background e pubblicare i risultati sul UIThread senza gestire direttamente i threads HandlerThread Permette di eseguire dei tasks in sequenza in una MessageQueue IntentService Permette di eseguire Intents fuori dal UIThread Componenti per gestire i threads
  • 14. Looper, Handler, Message e MessageQueue Il cuore della gestione dei Threads in Android
  • 15. � I componenti core del multithreading nel SDK di Android sono: � Looper, Handler, Message e MessageQueue � Lo UIThread è basato su tali componenti � Tutti i worker threads, creati con i componenti messi a disposizione dal SDK, sono costruiti sopra i componenti core � Permette di rendere safe la gestione dei threads Il core del multithreading in Android
  • 16. Dettaglio del core del multithreading Looper MessageQueue MessageHandler 1 1 0…*11…* 1
  • 18. Handler handlerA = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { Log.d("HANDLERA", "Message: "+msg.what); return true; } }); Message messageA1 = handlerA.obtainMessage(0); Message messageA2 = handlerA.obtainMessage(1); Handler handlerB = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { Log.d("HANDLERB", "Message: "+msg.what); return true; } }); Message messageB1 = handlerB.obtainMessage(2); Message messageB2 = handlerB.obtainMessage(3); handlerA.sendMessage(messageA1); handlerB.sendMessage(messageB1); handlerA.sendMessage(messageA2); handlerB.sendMessage(messageB2); Quiz Time / 1 it.englab.androidcourse.threads D/HANDLERA: Message: 0 it.englab.androidcourse.threads D/HANDLERB: Message: 2 it.englab.androidcourse.threads D/HANDLERA: Message: 1 it.englab.androidcourse.threads D/HANDLERB: Message: 3
  • 19. Handler handlerA = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { Log.d("HANDLERA", "Message: "+msg.what); return true; } }); Message messageA1 = handlerA.obtainMessage(0); Message messageA2 = handlerA.obtainMessage(1); Handler handlerB = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { Log.d("HANDLERB", "Message: "+msg.what); return true; } }); Message messageB1 = handlerB.obtainMessage(2); Message messageB2 = handlerB.obtainMessage(3); Message messageWithTarget = Message.obtain(); messageWithTarget.what = 4; messageWithTarget.setTarget(handlerA); handlerA.sendMessage(messageA1); handlerB.sendMessage(messageB1); handlerB.post(new Runnable() { @Override public void run() { Log.d("RUNNABLE", "Runnable task!"); } }); messageWithTarget.sendToTarget(); handlerA.sendMessage(messageA2); handlerB.sendMessage(messageB2); Quiz Time / 2 it.englab.androidcourse.threads D/HANDLERA: Message: 0 it.englab.androidcourse.threads D/HANDLERB: Message: 2 it.englab.androidcourse.threads D/RUNNABLE: Runnable task! it.englab.androidcourse.threads D/HANDLERA: Message: 4 it.englab.androidcourse.threads D/HANDLERA: Message: 1 it.englab.androidcourse.threads D/HANDLERB: Message: 3
  • 20. Gestire il lavoro pesante in background e interagire con la UI AsyncTask
  • 21. � Si crea estendendo la classe AsyncTask<Params, Progress, Result> � E’ ideale per operazioni non molto lunghe e che aggiornano la UI � Di default più esecuzioni di un AsyncTask sono eseguite in sequenza � E’ possibile interrompere un AsyncTask � ATTENZIONE! - E’ facile creare dei memory leaks!!! AsyncTask - Cos’è?
  • 22. Lavorare in background con un AsyncTask Main Thread (UI Thread) onPreExecute() Worker Thread doInBackground(){ Nostra fantastica funzione! } onPostExecute()onProgressUpdate() onProgressUpdate()
  • 23. AsyncTask - Esempio private class MyAsyncTask extends AsyncTask<Integer, Integer, String> { @Override protected void onPreExecute() { ... } @Override protected void onProgressUpdate(Integer... values) { ... } @Override protected String doInBackground(Integer... params) { ... } @Override protected void onPostExecute(String s) { ... } @Override protected void onCancelled() { ... } } MyAsyncTask myAsyncTask = new MyAsyncTask(); myAsyncTask.execute(10); MyAsyncTask myAsyncTask = new MyAsyncTask(); myAsyncTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR, 10); Esecuzione seriale Esecuzione in parallelo
  • 24. Postare messaggi in una MessageQueue HandlerThread
  • 25. � Si crea estendendo la classe HandlerThread oppure è possibile utilizzare la classe fornita dal SDK � E’ ideale per eseguire lunghe operazioni in background mantenendo il controllo dell’ordine di esecuzione � ATTENZIONE! - definire la priority. Di default ha la stessa priority del UIThread (usare la priority di Linux es.: Process.THREAD_PRIORITY_BACKGROUND) � ATTENZIONE! - invocare i metodi quit() o quitSafely() quando si vuole terminare il thread!!! HandlerThread - Cos’è?
  • 27. Gestire gli intents senza UI Service e IntentService
  • 28. � Si creano estendendo rispettivamente le classi Service e IntentService � Devono essere dichiarati nel AndroidManifest.xml con il tag <service> � Sono ideali per eseguire lunghe operazioni in background nascoste all’utente o invocabili da altre applicazioni � Se il processo è terminato dal sistema operativo hanno la possibilità di ripartire automaticamente � ATTENZIONE! - Il Service è eseguito sullo UI Thread � ATTENZIONE! - L’IntentService estende Service ed è reso disponibile dal SDK per gestire operazioni su un Worker Thread Service e IntentService - Cosa sono?
  • 29. Service - Esempio public class MyService extends Service { @Override public void onCreate() { super.onCreate(); } @Override public IBinder onBind(Intent intent) { /* Unico metodo obbligatorio da implementare */} @Override public int onStartCommand(Intent intent, int flags, int startId) { } @Override public void onDestroy() { super.onDestroy(); } } Intent intent = new Intent(context, MyService.class); context.startService(intent); Intent intent = new Intent(context, MyService.class); context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); Start Service Bind Service
  • 30. Data la sequenza di metodi invocati: startService(), startService(), bindService(), stopService(), unbindService(), stopService() Quando viene invocato il metodo onDestroy() del Service? A.Al primo stopService() B.Dopo il metodo unbindService() C.Dopo l’esecuzione dei due stopService() Quiz Time

Editor's Notes

  1. https://medium.com/google-developers/developing-for-android-i-understanding-the-mobile-context-fd2351b131f8#.kcbu40usv ciclo : Measure->Layout->Draw In onDraw(Canvas) Canvas object generates (or updates) a list of OpenGL-ES commands (displayList) to send to the GPU. RenderThread (added in L) is a thread helping the busy UI thread with the conversion of display lists to OpenGL commands, and sends them to the GPU. During which, the UI thread can start processing next frame.
  2. https://developer.android.com/reference/android/os/AsyncTask.html
  3. https://developer.android.com/reference/android/os/AsyncTask.html
  4. https://developer.android.com/guide/components/services.html