OutOfMemory et Bitmaps sous Android

Les applications Android sont démarrées en mode Sandbox, c’est à dire qu’elles tournent sur une machine virtuelle Dalvik isolée et gérant sa propre mémoire. A côté de ces VM, il y a la mémoire native, utilisée par l’OS lui même.

Si vous avez des problèmes de OutOfMemory, le premier réflexe est d’analyser le « Heap Dump » avec DDMS, puis de façon plus précise avec l’outil Mat, en suivant cet exellent tutorial:

http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html

 

Les Bitmaps ne sont pas visibles dans MAT

Pour utiliser cet outil, il faut néanmoins savoir une chose: les Bitmaps sont stockées dans l’espace natif, et non dans la mémoire allouée à la VM! Vous ne pourrez donc pas analyser les Bitmaps avec MAT.

Toutefois depuis l’OS 3.0, les Bitmaps dans visibles dans MAT, même si elles restent enregistrées dans la mémoire native.

{lang: 'fr'}

Custom Image in Windows Phone Panorama

By default a windows Phone Panorama can only accept a title. What if we want an image instead?
To add the image, we have to define the TitleTemplate attribute.

1
2
3
4
5
6
7
8
9
<Grid x:Name="LayoutRoot">
<controls:Panorama x:Name="panorama">
	<controls:Panorama.TitleTemplate>
		<DataTemplate>
                   <!--<TextBlock Text="Families" FontSize="90" Margin="0,50,0,0" />-->
		   <Image HorizontalAlignment="Right" Margin="0,80,200,0" Width="100" Height="100"  Source="/ApplicationIcon.png"/>
                </DataTemplate>
       </controls:Panorama.TitleTemplate>
...

Notes that we added a right margin of 200px. Without it, a small image will be repeated for each item of the panorama. It’s better to add a big image larger than the width of the screen.

{lang: 'fr'}

Optimisation Apache : forcer la mise en cache browser des éléments fixes

Pour forcer la mise en cache (par le browser) de certaines des images de votre application web, ajouter les lignes suivantes à votre httpd.conf:

<LocationMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)$">
Header set Cache-Control "max-age=86400, public"
</LocationMatch>

Il est ainsi posisble de forcer la mise en cache que sur certains répertoires de l’application et/ou certaines extensions de fichier. Le “86400″ représente la durée de rétention.

 

{lang: 'fr'}

Créer un cache avec google guava

javaPour des raisons de tenue à la charge, il est souvent nécessaire de créer des caches mémoire dans les applications. Le plus souvent, le choix se porte sur ehcache, ou une ConcurrentHashMap.

Il y a maintenant une autre possibilité : utiliser le CacheBuilder de google guava. Grace à une petite api de type DSL, vous pourrez configurer un cache en quelques lignes de code.(tirées de la javadoc)

Cache graphs = CacheBuilder.newBuilder()
       .concurrencyLevel(4)
       .weakKeys()
       .maximumSize(10000)
       .expireAfterWrite(10, TimeUnit.MINUTES)
       .build(
           new CacheLoader() {
             public Graph load(Key key) throws AnyException {
               return createExpensiveGraph(key);
             }
           });

Et voila on dispose d’un cache finement configuré (weak keys, taille limitée, temps d’expiration de 10min) sans avoir à le coder soit même comme avec une concurrentHashMap et sans avoir à intégrer tout ehcache. En bonus un objet CacheStats est même tout prêt à être exposé via JMX pour monitorer la pertinence de ce cache.

Pour l’intégrer facilement à votre projet : la dependence maven à déclarer :

5
6
7
8
9
<dependency>
     <groupId>com.google.guava</groupId>
     <artifactId>guava</artifactId>
     <version>10.0</version>
 </dependency>
{lang: 'fr'}

Faire une requête http en telnet avec Putty

Pour faire une requête GET en telnet avec PUTTY (utile pour vérfier les retours serveurs sans interprétation du Brower):
- Host Name ou IP: le nom du serveur cible
- Port: le port d’écoute du serveur Web (généralement 80)
- Connection Type: Raw
- Close window on exit: Never (sinon, le retour ne sera pas affiché).
Une fois la connexion ouverte, taper la requête. Par exemple:

1
GET / HTTP/1.0

