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. Android2EE est référencé en tant qu’organisme de formation, vous pouvez faire prendre en charge tout ou partie
du montant de cette formation par votre OPCA. Cette formation Initiation avancée à Android est éligible au titre
du DIF et CIF.
Lieu : Paris, Toulouse, Lyon
Durée : 5 jours
Prix : 2980 €
2
3. Lieu : Paris
Date : 10 - 13 Mai 2016
Durée : 4 jours
3
Lieu : Lyon
Date : 23 - 27 Mai 2016
Durée : 5 jours
Lieu : Paris
Date : 06 - 10 Juin 2016
Durée : 5 jours
5. Le contexte est tout
Papa, on peut atteindre Mach1 ?
La réponse dépend
du contexte !
DataCenter Desktop Tablette Smartphone
You're here
Light1, facile. Mach1, facile. Euh,... Qui me parle ?
Allégorie de la
puissance
6. Le contexte est tout
Papa, tu me sers à boire de l'eau, s'il te plait ?
Water
water.drink()
giveWater()
water.drink()
null
Le contexteLe contexte
NPE
9. La mémoire c'est la vie !
La mémoire
L'eau c'est la vie !
Pas sur Android
10. La mémoire c'est la vie !
La mémoire
Mais
Vous développez avec les
meilleurs devices de la planète !!!
Performance
Base de
données locale
et fichiers
Persistance
multi-processus
11. La mémoire dans la vrai vie de
vos utilisateurs !
La mémoire
HelloWorld.apk
== 1 Mo
faibles perfs pas d'espace
Appareil Standard :
8Go (4GO courant)
19. L'énergie
Les causes du gaspillage énergétique
CPU/GPU
Connexion (Http,...)
Drawable/Animations...
Mauvaises pratiques
de code
Services sans fin
20. L'énergie
Les causes du gaspillage énergétique
Mauvaises pratiques
de code
Putain de javaïstes
(Android c'est pas du Java sur des serveurs clusterisés !!!)
22. Le contexte mutualiste sous Android
La vie en collectivité
gmail
twitter
hangout
evernote
messsager
facebook
gmap
linkedIn
runstatic
your app
chrome
23. C'est le vivre tous ensemble qui est compliqué
(pensez, aussi, aux sanitaires du camping municipal de cet été)
La tragédie des biens
communs
31. Ne mettez pas à jour trop souvent, vous n'êtes pas seul !
Don't oversync.
Réseau
32. Dans un monde complexe
Synthèse
Mémoire Puissance Energie
et de responsabilité collectiveUn contexte de pénuries
60fps
UI Thread
intouchable
GC sans espace énergivore
38. Et quand il le fait, c'est facile, simple et bluffant.
C'est facile et c'est
fluide
39. Et grâce à vous, il possède des capacités qu'il n'avait pas.
ça me donne des super
pouvoirs!
40. Ne frustrer pas votre utilisateur, laissez le libre.
Sans authentification,
ni paiement.
41. Je veux faire un truc,
je peux le faire,
et quand je le fais,
ça marche !
Je sais ce que je fais.
C'est facile et c'est fluide.
Ca me donne des super
pouvoirs,
sans authentification,
ni paiement.
Le plaisir!
Combler un besoin utilisateur
avec efficacité.
L'utilisateur doit toujours savoir
ce qu'il fait, où il est.
Et quand il le fait, c'est facile,
simple et bluffant.
Et grâce à vous, il possède des
capacités qu'il n'avait pas.
Ne frustrer pas votre utilisateur,
laissez le libre.
Utilisateur Développeur
63. A la batterie
(à ce qu'il en reste surtout).
Adaptation
<receiver android:name=".BatteryLevelReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_BATTERY_LOW"/>
<action android:name="android.intent.action.ACTION_BATTERY_OKAY"/>
</intent-filter>
</receiver>
68. 74
Pour réaliser mon objectif utilisateur
Pour assumer mes responsabilités de développeur
Pour m'adapter aux contraintes du système
Pourquoi des bonnes pratiques ?
71. N'allouez pas d'objets.
Mémoire
N'allouez pas d'objets
dans les boucles et les
méthodes appelées
souvent (onDraw).
Mettez les objets en
cache (pour éviter de
les réallouer)
Utiliser des pools
d'objets au besoin
Utiliser des variables
de classes temporaires
(plutôt que des
variables de
méthodes)
Utiliser
ArrayMap/SimpleArray
Map/SparseArray au
lieux de HashMap
Définissez la taille de
vos ArrayList
72. N'allouez pas d'objets.
Mémoire
Pas d'itérateurs, il faut
for(int i=0;
i<malist.size();
i++)
Oubliez que les enums
existent
Que des librairies
spécifiquement écrites
pour Android
public static Objet obj;
peut générer des fuites
mémoires
Instanciez les objets à
la demande
LazyLoading
Préférez les
paramètres IO à vos
méthodes que la
création d'un objet
81. Les opérations UI Thread coûteuses.
UI Performance
Mesurer et Disposer.
Measure and Layout
Inflation. Initialisation de l'objet
Application.
82. Les opérations UI Thread coûteuses.
UI Performance
Mesurer et Disposer. Inflation. Initialisation de l'objet
Application.
Complexité de la
hiérarchie des vues.
RelativeLayout.
83. Pas d'animation durant les opérations coûteuses.
UI Performance
Les CustomViews permettent de simplifier le measure et layout.
Démarrage Rapide : Utilisez la Starting Window avec le bon thème.
Le ViewStub est ton ami.
Le LinearLayout est ton meilleur ami.
Choisissez bien vos Layouts en fonction de votre besoin.
Simplifiez vos IHM, aplatissez vos layouts.
87. Redirigez vos layouts
Gravez dans le marbre le nombre de fragments affichés
93
•reslayout
activity_a_two_panes_layout.xml (le vrai layout)
activity_a_one_panes_layout.xml (le vrai layout)
Dans ActivityA
setContentView(R.layout.main_activity)
• resvalues
layout_redirection.xml
<resources>
<item name="main_activity" type="layout">@layout/activity_one_panes</item>
<bool name="twoPane">false</bool>
</resources>
•resvalues-land || resvalues-large || reslayout-xlarge
layout_redirection.xml
<resources>
<item name="main_activity" type="layout">@layout/activity_two_panes</item>
<bool name="twoPane">true</bool>
</resources>
getRessource().getBoolean(R.bool.twoPane)
UI Pro Tips
88. Attention à l'overDraw.
UI Pro Tips
Utiliser la Starting Windows thèmée (vous pouvez mettre une image).
Utiliser le Context approprié.
Les vues ne doivent pas être connues des threads (autre que l'UI).
89. Ajoutez votre connectivité (Wifi,..,edge) à vos requêtes
Ajoutez la version des données à vos urls
/myservlet_v1.2
MyOnlyObject in Json+GZip
Ajoutez un TimeStamp à vos données dans vos bases de données (local & global)
Requêtez vos données en vous appuyant sur les timestamp
Utilisez json + gzip (ou mieux: des dataBuffer)
C'est le serveur qui effectue le Prefetch des données (basé sur votre connectivité)
TimeStamp
TimeStamp
/myservlet_v1.2?timespan=11215982/myservlet_v1.2?timespan=11215982&net=wifi
Utilisez la Big Cookies stratégie.
Réseau
91. Tu n'es pas un Javaïste
Parcelable c'est
super.... pour les
intents. Pas de
stockage disque !!!
Utilisez les structures
Android :
SparseArray,
ArrayMap,...
Oubliez que la
sérialisation existe.
Utilisez les
SharedPref
Préférez les types
primitifs et attention à
l'auto-boxing
92. Persistance
Jamais de chemin en
dur, jamais,
seulement du relatif.
N'utilisez pas SQLite
pour de la
persistance simple
Une base de
données peut
posséder plusieurs
Tables...
93. Objectif
Interne Fichiers privés, protégés, propres à l'application.
Détruits lors de la désinstallation.
Cache Fichiers temporaires propres à l'application.
Détruits lors de la désinstallation.
External Fichiers privés et publics, non-protégés, propres à l'application.
Détruits lors de la désinstallation.
Public
External
Fichiers publics, non-protégés.
Non détruits lors de la désinstallation.
Persistance
Pour les fichiers : Connaître
son objectif.
94. Méthode pour récupérer le dossier racine
Interne File getFilesDir()
Cache File getCacheDir()
External File getExternalStorage(String type);
Public
External
File getExternalStoragePublicDirectory(String type);
Persistance
Utilisez les classes File, FileInputStream et FileOutputStream.
ou BufferSink et BufferSource...
Quand on Connaît son objectif, tout devient facile.
95. Persistance
Vérifiez la présence
du fichier avant
d'écrire ou de lire...
//Then read your file
File filePicture = new File(subFolder, fileNameStr);
if(filePicture.exist()){
//Then open an InputStream on it your file
FileInputStream fis = new FileInputStream(filePicture);
96. Persistance
Vérifiez l'état de
l'ExternalDirectory
avant d'éssayer de
lire/écrire.
String externalStorageState = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(externalStorageState)) {
// We can read and write the media}
else if
(Environment.MEDIA_MOUNTED_READ_ONLY.equals(externalStorageState)) {
// We can only read the media}
98. Exception
Gérer vos exceptions !
Exception
ManagedException
isManaged
UserId
Error message
Technical data
Functional data
Others stuff
99. Exception
Gérer vos exceptions !
Exception Manager
ExceptionManager
.manage(...)
Team FeedBackUser FeedBack
SomeWhere in your code galaxy
100. Thread
Centralisez leur
gestion.
La gestion des
Threads est cruciale.
Faîtes attention aux
fuites mémoires.
Utilisez un
ServiceExecutor
Utilisez
PLUSIEURS
ServiceExecutor
MyApplication
ServiceExecutor
101. /** * The ThreadPool Executor for caching background */
ExecutorService executor = Executors.newFixedThreadPool(6, null);
/** * Launch a Runnable*/
public synchronized void cacheBackGround() {
executor.submit(mCacheBackgroundRunnnable);
}
Thread
ExecutorService
utilise un
ThreadPoolExecutor
102. if (executor != null) {
executor.shutdown(); // Disable new tasks from being submitted
try {//as long as your threads hasn't finished
while (!executor.isTerminated()) {
// Wait a while for existing tasks to terminate
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
// Cancel currently executing tasks
executor.shutdownNow();
Log.e("MyApp","Probably a memory leak here");
}
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
executor.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
Log.e("MyApp","Probably a memory leak here too");
}}}
Thread
N'oubliez pas de les
détruire
104. Memory Leak
Fuite mémoire
courante :
Un Thread
pointant vers
l'IHM
Thread I.H.M Thread de traitement
Activity Handler
sendMessage()
message
Thread I.H.M
Thread de traitement
Activity
sendMessage()
message
Handler
OnDestroy()
OnCreate()
Thread de traitement initiale
sendMessage()
Activity
fantôme
Handler
fantôme
Utilisez WeakReference
105. Fuite mémoire
courante :
Un Thread
pointant vers
l'IHM
Accorder le cycle de vie de la thread
avec celui de l'activité
Thread I.H.M Thread de traitement
Activity Handler
sendMessage()
Thread I.H.M Thread de traitement
Activity
sendMessage()
message
message
OnDestroy()
OnCreate()
Handler
Memory Leak
106. Fuite mémoire
courante :
Un Thread
pointant vers
l'IHM
Conserver la Thread
Thread I.H.M Thread de traitement
Activity Handler
sendMessage()
Thread I.H.M
Activity
message
message
Thread de traitement
Handler
sendMessage()
OnDestroy()
OnCreate()
!!!Attention ne pas utiliser en l’état !!!
!!! Fuite mémoire !!!
Memory Leak
107. Fuite mémoire
courante :
Non static inner
class
Utilisez les static inner class
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exampleOne();
}
private void exampleOne() {
new Thread() {
public void run() {
while (true) {SystemClock.sleep(1000); }
}}.start();
}}
Non Static Inner Class
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MyThread().start();
}
private static class MyThread extends Thread {
public void run() {
while (true) {SystemClock.sleep(1000);}
}}
}
Memory Leak
108. Fuite mémoire
courante :
private static
avec une
référence
implicite vers la
classe
englobante
Ne pas le faire
public class MainActivity extends Activity {
private static Drawable sBackground;
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
if (sBackground == null) {
sBackground = getDrawable(R.drawable.largebitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}}
Mem Leak
Memory Leak
109. public class Cst {
public static int MaConstante=1;
}
public class DamnedService extends Service {
private void somewhere(int val){
if(val==Cst.MaConstante){...}
}
}
Fuite mémoire
courante :
public static
dans un objet
lourd
Extraire les constantes dans une
classe dédiée
Mem Leak
Memory Leak
public class MainActivity extends Activity {
public static int MaConstante=1;
}
public class DamnedService extends Service {
private void somewhere(int val){
if(val==MainActivity.MaConstante){...}
}
}
110. Architecture
Activity est l'entrée
principale de votre
application.
Don't fuck around
with the system !
Un Service permet
de dire au système
"Je fais une longue
opération, considère
moi comme une
Activité visible sans
IHM."
BroadcastReceiver
indique au système
qu'il est intéressé
pour recevoir telle ou
telle information.
114. Un peu d'histoire
public class HistoryBattleActivity extends AppCompatActivity {
private static final String TAG = "HistoryBattleActivity";
private static final int CONTEXT_CURRENT=110274;
private static final int CONTEXT_HISTORY=131274;
/***********************************************************
* Attributes
**********************************************************/
BattleFragment battleFragment;
/**
* Current context History/current
*/
int currentContext=CONTEXT_CURRENT;
/***********************************************************
* Managing RoundTrip animation (VectorDrawable1 to VectorDrawable 2 and back again
**********************************************************
/**
* The LevelList that contains only two AnimatedVectorDrawable,
* the ones used to go from on to the other
*/
LevelListDrawable backupRoundTrip;
/**
* The current AnimatedVector diaplsyed by the RoundTrip
*/
AnimatedVectorDrawable contextDrawable;
/**
* To know is the animation have been already launched
*/
boolean backupRoundTripFirstLaunched=true;
/**
* Historical battles
*/
ArrayList<Long> battlesId=null;
/***********************************************************
* Attributes for the ViewPager
**********************************************************/
/**
* The TabLayout itself :)
*/
TabLayout tabLayout;
/**
* The page Adapter : Manage the list of views (in fact here, it's fragments)
* And send them to the ViewPager
*/
private MyPagerAdapter pagerAdapter;
/**
* The ViewPager is a ViewGroup that manage the swipe from left to right to left
* Like a listView with a gesture listener...
*/
private ViewPager viewPager;
/***********************************************************
* Managing the Life cycle
**********************************************************/
**********************************************************/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate() called");
setContentView(R.layout.activity_history);
//find the Toolbar
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
//use it as your action bar
setSupportActionBar(toolbar);
getSupportActionBar().setSubtitle(getString(R.string.history_fragment_subtitle));
getSupportActionBar().setTitle(getString(R.string.history_fragment_title));
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
//Define its gravity and its mode
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
//Define the color to use (depending on the state a different color should be disaplyed)
//Works only if done before adding tabs
tabLayout.setTabTextColors(getResources().getColorStateList(R.color.tab_selector_color));
//instanciate the PageAdapter
pagerAdapter=new MyPagerAdapter(this,true);
//Find the viewPager
viewPager = (ViewPager) super.findViewById(R.id.viewpager);
// Affectation de l'adapter au ViewPager
viewPager.setAdapter(pagerAdapter);
viewPager.setClipToPadding(true);
//Add animation when the page are swiped
//this instanciation only works with honeyComb and more
//if you want it all version use AnimatorProxy of the nineoldAndroid lib
//@see:http://stackoverflow.com/questions/15767729/backwards-compatible-pagetransformer
//TODO uncomment those lines and the opengl bug disappears
// if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB){
// viewPager.setPageTransformer(true, new MyPageTransformer());
// }
//AND CLUE TABLAYOUT AND VIEWPAGER
tabLayout.setupWithViewPager(viewPager);
}
@Override
protected void onStart() {
super.onStart();
//track entrance
Log.e(TAG, "onStart() has been called");
EventBus.getDefault().register(this);
}
@Override
protected void onResume() {
super.onResume();
//track entrance
Log.e(TAG, "onResume() has been called");
}
} @Override
protected void onStop() {
super.onStop();
//track entrance
Log.e(TAG, "onStop() has been called");
EventBus.getDefault().unregister(this);
}
@Override
public void onBackPressed() {
if(((MyApplication)getApplication()).isCigaretPanelOpen){
//do nothing the fragment will just change its state
battleFragment.onBack();
}else{
super.onBackPressed();
}
}
@Subscribe(threadMode = ThreadMode.MAIN)//EventBus
public void updateUI(FullUpdateEvent event) {
//TODO update properly
Log.e(TAG, "fullUpdate() called with: " + "notUsed = [" + event + "]");
//rebuild every thing:$
pagerAdapter.notifyRebuildAll();
if(event.isSwitchActivity()){
switchContext();
}else{
tabLayout.setupWithViewPager(viewPager);
}
}
/***********************************************************
* Managing menu
**********************************************************/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater=getMenuInflater();
inflater.inflate(R.menu.history_menu, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.menu_switch_context:
switchContext();
break;
}
return super.onOptionsItemSelected(item);
}
/***********************************************************
* Managing backup button round trip
**********************************************************/
/**
* Switch context from history to current (and vis versa)
* Launch the animation on the currentAnimatedVectorDrawable
*/
private void switchContext(){
Intent startNewContext=new Intent(this, CurrentBattleActivity.class);
startNewContext.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(startNewContext);
}
}
Au début, il y avait la classe dieu (God Class)
On a une petite évol ! On va tester l'évol...
Bref, on avait fait de la m***e
115. Un peu d'histoire
public class HistoryBattleActivity extends AppCompatActivity {
private static final String TAG = "HistoryBattleActivity";
private static final int CONTEXT_CURRENT=110274;
private static final int CONTEXT_HISTORY=131274;
/***********************************************************
* Attributes
**********************************************************/
BattleFragment battleFragment;
/**
* Current context History/current
*/
int currentContext=CONTEXT_CURRENT;
/***********************************************************
* Managing RoundTrip animation (VectorDrawable1 to VectorDrawable 2 and back again
**********************************************************
/**
* The LevelList that contains only two AnimatedVectorDrawable,
* the ones used to go from on to the other
*/
LevelListDrawable backupRoundTrip;
/**
* The current AnimatedVector diaplsyed by the RoundTrip
*/
AnimatedVectorDrawable contextDrawable;
/**
* To know is the animation have been already launched
*/
boolean backupRoundTripFirstLaunched=true;
/**
* Historical battles
*/
ArrayList<Long> battlesId=null;
/***********************************************************
* Attributes for the ViewPager
**********************************************************/
/**
* The TabLayout itself :)
*/
TabLayout tabLayout;
/**
* The page Adapter : Manage the list of views (in fact here, it's fragments)
* And send them to the ViewPager
*/
private MyPagerAdapter pagerAdapter;
/**
* The ViewPager is a ViewGroup that manage the swipe from left to right to left
* Like a listView with a gesture listener...
*/
private ViewPager viewPager;
/***********************************************************
* Managing the Life cycle
**********************************************************/
**********************************************************/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate() called");
setContentView(R.layout.activity_history);
//find the Toolbar
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
//use it as your action bar
setSupportActionBar(toolbar);
getSupportActionBar().setSubtitle(getString(R.string.history_fragment_subtitle));
getSupportActionBar().setTitle(getString(R.string.history_fragment_title));
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
//Define its gravity and its mode
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
//Define the color to use (depending on the state a different color should be disaplyed)
//Works only if done before adding tabs
tabLayout.setTabTextColors(getResources().getColorStateList(R.color.tab_selector_color));
//instanciate the PageAdapter
pagerAdapter=new MyPagerAdapter(this,true);
//Find the viewPager
viewPager = (ViewPager) super.findViewById(R.id.viewpager);
// Affectation de l'adapter au ViewPager
viewPager.setAdapter(pagerAdapter);
viewPager.setClipToPadding(true);
//Add animation when the page are swiped
//this instanciation only works with honeyComb and more
//if you want it all version use AnimatorProxy of the nineoldAndroid lib
//@see:http://stackoverflow.com/questions/15767729/backwards-compatible-pagetransformer
//TODO uncomment those lines and the opengl bug disappears
// if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB){
// viewPager.setPageTransformer(true, new MyPageTransformer());
// }
//AND CLUE TABLAYOUT AND VIEWPAGER
tabLayout.setupWithViewPager(viewPager);
}
@Override
protected void onStart() {
super.onStart();
//track entrance
Log.e(TAG, "onStart() has been called");
EventBus.getDefault().register(this);
}
@Override
protected void onResume() {
super.onResume();
//track entrance
Log.e(TAG, "onResume() has been called");
}
} @Override
protected void onStop() {
super.onStop();
//track entrance
Log.e(TAG, "onStop() has been called");
EventBus.getDefault().unregister(this);
}
@Override
public void onBackPressed() {
if(((MyApplication)getApplication()).isCigaretPanelOpen){
//do nothing the fragment will just change its state
battleFragment.onBack();
}else{
super.onBackPressed();
}
}
@Subscribe(threadMode = ThreadMode.MAIN)//EventBus
public void updateUI(FullUpdateEvent event) {
//TODO update properly
Log.e(TAG, "fullUpdate() called with: " + "notUsed = [" + event + "]");
//rebuild every thing:$
pagerAdapter.notifyRebuildAll();
if(event.isSwitchActivity()){
switchContext();
}else{
tabLayout.setupWithViewPager(viewPager);
}
}
/***********************************************************
* Managing menu
**********************************************************/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater=getMenuInflater();
inflater.inflate(R.menu.history_menu, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.menu_switch_context:
switchContext();
break;
}
return super.onOptionsItemSelected(item);
}
/***********************************************************
* Managing backup button round trip
**********************************************************/
/**
* Switch context from history to current (and vis versa)
* Launch the animation on the currentAnimatedVectorDrawable
*/
private void switchContext(){
Intent startNewContext=new Intent(this, CurrentBattleActivity.class);
startNewContext.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(startNewContext);
}
}
On va découpler la vue du reste de l'application
116. Un peu d'histoire
public class HistoryBattleActivity extends AppCompatActivity {
private static final String TAG = "HistoryBattleActivity";
private static final int CONTEXT_CURRENT=110274;
private static final int CONTEXT_HISTORY=131274;
/***********************************************************
* Attributes
**********************************************************/
BattleFragment battleFragment;
/**
* Current context History/current
*/
int currentContext=CONTEXT_CURRENT;
/***********************************************************
* Managing RoundTrip animation (VectorDrawable1 to VectorDrawable 2 and back again
**********************************************************
/**
* The LevelList that contains only two AnimatedVectorDrawable,
* the ones used to go from on to the other
*/
LevelListDrawable backupRoundTrip;
/**
* The current AnimatedVector diaplsyed by the RoundTrip
*/
AnimatedVectorDrawable contextDrawable;
/**
* To know is the animation have been already launched
*/
boolean backupRoundTripFirstLaunched=true;
/**
* Historical battles
*/
ArrayList<Long> battlesId=null;
/***********************************************************
* Attributes for the ViewPager
**********************************************************/
/**
* The TabLayout itself :)
*/
TabLayout tabLayout;
/**
* The page Adapter : Manage the list of views (in fact here, it's fragments)
* And send them to the ViewPager
*/
private MyPagerAdapter pagerAdapter;
/**
* The ViewPager is a ViewGroup that manage the swipe from left to right to left
* Like a listView with a gesture listener...
*/
private ViewPager viewPager;
/***********************************************************
* Managing the Life cycle
**********************************************************/
**********************************************************/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate() called");
setContentView(R.layout.activity_history);
//find the Toolbar
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
//use it as your action bar
setSupportActionBar(toolbar);
getSupportActionBar().setSubtitle(getString(R.string.history_fragment_subtitle));
getSupportActionBar().setTitle(getString(R.string.history_fragment_title));
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
//Define its gravity and its mode
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
//Define the color to use (depending on the state a different color should be disaplyed)
//Works only if done before adding tabs
tabLayout.setTabTextColors(getResources().getColorStateList(R.color.tab_selector_color));
//instanciate the PageAdapter
pagerAdapter=new MyPagerAdapter(this,true);
//Find the viewPager
viewPager = (ViewPager) super.findViewById(R.id.viewpager);
// Affectation de l'adapter au ViewPager
viewPager.setAdapter(pagerAdapter);
viewPager.setClipToPadding(true);
//Add animation when the page are swiped
//this instanciation only works with honeyComb and more
//if you want it all version use AnimatorProxy of the nineoldAndroid lib
//@see:http://stackoverflow.com/questions/15767729/backwards-compatible-pagetransformer
//TODO uncomment those lines and the opengl bug disappears
// if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB){
// viewPager.setPageTransformer(true, new MyPageTransformer());
// }
//AND CLUE TABLAYOUT AND VIEWPAGER
tabLayout.setupWithViewPager(viewPager);
}
@Override
protected void onStart() {
super.onStart();
//track entrance
Log.e(TAG, "onStart() has been called");
EventBus.getDefault().register(this);
}
@Override
protected void onResume() {
super.onResume();
//track entrance
Log.e(TAG, "onResume() has been called");
}
} @Override
protected void onStop() {
super.onStop();
//track entrance
Log.e(TAG, "onStop() has been called");
EventBus.getDefault().unregister(this);
}
@Override
public void onBackPressed() {
if(((MyApplication)getApplication()).isCigaretPanelOpen){
//do nothing the fragment will just change its state
battleFragment.onBack();
}else{
super.onBackPressed();
}
}
@Subscribe(threadMode = ThreadMode.MAIN)//EventBus
public void updateUI(FullUpdateEvent event) {
//TODO update properly
Log.e(TAG, "fullUpdate() called with: " + "notUsed = [" + event + "]");
//rebuild every thing:$
pagerAdapter.notifyRebuildAll();
if(event.isSwitchActivity()){
switchContext();
}else{
tabLayout.setupWithViewPager(viewPager);
}
}
/***********************************************************
* Managing menu
**********************************************************/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater=getMenuInflater();
inflater.inflate(R.menu.history_menu, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.menu_switch_context:
switchContext();
break;
}
return super.onOptionsItemSelected(item);
}
/***********************************************************
* Managing backup button round trip
**********************************************************/
/**
* Switch context from history to current (and vis versa)
* Launch the animation on the currentAnimatedVectorDrawable
*/
private void switchContext(){
Intent startNewContext=new Intent(this, CurrentBattleActivity.class);
startNewContext.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(startNewContext);
}
}
On va découpler la vue du reste de l'application
Model
Vue Controlleur
Modifie
Préviens
Mets à jour
Pour fêter ça, on a dansé la danse de la victoire
Le modèle MVC était né!
117. Un peu d'histoire
puis vint le modèle M(VC) swing
Model
Vue&Controller
ModifieMets à jour
Vue Controlleur
119. Un peu d'histoire
le modèle M(VC) swing
Gère
les données
affichées
Model
Affiche les données
interagit avec l'utilisateur
Vue&Controller
1
1
120. Un peu d'histoire
Apparition du MVP
Gère
les données affichées
Model
Affiche les données
interagit avec l'utilisateur
Vue&Controller
Presenter
La logique Business
Et les données
Model
View
121. Un peu d'histoire
Apparition du MVP
Gère
les données affichées
Presenter
Affiche les données
interagit avec l'utilisateur
View
La logique Business
Et les données
Model
1
1
ModifieMets à jour
Dialogue
122. Un peu d'histoire
Apparition de la programmation par contrat
(Interfaces)
Gère
les données affichées
Presenter
Affiche les données
interagit avec l'utilisateur
View
La logique Business
Et les données
Model
1
0
PresenterIntf
ViewIntf
0
1
123. Un peu d'histoire
Pour les tests !!!
Model
Presenter
View
PresenterIntf
ViewIntf
MockPresenter
MockView
124. Saveur=TestPresenter
Un peu d'histoire
Avec Gradle, c'est le bonheur des tests.
Saveur=TestVueSaveur=PROD
Model
Presenter
View
0
1
MockPresenter
MockViewView
Presenter
0
1
0
1
MockModel
125. Un peu d'histoire
Apparition du MVVM
Gère
les données affichées
Presenter
Affiche les données
interagit avec l'utilisateur
View
La logique Business
Et les données
Model
1
1
ModifieMets à jour
Dialogue
ViewModel
1
n
127. Architecture
But wait ! Première remarque.
MVC MVP MVVM FLUX
On s'en fout !!! C'est pas ça l'important !!!
C'est pareil à trois poils de *** prêt !
149. Modèle n-tier
Architecture de la vraie vie
Service
Vues
COM
DAO
Transverse
Use case
1
Use case
2
Use case
3
On commence
par un use case
et après on
réutilise dès
qu'on peut
152. Vues
164
Rangez votre package view !
Un sous package par fragment
Un sous package par composant
graphique complexe de l'activité
Au root du package, l'activité et son
presenter
Un package par vue
153. Limiter les dépendances
165
Couche IHM
HumanViewParentIntf HumanPresParentIntf
FamilyView FamilyPresenter
1 1
HumanView HumanPresenter
1 1
use use
Vues
Vue contenue et réutilisable
Utilisez des interfaces
Couche IHM
HumanViewParentIntf HumanPresParentIntf
FamilyActivity FamilyPresenter
1 1
HumanFragment HumanPresenter
1 1
use use
155. MVP + N-Tier
mettre en place le principe de
séparation des
responsabilités
Etre testable unitairement
simplement
permettre de s'adapter aux
changements du framework
Synthèse
Sans impact, bref d'être indépendant de l'implémentation et de
pouvoir la modifier et la tester
permettre de faire évoluer les
UI, la BD,...
permettre de changer de
librairies
Le contexte = pénurie et vie en collectivité d'enfants
5 minutes
bref chet haase stuff
Une question simple
Une réponse complexe qui dépend du contexte
Une question simple
Une réponse complexe qui dépend du contexte
Il ne faut jamais ignoré le contexte et celui ci peut être multiple (a plusieurs niveaux)
Memory c'est les perfs: plus on alloue, plus on désalloue, plus on fait marcher le GC & plus on stress le CPU
Memory: c'est aussi plus tu en prends moins tu en laisse aux autres
Memory: les images
=> entraine des bonnes pratiques de rédaction de code spécifique à Android
Memory c'est les perfs: plus on alloue, plus on désalloue, plus on fait marcher le GC & plus on stress le CPU
Memory: c'est aussi plus tu en prends moins tu en laisse aux autres
Memory: les images
=> entraine des bonnes pratiques de rédaction de code spécifique à Android
Memory c'est les perfs: plus on alloue, plus on désalloue, plus on fait marcher le GC & plus on stress le CPU
Memory: c'est aussi plus tu en prends moins tu en laisse aux autres
Memory: les images
=> entraine des bonnes pratiques de rédaction de code spécifique à Android
Memory c'est les perfs: plus on alloue, plus on désalloue, plus on fait marcher le GC & plus on stress le CPU
Memory: c'est aussi plus tu en prends moins tu en laisse aux autres
Memory: les images
=> entraine des bonnes pratiques de rédaction de code spécifique à Android
Multi processus utilise la mémoire (si ils en utilise ou en garde) c'est le drame (LRU cach strategy tout ça)
Et pire la réalité pour nos utilisateur, ils ont des appareils pourris ! Il faut tester sur des appareils pourris aussi
et dans ces 4Go il faut y mettre le système, facebook, les photo de l'utilisateur et ses jeux, ses applis, ses chansons...
1)We have the slower CPU of the world and our users slowest
2)Stressing the CPU drains the battery. System anwser: down clocking the CPU (except if you check the device, runs animations permanently,
3)GPU is the same (Bitmaps loading).
1)We have the slower CPU of the world and our users slowest
2)Stressing the CPU drains the battery. System anwser: down clocking the CPU (except if you check the device, runs animations permanently,
3)GPU is the same (Bitmaps loading).
1)We have the slower CPU of the world and our users slowest
2)Stressing the CPU drains the battery. System anwser: down clocking the CPU (except if you check the device, runs animations permanently,... => you get the CPU at its max speed => drain the battery)
3)GPU is the same (Bitmaps loading).
Votre problème principal car c'est celui de votre utilisateur
Qui parmis vous possède un chargeur ? un chargeur portable ?
Votre problème principal car c'est celui de votre utilisateur
Les bonnes pratiques de code et d'architecture
C'est pour ça que vous êtes là non ? évitez tout ça :)
Votre problème principal car c'est celui de votre utilisateur
La vie en collectivité: Dès que l'un en fait trop cela impacte les autres,
Dès que l'un tue la batterie pour son propre confort, c'est l'appareil qui parait pourri
Dès que l'un prend trop de ressources, ce sont les autres qui en manquent
Si vous en avez trop pris, votre vie dans le LRUCach sera limité, vous ne resterez pas longtemps => votre app prendra du temps à se re-lancer
Ne te penses pas plus important que ne l'est.
Chacun agit celon son propre besoin, ses propres responsabilités, aux mieux pour lui mais pas au mieux pour tous.
Aucune app n'est responsable de l'apparente pietre qualité du device, si on les analyse, rien n'en sortira.
C'est la somme des agissements de chacun, c'est chaque détail, qui ferra qu'à la fin de la journée le device sera épuisé
(exemple l'over sync de toutes les applications si il y en a 100, à 14h plus de batterie...)
5 minutes
fluide => toutes les apps cohabitent
Les super pouvoirs== cadeau des dieux grecs (photo du monde, communication à des distances de dingues, voir, écouter, apprendre, être en lien avec des gens lointains...)
60 fois par seconde une fenetre s'ouvre pour redessiner l'écran, si vous n'êtes pas là, pas pret, vous ratez la fenetre, la prochaine est dans 16 ms...
C'est une de vous plus grosses contrainte
Freeze every body to work
10-20 ms facile avec des super fast device (Dalvik)
2-3mps avec ART
More object (=> avoid allocating) more complex more long
Et surtout tu rates ta fenetre des 16ms pour etre redessiner, imagine ce que ca donne durant une animation.
Pour les ListView c'était super important
Moi, ça m'est arrivé souvent d'avoir le not enough space
Est-ce que quelqu'un parmis vous à déjà implémenté et utilisé ce code?
Dessin de RetoMeier GoogleIo2012
Utilisation de la big cookies startegy (on y reviendra)
Dessin de RetoMeier GoogleIo2012
Utilisation de la big cookies startegy (on y reviendra)
10 minutes
Laissez les gens rencontrer votre application. Le schema de Damien Mabin était super DroidConGreece
10 minutes
Connaitre, comprendre, accepter le monde Android et ses contraintes, évoquées slides précédents
Tu es au service de l'utilisateur, on ne l'oblige pas, on lui parle avec cordialité, on ne le frustre pas tout ça tout ça
et on le respecte. On est tous d'accord?
Du coup qui a déjà codé ces balises ?
Vous avez déjà codé pour être compliant avec l'accessibilité ?
L'histoire du startup we, premier reflexe, on va vendre nos données utilisateurs
Protéger les données de vos utilisateurs
L'histoire du startup we, premier reflexe, on va vendre nos données utilisateurs
Protéger les données de vos utilisateurs
drawable-xhdpi
layout-small
Souvenez vous dans le désert
Il y aussi Google et facebook qui partent faire des tests dans des zones inaccessibles
Il y a aussi enoirmenet d'adaptation à ce facteur dans les différents
Ca va beaucoup mieux avec la SupportLibrary mais c'est pas encore ça
Les animations par exemple
Vous n'êtes pas à l'abris des bugs spécifiques
30 minutes
Il y a les miennes et celles de chet
Parce que sans de bonnes pratiques de code vous n'arriverez pas à atteindre ces objectifs
C'est dans les détails que le diable se cache et c'est pour ça que c'est bonne pratique sont importantes
Votre utilisation de la mémoire est le point le plus important pour voir a quel point l'application se comporte bien.
(taille de l'empreinte mémoire ainsi que sa gestion (et donc le passage de GC)
Il y a des endroits de code (getView, onDraw) où il ne faut ni déclarer ni allouer d'objets
Mettre les objets en cach permet de ne pas les réallouer n fois
Au besoin la mise en place d'un Pool d'objets peut-être la bonne solution
Mettre des variables temporaires en variable de classe (static) [ListView ArrayAdapter.getView]
Les HashMap ne sont pas a utiliser
Définissez la taille des ArrayList si vous pouvez, evite la réallocation en doublant l'espace mem+la recopie des données
Pas d'itérateurs ca créé un objet (et alors ?) du coup for(Object o:toto){for(AnotherObject ao:o.truc())} c'est allouer un autre objet dans un objet
Les énumérations requierent plus d'espace pour implémenter de simples constantes primitives, il vaut mieux des constantes directement. Ca prend trop de place, il ne faut surtout pas dire que c'est une bonne pratique, car tout avec des enums et c'est la mort mémoire
Les librairies, c'est clair.
public static c'est Romain Guy au Paug
C'est une philosophie sous Android de n'initialiser les objet qu'a la demande pour éviter des temps de demarrage digne d'eclipse.
Cela signifie qu'il est mieux parfois de passer en paramètre le retour de la méthode Rect getRect() =>getRect(Rect return)
Les services: l'exemple du scruteur de changement de connnectivité réseau au lieux de la mise en place d'un broadcastreceiver
onTrimMemory (api level 14) vous appele quand on vous demande liberez de la memoire le systeme n'en peut plus.=> exemple de Bitmap dans le LruCach
Les services: l'exemple du scruteur de changement de connnectivité réseau au lieux de la mise en place d'un broadcastreceiver
onTrimMemory (api level 14) vous appele quand on vous demande liberez de la memoire le systeme n'en peut plus.=> exemple de Bitmap dans le LruCach
Très utile pour produire différents apk avec un if else à la compilation:
Pour test/prod
Différente densité == économie de taille d'apk avec les images
Différentes version pour une gestion spécifique (post hc et pre hc
Différents processeurs (intel, amd...
Measure and Layout : happen in the ui threa d and it's time consuming (depends on view complexity)
Inflation: same
Init App: rallonge le temps de lancement (dans oncreate) c'est dans l'uithread
Drawing the layout is a two pass process:
measuring pass - implemented in the measure(int, int) method and is a top-down traversal of the view hierarchy. Every view stores its measurements.
layout pass - implemented in the layout(int, int, int, int) method is also a top-down traversal of the view hierarchy. During this phase each layout manager is responsible for positioning all of its children. It uses the sizes computed in the measure pass.
Note
The measure and layout step always happen together.
Note
Layout managers can run the measure pass several times. For example LinearLayout supports the weight attribute which distributes the remaining empty space among views and RelativeLayout measures child views several times to solve constraints given in the layout file.
A view or activity can retrigger the measure and layout pass with a call to the requestLayout() method.
Complexité augmente le nombre de passage (complexité dépend de la profondeur)
RelativeLayout jamais en début de fichier !! double le nombre de passe necessaire
Pas d'animations, soit on les fait avant et on inflate/change les view, soit on le fait après.
Préferrez les animations qui ne change pas le layout (Layout.param non,non, non, mais TranslationX, ... oui oui oui)
Remplacer
CutomView: Exemple de facebook dans ses listes, et de manière générale on fige les dimensions plus vite avec un passage unique.
La starting Window: Utilise le theme de l'application pour afficher une fenêtre de démarrage.
Ils te permettent de simplifier tes layouts (dont l'objectif est le placement des composants)
Ils te permettent de centraliser les caractéristiques graphiques, de les organiser et de les classer
Ils te permettent ^d'être cohérent globlement et de facilement évoluer
Ils te permettent de simplifier tes layouts (dont l'objectif est le placement des composants)
Ils te permettent de centraliser les caractéristiques graphiques, de les organiser et de les classer
Ils te permettent ^d'être cohérent globlement et de facilement évoluer
Ils te permettent de simplifier tes layouts (dont l'objectif est le placement des composants)
Ils te permettent de centraliser les caractéristiques graphiques, de les organiser et de les classer
Ils te permettent ^d'être cohérent globalement et de facilement évoluer
OverDraw: Redessiner plusieurs fois un même pixel (Debug GPU overDraw)
Pour la starting window c'est le blog de cyril sur le démarage des apps et sur l'overdraw
Pour la context, on ne peut pas utiliser le context app pour créer du GUI car il lui manque le thème/ idem pour la récupération de ressources
Les vues ne sont pas connues des Threads, sinon fuite mémoire potentielle
GCM manager pour les synchronisation cliente
GCM pour que le serveur previenne le client
Parcelable pas d'enregistrement sur disque: Le mieux c'est Json + zip
Sérialisation elle stocke trop d'informations pour assurer une persistance disque ad vitam eternae, trop lente, à proscrire.
Ici MaConstante ne sera jamais GC, vu qu'elle implicitement retient l'instance de la classe qui l'a créé la première fois, c'est mort, fuite mémoire de la taille d'une activité
Donnez les PoolExecutor que tu utilises (1 pour la BD, 4 pour le reste cancelable, 4 pour le non cancelable)
Les classes internes non statiques conservent une référence vers leur classe englobante.
Les classes internes non statiques conservent une référence vers leur classe englobante.
Les classes internes non statiques conservent une référence vers leur classe englobante.
Drawable pointe implicitement vers lActivité qui l'a instancié, donc le mettre en statique pour sauver le temps de chargmenet du bitmap lors de la rotation est une erreur horrible. car on retient toujours la première instance de l'activité
Ici MaConstante ne sera jamais GC, vu qu'elle implicitement retient l'instance de la classe qui l'a créé la première fois, c'est mort, fuite mémoire de la taille d'une activité
Don't fuck around== Chaque brique du système possède une semantique très precise, il faut l'utiliser en coherence avec cette semantique.
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. If you do not need either of these behaviors (Bound/Start), you should not use a Service.
For example, if you have background work to do that does not need to keep itself running (such as downloading content for your UI that you could resume later when the user returns to the UI) you should use local threading primitives such as AsyncTask, Loader, HandlerThread, etc. Using a Service is much more resource-intensive (as your service needs to be tracked as part of the global system state)
Le mot service prête a confusion !!
Utiliser un broadcast pour écvouter un event plutot qu'un service qui tourney sans arret.
systrace= voir combine de temps on passe dans une méthode
Analyse des écrans: Animations, View hierarchy, Pixel perfect, overdraw,
20 minutes
Explication de chaque modèle via un schéma pour chacun
La classe Dieu, elle fait tout dans une meme classe, presentation des données, interactions utilisateurs, animations, appel BD, appel réseau... 100 000 lignes de code, aucun tests
La classe Dieu, elle fait tout dans une meme classe, presentation des données, interactions utilisateurs, animations, appel BD, appel réseau...
Et on a inventé le MVC : decouple la vue, de l'interaction, de la representation.
La classe Dieu, elle fait tout dans une meme classe, presentation des données, interactions utilisateurs, animations, appel BD, appel réseau...
La classe Dieu, elle fait tout dans une meme classe, presentation des données, interactions utilisateurs, animations, appel BD, appel réseau...
On a relié View et Controller, c'est là qu'on a mis les listener dans les vues
La liaison est 1-1, on sait alors que c'est du pipo pour l'rdi que c'est fait pour les humains et pour les tests
La classe Dieu, elle fait tout dans une meme classe, presentation des données, interactions utilisateurs, animations, appel BD, appel réseau...
La classe Dieu, elle fait tout dans une meme classe, presentation des données, interactions utilisateurs, animations, appel BD, appel réseau...
La classe Dieu, elle fait tout dans une meme classe, presentation des données, interactions utilisateurs, animations, appel BD, appel réseau...
La classe Dieu, elle fait tout dans une meme classe, presentation des données, interactions utilisateurs, animations, appel BD, appel réseau...
La classe Dieu, elle fait tout dans une meme classe, presentation des données, interactions utilisateurs, animations, appel BD, appel réseau...
Grace a gradle et aux saveurs on peut automatiser les confs poru executer les tests
Et aussi il y a du DataBinding
J'ai vu des flux tweeter où les gars s'engueulaient pedant 500 tweets sur le sujet (mais tu fais du MVPM toi, ... blazbla)
Principe de separation des couches, des responsabilités
Isolation des composants = Interfaces
Chaque bloc est indépendant des autres
L'importance des flêches de dependence. Qui connait qui.?
n-tier== n niveau logique (tier=niveau logique)
Les services métiers ne sont pas des services Android
ce sont les traitements métiers, la couche business/métier
Les services Android c'est une toute autre notion
On sépare le code, on le ventile, on l'aère
A scindé en plein de partie, c'est globalement compliqué, mais localement simple
Chaque couche est responsable d'une partie precise de l'application: Algorithme, HTTP, DAO, Vues...
Car chaque couche/composant possède un contrat très précis décrit par son interface
Car modulaire
Car modulaire
Car ils ne dependent de personne
JUnit et bouchons
Les tests apportent la sécurité pour le projet
Les tests apportent la sécurité pour le projet
La couche IHM contient l’ensemble des écrans et se scinde en plusieurs sous-packages.
Une application hiérarchise ses vues, cette hiérarchie doit se retrouver dans vos packages.
La couche IHM contient l’ensemble des écrans et se scinde en plusieurs sous-packages.
Une application hiérarchise ses vues, cette hiérarchie doit se retrouver dans vos packages.