Intégrer Kotlin dans une application Android

Aujourd’hui je vous propose de sortir des sentiers battus et d’expérimenter un langage encore peu connu : Kotlin. Développé par JetBrains, qui développe entre autre IntelliJ IDEA, ce langage est « compatible JVM ». Cela veut dire qu’on peut l’utiliser pour écrire des applications Java, soit en se passant totalement de Java, soit en mélangeant les deux langages. Le code Kotlin peut aussi être compilé en Javascript, mais ce n’est pas le propos.

Sans rentrer dans les détails, Kotlin apporte par rapport à Java 7 les choses suivantes :

  • des éléments de programmation fonctionnelle
  • plus de sécurité statique : les NullPointerExceptions sont impossibles grâce à un astucieux système de typage
  • un système de propriétés qui permet notamment de se débarrasser des getters/setters de Java
  • etc., je vous invite à jeter un oeil au comparatif Java/Kotlin

Pour être complet, je précise qu’il existe d’autres langages compatibles JVM, les plus connus étant Scala et Groovy, qui sont aussi des langages dits « fonctionnels ». Là où Kotlin sort du lot, c’est qu’il peut s’intégrer assez facilement dans un projet Android, car son runtime est très léger, mais aussi parce-qu’il s’intègre bien à Android Studio (en même temps l’IDE est conçu par JetBrains, donc c’est la moindre des choses). Ce n’est pas aussi simple pour ses concurrents : avec Scala, certains semblent y arriver, pour ma part cela n’a jamais fonctionné. Quant à Groovy, cela fait plutôt partie du domaine de l’expérimental mais les concepteurs du langage semblent s’y intéresser, donc ce sera peut-être possible un jour.

Je tiens à préciser que Java 8 apporte aussi des éléments de programmation fonctionnelle, mais malheureusement le SDK Android a longtemps fonctionné sur Java 6 et vient tout juste de passer à Java 7. D’où la recherche d’alternatives.

Comment intégrer Kotlin dans mon projet Android ?

Si c’est un projet Gradle, cet article explique très bien comment faire. Cela se corse quand il s’agit d’un projet Maven, car il existe une incompatibilité entre les plugins Maven pour Android et Kotlin : en effet le plugin Android ajoute au classpath un répertoire qui n’existe (généralement) pas, ce qui ne plait pas du tout au plugin Kotlin. Finalement ce problème peut être contourné rapidement car il s’agit juste de faire créer par Maven le répertoire en question lors du process de build.

Ceci fait, il ne reste plus qu’à installer le plugin Kotlin pour Android Studio, et le tour est joué.

Exemple en Java :

et l’équivalent en Kotlin :

Il est difficile de résumer la puissance d’un langage en deux captures d’écran, je vous invite donc à essayer par vous même la démo en ligne de Kotlin.

Si vous voulez tester par vous-même ou intégrer Kotlin dans votre propre projet Android, je vous invite à récuperer sur GitHub mon projet « Hello World » :

https://github.com/clemp6r/hello-android-kotlin

DroidCon Paris 2013, j’y étais ! (partie 2)

Dans mon article précédent, je vous avais détaillé mon planning sur la première journée de la DroidCon Paris 2013. Je continue aujourd’hui avec la liste des conférences que j’ai suivi le deuxième jour :

Facebook vs Square
Fabien Devos (Facebook) et Pierre-Yves Ricau (Square)
Présentation croisée des méthodes de travail des équipes Android chez Facebook et Square . Ecriture du code, intégration, livraison, tous les process y ont été décrits. On ne dirait pas comme ça, mais la quantité d’outils qu’ils utilisent est assez énorme.

Genymotion
Cédric Ravalec (Genymobile)
Les équipes de Genymobile ont profité de la DroidCon pour la sortir la première version de leur émulateur Genymotion. Il s’agit d’un émulateur Android qui se démarque de celui de Google par ses performances et son outillage. Celui-ci gère plutôt bien l’accélération OpenGL, et fournit des « widgets » pour contrôler le GPS et la batterie (d’autres sont prévus).

Secure your app: Fight the leaks! (slides)
Eyal Lezmy (Samsung)
Présentation axée sur la sécurité : permissions, stockage des données, chiffrage. J’ai bien aimé le conseil à la fin : trop de sécurité tue la sécurité ! Explication : il ne faut pas aller trop loin dans la sécurité car cela nuit généralement à l’expérience utilisateur. Résultat, il va vouloir contourner le système, ou pire, il ne va plus utiliser votre application…

Sneak peek preview of RhoMobile suite v4
Mark Kirstein (Motorola Solutions)
RhoMobile Suite et un ensemble d’outils permettant de développer des applications mobiles multi-OS. Les écrans sont conçus en HTML5, et le reste se fait en Ruby. Les concepteurs sont venus nous présenter la version 4 de RhoMobile et ont mis l’accent sur les performances et les services tels que RhoConnect (synchronisation de données dans le cloud), RhoElements (fonctionnalités supplémentaires payantes mais très ciblées), et RhoHub (environnement de développement en ligne).

Introduction to Google Glass!
Alain Regnier (Alto Labs)
Démonstration Google Glass par l’un des rares français (peut-être le seul ?) à avoir pu obtenir un prototype. Alain Regnier nous à décrit le système (basé sur Android 4.0) ainsi que les APIs disponibles et les pratiques à respecter pour faire une application Google Glass : n’afficher que ce qui est important, privilégier un fort contraste, et ne jamais surprendre l’utilisateur.

Donnez le pouvoir de build à votre PO (slides)
Mathieu Hausherr (OCTO), Aurélien Rambaux (OCTO)
Retour d’expérience sur un outillage mis en place dans le but de permettre au Product Owner de construire lui-même l’application quand il le souhaite. Celui-ci peut paramétrer le build (mode dev, prod, etc.), il peut donc tester l’application quand il en a besoin, et cela permet aux développeurs de ne pas être dérangés dans leur glorieux travail :-).

