Depuis maintenant 7 ans que je développe sous Android, ma principale préoccupation a toujours été l'architecture.
Et si nous prenions quelques heures pour en discuter ?
Je vous propose une vision globale et synthétique s'appuyant sur des exemples concrets, sur les principes et conseils de référence des équipes Google et sur des modèles d'architecture (MVP/n-tiers) et leur mises en place.
Au programme:
Le contexte Android,
L'objectif utilisateur,
La responsabilité du développeur,
Les bonnes pratiques (celles de Chet Haase, Romain Guy et les miennes),
Les principes d'architecture (n-tiers, MVP et MVVM),
Leur application sur Android (services, threads, Application ...),
Le déploiement continue,
Les librairies incontournables du moment,
Un exemple concret d'utilisation d'un service REST (up et download)
et bien sûr un projet github est associé à cette conférence pour que le code soit avec vous !
2. 2
And so ?
2
Avec un objectif utilisateur fort
Dans un contexte de pénurie et un monde complexe
De grosses responsabilités
Des bonnes pratiques
Des habitudes
Des Design Patterns
Et une p**n d'archi générique
Je m'en sors
comment, moi?
4. 4
Quand mon application est en
foreground, je veux toute la
puissance dont j'ai besoin !
Objectif
5. 5
Quand mon application part en background,
je veux restituer le plus de ressources possible au système
(pour que les autres puissent l'utiliser).
Fuck I am
going in
background
Take that
services !
Objectif
6. 6
Et comme je ne veux pas que mes utilisateurs subissent ça
Objectif
7. Et comme je ne veux pas que mes utilisateurs subissent ça
7
Ma philosophie est le LazyLoading
Objectif
11. 11
Démarre avant n'importe laquelle de vos classes
L'Objet Application
Conserve l'état global de l'application
Se termine après n'importe laquelle de vos classes
Est le Contexte de votre application
Application
13. Application
13
L'Objet Application
Et le retrouver où que vous soyez: Design Pattern du Singleton
public class DesignApplication extends Application {
private static DesignApplication instance;
public static DesignApplication getInstance() {
return instance;}
public void onCreate() {
super.onCreate();
instance = this;…}
16. 16
Application
User user=null;
public void getUser() {
if(user==null) {
//Do what you need to do
//1)startActivity(LoginActivtyIntent);
//ou 2)retireve it from a SharedPreference||DataBase||File...
}
return user;
}
La classe Application est responsable de l'instanciation
des champs qu'elle persiste
L'Objet Application
18. Les Services
"Service (la classe Android):
Service is how an application indicates to the operating system
that it needs to perform a longer-running operation outside of the
normal Activity UI flow.
It may be self-running (through Context.startService()), or running
on behalf of of another process (through Context.bindService()).
If you do not need either of these behaviors, you should not
use a Service."
Service Android == Service Système
19. Les Services
La couche Service (architecture n-tier):
Elle correspond à la partie fonctionnelle de l'application, celle qui
implémente la « logique », et qui décrit les opérations que
l'application opère sur les données.
Les différentes règles de gestion et de contrôle du système sont
mises en œuvre dans cette couche.
20. Les Services
Les services Android sont-ils des services métiers?
Les services métiers sont-ils des services Android ?
Don't fuck around
with the system !
21. Les Services
Doit être considéré comme une
Activity sans IHM...
21
Services
AndroidServices
SingletonServices
Assurer l'unique instance (best to
do that using a ServiceManager
instead of static)
S'exécute dans le Thread UI
Est fait pour des tâches longues
Quand actif est conservé dans le
LRUCache as long as possible....
Plus gros qu'un simple pojo
Est légère (simple pojo)
Ainsi sont les Services métiers côté
serveur
On peut/doit aussi maîtriser leur
cycle de vie
??
25. Les instancier à la demande
25
Il faut qu'on arrive avec nos services métiers à:
ArchiDroid
Ne les instancier QU'une seule fois
Mettre en place l'asynchronicité
Ne pas suivre le cycle de vie des activités
mais le cycle de vie de l'application
Les laisser terminer leur traitement
26. 26
Instanciation à la demande
private void launchServiceAsync() {
// load your service...
ServiceManager.instance.getHumanService(new OnServiceCallBack() {
public void onServiceCallBack(MService service) {
// so just call the method you want of your service
((HumanService) service).getHuman("toto");
}});}
the callBack pattern
View Presenter
HumanService
LazyLoading
Services
Manager
Activity
AndroidServices
SingletonServices
28. 28
public class ServiceManager {
/**
* The list of the bound services to this used to unbind the service. A service is pointed by its serviceConnection. */
List<ServiceConnection> boundServices = null;
List<Service> serviceAndroid = null; List<ServiceBusiness> singletonServices = null;
/** * Empty constructor to instantiate the list of bound services */
private ServiceManager() {boundServices = new ArrayList<ServiceConnection>();...}
/** Destructor **/
/** * This method is called by MApplication to unbind all the services and let them die when your application die */
public synchronized void unbindAndDie() {// Browse all the services and unbind them all
for (ServiceConnection sConn : boundServices) {
//first unbind the service
MAppInstance.ins.getApplication().unbindService(sConn);
}
for (Service servAndroid: servicesAndroid) {
servAndroid = null;} boundServices.clear();
//Do the same with your Singleton Service
for (ServiceBuisness singleton : singletonServices) {
singleton=null;}
//Kill your executor services
//the ones that has to let your thread finish
//the ones that has to finish right now
}
Instanciation à la demande
29. 29
private ExecutorService keepAliveThreadsExcecutor = null;
/*** @return the cancelableThreadsExceutor */
public final ExecutorService getKeepAliveThreadsExecutor() {
if (keepAliveThreadsExceutor == null) {
keepAliveThreadsExceutor = Executors.newFixedThreadPool(4, new BackgroundThreadFactory());
}
return keepAliveThreadsExceutor;
}
/**ShutDown the ExecutorService but wait for thread to finish their job */
private void killKeepAliveThreadExecutor() {
if (keepAliveThreadsExceutor != null) {
keepAliveThreadsExceutor.shutdown(); // Disable new tasks from being submitted
try {// as long as your threads hasn't finished
while (!keepAliveThreadsExceutor.isTerminated()) {
// Wait a while for existing tasks to terminate
if (!keepAliveThreadsExceutor.awaitTermination(5, TimeUnit.SECONDS)) {
// Cancel currently executing tasks
keepAliveThreadsExceutor.shutdown();
Log.e("MyApp", "Probably a memory leak here");
}
}
} catch (InterruptedException ie) { keepAliveThreadsExceutor.shutdownNow();
keepAliveThreadsExceutor=null;
Log.e("MyApp", "Probably a memory leak here too");
} }
keepAliveThreadsExceutor=null;}
Instanciation à la demande
33. Garde le Service en vie
(car conserve le ServiceManager en vie)
Garde l'application en vie
(car toujours Bound avec le ServiceManager)
33
Persister l'instanciation
Application
0-1
HumanService
Services
Manager
0-1
Dead Lock occurs !
AndroidServices
34. Garde le Service en vie
(car conserve le ServiceManager en vie)
Garde l'application en vie
(car toujours Bound avec le ServiceManager)
34
Persister l'instanciation
Application
0-1
HumanService
Services
Manager
0-1
Dead Lock occurs !
AndroidServices
35. Garde le Service en vie
(car conserve le ServiceManager en vie)
Garde l'application en vie
(car toujours Bound avec le ServiceManager)
35
Persister l'instanciation
Application
0-1
HumanService
Services
Manager
0-1
Dead Lock occurs !
AndroidServices
Ne pas suivre le cycle de vie des activités
mais le cycle de vie de l'application
La solution :
36. Cycle de vie de l'application
C'est quoi le cycle de vie de l'application ?
Quand il n'y a plus d'activités visibles depuis une
seconde, je peux considérer que mon application est
terminée.
37. Cycle de vie de l'application
Le one second pattern: A release memory pattern
IsActivity
Alive
37
Application
View
Services
Manager
onStop
onStart
Runnable mServiceKiller;
Launch it
In1Second
if(false) unbindAndDie()
if false
/** Destructor **/
/** * This method is called by MApplication to unbind all
the services and let them die when your application die */
public synchronized void unbindAndDie() {
// Browse all the services and unbind them all
for (ServiceConnection sConn : boundServices) {
//first unbind the service
unbindService(sConn);}
dService = null;boundServices.clear();}
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
public void onActivityStopped(Activity activity) {isActivityAlive.set(false);}
public void onActivityCreated(Activity activity) {isActivityAlive.set(true);}});
// launch the Runnable mServiceKiller in 1 seconds
getServiceKillerHanlder().postDelayed(getServiceKiller(), 1000);
IsActivity
Alive
38. Cycle de vie de l'application
Réagissez au CallBack onMemoryLow
ApplicationServices
Manager
onLowMemory (...)
System
unbindAndDie()
Le LazyLoading est votre philosophie
39. 39
Assurer l'Asynchronicité
Sur Android, il faut être asynchrone, tous le monde le sait !
Ceux qui codent la couche DAO le savent
Ceux qui codent la couche vue le savent
Thread(Thread(Thread(Thread(do something))))
Ceux qui codent la couche de com le savent
Ceux qui codent la couche Service le savent
40. 40
Assurer l'Asynchronicité
Un seule règle: La couche service est votre barrière
d'Asynchronicté.
Chaque méthode publique de chaque service s'exécute dans
un Thread.
Et nulle part ailleurs de Thread est lancé.
(à part les Animations Thread de gingerbread, putain de gingerbread)
41. 41
Assurer l'Asynchronicité
public class MyBusinessService{...
public void loadDataAsync(int itemId) {
MyApp.instance.getServiceManager()
.getCancelableThreadsExecutor()
.submit(new RunnableLoadData(itemId));
}
private class RunnableLoadData implements Runnable {
public int itemId;
public RunnableLoadData(int itemId) {this.itemId=itemId}
public void run() {
loadDataSync(itemId);}
}
public void loadDataSync(int itemId) {
// your code here}
42. Assurer l'Asynchronicité
Vous pouvez utiliser PoolExecutor + Runnable
Vous pouvez utiliser les IntentService
Vous pouvez utiliser les AysncTask
https://www.youtube.com/watch?v=jtlRNNhane0&index=4&list=PLWz5rJ2
EKKc9CBxr3BVjPTPoDPLdPIFCE
Vous pouvez utiliser les HandlerThread
Vous pouvez utiliser les AysncTask
43. Chaque appel à un service se fait de manière asynchrone
43
HumanService
View Presenter
doSomething()
{
//code...
Human hum =getHumanService().getHuman("toto");
//other code using hum
txvHumanName.setText(hum.getName());
}
call
Failed
It's asynchronous
What we used to do
before
Asynchronicité
44. 44
Services
View Presenter
doSomething()
{ getHumanService().getHuman("toto"); }
call
What we need to do now
doSomethingPart2(Human hum)
{ //other code using hum
txvHumanName.setText(hum.getName()
);}
async return
Yes
good way
Assurer l'Asynchronicité
Chaque appel à un service se fait de manière asynchrone
Les méthodes des Activités doivent être segmentées
45. Les instancier à la demande
45
Il faut qu'on arrive avec nos services métiers à:
ArchiDroid
Ne les instancier QU'une seule fois
Mettre en place l'asynchronicité
Ne pas suivre le cycle de vie des activités
mais le cycle de vie de l'application
Les laisser terminer leur traitement
46. Disponible sur mon GitHub
46
J'utilise le Live Template
Et le code
arch_Service_Method_WithArgs
arch_ServManager_CancelableThread
arch_ServManager_KeepAliveThread
arch_Service_Method
et plus ...
68. Test
Comment on fait ?
Instrumentation
Tests
Unit Tests
S'exécute sur Android
Pas de fake du système
S'exécute sur la JVM
Fake des composants
système (Mockito)
69. Test
Unit Tests
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
System.out.println("ExampleUnitTest");
assertEquals(4, 2 + 2);
}
}
70. Test
Instrumentation Tests
public class CityServiceTest extends AndroidTestCase {
public void setUp() {super.setUp();
EventBus.getDefault().register(this);
}
public void tearDown() { super.tearDown();
EventBus.getDefault().unregister(this);
}
public void testFindCityByNameAsync() {
MyApplication.instance
.getServiceManager()
.getCityService()
.findCityByNameAsync(testedCityName) }}
71. Test
Instrumentation Tests
@Subscribe
public void onEvent(FindCitiesResponseEvent event){
assertNotNull(event);
assertNotNull(event.getFindCitiesResponse());
assertNotNull(event.getFindCitiesResponse().getCities());
assertNotNull(event.getFindCitiesResponse().getCities().get(0))
DataCheck.getInstance()
.checkCity(event.getFindCitiesResponse().getCities().get(0));
}
73. Test
Mais là, comment on fait ?
Services
AndroidServices
SingletonServices
Service
knows Communication
Com
I
N
T
F
knows
D.A.O.
DAO
I
N
T
F
Tests
JUNIT
On joue avec les saveurs
?
?
?
74. Test
On joue avec les saveurs
et les injecteurs
Mais là, comment on fait ?
public class Injector {
public static ServiceManagerMocked getServiceManager(){
return new ServiceManagerMocked();
}
}
public class Injector {
public static ServiceManagerMocked getServiceManager(){
return new ServiceManager ();
}
}
75. Test
On joue avec les saveurs
et les injecteurs
Mais là, comment on fait ?
// If you need to add more flavors, consider using flavor dimensions.
productFlavors {
mock {
applicationIdSuffix = ".mock"
}
prod { }
}
77. Modèle n-tier
MockView
View = MVP
Presenter
ViewIntf
1
1
JUNIT
Services
AndroidServices
SingletonServices
Service
I
N
T
F
Tests
Tester
mockDebug
78. Modèle n-tier
View = MVP
Presenter
View
1
1
Services
AndroidServices
SingletonServices
Service
I
N
T
F
Espresso
AndroidTest SDK
Tests
Tester
mockDebug
79. Test
Et après ? Industrialisation via l'intégration continue.
mais c'est toujours un peu l'enfer... on ne vous le cache pas
80. Test
Et après ? Le déploiement continue.
Oui c'est possible.
82. Librairies
Un besoin
Une fonctionnalité
Une librairie
Utilisez une librairie parce que vous en avez besoin,
pas pour le plaisir !
Moins vous avez de librairies sur votre projet,
mieux vous vous porterez.
Choisissez avec attention vos librairies, c'est critique,
ne merdez pas.
98. Librairies
Supprimer les Intents pour la communication interne
Les bus évènementiels
OTTO EventBus
A n'utiliser que pour les retours d'appels ou les évènements
101. public class City extends SugarRecord {
private int cityId;
private String name;
private Coordinates coordinates;
@Ignore
private List<WeatherMetaData_City> metaDataList = null;
/**Because of SugarOrm bad management of one to many relationship*/
public List<WeatherMetaData_City> getMetaDataList() {
if(metaDataList==null){
//load them
metaDataList= WeatherMetaData_City.find(WeatherMetaData_City.class,
"CITY = ?", Long.toString(getId()));
}
return metaDataList;
}
SugarOrm
102. SugarOrm
public long save(City city){
long id;
//you need to manually save others elements (first)
Coordinates.save(city.getCoordinates());
WeatherDetails.save(city.getWeatherDetails());
Wind.save(city.getWind());
Clouds.save(city.getClouds());
Ephemeris.save(city.getEphemeris());
id = City.save(city);
103. SugarOrm
public void delete(City city){
if(city.getId()!=null) {
//you need to manually delete others elements (first)
Coordinates.delete(city.getCoordinates());
WeatherDetails.delete(city.getWeatherDetails());
Wind.delete(city.getWind());
Clouds.delete(city.getClouds());
Ephemeris.delete(city.getEphemeris());
City.delete(city);
}
}
131. Je cherche un client
Pour m'engager
Sur la réussite de son produit Android
(avec un objectif de haute qualité)
en remote (ou Presque)
en tant que tech lead
en freelance
132. Lieu : Paris
Date : 10 - 13 Mai 2016
Durée : 4 jours
138
Lieu : Lyon
Date : 23 - 27 Mai 2016
Durée : 5 jours
Lieu : Paris
Date : 06 - 10 Juin 2016
Durée : 5 jours
En effet, les vues possèdent le MVP qui est rodé et sont gérées plus ou moins directement par le système
Les couches Com et Dao n'ont rien de particulières dans ce système
Il nous reste donc à affiner les services, leur instanciations, leur fonctionnement
Le point fondamental de toute application est l'utilisation de l'objet application
Le point fondamental de toute application est l'utilisation de l'objet application
Tu dois te l'approprier car tu vas en avoir besoin un peu partout dans ton app.
Le principe du Singleton est ultra important, la question que je me pose encore est quand est-ce qu'il doit être garbage collecté et si ça pose un problème.
Le principe du Singleton est ultra important, la question que je me pose encore est quand est-ce qu'il doit être garbage collecté et si ça pose un problème.
Le point fondamental de toute application est l'utilisation de l'objet application
HandlerThread, c'est comme l'IntentService, ca nous donne un Thread avec son looper
Pourquoi pas les AsyncTask... ben c'est que c'est facile de
All Asynctask will use the same thread to execute themself=> it will be executed in sequential (can kill your work)
How can you cancel an AsyncTask ??? => en fait n'annule pas la task mais annule le bordel et on passe dans onCancel et pas dans onPostExecite
MemoryLeak (AsyncTask declare as an inner class of the Activity fuite memoire commune, ou qui pointe vers un champ de l'activité...)
Pour la petite histoire, j'ai commence deux app dernièrement, l'une c'est le tp de cette conf et l'autre c'était pour moi, pour arreter de fumer et me motive
Bon dans la première j'ai mis de facto ce que je vous ai dit et dans la seconde, j'ai commence cool, en mode lazy feature mais petit à petit ben je les ai toutes implémentées car plus le projet avance plus il se complexifie plus on y ajoute les techniques d'architecture que je viens de vous expliquer. Elles viennent naturelement résoudre les problèmes qui se soulèvent.
15 minutes
Inspiré de la Vidéo de DevSummit et en particluier son l'exemple
C'est là où GCManager ou JobScheduler entrent en action
10 minutes
Quand on parle de test, la première chose qui me vient à l'esprit c'est merci AndroidStudio Merci Gradle
Parce que Eclipse + Maven c'était juste l'enfer
No test no trust
No test no trust
No test no trust
No test no trust
No test no trust
No test no trust
No test no trust
No test no trust
No test no trust
Version de 8 à 24
Locale Fr, En, Es
Screen density: l,m,h,xh,xxh,xxxh
Screen size: s,n,l,xl
Réseau Wifi, 3 g, edge
Autres: ca peut être poru chaque permission accepté/pas accepté ou poru les ressources présente GPS ou pas GoolgePlayService ou pas...
On va séparer en Gui / not Gui
16*3=48
3*6*4=72
16*3=48
3*6*4=72
Avantage: Rapide mais s'execute dans la JVM aucune brique android
On peut utiliser Mockito pour Mock les objets du système
S'execute sur un Appareil (émulateur ou reel)
Possède tout l'environnement Android pour s'exécuter
S'execute sur un Appareil (émulateur ou reel)
Possède tout l'environnement Android pour s'exécuter
Car ils ne dependent de personne
Parce que là, il y a le problème de l'isolation de la couche service poru s'assurer des résultat
Parce que là, il y a le problème de l'isolation de la couche service poru s'assurer des résultat
Parce que là, il y a le problème de l'isolation de la couche service poru s'assurer des résultat
JUnit et bouchons
Parce que là, il y a le problème de l'isolation de la couche service poru s'assurer des résultat
Parce que là, il y a le problème de l'isolation de la couche service poru s'assurer des résultat
20 minutes
Parce que là, il y a le problème de l'isolation de la couche service poru s'assurer des résultat
Parce que là, il y a le problème de l'isolation de la couche service poru s'assurer des résultat
Parce que là, il y a le problème de l'isolation de la couche service poru s'assurer des résultat
Parce que là, il y a le problème de l'isolation de la couche service poru s'assurer des résultat
Ce qui vous permet de vous concentrer sur l'important: Algo des services et des vues