Puis 2 fois “entrée”.
Pour faire la même chose en https, utiliser openssl (téléchargeable ici : http://www.slproweb.com/products/Win32OpenSSL.html)
openssl s_client -connect www.somesite:443
ensuite:

1
GET /index.html HTTP/1.1
{lang: 'fr'}

Comparer des fichiers distants en ligne de commande

Il est souvent utile de faire un diff entre deux fichiers sur un serveur.  C’est là que la commande vimdiff est magique ! (equivalent de vim -d )

vimdiff file1 file2

vous donnera l’affichage suivant :

vimdiff 2 colonnes

vimdiff 2 colonnes

Encore mieux : vous pouvez comparer n fichiers (avec un grand écran c’est plus facile)

vimdiff file1 file2 file3
vimdiff 3 colonnes

vimdiff 3 colonnes

 

Quelques petites options a connaitre :

  • Diviser en horizontal plutôt qu’en vertical vimdiff -o  file1 file2 file 3
  • Changer de buffer : ctrl+w ctrl+w  (oui : ctrl + w deux fois)

 

et le fin du fin, comparer des fichiers distants grace à ssh :

vimdiff scp://login@machine1//path/to/file.txt scp://login@machine2//path/to/file.txt

Pour arriver a trouver une différence de configuration entre deux serveurs, c’est très utile !

{lang: 'fr'}

Limitations des bases de données Blackberry

Avec l’OS 5, RIM a intégré les bases SQLite dans les Blackberry. Avant de commencer à utiliser celles-ci, il peut être utile de savoir ses limites. Cet article expose diverses informations utiles.

Versions de SQLite utilisées:

  • OS 5 utilise la version 3.6.16
  • OS 6 utilise la version 3.6.21

INSERT:
Toute insertion/update de donnée dans un BLOB est limité à 4ko.

IN:
Le nombre de cas dans une requête utilisant IN est limité à environs 500

Taille de l’application
On peut être amener à préembarquer la base de donnée avec l’application. Or le package est limité à 14Mo, il faut donc penser à mettre en place un service de téléchargement pour le premier démarrage.

Toutes les informations officielles concernant le développement sont situées ici

{lang: 'fr'}

Maintenir plusieurs versions d’une application Blackberry

Pour gérer les multiples OS Blackberry ou déployer aisément différentes versions d’une application (version gratuite / payante, …) il est pratique d’utiliser les préprocesseurs.

//#preprocess
 
//#ifndef BlackBerrySDK4.5.0 | BlackBerrySDK4.6.0 | BlackBerrySDK4.6.1 | BlackBerrySDK4.7.0
import net.rim.device.api.database.Database;
import net.rim.device.api.database.DatabaseFactory;
import net.rim.device.api.io.URI;
//#endif
 
public class DemoClass {
 
public static void doSomething(){
//#ifndef BlackBerrySDK4.5.0 | BlackBerrySDK4.6.0 | BlackBerrySDK4.6.1 | BlackBerrySDK4.7.0
try {
URI myURI = URI.create("databaseDemo");
Database d = DatabaseFactory.open(myURI);
}
catch (Exception e) {
}
 
//#else
System.out.println("No database for your OS");
//#endif
}
}

Il y a 3 étapes à suivre:

  • Mettre //#preprocess au tout début du fichier
  • Encadrer le code spécifique par des balises #ifdef, #ifndef, #else et #endif
  • Pour compiler la bonne version, il suffit de choisir la JRE correspondante sur eclipse
Ensuite, ouvrez le fichier manfiest.xml sur l’onglet « Build ». Vous pouvez ainsi créer une nouvelle condition de préprocesseur et l’activer/la désactiver pour la compilation.

Présente les conditions de préprocesseurs disponibles

 

{lang: 'fr'}

Obtenir le userId dans un ModelListener Liferay

Lorsqu’on est dans un ModelListener, nous n’avons pas la request et donc à priori aucun moyen d’obtenir le userId. Pourtant, en regardant la classe com.liferay.portal.service.base.PrincipalBean (dont tous les services liferay héritent) on aperçoit la méthode getUserId() ci-dessous.

    public long getUserId() throws PrincipalException {
    String name = PrincipalThreadLocal.getName();
 
    if (name == null) {
    throw new PrincipalException();
    }
 
    if (Validator.isNull(name)) {
    throw new PrincipalException("Principal cannot be null");
    }
    else {
    for (int i = 0; i < ANONYMOUS_NAMES.length; i++) {
    if (name.equalsIgnoreCase(ANONYMOUS_NAMES[i])) {
    throw new PrincipalException(
    "Principal cannot be " + ANONYMOUS_NAMES[i]);
    }
    }
    }
 
    return GetterUtil.getLong(name);
    }

On a donc juste à réutiliser le code de cette méthode dans un ModelListener pour récupérer le userId courant.

{lang: 'fr'}