Pas de confiance, faites des tests! (slides)
Olivier Gonthier (Zenika)
Les tests sur Android : pourquoi, comment ? Une présentation très complète du framework de test Android ainsi que de quelques outils bien pratiques : DataFactory (génération de données de test), FEST Android (assertions), et aussi Robolectric, Mockito, Robotium, Spoon…

Bonne Pratique & Tips sur le développement Android (slides)
Jean-François Garreau (Binoned)
Quelques conseils pour débuter sur Android, notamment pour ceux qui viennent du monde Java et qui auraient tendance à appliquer les mêmes recettes à tort. Sujets abordés : performances, UI patterns, bonnes pratiques, et quelques librairies.

Musclez vos applis Android avec les outils du monde Java (slides)
Jérôme Van Der Linden (OCTO), Stéphane Nicolas (OCTO)
Une revue de outils du monde Java qui peuvent aussi être utilisés sur Android pour faire des tests et de la qualité. J’en cite quelques-uns : FEST, Mockito, Easymock, Sonar, Emma, Jacoco… Bon ok, tout ça peut être compliqué à mettre en place, mais ils ont pensé à tout et ont lancé un projet GitHub : il s’agit d’une application Android industrialisée avec tous ces outils. Vous n’avez qu’à récupérer le code, enlever ce dont vous n’avez pas besoin, et vous avez alors un bon point de départ pour votre nouveau projet Android.

Conclusion

Ces deux journées étaient bien remplies. Je fais du développement Android depuis plusieurs années, et pourtant j’étais passé à côté de plein de choses. J’ai découvert beaucoup de librairies (j’ai commencé à en utiliser certaines depuis), beaucoup d’outils, et surtout une communauté française très active.

Je pense revenir sur quelques points précis dans les semaines à venir. Je ne sais pas encore lesquels, mais il y a de quoi faire : je pense notamment à Genymotion et RoboSpice !

DroidCon Paris 2013, j’y étais ! (partie 1)

Si vous suivez le blog, vous n’êtes pas sans savoir que je suis allé la semaine dernière à la conférence DroidCon à Paris. Il est maintenant temps de faire le bilan, je vais donc essayer de faire le tri dans mes notes pour vous résumer ce qui s’y est passé. Les présentations étaient réparties dans trois salles : une grande et deux petites. Il fallait donc souvent choisir entre trois présentations, par conséquent j’en ai raté certaines qui m’auraient sûrement m’intéressé. Je ne rentrerai pas dans les détails pour l’instant, je vais juste faire un résumé des présentations que j’ai suivi, et je ferai plus tard quelques articles pour parler plus en détails de certains sujets particulièrement intéressants.

Voici mon planning pour le premier jour :

PKI sur Android
Philppe Prados – OCTO
Comment distribuer des certificats à une application Android, et comment les stocker de manière sécurisée.

Android in a Web of Things: NFC, Barcodes, Arduino and the Cloud!
Dominique Guinard – EVRYTHNG
Vue d’ensemble de ce qu’il est possible de faire avec Android et ce qu’on appelle l’Internet des objets : codes-barres, NFC, Arduino, etc.

Soyez productifs dans votre developpement sous android!
Alexandre Thomas – Excylis, contributeur AndroidAnnotations, AndroidKickstrartR
Trucs et astuces pour développer rapidement : librairies, templates de projets, raccourcis Eclipse, etc.

Barcamp
Le principe : n’importe qui peut venir pitcher pendant 2 minutes la présentation qu’il souhaite faire, et selon l’accueil du public des intervenants sont retenus ou non pour faire leur présentation l’après-midi. En l’occurrence il y avait une quinzaine de personnes, et il y avait de la place quasiment pour tout le monde. J’ai pu assister aux présentations suivantes :
– Franz Haslbeck est venu nous parler de deux concours organisés par l’agence spatiale européenne,
– Une présentation des montres intelligentes i’m Watch avec les bonnes pratiques à respecter quand on développe pour des montres,
– Une présentation de la librairie RoboSpice par son créateur Stéphane Nicolas (OCTO)
Il y a eu ensuite quelques démos d’applications en tout genre, comme Oopost et TestFairy.

Democamp
Le democamp est une session durant laquelle des jeunes entreprises viennent présenter leur projet en quelques minutes devant des investisseurs potentiels. Ces derniers désignent ensuite un gagnant parmi les candidats (je ne plus ce qu’il gagne, d’ailleurs – au minimum ça leur fait une bonne pub). Le gagnant : Pixowl, éditeur de jeux social/casual pour mobiles.

…et c’est déjà pas mal pour ce premier jour. Je vous parlerai de la deuxième journée dans un article à venir très bientôt, avec notamment l’intervention des développeurs de Facebook et Square, la présentation de Genymotion, un nouvel émulateur Android ultra-performant lancé pour l’occasion, et la démo de Google Glass !

On cherche un nom pour ce petit monstre, n'hésitez pas à nous faire part de vos suggestions !

Site DroidCon Paris 2013
Photos de l’événement sur G+ 

DroidCon Paris 2013, c’est parti !

La DroidCon Paris se tiendra les 17 et 18 juin. Les conférences DroidCon, qui ont pour but de rassembler les développeurs et les entreprises travaillant autour d’Android, existent depuis 2009 et ont été tenues dans différents pays (principalement en Europe), mais c’est la première fois qu’elle a lieu en France, et OD sera de la partie ! Je vous ferai part dans les jours qui viennent de ce que j’y aurai vu…

On aime, on partage #5

Bienvenue dans la série « On aime, on partage » d’Objet Direct ! Chaque semaine retrouvez les meilleurs articles du web issues de notre veille technologique.

Agilité

Sit Down and Learn from the Master: The ScrumMaster’s Responsibility to Educate

