La conférence « Architecture Android » de Mathias Seguy (Android2EE).
Si l’architecture d’applications Android vous questionne, vous intéresse, vous mystifie cette conférence est faîte pour vous.
Elle s’attachera à décrire les design patterns, contraintes et bonnes pratiques Android. Cela nous permettra de concevoir une architecture propre aux applications Android.
La conférence se conclura en donner les pro-tips Android provenant des GoogleIO et Devoxx qu’il faut connaitre pour concevoir des applications pertinentes sous Android.
Cette conférence a eu lieu le mardi 19 Mars 2013, à 19h.
Si vous êtes un JUG ou un AUG et que cette conférence vous interesse, n'hésitez pas à me contacter.
Si vous souhaitez apprendre la technologie Android, contacter moi: mathias.seguy@android2ee.com, je suis formateur Android et les formations Android que je dispense sont exceptionnelles.
Speaker:Mathias est le fondateur de la société Android2ee spécialisée dans la technologie Android.
Il est :
• formateur Android,
• expert logiciel Android,
• speaker Android sur de grandes conférences Java : Devoxx France, Eclipse Day Toulouse, JCertif Africa, Toulouse JUG, CocoAhead,…
• Rédacteur Android sur Developpez.com ;
• Programmateur Android : MyLight, MyTorch, MySensors, JCertifMobile disponibles sur GooglePlay ;
• Docteur en Mathématiques Fondamentales et Ingénieur de l’ENSEEIHT ;
• Expert technique de l’agence nationale de la recherche française ;
Il présentera au cours de cette conférence sa vision sur la mise en place d’une architecture d’une application Android pertinente et partagera les meilleurs pro-tips (astuces de pro) de sa connaissance. A ne pas manquez.
Mathias Séguy
mathias.seguy@android2ee.com
Fondateur Android2EE
Formation – Expertise – Consulting Android.
Ebooks pour apprendre la programmation sous Android.
2. Formateur Consultant Expert Android
mathias.seguy.it@gmail.com (mail)
@android2ee (twitter)
Fondateur Android2EE – Formation Expertise Android
Auteur d’EBooks sur la programmation Android (Android2ee.com)
Docteur en Mathématiques Fondamentales
Expert Technique de l'Agence Nationale de la Recherche
Rédacteur sur Developpez.com
Blogs Android : Android2EE sur DVP et Android2ee sur BlogSpot
Doctorat Mathématiques
Fondamentales Via CapGemini Via Sopra Naissance Android2EE
Siemens Magnus DGA CNES Airbus Airbus Airbus STI Android2EE
VDO
03 04 05 06 07 08 09 10 11 12
Java J2EE
Android
Manager Manager
Leader Leader Leader Leader Directeur Fondateur
Développeur Développeur IHM Technique Technique Manager Technique Technique Android2EE
Master Informatique de Chez STI
l’ENSEEIHT
2
3. • Pourquoi de l’architecture ?
• Architecture d’applications Stand Alone (Swing)
• Android Application Design
• Android ProTips
• MutliVersionning & Resources are your
best friends
• Automatiser les tests !
3
7. Le modèle N-Tiers et ses dépendances.
Chaque couche possède une interface.
Chaque couche possède un cahier des charges très précis.
Les dépendances entres les packages sont extrêmement importantes.
Internet
Façade
COM
COM
Façade DataBase
IHM Service
Service Façade
DAO
DAO
FileSystem
Use or not
Transverse
Use
7
8. Les fondations de l’application.
La couche transverse est connue de tous mais ne connait personne.
Objet Service Utilitaire Transverse Service des Exceptions
métiers
PojoA Log HttpUtil ExceptionManager
PojoB Tracker DateUtil ExceptionManaged
DtoA StringUtil
8
9. Le cerveau de l’application.
La couche service contient les services métiers.
LoginSrv SimulationSrv MainStreamSrv
MainStreamSrvIntf
SimulationSrvIntf
LoginSrvIntf
LoginSrvImpl MainStreamSrvImpl
SimulationSrvImpl
LoginUtil MSDisplaySrv
LoginCore MsUpdateSrv
9
10. La mémoire de l’application.
La couche DAO : Data Access Object.
Elle n’expose ses services qu’au travers d’interfaces.
Utiliser les factory.
Expose les méthodes CRUD (Create, Read, Update, Delete) et autres méthodes spécifiques.
Human SqlUtil
HumanDaoFactory SqlUtil
HumanDaoIntf
HumanDaoHb8
HumanDaoJdbc
HumanDaoFS
10
11. La bouche et les oreilles de l’application.
La couche COM (communication) prend en charge l’accès aux données et services déportés sur un serveur (web ou autre).
On ne doit pas spécifier le type de communication mais le service rendu.
ForecastCom
HttpComUtil
ForecastComIntf
use
HttpComUtilIntf
ForecastComImpl
HttpComUtilImpl
11
12. Hiérarchie des packages de vues.
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.
Cohérence
12
13. Le modèle Model – (Vue Controller).
Le deuxième concept fort est la séparation entre l’affichage des objets et leur gestion.
La view affiche
le model gère les données.
Le model se charge de la gestion des objets affichés et la communication avec les services.
1 1
FamilyView FamilyModel FamilySrvIntf
0 0
use
n n
1 1
HumanView HumanModel HumanSrvIntf
0 0
n n use
1 1
AdressView AdressModel AdressSrvIntf
13
15. Houston, we’ve got a problem.
Avec Android le problème est « Comment mettre en place ce Design en respect du cycle de vie des activités? ».
Certaines application Swing ont un temps de boot de 30 secondes, et ce n’est pas possible sur Android.
15
16. Les contraintes sont:
Il faut que notre Design réussisse quatre challenges:
•On ne doit instancier un élément qu’à la demande;
•Quand un élément est instancié, il faut qu’il le reste;
•On ne doit pas détruire les éléments instanciés quand l’activité est
déconstruite pour être immédiatement reconstruite;
•On doit pouvoir laisser les traitements se terminer (au sens use case) tout en
étant capable de leur signaler que l’application est terminée...
16
17. L’enfer de la fragmentation
Notre Design doit être capable de :
• Lancer l’application en mode Post-HoneyComb/ICS/JB ou en mode pre HoneyComb / ICS / JB,
•Instancier des composants en fonction de la version du système
17
18. Le Pattern Parrallel Activity étendu :
Instancier l’implémentation d’un service / d'une brique applicative en fonction de la
version du système. Human
AServiceFactory
AServiceIntf
AServiceV6Impl
AServiceV11Imp
AServiceV13Imp
18
20. Parrallel Activity Pattern and fragments :
Les activités sont devenues des coquilles vides qui écoutent les Intents, imposent le cycle de vie et re-routent les évènements vers les
fragments qui les composent. C’est la nouvelle philosophie générale.
Votre application lancera une première activité qui aura comme unique objectif de lancer soit l’activité dans son mode pré-HoneyComb soit
dans son mode post HC.
Ces deux activités utiliseront les mêmes classes de type Fragment (grâce à la support-librairy).
Il faut décliner chaque Intent en post et pré HC.
ActivityLegacy 0
1
LauncherActivity Fragment
1
ActivityHC 0
20
21. Les fragment et les support.v4.app.Fragment ne sont pas compatibles:
Ils ne possèdent pas d’interface commune.
Duplication du code de vos fragments (seules différences leurs imports).
0 1
ActivityLegacy FragmentLeg
LauncherActivity
0 1
ActivityHC FragmentHC
21
22. Les fragments (post-HC et pre-HC) partagent le même modèle.
Les fragments sont purement du code dupliqué. Les modèles sont indépendants de la version du système.
Chaque Fragment est associé à son modèle. Chaque activité se décline en deux types : pré et post HC.
0 1
ActivityLegacy FragmentLegacy
1
1 use
LauncherActivity FragmentModel AServiceSrvIntf
1
0 1 1
ActivityHC FragmentHC
22
23. Le modèle MVC Android.
Le modèle MVC Android se décline en trois types :
0 1
None : Les données à afficher sont en read-only, peu de traitement au ActivityLegacy FragmentLegacy
niveau des données. LauncherActivity
0 1
ActivityHC FragmentHC
Simple : Chaque fragment gère ses données au moyen de son modèle.
Full : L’activité et les fragments ont des données corrélées qu’il faut
synchroniser au niveau des modèles.
0 1 0 1
ActivityLegacy FragmentLegacy ActivityLegacy FragmentLegacy
1 1 1
use ParentIntf
1 1
LauncherActivity 1
FragmentModel LauncherActivity FragmentModel
1
ActivityModel
1 1
0 1 1 1 1
0 1
ActivityHC FragmentHC ActivityHC FragmentHC
23
24. Votre package UI doit alors ressembler à
Attention à la duplication du code
quelque chose comme le schéma ci-contre:
Les activités/fragments Legacy, HC, ICS, JB possèdent le même modèle.
Il reste les problématiques :
•de la démultiplication des activités qui est plus fatigante en termes :
• de code,
•de maintenabilité
•et la duplication de code des fragments.
24
26. Base class for those who need to maintain global application state.
La classe application va nous permettre de répondre aux problématiques suivantes :
instancier un élément qu’à la demande;
un élément est instancié doit le rester;
On ne doit pas détruire les éléments instanciés ne doivent pas être détruit quand l’activité est déconstruite/reconstruite;
On doit pouvoir laisser les traitements se terminer
L’objet application est créé dès que l’un des composants de votre application est instanciée et est détruite quand le dernier
composant est détruit.
public class DesignApplication extends Application { Déclaration dans le AndroidManifest.xml
HumanDaoIntf humanDao;
/** * @return the humanDao */ <application
public final synchronized HumanDaoIntf getHumanDao() { android:icon="@drawable/ic_launcher"
if(humanDao==null) { android:label="@string/app_name" android:name=".MyApplication">
humanDao=new HumanDaoCPImpl();
}
return humanDao;
}
}
26
27. Application, où es-tu?
Utilisez le pattern du singleton.
public class DesignApplication extends Déclaration dans le AndroidManifest.xml
Application {
@Override <application
public void onCreate() { android:icon="@drawable/ic_launcher"
super.onCreate(); android:label="@string/app_name" android:name=".MyApplication">
//First record the Application object
AppSingleton.INSTANCE.setApp(this);
…
} Le code pour récupérer l’Application quand vous êtes ni dans
une Activity ni dans un service :
public enum AppSingleton {
//Retrouver l’application
INSTANCE;
AppSingleton.INSTANCE.getApp();
private DesignApplication application;
/** * @return */
public DesignApplication getApp() {
return application; Le code pour récupérer l’Application quand vous êtes dans
} une Activity ou dans un service :
/** * @param app */ //Retrieve the application
public void setApp(Application app) { (DesignApplication )getApplication();
application=app;
}
}
27
28. Les services métiers doivent :
finir leur traitement,
être instancié à la demande,
persister leur instanciation,
ne pas suivre le cycle de vie de l'activité mais celui de l'application.
Pour résoudre ce problème vous avez deux choix :
•Soit votre service métier est un service Android.
•Soit ce n’est pas un service Android et il faut le faire à la main.
Le second cas est complexe est n’est pas recommandé.
28
29. Les services Android sont les services métiers de l'application.
Net
Façade
COM
COM
Service
Couche Façade DB
Métier
IHM Service
Android Façade
DAO
DAO
FS
Use or not
Transverse
Use
29
30. Le ServiceLoader
Le service Loader a pour objectif :
•de lancer les services de l'application,
•de se lier à eux en mode bind (et ainsi conserver un pointeur vers eux),
•de se délier à eux quand l'application s'arrête (et ainsi de les arreter).
LazyLoading
Pour cela,
DaughterService2
1-1 •C'est un singleton (une
énumération)
ServiceLoader DaughterService1 •Il connait ses services via une
1-1 liste de ServiceConnection
ServiceLoader
•Possède une méthode d'arrêt.
Caption BIND
1
LazyLoading Et il faut l'écrire à la main.
WorkFlow getService N'appartient pas au SDK.
MService
2
callBack
Contains
Extends DaughterActivity
service.doSomething()
3
30
31. Le serviceLoader : Mise en place du LazyLoading
private void launchServiceAsync() { Activity /** * The method to be called by the activities ServiceLoader
// load your service... @return the dService (could be null if not started yet) */
ServiceLoader.instance.getdService(new OnServiceCallBack() { public void getdService(OnServiceCallBack onServiceCallBack) {
public void onServiceCallBack(MService service) { startDummySrv(); // insure the service is started
// so just call the method you want of your service // then if the service is null add the callBack to the list of elements waiting for the service
((DummyService) service).doSomethingAsynch(getActivityId()); to be instantiated
}});} if (dService == null) {// add to the callback list
onDServiceCallBack.add(onServiceCallBack);
/** * The service you want to expose to activities */ ServiceLoader } else {// else, service is not null, just use directly the callback
private DummyService dService = null;
onServiceCallBack.onServiceCallBack(dService);
/** * The list of the callback of the service instantiation */
}}
private List<OnServiceCallBack> onDServiceCallBack;
/** * Method to start the DummyService service */ /** * The object to handle the connection of this with the service. So this will keep a pointer on
private void startDummySrv() {// insure the list is not null the service not loosing it in the memory. */ ServiceLoader
if (onDServiceCallBack == null) {onDServiceCallBack = new private ServiceConnection dummySrvConn = new ServiceConnection() {
ArrayList<OnServiceCallBack>();} // as usual deconnexion
// if the service is null, launch it using the Bind process @Override
if (dService == null) { public void onServiceDisconnected(ComponentName name) {dService = null;}
Intent service = new // as usual connection
Intent(MAppInstance.ins.getApplication(), DummyService.class) @Override
; public void onServiceConnected(ComponentName name, IBinder service) {
MAppInstance.ins.getApplication().bindService(service, dummySrv // use the IBinder
Conn,Service.START_STICKY);} dService = (DummyService) ((MService.LocalBinder) service).getService();
// add it to the list of bound services (if not in yet): // notify callback the service is awake
if(!boundServices.contains(dummySrvConn)) for (OnServiceCallBack callBack : onDServiceCallBack) {
{boundServices.add(dummySrvConn);} callBack.onServiceCallBack(dService);
} }// and clear the callback list
onDServiceCallBack.clear(); onDServiceCallBack = null;}}; 31
32. Persister les services : l'objet Application instancie le ServiceLoader
Persitence des services public class MApplication extends
Application {
DaughterService2 /** * Just keep a reference on it to
Persistence 1-1
avoid unusefull
1-0 destruction/recreation of services */
Caption Application ServiceLoader DaughterService1 private ServiceLoader sLoader;
1-1
@Override
public void onCreate() {
WorkFlow BIND
...
1
LazyLoading // keep the ref
getService sLoader =
Contains MService
2 ServiceLoader.instance;
callBack }
Extends /**
* This method has to be called when
DaughterActivity the application died. It kill all the
service.doSomething() services So find the activity that quit
3 the application and add that method
*/
public void onBackPressed() {
// Call the application object and
release the services
sLoader.unbindAndDie();
}
}
32
33. Communication
Service-Activity
1
DaughterActivity DaughterService1
service.doSomething(String ActivityId)
4
onServiceCallBack 2
(int serviceMethodId,
MActivity callBack(methodID, activityID, result);
Object result)
3
1-1
Build And Launch Intent to the Activity using its ActivityId
BroadCastReceiver ServiceHelper
Caption WorkFlow Contains Extends
33
34. Objectif pour le développeur final : peu de code.
public abstract class MyActivity extends MActivity {... Activity /** Retrieving results from business services calls *******/ Activity
private void launchServiceAsync() { /** The method to implement to root your service call with the method that handle
// find your service...(lazy loading part) the return. It's called back via intent through MActivity. So you just need to check
ServiceLoader.instance.getdService(new OnServiceCallBack() { your method id and do something with your result */
public void onServiceCallBack(MService service) {
4 protected void onServiceCallBack(int serviceMethodId, Object result) {
// so just call the method you want of your service
((DummyService) service).doSomethingAsynch(getActivityId());}});} 1
switch (serviceMethodId) {
case DummyService.doSomethingID:
public class DummyService extends MService { // call your result method doSomethingResult(result)
Service onServiceResult((ConstantData) result); 5
/**
* The id of the method doSomething break;
Activity
* It has to be unique for your whole application and for each service'method default:
of it. break;}
*/ }
public static final int doSomethingID = 10000101; private void onServiceResult(ConstantData message) { 6
/** * A dummy method with the return sent to the calling activity // You can also display a indeterminate progress bar
* * @param activityId The unique id of the calling activity */ simpleServiceCall--;
public void doSomething(String activityId) { 2 if(simpleServiceCall==0) {
// do your treatment prbResultSimple.setVisibility(View.GONE);}
ConstantData data = new ConstantData("SomeResuls", 11021974, true); txvResultSimple.setText(message.message);}
// And the important part is here
// When your treatment is over, just use the serviceHelper to contact the calling
Activity and to send the method return to that activity
ServiceHelper.instance.callBack(doSomethingID, activityId, data); 3
}
34
35. L'activité mère
public abstract class MActivity extends Activity { 1) Mise en place de l'écoute transparente des Intents via une
/** * @return a unique identifier of the activity to be attached to intents that have to be activité mère.
calledBack by the activity */ 2) Enregistrement en tant qu'écouteur d'intents qui ont pour
public String getActivityId() {// so use the canonical name it should be enough
action le getActivityId
return getClass().getCanonicalName();}
3) Parcing du retour dans le BroadcastReceiver et appel de
protected abstract void onServiceCallBack(int serviceMethodId, Object result); la méthode onServiceCallBack
private BroadcastReceiver serviceCallBackReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("MActivity", "serviceCallBackReceiver:onReceive, called: " + intent.getAction());
// first be sure to listen for the right intent
if (intent.getAction() == getActivityId()) {
// retrieve the type of the result object
String resType = intent.getStringExtra(ServiceHelper.SRV_MTH_RES_TYPE);
// using that type, retrieve the result object
// there will be a lot of case, should be the same case than the number of
@Override
// intent.get**Extra method
protected void onResume() {
if (resType.equals(ServiceHelper.Parcelable)) {
super.onResume();
onServiceCallBack(intent.getIntExtra(ServiceHelper.SRV_MTH_ID, -1),
registerReceiver(serviceCallBackReceiver, new IntentFilter(getActivityId()));}
intent.getParcelableExtra(ServiceHelper.SRV_MTH_RES));
@Override
} else if (resType.equals(ServiceHelper.String)) {
protected void onPause() {
// other case
super.onPause();
onServiceCallBack(intent.getIntExtra(ServiceHelper.SRV_MTH_ID, -1),
unregisterReceiver(serviceCallBackReceiver);}
intent.getStringExtra(ServiceHelper.SRV_MTH_RES));
}
}// and so on}}};
35
36. Le service mère
1) Mise en place du Binding automatique des services avec le
service locator
public class MService extends Service {
/** The binder process ***********/
/** * The Binder */
private final Binder binder = new LocalBinder();
/** *This class aims to be a local Binder that keep and share a pointer on the instance of this
*/
public class LocalBinder extends Binder {
// as usual
public MService getService() {// return the instance of the service
return MService.this;}
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
36
37. Les services filles
1) Déclare les identifiants de méthode du service
2) Mets en place les méthodes du services
3) Et renvoie toujours le résultat via:
ServiceHelper.instance.callBack(doSomethingID, activityId, data);
public class DummyService extends MService {
/**
* The id of the method doSomething
* It has to be unique for your whole application and for each service'method of it.
*/
public static final int doSomethingID = 10000101;
/** * A dummy method with the return sent to the calling activity
* * @param activityId The unique id of the calling activity */
public void doSomething(String activityId) {
// do your treatment
ConstantData data = new ConstantData("SomeResuls", 11021974, true);
// And the important part is here
// When your treatment is over, just use the serviceHelper to contact the calling Activity
// and to send the method return to that activity
ServiceHelper.instance.callBack(doSomethingID, activityId, data);
}
37
38. Le ServiceHelper
1) Construit et envoie les Intents dans le système pour être automatiquement récupérés par les activités appelantes.
public enum ServiceHelper { /** CallBack ******/
instance; /** * This method just create an Intent and launched it
/** Constants ***/ * @param serviceMethodId : The method that ask for sending the result to the activity
/** The constant to be used by intent to carry the service's method's * @param activityId : The activity that calls the method, the intent will be received by it
id */ * @param result: The object to carry (the result object of the method) */
public static final String SRV_MTH_ID = public void callBack(int serviceMethodId, String activityId, Object result) {
"com.android2ee.service.method.id"; //create the Intent
/** * The constant to be used by intent to carry the service's Intent callBack = new Intent(activityId);
method's result */ //add the service method id
public static final String SRV_MTH_RES = callBack.putExtra(SRV_MTH_ID, serviceMethodId);
"com.android2ee.service.method.result"; //your object should implements Parcelable
/** * The constant to be used by intent to carry the service's //add the type of the result object (to unparse)
method's result */ if (result instanceof Parcelable) {
public static final String SRV_MTH_RES_TYPE = callBack.putExtra(SRV_MTH_RES, (Parcelable) result);
com.android2ee.service.method.result.type"; callBack.putExtra(SRV_MTH_RES_TYPE,Parcelable);
}else if( result instanceof String) {
callBack.putExtra(SRV_MTH_RES, (String) result);
callBack.putExtra(SRV_MTH_RES_TYPE,String);
}//and so on
//Then send the Intent
MAppInstance.ins.get().sendBroadcast(callBack);
}
}
38
39. ServiceHelper et ServiceLoader full pattern
DaughterService2
Persistence 1-1
Caption 1-0
Application ServiceLoader DaughterService1
1-1
WorkFlow
BIND
1
Contains LazyLoading
getService
MService
2
Extends
callBack
Communication
DaughterActivity Service-Activity
6 service.doSomething()
onServiceCallBack 3 4
(int serviceMethodId,
MActivity callBack(methodID, activityID, result);
Object result)
5
1-1
Build And Launch Intent to the Activity using its ActivityId
BroadCastReceiver ServiceHelper
39
40. Le projet MythicServiceHelper imlplémente ce pattern (version alpha draft)
Tout le code est disponible :
https://github.com/MathiasSeguy-Android2EE/MythicServiceHelper
40
41. Les services métiers sont des services Android.
Application Service Net
Façade
Métier COM
COM
Android
Couche
IHM Façade
Service ServiceLoader DB
MActivity ServiceHelper Façade
start DAO
MService DAO
FS
Use or not
Transverse
Use
Préserver les interfaces
Il est important de pouvoir changer la manière dont fonctionne un service sans avoir à modifier le reste de l‘application. Il doit pouvoir
conserver son interface tout en changeant son implémentation sans pour autant impacter le reste de l’application.
41
42. La couche BroadCast est une couche à part entière
BroadCast
Receiver Net
Façade
COM
COM
Service DB
Couche Façade
Métier
IHM Service
Android
start
Façade
DAO
DAO
FS
Use or not
Transverse
Use
42
43. Où se trouve le ContentProvider ?
La couche DAO pose le problème de « où mettre les ContentProvider ? ».
Pour rappel:
“You don't need to develop your own provider if you don't intend to share your data with other applications. However, you do need your
own provider to provide custom search suggestions in your own application. You also need your own provider if you want to copy and
paste complex data or files from your application to other applications.” dixit developer.android.com
les ContentProviders sont vos DAO publiques
Ainsi vos DAO contiennent bien vos ContentProvider et le Design est celui-ci :
Cela ne veut pas dire que toutes vos DAO doivent posséder un
ContentProvider, mais que si elles en contiennent un elles le mettent à la
Human même hauteur que son interface. En effet, il n’y a qu’une implémentation
pour un ContentProvider.
HumanContentProvider HumanDBOpenHelper
HumanDaoIntf
HumanDaoSQLite Se posera à vous la question de la redondance des méthodes. Pour cela
vous pouvez re-router les méthodes de votre ContentProvider vers votre
HumanDaoOrlite HumanDaoFactory interface de DAO, ce qui est le plus propre.
HumanDaoFS
43
44. Pas de changement avec l'architecture n-tiers usuelle d'applications stand-alones.
NDK.
44
45. J’attire votre attention sur la nécessité de mettre en place un gestionnaire d’exception qui vous permettent de les logger/persister en
base/afficher à l’utilisateur.
Service des Exceptions
ExceptionManager
ExceptionManaged
45
46. BroadCast
Receiver Net
Application
Façade
COM
COM
Couche Service Métier Android
IHM
ServiceLoader DB
Façade
Service
MActivity ServiceHelper
start
Façade
DAO
MService DAO
FS
Use or not
Transverse
Use
46
47. La solution au problème des Threads de traitements.
Thread I.H.M Thread de traitement
Service
OnDestroy() sendMessage() REST Call
OnCreate()
Handler
BIND
message
Activity
47
49. From Google I/O And Devoxx
Ce chapitre est un extrait des « meilleurs » proTips donnés par les équipes Google lors des GoogleI/O, notamment :
•Android ProTips, Reto Meier, Google I/O 2011
•Making Good Apps Great , Reto Meier, Google I/O 2012
•Android Graphics, Animations and Tips & Tricks, Romain Guy & Chet Haase, Devoxx 2010
Merci à eux pour ces présentations.
49
50. Use ViewStub
La ViewStub permet le lazy loading de layouts.
<ViewStub
android:id="@+id/stub_import"
android:inflatedId="@+id/panel_import"
android:layout="@layout/progress_overlay"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
id/panel_import
findViewById(R.id.stub_import).setVisibility(View.VISIBLE);
// or
View importPanel = ((ViewStub)
id/stub_import findViewById(R.id.stub_import)).inflate();
From Android Graphics, Animations and Tips & Tricks, Romain Guy & Chet Haase, Devoxx 2010
50
51. Use merge et include
Without merge With merge
Le merge permet d’inclure les layouts les uns dans les
autres sans redondance. Cela permet aussi la
décomposition et la réutilisation des layout.
<!-- The merge tag must be the root tag -->
<merge
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Content -->
</merge>
<!– The include tag is the one to use-->
<LinearLayout …>
<include layout="@layout/subLayout" />
<LinearLayout> .. </LinearLayout>
</LinearLayout>
From Android Graphics, Animations and Tips & Tricks, Romain Guy & Chet Haase, Devoxx 2010
51
52. ListView and ArrayAdpater : Use ViewHolder and convertView
public View getView(int position, View convertView, ViewGroup parent) { static class ViewHolder {
ViewHolder holder; TextView text;
if (convertView == null) { ImageView icon;
convertView = mInflater.inflate(R.layout.list_item_icon_text, parent, false); }
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(DATA[position]);
holder.icon.setImageBitmap((position & 1) ==? mIcon1 : mIcon2);
return convertView;
}
From Android Graphics, Animations and Tips & Tricks, Romain Guy & Chet Haase, Devoxx 2010
52
54. KeyBoard and EditText : Customize KeyBoard and set Action
Quand vous définissez un champ de type EditText vous devez définir :
• Son type de clavier
• L’action IME
• (et le Hint)
<EditText EditText.OnEditorActionListener myActionListener = new
android:id="@+id/editEmailInput" EditText.OnEditorActionListener() {
android:layout_width="match_parent" @Override
android:layout_height="wrap_content" public boolean onEditorAction(EditText v, int actionId, KeyEvent event) {
android:hint="@string/compose_email_hint" if (actionId == EditorInfo.IME_ACTION_DONE) {
android:imeOptions="actionSend|flagNoEnterAction" // do here your stuff f
android:inputType="textShortMessage| return true;
textAutoCorrect| }
textCapSentences" /> return false;
}
};
Android ProTips, Reto Meier, Google I/O 2011
54
55. Always be asynchronous
Rendez tout asynchrone en utilisant:
•le ServiceHelper;
•les Handler, Asynctask;
•IntentService (le système des Intents quoi);
•AsyncQueryHandler (le GetContentResolver en un sens);
•Loader (abstractClass) and CursorLoader (HoneyComb only).
Android ProTips, Reto Meier, Google I/O 2011
55
56. Use the big cookies strategy when possible
Préférez transférer un gros paquet de données au travers d’internet quand c’est possible plutôt que plein de petits paquets.
int prefetchCacheSize = DEFAULT_PREFETCH_CACHE;
switch (activeNetwork.getType()) {
case ConnectivityManager.TYPE_WIFI:
prefetchCacheSize = MAX_PREFETCH_CACHE;
break;
case ConnectivityManager.TYPE_MOBILE): {
switch (telephonyManager.getNetworkType()) {
case TelephonyManager.NETWORK_TYPE_LTE:
case TelephonyManager.NETWORK_TYPE_HSPAP:
prefetchCacheSize *= 4; break;
case TelephonyManager.NETWORK_TYPE_EDGE:
case TelephonyManager.NETWORK_TYPE_GPRS:
prefetchCacheSize /= 2; break;
default: break;
} break;
}
default: break;
}
Making Good Apps Great, Reto Meier, Google I/O 2012
56
58. From Google I/O 2012
Ce chapitre est un extrait de la conférence des Google I/O 2012 :
•Multiversioning Android, Bruno Oliveira & Adam Powell , Google I/O 2012
Qui est exceptionnel sur la lutte contre la fragmentation, un très grand moment, merci à eux.
58
59. Be Lazy
Utiliser une classe abstraite qui sera instanciée par une factory et renverra l’implémentation correspondant à le version du système.
Activity
public abstract class VersionedLoremIpsum {
VersionedLoremIpsum li;
public abstract String doLorem();
li=VLIFactory.get()
public abstract int doIpsum();
}
public class EclairLoremIpsum public class FroyoLoremIpsum VLIFactory
extends VersionedLoremIpsum { extends EclairLoremIpsum { public static VersionedLoremIpsum get(
public String doLorem() { public String doLorem() { int sdk = Build.VERSION.SDK_INT;
// do lorem, Eclair-style String l = super.doLorem(); if (sdk <= Build.VERSION_CODES.ECLAIR) {
} // additional processing li = new EclairLoremIpsum();
public abstract int doIpsum() { return l; } else if (sdk <=
// deliver ipsum, a là Eclair } Build.VERSION_CODES.FROYO) {
} public abstract int doIpsum() { li = new FroyoLoremIpsum();
} ... } else {
li = new GingerbreadLoremIpsum();
}
)
Multiversioning Android, Bruno Oliveira & Adam Powell , Google I/O 2012
59
60. Resources are your best friend, use them.
Utilisez des booléens pour savoir quelle est la version du système. Ils s’utilisent dans le code et dans le Manifest.xml.
Resvalues-v14bools.xml Resvaluesbools.xml
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<bool name="postICS">true</bool> <bool name="postICS">false</bool>
<bool name="preICS">false</bool> <bool name="preICS">true</bool>
</resources> </resources>
SomeWhere in java Manifest
Resources res = getResources(); <receiver android:name="MyPreICSAppWidget"
boolean postICS = android:enabled="@bool/preICS">
res.getBoolean(R.bool.postICS); ...
if (postICS) { </receiver>
// do something cool and cutting-edge <receiver android:name="MyPostICSAppWidget"
} else { android:enabled="@bool/postICS">
// do something old-school but elegant! ...
} </receiver>
Multiversioning Android, Bruno Oliveira & Adam Powell , Google I/O 2012
60
61. Hériter du thème de la version.
Restez cohérent avec le thème du SDK de l’appareil cible, conservez une application cohérente avec l’appareil.
resvalues-v11theme.xml MyTheme
<style name="MyAppThemeBase" parent="@android:style/Theme.Holo" />
<style name="MyButtonStyle" parent="@android:style/Widget.Holo.Button">
MyBaseTheme
resvaluestheme.xml
<style name="MyAppThemeBase" parent="@android:style/Theme" />
<style name="MyAppTheme" parent="MyAppThemeBase">
<style name="MyButtonStyle" parent="@android:style/Widget.Button">
Theme Theme.Holo
Multiversioning Android, Bruno Oliveira & Adam Powell , Google I/O 2012
61
62. Utiliser les Fragments et l’ActionBar sur toutes vos versions.
Ayez une application identique quelque soit la version du système et utilisez le même fichier de layout.
public class BaseActivityHC extends Activity{...}
public class BaseActivityLegacy extends android.support.v4.app.FragmentActivity {...}
android.support.v4.app.ActionBarActivity
public class SpeakerDetailFragmentHC extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.speaker_detail, container, false);
return view;
}
public class SpeakerDetailFragment extends android.support.v4.app.Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.speaker_detail, container, false);
return view;
}
Multiversioning Android, Bruno Oliveira & Adam Powell , Google I/O 2012
62
63. Utiliser les notifications PreJB, PostJB.
Utilisez les notifications sur toutes les versions avec le NotificationCompat.Builder.
Les notifications sont une des grandes évolutions de JellyBean.
Le NotificationBuilder a été délivré pour HC, mais si vous voulez profitez des évolutions sur toutes les versions, il faut utiliser le
NotificationCompat.Builder.
if version >= JellyBean
use Notification.Builder
else
use NotificationCompat.Builder
private void pushInboxNotifications() {
Notification notification = new Notification.Builder(this)
.setContentTitle("10 New emails for me")
.setContentText("subject")
.setSmallIcon(android.R.drawable.ic_menu_agenda)
.setStyle(Notification.InboxStyle)
.addLine("Line 1, i can add more if i want")
.addAction(R.drawable.icon,R.string.notification_message,new PendingIntent(....))
.setSound(aSound)
.build();
NotificationManager
notificationManager=(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, notification);
}
Multiversioning Android, Bruno Oliveira & Adam Powell , Google I/O 2012
63
64. L’ordre des boutons a changé depuis ICS.
Avant ICS le bouton OK est à gauche, le bouton non ou cancel à droite.
Après ICS le bouton OK est à droite, le bouton non ou cancel à gauche.
Multiversioning Android, Bruno Oliveira & Adam Powell , Google I/O 2012
64
65. Min, max et target SDK importe.
Définissez toujours votre targetSdk comme le plus haut possible pour profiter des nouveautés du système.
En d’autres termes, toujours tester votre application avec la plus haute version du système et si elle fonctionne, définissez votre
targetSDK comme étant celui-là.
<uses-sdk
android:maxSdkVersion="16"
android:minSdkVersion="8"
android:targetSdkVersion="16" />
ps: le maxSDK s'utilise uniquement pour proposer différents apk en fonction de la version du système au niveau de GooglePlay.
Multiversioning Android, Bruno Oliveira & Adam Powell , Google I/O 2012
65
67. Ne pas oublier le projet de Tests.
L’architecte a à charge la conception de l’application mais aussi la conception de l’application
de test.
Ne l'oubliez pas.
Pour aller plus loin, vous pouvez acheter :
Android An Entreprise Edition Vision
67
68. Ces outils sont :
• Le SDK de Test fourni par Android,
• JUnit 3 (intégré au SDK d’Android),
• Maven pour construire l’application,
• Jenkins pour lancer Maven et passer les tests sur différents émulateurs
68
69. Communauté Google+ Android projects on Github
Github open source projects
https://plus.google.com/u/0/communities/100609058582053363304
69
70. Cette conférence utilise les références suivantes :
Les EBooks et tutoriels d’Android2ee : http://www.android2ee.com
Les sites de référence Android:
• http://developer.android.com/index.html
• http://android-developers.blogspot.fr/
• http://www.google.com/events/io/2011/sessions.html
• https://developers.google.com/events/io/sessions
Sur Android2ee, vous trouverez les tutoriels libres suivants pour approfondir les notions présentées:
• Les capteurs
• Les thread de traitement (Handler et fuite mémoire)
• Les AppWidgets
• Construction dynamique d’IHM
• Service REST
• Lecteur de flux RSS
• Le fichier POM pour mavéniser vos projets
• Comment préparer ses livraisons sur GooglePlay
Sur developpez.com vous trouverez les articles du conférencier :
• Déployer son application Android et obtenir sa clef MapView.
• Construire dynamiquement ses IHM Android
• Les capteurs Android
• Thread, Handler, AsyncTask et fuites mémoires
• Investir l'écran d'accueil Android avec les AppWidgets
• Android, Livrer son projet sur GooglePlay
70
71. Merci pour votre attention.
android2ee.com.
#android2ee Jcertif Mobile
sur GooglePlay
MySensors, MyLight et MyTorch mathias.seguy@android2ee.com
disponible sur GooglePlay
71