|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: RoboGuice 2.0 |
| 4 | +author: johanpoirier |
| 5 | +tags: [roboguice, google, guice, di, ioc, android, framework] |
| 6 | +--- |
| 7 | + |
| 8 | +Dans le cadre de mon étude des divers frameworks pour le développement java sur Android (voir mes articles précedents : [Tour d'horizon des frameworks pour Android](http://pullrequest.org/2012/02/29/tour-d-horizon-des-frameworks-java-pour-android.html) et [ORMLite pour Android](http://ormlite.com/sqlite_java_android_orm.shtml)), et après avoir introduit ORMLite pour Android dans mon application démo (à voir [ici](https://github.com/johanpoirier/Android-Booking-Demo) sur Github), j'ai ajouté le framework **RoboGuice** dans sa version 2.0. [RoboGuice](http://code.google.com/p/roboguice/) est un framework d'injection de dépendance basé sur le fameux Google Guice 3.0 et adapté pour les besoins d'Android. |
| 9 | + |
| 10 | +<p class="center"> |
| 11 | + <img src="http://roboguice.googlecode.com/files/roboguice.png" border="0" width="220" /> |
| 12 | +</p> |
| 13 | + |
| 14 | + |
| 15 | +## L'injection de dépendances |
| 16 | + |
| 17 | +L'injection dans RoboGuice (et Google Guice) se fait via la description du graph de dépendances. Cela se fait via la déclaration de modules : |
| 18 | + |
| 19 | +{% highlight java %} |
| 20 | +// Main module for the app |
| 21 | +public class BookingModule extends AbstractModule { |
| 22 | + |
| 23 | + private Context context; |
| 24 | + |
| 25 | + public BookingModule(Context context) { |
| 26 | + this.context = context; |
| 27 | + } |
| 28 | + |
| 29 | + @Override |
| 30 | + protected void configure() { |
| 31 | + bind(UserDao.class).toProvider(new DaoProvider<User, UserDao>(OpenHelperManager.getHelper(context, DatabaseHelper.class).getConnectionSource(), User.class)); |
| 32 | + bind(HotelDao.class).toProvider(new DaoProvider<Hotel, HotelDao>(OpenHelperManager.getHelper(context, DatabaseHelper.class).getConnectionSource(), Hotel.class)); |
| 33 | + bind(BookingDao.class).toProvider(new DaoProvider<Booking, BookingDao>(OpenHelperManager.getHelper(context, DatabaseHelper.class).getConnectionSource(), Booking.class)); |
| 34 | + bind(BookingService.class).to(BookingServiceImpl.class); |
| 35 | + } |
| 36 | +} |
| 37 | +{% endhighlight %} |
| 38 | + |
| 39 | +Pour faire court, on dit à RoboGuice quels sont nos "beans" à instancier et comment les instancier. |
| 40 | +Pour qu'Android connaisse nos modules, il faut lui indiquer où les chercher via un seul fichier de configuration XML **roboguice.xml** situé dans res/values : |
| 41 | + |
| 42 | +{% highlight xml %} |
| 43 | +<resources> |
| 44 | + <string-array name="roboguice_modules"> |
| 45 | + <item>org.pullrequest.android.bookingnative.module.BookingModule</item> |
| 46 | + </string-array> |
| 47 | +</resources> |
| 48 | +{% endhighlight %} |
| 49 | + |
| 50 | + |
| 51 | +### La configuration : les "bindings" |
| 52 | + |
| 53 | +Dans l'exemple précédent, nous pouvons voir deux types de binding différents (et les plus courrament utilisés) : |
| 54 | + |
| 55 | +- Les **linked bindings** : on lie une interface à une implémation |
| 56 | +- Les **provider bindings** : un provider lie une interface à une instance selon certains paramètres |
| 57 | + |
| 58 | +{% highlight java %} |
| 59 | +// DAO provider for ORMLite |
| 60 | +public class DaoProvider<T, D extends Dao<T, ?>> implements Provider<D> { |
| 61 | + protected ConnectionSource conn; |
| 62 | + protected Class<T> clazz; |
| 63 | + |
| 64 | + public DaoProvider(ConnectionSource conn, Class<T> clazz) { |
| 65 | + this.conn = conn; |
| 66 | + this.clazz = clazz; |
| 67 | + } |
| 68 | + |
| 69 | + @Override |
| 70 | + public D get() { |
| 71 | + try { |
| 72 | + D dao = DaoManager.createDao(conn, clazz); |
| 73 | + return dao; |
| 74 | + } catch (SQLException e) { |
| 75 | + e.printStackTrace(); |
| 76 | + return null; |
| 77 | + } |
| 78 | + } |
| 79 | +} |
| 80 | +{% endhighlight %} |
| 81 | + |
| 82 | +Dans l'exemple ci-dessus, nous avons besoin d'un provider pour créer nos DAOs car ORMLite nous fournit une fabrique de DAO. Le provider prend simplement la source de connexion à la bdd et la classe de l'objet du modèle pour générer le DAO correspondant (UserDaoImpl) et le lier à l'interface en question (UserDao). Nous ne pouvons pas utiliser un linked binding ici car la ConnectionSource ne peut pas être injectée. |
| 83 | + |
| 84 | +Vous trouverez toute la documentation sur les bindings sur le site de [Google Guice](http://code.google.com/p/google-guice/wiki/Bindings). |
| 85 | + |
| 86 | + |
| 87 | +### L'utilisation dans nos classes |
| 88 | + |
| 89 | +Pour que l'injection ait lieu, il faut que l'injector de RoboGuice soit appelé. Nous pouvons distinguer 3 cas de figures : |
| 90 | + |
| 91 | +#### L'injection dans les "beans" déjà pris en charge par RoboGuice |
| 92 | + |
| 93 | +{% highlight java %} |
| 94 | +// Main service for the app |
| 95 | +public class BookingServiceImpl implements BookingService { |
| 96 | + |
| 97 | + @Inject |
| 98 | + private UserDao userDao; |
| 99 | + |
| 100 | + @Inject |
| 101 | + private BookingDao bookingDao; |
| 102 | + |
| 103 | + @Inject |
| 104 | + private HotelDao hotelDao; |
| 105 | + |
| 106 | + ... |
| 107 | +} |
| 108 | +{% endhighlight %} |
| 109 | + |
| 110 | +BookingServiceImpl est l'implémentation de BookingService qui est déclaré dans mes modules. Il est donc déjà pris en charge par RoboGuice et l'injection de champs via les **@Inject** est effectué à l'instantiation de celui-ci. D'autres type d'injections sont possibles comme l'injection de constructeurs, de méthodes ou encore l'injection statique (voir la doc [ici](http://code.google.com/p/google-guice/wiki/Injections)). |
| 111 | + |
| 112 | +#### L'injection dans les Activity, Service, AsyncTask et autres classes d'Android |
| 113 | + |
| 114 | +RoboGuice est une version de Google Guice pour Android, il a donc quelques spécificités. Il surcharge donc certaines classes de base du framework de développement natif indispensables à la création d'une application. Pour en citer quelques unes (voir la [liste complète](http://code.google.com/p/roboguice/wiki/InheritingFromRoboGuice)) : |
| 115 | + |
| 116 | +- RoboActivity |
| 117 | +- RoboService |
| 118 | +- RoboAsyncTask |
| 119 | + |
| 120 | +Les classes surchargées sont donc prises en compte par RoboGuice et l'injection aura donc lieu, comme dans l'exemple suivant : |
| 121 | + |
| 122 | +{% highlight java %} |
| 123 | +// Display bookings |
| 124 | +public class MyBookings extends RoboActivity { |
| 125 | + |
| 126 | + @Inject |
| 127 | + private UserDao userDao; |
| 128 | + |
| 129 | + @InjectView(R.id.buttonHotels) |
| 130 | + private Button hotelsButton; |
| 131 | + |
| 132 | + @InjectResource(R.drawable.ic_book_hotel) |
| 133 | + private Drawable newContentImg; |
| 134 | + |
| 135 | + ... |
| 136 | +} |
| 137 | +{% endhighlight %} |
| 138 | + |
| 139 | +#### L'injection manuelle |
| 140 | + |
| 141 | +Dans certains cas, il n'est pas possible d'hériter directement d'une classe de RoboGuice et il va falloir appeler l'injecteur manuellement : |
| 142 | + |
| 143 | +{% highlight java %} |
| 144 | +// Inject only members, no ui available |
| 145 | +@Override |
| 146 | +protected void onCreate(Bundle savedInstanceState) { |
| 147 | + RoboGuice.getInjector(this).injectMembersWithoutViews(this); |
| 148 | + super.onCreate(savedInstanceState); |
| 149 | +} |
| 150 | + |
| 151 | +@Override |
| 152 | +public void onContentChanged() { |
| 153 | + super.onContentChanged(); |
| 154 | + RoboGuice.getInjector(this).injectViewMembers(this); |
| 155 | +} |
| 156 | + |
| 157 | +@Override |
| 158 | +protected void onDestroy() { |
| 159 | + try { |
| 160 | + RoboGuice.destroyInjector(this); |
| 161 | + } finally { |
| 162 | + super.onDestroy(); |
| 163 | + } |
| 164 | +} |
| 165 | +{% endhighlight %} |
| 166 | + |
| 167 | + |
| 168 | +## Les injections spécifiques à Android |
| 169 | + |
| 170 | +La liste complète est disponible [ici](http://code.google.com/p/roboguice/wiki/ProvidedInjections) mais en voilà quelques exemples : |
| 171 | + |
| 172 | +### La vue de l'activité |
| 173 | + |
| 174 | +{% highlight java %} |
| 175 | +// Replaces setContent |
| 176 | +@ContentView(R.layout.my_bookings) |
| 177 | +public class MyBookings extends RoboActivity { |
| 178 | + ... |
| 179 | +} |
| 180 | +{% endhighlight %} |
| 181 | + |
| 182 | +### Les widgets |
| 183 | + |
| 184 | +{% highlight java %} |
| 185 | +@InjectView(R.id.hotelsButton) |
| 186 | +private Button hotelsButton; |
| 187 | +{% endhighlight %} |
| 188 | + |
| 189 | +### Les ressources |
| 190 | + |
| 191 | +{% highlight java %} |
| 192 | +@InjectResource(R.drawable.ic_book_hotel) |
| 193 | +private Drawable bookHotelImage; |
| 194 | +{% endhighlight %} |
| 195 | + |
| 196 | +### Les services systèmes d'Android (WifiManager, LocationManager, ...) |
| 197 | + |
| 198 | +{% highlight java %} |
| 199 | +@Inject |
| 200 | +private LocationManager locationManager; |
| 201 | +{% endhighlight %} |
| 202 | + |
| 203 | + |
| 204 | +## Les performances |
| 205 | + |
| 206 | +L'utilisation d'un tel framework sur un mobile nous pousse bien évidemment à nus intéresser aux performances. RoboGuice va-t-il pénaliser l'expérience utilisateur de mon application ? et bien... oui et non. Je m'explique : |
| 207 | + |
| 208 | +- **oui** car le temps de démarrage de mon application en a pris un coup (nous allons voir les chiffres juste après) |
| 209 | +- **non** car après le démarrage, je n'ai pas vu ni mesuré de latences dans l'utilisation de l'application |
| 210 | + |
| 211 | +En chiffres donc, j'ai utilisé [traceview](http://developer.android.com/guide/developing/debugging/debugging-tracing.html) pour mesurer le temps de démarrage de l'application sur mon Nexus S. J'ai utilisé 2 versions de l'application, une avec RoboGuice et l'autre sans : |
| 212 | + |
| 213 | +- avec : environ 2,2 secondes (dont 1,4s alloué à la création de l'injecteur) |
| 214 | +- sans : environ 0,2 seconde |
| 215 | + |
| 216 | +On voit donc qu'au démarrage de l'application, l'instanciation du framework prend quelques secondes si l'application n'est pas en mémoire (pour des utilisations ultérieurs et si le process n'a pas été tué par Android, le démarrage sera quasi immédiat). |
| 217 | + |
| 218 | + |
| 219 | +## Conclusion |
| 220 | + |
| 221 | +RoboGuice est un très bon framework, qui nous facilite l'écriture de nos applications. Il apporte l'injection de dépendances à laquelle nous sommes tant habitués en tant qu'utilisateur de Spring. [Spring for Android](http://www.springsource.org/spring-android) n'apportant pas cette fonctionnalité, RoboGuice est la meilleure alternative sur Android. |
| 222 | + |
| 223 | +Pourtant l'impact de RoboGuice sur le temps de démarrage de l'application me fait émettre quelques réserves. Tout dépendera de l'utilisation cible de votre application. Si elle doit être utilisée souvent mais pour une durée brève (prendre des notes par exemple), je ne recommande pas RoboGuice. Pour des applications plus complexes et qui nécessitent une utilisation plus longue, quelques secondes de démarrage bien gérées (tout est une histoire de ressenti, n'est-ce pas Apple ?) ne devraient pas poser de problème. |
0 commit comments