Être ScumMaster n’est pas un rôle facile.

Dans l’article qui suit, Brock Argue nous parle de la responsabilité du ScrumMaster d’éduquer non seulement l’équipe, mais aussi l’organisation dans laquelle il évolue.

En effet, la réussite de projets agiles passe avant tout par la compréhension du mode de fonctionnement. Tous les intervenants, directs ou non, sont concernés, ceci s’inscrivant plus globalement dans un cadre de transformation/transition agile.

http://www.scrumalliance.org/articles/521-sit-down-and-learn-from-the-master-the-scrummasters-responsibility-to-educate

Enjoy 😉

Not Using Test-First? You’re Doing it Wrong

Vous connaissez sûrement le TDD … mais connaissez vous plus globalement le « Test First » ?

Même si le parti pris de l’auteur (clairement visible ne serait-ce que dans le titre de l’article) peut paraître assez radical (et pas de l’avis de tous), le corps de l’article est néanmoins succins mais très intéressant.

Écrire des tests est une étape nécessaire s’il on veut à la fois répondre correctement au besoin du client, faire/valider quelque chose qui marche (et non qui tombe en marche :o) et bien sûr s’assurer le moins possible de bugs/régressions lors de développement futurs.

Et si chacun peut avoir (Acceptance Tests) et/ou écrire (TDD/BDD) des tests avant même de commencer à coder la fonctionnalité, alors que demander de plus … à vous le succès !

http://agile.dzone.com/articles/not-using-test-first-youre?

Java et technos web

Java 8 gets tougher security and statically-linked JNI libraries

On en parlait récemment, Mark Reinhold (Oracle) a annoncé vouloir repousser de 6 mois la sortie de Java 8 afin que l’équipe puisse se concentrer sur les problèmes de sécurité. On apprend maintenant que cela permettra aussi de rajouter quelques fonctionnalités initialement prévues pour la version suivante. L’une d’elle ajoutera la possibilité de packager une application Java et la JVM dans un seul et même exécutable, ce qui permettra de déployer facilement des applications sur des systèmes ne disposant pas de JVM. C’est notamment le cas sur iOS : on comprend donc pourquoi cette fonctionnalité est stratégique pour Oracle.

http://jaxenter.com/java-8-gets-tougher-security-and-statically-linked-jni-libraries-46881.html

Play Framework : pourquoi j’ai migré de V2 à V1

Lors des Human Talks du 12 février 2013, Xavier Nopre nous avait fait une présentation du framework Play (V1) (voir la vidéo et les slides). En introduction il nous expliquait avoir migré une application en Play 2 vers la V1, pour diverses raisons qu’il n’avait pas eu le temps de détailler. C’est maintenant chose faite grâce à cet article dans lequel il détaille point par point les problèmes qu’il a rencontré avec la version 2 du framework

http://xnopre.blogspot.fr/2013/05/play-framework-pourquoi-jai-migre-de-v2.html

Merci à Mathieu Laurent pour sa contribution cette semaine.

Premiers essais avec Android Studio

Comme chaque année, la conférence Google I/O apporte son lot de surprises. Je ne vais pas faire la liste des nouveautés annoncées mais uniquement vous parler du nouvel environnement de développement intégré pour Android, annoncé hier lors de la keynote : Android Studio. Ce nouvel EDI n’est plus basé sur Eclipse, comme c’était le cas avec ADT, mais sur IntelliJ, ce qui ravira pas mal de développeurs qui le connaissent déjà. En effet l’inertie d’Eclipse commençait à être un frein pour de nouveaux ajouts et la plate-forme IntelliJ apporte un peu de fraîcheur et de nouvelles possibilités.

Android Studio est disponible dès aujourd’hui en version 0.1 « early access preview », c’est-à-dire qu’il s’agit d’une version incomplète et potentiellement instable, mais qui permet de se faire une idée de ce dont il s’agit.

Après quelques tests (version Windows), voici ce que je peux en dire :

Une fois l’installation passée, petite déception, l’appli ne se lance pas. Une petite recherche sur Google+ et j’apprends qu’il faut déclarer une variable d’environnement « ANDROID_STUDIO_JDK » qui pointe sur le JDK (màj 22/05 : fonctionne aussi avec JAVA_HOME ou JDK_HOME). Ceci réglé  l’appli démarre bien, et je peux enfin commencer à jouer. Ayant utilisé un peu IntelliJ 12 dans sa version de base avec le plugin Android, je remarque quelques différences visuelles. Globalement l’intégration d’Android est visuellement mieux « finie » sur Android Studio que sur IntelliJ, même si au premier abord les différences sont peu visibles.


Je lance l’éditeur de layout, et là les changements se montrent : la vue de prévisualisation du layout ressemble enfin à un vrai appareil Android, les icônes de la palette sont plus lisibles, et en un clic je peux voir le résultat simultanément sur plusieurs tailles d’écran représentatives. C’est aussi le cas quand on passe en mode d’édition XML grâce à la vue « preview » qui s’affiche sur le côté.

Le seul point négatif que je peux soulever concerne le debugger : celui-ci ne fonctionne pas car le debugger n’arrive pas à se connecter au processus. Mais ceci est pardonnable étant donné qu’il s’agit d’une version 0.1 signalée comme étant incomplète, et ce sera sûrement résolu dans les semaines à venir. C’était parce-que j’avais un autre IDE ouvert qui prenait le pas sur la connection ADB :-). Donc en fait, je n’ai pas de point négatif à soulever, il faut juste bien vérifier qu’il n’y a pas un Eclipse/IntelliJ qui tourne en arrière-plan.

Pour le reste, cela fonctionne bien. Pour que la transition avec ADT soit moins douloureuse, vous pouvez configurer les raccourcis comme dans Eclipse. Si vous ne vous sentez pas prêts à utiliser un EDI incomplet mais que vous voulez préparer votre transition, vous pouvez commencer par utiliser IntelliJ, et prévoir de passer à Android Studio dès qu’une version finale sortira. En plus, le thème Darcula est classe !

Je vous invite à jeter un oeil à la keynote (34è minute), certaines fonctionnalités sont assez bluffantes.

Pour finir je précise qu’Android Studio, tout comme ADT, sera distribué à la fois sous forme de bundle stand-alone, ou sous forme de plugin intégré à IntelliJ 13. D’ailleurs ce dernier a aussi été rendu disponible en version « early access preview ».

Android Studio : http://developer.android.com/sdk/installing/studio.html
IntelliJ 13 : http://blogs.jetbrains.com/idea/2013/05/intellij-idea-13-early-preview-is-out

Android : comment gérer la fragmentation des versions d’API

Android est bien connu pour être un écosystème très fragmenté, tant au niveau du matériel que du logiciel :

  • Ecrans (taille physique – densité – résolution)
  • Capacités (puissance CPU, mémoire, GPU)
  • Architecture matérielle (ARM, Intel)
  • Format de l’appareil : présence d’un clavier physique, orientation naturelle de l’écran…
  • Présence d’une surcouche constructeur
  • Version d’Android

Cela peut rebuter les développeurs débutants, mais avec un peu de pratique c’est facilement gérable. Je vais me concentrer aujourd’hui sur le dernier point, à savoir la fragmentation des versions d’Android, et je vais essayer de vous donner quelques astuces pour que ce ne soit plus un problème.

En premier lieu, faisons le point sur le mode d’évolution de l’API.  Chaque version apporte de nouvelles fonctionnalités, mais aucune fonctionnalité n’est jamais supprimée lors de l’arrivée d’une nouvelle version. Au pire, certaines passent en deprecated, mais elles restent utilisables. La conséquence de cela est que les applications écrites pour une API version N sera automatiquement compatible avec l’API N+1, N+2, N+3, etc. (principe de retrocompatibilité). En pratique, le passage à une nouvelle version peut poser quelques problèmes à cause d’effets de bords, mais ils sont généralement facile à résoudre. Le seul vrai problème qui se pose, c’est lorsqu’on veut utiliser des fonctionnalités récente qui n’existent qu’à partir d’une certaine version de l’API. Dans ce cas il faut faire des choix :

  • ne plus supporter les versions d’API précédentes,
  • ne pas utiliser ces fonctionnalités,
  • ou bien faire le compromis entre les deux, à savoir adapter le comportement de l’application en fonction de la version détectée au runtime.

En pratique il faudra faire un mélange des trois 🙂 car pour chaque fonctionnalité le pour et le contre doit être pesé.

Pour commencer, il faut se renseigner sur les chiffres. Des statistiques sur la distribution des versions sont mises à jour toutes les deux semaines et sont disponibles à cette adresse : http://developer.android.com/about/dashboards/index.html. C’est une bonne aide à la décision. Il faut être pragmatique : ne pas hésiter à retirer le support des vieilles versions qui ne sont quasiment plus utilisées. La perte d’utilisateurs potentiels et légère, et le gain en terme de disponibilité d’API est forcément intéressant. Comme il sort en moyenne une nouvelle version tous les 6 mois, il peut être judicieux de se poser la question du retrait d’une vieille version tous les 6 mois également.

Version Codename API Distribution
1.5 Cupcake 3 0.1%
1.6 Donut 4 0.4%
2.1 Eclair 7 3.4%
2.2 Froyo 8 12.9%
2.3 – 2.3.2 Gingerbread 9 0.3%
2.3.3 – 2.3.7 10 55.5%
3.1 Honeycomb 12 0.4%
3.2 13 1.5%
4.0.3 – 4.0.4 Ice Cream Sandwich 15 23.7%
4.1 Jelly Bean 16 1.8%

Source : http://developer.android.com (01/10/2012)

A l’heure où j’écris ces lignes, les versions 1.5 et 1.6 ne constituent que 0.5% des appareils en circulation. Il me semblerait pertinent de ne plus les supporter, mais c’est aussi une décision marketing qui va dépendre d’autres facteurs.

Mais revenons à ce que j’ai déclaré plus haut : il est possible d’adapter le comportement de l’application en fonction de la version détectée. Cela permet, dans la même application, d’utiliser des fonctionnalités récentes lorsque celles-ci sont disponibles, et de se rabattre sur un fonctionnement plus simple si la fonction n’est pas disponible. Pour pouvoir faire cela, il faut juste respecter un principe simple : lorsqu’une classe de notre application va être utilisée, elle est chargée et vérifiée par la VM, et si dans cette classe on fait appel à une classe/méthode qui n’existe pas sur le système, l’application plante (cf. VerifyError). Par conséquent, notre classe ne doit être chargée (donc appelée) que si l’on a d’abord vérifié la version de l’API. Par exemple, imaginons que nous voulons faire appel à une méthode de la classe View qui n’existe que sous Jelly Bean et qui s’appelle « doSomeJellyBeanStuff() ».

Le code suivant ne marchera pas et fera crasher l’application si elle est exécutée sur une version antérieure à Jelly Bean :

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    myView.doSomeJellyBeanStuff();
} else {
    myView.doSomeFallbackStuff();
}

Cela ne fonctionne pas car lorsque notre classe est chargée, la VM vérifie que la méthode View.doSomeJellyBeanStuff() existe, ce qui n’est vrai que sur Jelly Bean. Il faut donc passer par une classe intermédiaire, qui ne va être chargée que si la version est Jelly Bean ou ultérieure.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    new JellyBeanMethodWrapper().doStuff(myView);
} else {
    myView.doSomeFallbackStuff();
}

Il faut aussi créer la classe JellyBeanMethodWrapper :

public class JellyBeanMethodWrapper {
    public void doStuff(View myView) {
        myView.doSomeJellyBeanStuff();
    }
}

Si on ne veut pas créer trop classes, on peut aussi passer par des classes anonymes. Les Runnables sont très pratiques pour ça :

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    new Runnable() {
        public void run() {
            myView.doSomeJellyBeanStuff();
        }
    }.run();
} else {
    myView.doSomeFallbackStuff();
}

Ce dernier exemple fonctionne bien car la classe anonyme n’est chargée, donc vérifiée, que dans un contexte Jelly Bean.

Pour pouvoir faire cela, encore faut-il avoir connaissance de ce qui est disponible et ce qui ne l’est pas sur les différentes versions. La documentation de référence précise, pour chaque classe ou méthode, le numéro de version à partir de laquelle elle est disponible.

Il est aussi de filtrer la documentation par version d’API. Cela a notamment pour effet de griser les méthodes qui n’existent pas dans les versions inférieures.

Enfin, Eclipse est capable de détecter (grâce à l’outil Lint) l’utilisation d’une classe ou méthode qui n’existe pas sur la version d’API minimum déclarée dans le Manifest de l’application.

N’hésitez à commenter cet article si vous avez des questions !

Android : optimisation des performances 3 – Le code natif

Après avoir passé en revue les problématiques de performance relatives à la fluidité de l’interface et à l’utilisation de la mémoire, je vais maintenant vous présenter deux alternatives permettant de faire du natif. L’utilisation du langage Java peut parfois être une barrière quand il s’agit de faire des algorithmes nécessitants de grosses performances, tels que du traitement vidéo, de la simulation, ou toute autre opération utilisant intensivement le processeur. Afin de se rapprocher de la machine, Android fournit deux méthodes pour écrire du code bas-niveau performant :

  • le NDK, qui permet de faire du code natif C/C++
  • et RenderScript, solution propre à Android basée sur la syntaxe C99

Le fait qu’il existe deux façons de faire natif peut être déroutant, mais les approches sont assez différentes et chacune apporte son lot d’avantages et d’inconvénients que je vais tenter de décrire.

NDK

Le NDK est un ensemble d’outils permettant d’embarquer dans une application Android des composants logiciels écrits en C ou C++. Il fournit un ensemble de librairies standards, ce qui peut permettre de réutiliser du code C/C++ existant pour l’intégrer à une application Android. Le code est compilé, et ne peut fonctionner qu’avec l’architecture pour laquelle il a été compilé (ARM v5, ARM v7, x86), ce qui impose de générer un APK pour chaque architecture cible.

L’interface entre la couche Java et la couche native est faite avec JNI (Java Native Interface), ce qui est fait couramment dans le monde Java.

En résumé, les plus :

  • C/C++ standard, réutilisable
Les moins :
  • JNI lourd à mettre en place
  • Un APK par type de processeur

RenderScript

Alors que le NDK existe depuis Android 1.5, RenderScript (RS) est apparu avec Honeycomb (Android 3.0). Il permet d’écrire du code bas niveau, mais contrairement au NDK, il est indépendant d’une plate-forme. Il peut exécuter le code sur différents types de processeurs : actuellement uniquement les CPUs sont supportés (ARM, x86), mais un support GPU et DSP est prévu. Afin de permettre ce fonctionnement multi-architecture, le code est compilé directement par l’appareil au runtime. RenderScript permet aussi de paralléliser le code sur plusieurs coeurs, de manière automatique et intelligente. A cause de cette particularité multi plate-forme, les librairies standards du NDK ne sont pas disponibles sous RenderScript. Toutefois, d’autres APIs ciblées pour les calculs à haute performance sont fournies : mathématiques, gestion de la mémoire, matrices, vecteurs, etc. Il est notamment très adapté pour faire du traitement d’images car il permet de faire des choses complexes en très peu de lignes.

Afin d’illustrer la syntaxe et les possibilités de RenderScript, voici un extrait de code que l’on peut trouver sur les exemples officiels et qui est parfaitement expliqué sur ce blog. Il s’agit d’un algorithme de saturation d’image :

void root(const uchar4 *in, uchar4 *out, uint32_t x, uint32_t y) {
    float3 pixel = convert_float4(in[0]).rgb;
    pixel = rsMatrixMultiply(&colorMat, pixel);
    pixel = clamp(pixel, 0.f, 255.f);
    pixel = (pixel - inBlack) * overInWMinInB;
    if (gamma != 1.0f)
        pixel = pow(pixel, (float3)gamma);
    pixel = pixel * outWMinOutB + outBlack;
    pixel = clamp(pixel, 0.f, 255.f);
    out->xyz = convert_uchar3(pixel);
}

On voit que grâce aux types et fonctions fournis dans RenderScript, il faut très peu de lignes de code pour faire cet algorithme. Il en aurait fallu quatre fois plus en C/NDK.

En pratique, Google a utilisé RenderScript pour le rendu du mur 3D de son application YouTube, ainsi que pour l’application Music.

L’autre particularité est que les fonction écrites en RenderScript n’ont pas de valeur de retour, elles sont asynchrones : lorsqu’on fait appel à une fonction RS depuis Java, l’appel est placé dans une file d’attente, est exécuté dès que possible. Si besoin, le code RS peut ensuite envoyer un message à la couche Java lorsqu’il a terminé un calcul, afin de lui fournir un résultat.

Les plus :

  • Multi plate-forme
  • Potentiellement plus performant
Les moins :
  • Framework propre à Android qui demande une période d’apprentissage

Conclusion

Dans les deux cas, ces frameworks ne sont pas là pour remplacer Java : ils ne permettent pas de construire des applications de A à Z avec du code natif, car Android reste un framework Java. Comme cela augmente la complexité du logiciel, on ne les utilise que lorsque l’on doit écrire des fonctions indépendantes qui ont de gros besoins en termes de performance. S’il faut les comparer, je pense que RenderScript est plus simple à mettre en place, à utiliser, il est potentiellement plus performant, et devrait donc être la solution privilégiée pour faire du natif, tandis que le NDK tend à devenir moins utile, sauf dans le cas où on voudrait réutiliser du code C/C++ existant.

Pour ce qui est des graphismes 3D, il ne faut pas foncer tête baissée vers une solution native : l’API OpenGL de Java est très performante car les calculs sont faits par le GPU.

Bon code !

Mise à jour 14/11/2012 : L’équipe Android a annoncé que la tablette Nexus 10 sous Android 4.2 sera le premier appareil à pouvoir exécuter le Renderscript directement sur le GPU (source).

Android : optimisation des performances 2 – La mémoire

Dans l’article précédent, j’avais abordé les problèmes de fluidité et d’asynchronisme. Aujourd’hui, je vais aborder un autre sujet très important quand il s’agit d’optimiser des applications Android : la mémoire.

La mémoire disponible pour une application Android est très limitée par rapport à une application “desktop”. Cette limite dépend des appareils, mais dans le pire des cas celle-ci peut être de 16 Mo. Il faut donc faire très attention à ne jamais s’en approcher, car cela peut-être une source d’instabilité : si l’application ne dispose plus de mémoire, elle risque de crasher en générant une erreur de type “OutOfMemoryError”.

Dans le cadre d’un algorithme qui aurait besoin d’analyser de grosses quantités de données, il faut éviter de garder en mémoire la totalité des données, et essayer de traiter les donnés au fil de l’eau. Par exemple, si on a besoin de parser un gros document XML, il faut préférer l’utilisation d’une API de type SAX, qui traite les données au fur et à mesure qu’elles arrivent, plutôt que de charger l’intégralité du document en mémoire et l’analyser ensuite (ex : API DOM). Plus généralement, il ne faut pas hésiter à abuser des flux (InputStream/OutputStream) qui permettent la lecture/écriture de données en sollicitant très peu de mémoire.

Exemple simple : vous voulez télécharger un fichier depuis le web, et le stocker sur la carte SD. Une mauvaise approche consisterait à télécharger entièrement les données pour les stocker dans un tableau byte[], et une fois le téléchargement terminé, écrire les données de ce tableau dans le fichier :

private void download(InputStream inputStream, FileOutputStream outputStream) {
    byte[] buffer = readAllData(inputStream);
    writeAllData(outputStream, buffer);
}

Si le fichier à télécharger fait 100 ko, ça marche, s’il fait 100 Mo, l’application plante. Une meilleure solution consiste à lire et écrire les données au fur et à mesure, par petits blocs :

private void download(InputStream inputStream, FileOutputStream outputStream) {
    byte[] buffer = new byte[BLOCK_SIZE];
    int bytesRead;
    while ((bytesRead = inputStream.read(buffer)) != 0) {
        outputStream.write(buffer, 0, bytesRead);
    }
}

Les images sont aussi très gourmandes en mémoire. Par exemple, si votre application a besoin d’afficher un grand nombre d’images téléchargés, vous allez potentiellement vouloir les garder en mémoire pour pouvoir les réafficher plus rapidement : plutôt que de garder en mémoire une grosse quantité d’objets Bitmap, stockez les images sur le système de fichier. Mieux, une solution intermédiaire consiste à mixer les deux, en créant un cache mémoire à taille limitée. Voir notamment cet article sur la création de caches d’images.

La quantité de mémoire utilisée par une application peut être obtenue depuis la vue « Heap » de la perspective DDMS. Après avoir sélectionné l’application dans la vue « Devices », il faut cliquer sur le bouton « Update Heap » de cette même vue. La vue « Heap » affiche alors la quantité de mémoire utilisée par l’application (cliquez sur « Cause GC » si les données n’apparaissent pas tout de suite). Dans cette vue, la valeur qu’il faut surveiller est « Allocated », c’est-à-dire la quantité de mémoire allouée actuellement. La colonne « % Used » est à prendre avec des pincettes, car c’est le pourcentage de mémoire utilisée par rapport à taille du tas (heap), mais cette dernière varie en fonction des besoins (jusqu’au maximum autorisé).

Dans le cas où l’application a tendance à crasher pour des problèmes de mémoire, cet outil peut servir à savoir à quel moment l’utilisation de la mémoire devient importante, si cela arrive d’un coup ou progressivement, etc. Pour aller plus loin, il va falloir faire appel à Memory Analyzer.

Memory Analyzer

Il peut arriver que l’application pose un problème d’utilisation mémoire sans qu’on sache précisément d’où vient le problème. Dans ce cas, il existe une fonctionnalité du SDK qui permet de dumper la mémoire au format HPROF afin de pouvoir ensuite l’analyser avec le plugin Eclipse Memory Analyzer (il faut l’installer au préalable).

Pour cela, il faut lancer l’application (sur un terminal ou un émulateur), puis depuis la fenêtre “Devices”, sélectionner le processus correspondant l’application, puis cliquer sur le bouton “Dump HPROF File”.


L’opération peut prendre quelques secondes. Dès que l’emprunte mémoire est récupérée, le plugin Memory Analyzer ouvre automatiquement le fichier HPROF, et l’analyse peut commencer. Ce plugin offre plusieurs vues permettant de faire une analyse mémoire sous plusieurs angles, mais la plus intéressante selon moi est la vue “Dominator Tree”, qui affiche la liste des objets qui prennent le plus de mémoire.


Dans la première colonne sont affichés les objets, nommés par leur classe et hashcode.

La colonne suivante, “Shallow Heap” affiche la quantité de mémoire utilisée par chaque objet, en octets.

La colonne “Retained Heap” équivaut à la quantité de mémoire utilisée par chaque objet (Shallow Heap), à laquelle on rajoute la mémoire utilisée par les objets dépendants. C’est l’indicateur le plus intéressant, car il correspond à la quantité de mémoire qui pourrait être libérée si l’objet était libéré. Voir explication détaillée.

La colonne “Percentage” représente le rapport entre mémoire retenue par un objet (retained heap) et mémoire totale.

Exemple : sur la capture d’écran ci-dessus, on voit sur la 3ème ligne qu’il existe un objet Bitmap (une image) pour lequel est alloué 1 Mo de mémoire. Il s’agit sûrement d’une image de 512×512 pixels (car 512*512*4 = 1M). On peut alors se poser la question de l’utilité de cette image, et poursuivre l’investigation en inspectant cet objet, ses références entrantes et sortantes, etc.

Cette vue permet donc de repérer les objets les plus importants, mais si le problème vient de l’utilisation d’un trop grand nombre de petits objets, cette vue est inefficace. Il faut alors utiliser la vue “Histogram”, qui affiche, en fonction du type, le nombre d’objets instanciés et la mémoire utilisée.


Pour chaque type, il est ensuite possible d’afficher la liste complète des objets et d’inspecter leur contenu. Il est aussi possible d’afficher des graphes de dépendances (le même qu’utilise le GC pour déterminer les objets à libérer), ce qui peut permettre de remonter à la source du problème.

Dans la capture d’écran ci-dessus, on peut voir sur la première ligne que 7,5 Mo de mémoire sont occupés par des byte[] (tableaux d’octets bruts). Cela n’a rien d’étonnant quand on sait que les images sont stockées dans des byte[] et que c’est souvent ce qui prend le plus de mémoire dans une application. D’ailleurs, on peut voir sur la dernière ligne qu’il existe 282 objets Bitmaps, et que ceux-ci retiennent plus de 7 Mo de mémoire, ce qui confirme ce raisonnement : Bitmap est une classe Android utilisée pour encapsuler des images, et les données brutes de l’image sont stockées à l’intérieur d’un objet Bitmap dans un byte[].

En parlant de Bitmaps, il y a une chose importante à savoir : avant Honeycomb (3.0), les données des images étaient allouées sur une mémoire externe à la VM Dalvik, il était donc impossible d’analyser ces données avec Memory Analyzer ou la vue « Heap ». Le travail est donc plus compliqué quand on veut résoudre des problèmes de mémoires sur un appareil <3.0 car on ne voit que la partie émergée de l’Iceberg…

A lire aussi : http://android-developers.blogspot.fr/2011/03/memory-analysis-for-android.html

La semaine prochaine, nous verrons les alternatives existantes pour programmer en code natif afin d’écrire des programmes ultra-performants avec le NDK et RenderScript.

Android : optimisation des performances 1 – Fluidité de l’interface

Le but de cette série d’articles est de vous aider à réaliser des applications Android performantes, en vous présentant les bonnes pratiques, les pièges à éviter, et surtout les outils que vous avez à votre disposition pour vous aider dans les différentes étapes de l’optimisation. Cet article s’adresse aux développeurs d’applications Android, quelques bases sont donc requises.

Les problèmes de performances peuvent être de nature variée :

  • Fluidité de l’interface
  • Rapidité des traitements
  • Instabilité (pour cause de manque de mémoire)
  • Utilisation trop intensive de la batterie (dans le cas de traitements en arrière-plan)

Pour les habitués des applications “desktop” ou “web”, il faut être conscient que l’application va tourner sur des appareils beaucoup plus limités qu’un ordinateur classique. Les CPUs sont généralement moins puissants, et la quantité de mémoire disponible est beaucoup plus faible. Surtout, les appareils existants sont très variés en terme de capacités, et si une application veut cibler le plus d’appareils possibles, elle se doit de tourner correctement sur les smartphones sortis il y a quelques années. Si vous avez un HTC Magic sous la main, c’est l’idéal, car il compte parmi les plus vieux et les moins puissants.

Il faut toutefois être prudent car la mise en œuvre d’optimisations peut avoir un impact sur la maintenabilité du code en réduisant sa lisibilité et en augmentant la complexité. Elles doivent donc être utilisées avec pragmatisme, et certaines ne devraient pas être appliquées si le gain de performance est trop minime. En outre, il faut toujours mesurer avant de faire une optimisation, afin d’être sûr qu’il y a un réel besoin, de cibler le problème, et de pouvoir comparer les mesures avec celles que l’on effectuera une fois l’optimisation réalisée (parfois pour se rendre compte que ça n’a servi à rien…).

Nous allons ici nous concentrer sur les problèmes de fluidité, en prenant le cas des ListViews. Si une ListView saccade lors d’un déroulement vers le bas ou vers le haut, c’est qu’elle est probablement mal construite : la création ou le rendu des éléments de la liste prend trop de temps. Deux facteurs peuvent en être la cause :

  • une hiérarchie de vues trop complexe
  • un blocage du thread principal à cause d’opérations d’entrée/sortie bloquantes

Cas où la hiérarchie des vues est trop complexe

La construction des vues est un processus récursif. Plus l’arborescence des vues est profonde, plus la phase de construction va être longue. Dans ce cas, la seule solution consiste à simplifier la hiérarchie de vues. En premier, il faut essayer de retirer quelques éléments qui ne sont pas vitaux. Ensuite, il faut préférer l’usage de RelativeLayouts, plutôt que faire des enchaînements complexes de layouts. Ce type de layout permet de créer des vues complexes tout en gardant une hiérarchie simple.

En résumé :

  • ne pas charger les écrans
  • utiliser des RelativeLayout quand c’est possible

Le SDK fournit un outil qui permet d’analyser les layouts et leurs performances : hierarchyviewer. Cet outil permet de visualiser l’arbre des vues, les propriétés de chaque vue, et affiche des indicateurs de performance qui permettent d’identifier les vues qui prennent le plus de temps à être créées.

Les points colorés en vert, jaune, ou rouge sont des indicateurs de performance. Ces indicateurs représentent la vitesse de rendu d’une vue, par rapport aux autres vues. Il y a trois indicateurs, un pour chaque étape du rendu : measure, layout, draw.
Voir aussi : http://developer.android.com/tools/debugging/debugging-ui.html

Cas où le thread principal fait des traitements bloquants

La vue est construite sur le thread principal, appelé “main Thread”, ou “Thread UI”. Par défaut, le code qu’on écrit est exécuté dans ce thread. Si l’application a besoin de faire des traitements longs, comme des accès disques, ou des requêtes HTTP, ces traitements doivent être faits sur d’autres Thread, en asynchrone. Dans le cas contraire, l’interface risque d’être bloquée, et si le blocage est trop long, on risque une ANR.

Dans le cadre de notre ListView, il se peut qu’on veuille afficher, à l’intérieur d’un élément de la liste, une image dont on possède l’URL. Si cette image est téléchargée et générée directement depuis le getView() de l’Adapter, l’interface va être bloquée pendant plusieurs secondes, ce qui n’est pas envisageable. Il faut donc faire ce traitement en asynchrone.

D’une manière générale, il faut éviter tous les traitements longs sur le thread principal, tels que les accès disque, base de données, réseau, et les traitements CPU complexes comme le parsing, le traitement d’images, etc.

Faire des traitements asynchrones

Pour faire des traitements asynchrones, il existe plusieurs méthodes : créer un Thread fils, utiliser les AsyncTasks, passer directement par un ThreadPoolExecutor… mais toutes ces méthodes reviennent au même : dans tous les cas, il s’agit d’exécuter du code sur un autre Thread, et une fois le traitement terminé, revenir sur le thread principal pour mettre à jour la vue. (Je précise que les vues ne doivent pas être modifiées depuis un thread autre que le thread principal, sous peine de dysfonctionnements graves.)

Exemple simple : on a besoin d’afficher une image dont on connaît l’URL. On va d’abord initialiser une ImageView, puis on va télécharger l’image dans un autre Thread, et enfin on va revenir sur le thread principal pour appliquer l’image à l’ImageView.

final ImageView imageView = (ImageView) view.findViewById(R.id.image);
imageView.setImageBitmap(null); // supprime l'image précédente

new Thread() {
    public void run() {
        final Bitmap bitmap = downloadBitmap(url);

        // ce Runnable sera exécuté sur le thread principal
        runOnUiThread(new Runnable() {
            public void run() {
                imageView.setImageBitmap(bitmap);
            }
        });
    }
}.start();

Cette méthode est très simple mais peu lisible car elle nécessite l’imbrication de classes anonymes. Toutefois le concept est là, à vous de le mettre en pratique selon vos besoins. On peut aussi utiliser AsyncTask, une classe qui a pour but de simplifier la gestion de l’asynchronisme en abstrayant de cette gestion des threads. De plus, les AsyncTask sont plus performantes car les threads sont réutilisés. Pour le même exemple avec une AsyncTask, cela ressemblerait à :

final ImageView imageView = (ImageView) view.findViewById(R.id.image);
imageView.setImageBitmap(null);

new AsyncTack<String, Void, Bitmap>() {

    protected Bitmap doInBackground(String... params) {
        String url = params[0];
        Bitmap bitmap = downloadBitmap(url);
        return bitmap;
    }

    protected void onPostExecute(Bitmap bitmap) {
        imageView.setImageBitmap(bitmap);
    }
}.execute(url);

On pourra ensuite en faire une classe réutilisable si on a besoin de faire la même chose à plusieurs endroits.

Traceview

Parfois il peut être difficile de détecter la source des ralentissements. Heureusement, l’outillage du SDK Android est suffisamment fourni, et pour cela l’outil traceview permet d’inspecter le déroulement de l’application sur un intervalle de temps donné. Une fois activé, cet outil va mémoriser tous les appels de fonctions faits par l’application, et va représenter ces appels sur un graphe chronologique. Il faut le lancer au bon moment, et l’arrêter dès que les algorithmes à inspecter ont été exécutés, afin d’éviter de polluer le résultat avec des informations en trop.

Pour activer la collecte d’information traceview, il faut sélectionner son application et cliquer sur bouton “Start Method Profiling” dans le vue DDMS. Il faut cliquer à nouveau sur ce bouton pour l’arrêter et récupérer le résultat.

La partie supérieure de l’écran de résultat affiche la chronologie d’exécution, les couleurs correspondant à des méthodes listés dans la partie inférieure. On peut passer le curseur dessus pour avoir plus d’information à instant donné. A gauche est affiché le thread concerné : s’il y a eu plusieurs threads sollicités, plusieurs timelines s’afficheront les unes sous les autres.

La partie inférieure de l’écran affiche, pour chaque méthode appelée durant la phase de profiling, différentes statistiques :

  • Incl Cpu Time : temps cumulé pendant lequel la méthode était en cours d’exécution, en incluant les appels à d’autres méthodes faits par cette méthode
  • Excl Cpu Time : idem, mais cette fois sans compter les appels à d’autres méthodes faits par cette méthode
  • Calls+RecurCalls : nombre de fois où cette méthode a été appelée + nombre de fois où cette méthode s’est appelée elle-même
  • Cpu Time/Call : temps moyen d’exécution de la méthode

Tout ceci permet d’identifier les méthodes qui prennent trop de temps à l’intérieur du thread principal, mais plus généralement traceview peut aussi être utile dans d’autres contextes liés à des problématiques d’algorithmes trop lents.

Nous verrons la semaine prochaine comment surveiller et optimiser notre utilisation mémoire afin d’éviter les ralentissements et crashs qui peuvent se produire lorsque la mémoire est trop sollicitée.