Accueil > Code > Expériences Android

Expériences Android

jeudi 11 octobre 2012, par Nicolas

Pour un projet, j’ai été amené à développer une petite application pour une tablette Android.

Avoir un seul support (version du système et taille de l’écran) pour lequel développer simplifie pas mal de choses.

J’ai cependant rencontré quelques bugs obscurs, ou alors des comportements pas très bien documentés...

Les styles et les ressources, tellement pratiques...

Commençons par un point positif, il est très simple d’implémenter des dispositions différentes via le système de ressources de base.

Exemple basique : faire un style de base pour un bouton (taille et couleur des caractères, marge, hauteur par défaut, image de fond), faire un style version normale et un style version « paysage » ne changeant que la hauteur, et rajouter une image de fond adaptée au mode paysage (plus haute, par exemple).

Du coup on peut dans un layout intégrer ce bouton stylé, et il s’adaptera automatiquement en hauteur.

En utilisant correctement les styles, on peut donc réussir à avoir un seul layout pour deux résultats assez différents selon l’orientation.

En bonus, on peut même avoir par exemple un android:onClick défini pour tous les boutons, il suffit ensuite d’utiliser le android:tag pour les distinguer.

Seul point faible, NetBeans n’aide pas sur l’auto-complétion dans les styles [1].

Du plein écran ? Pas sur ma tablette !

Pour cette application, il fallait afficher des vidéos en plein écran.

La tablette étant une version Android 4, j’ai tenté d’utiliser le drapeau SYSTEM_UI_FLAG_HIDE_NAVIGATION en plus du FEATURE_NO_TITLE et FLAG_FULLSCREEN.

Et bien non, pas de succès.

J’ai essayé de nombreuses choses, y compris ne pas utiliser de MediaController en pensant que l’affichage perturbait le mode plein écran.

En désespoir de cause, j’ai récupéré les sources officielles de l’application « Gallery » fournie sur Android, compilé et installé [2]. J’ai poussé le vice jusqu’à l’installer comme application système [3].

Surprise, pas de plein écran non plus alors que la version pré-installée en est capable !

Moralité, j’ai finalement utilisé l’application pré-installée via un Intent, en signalant qu’il fallait définir une application par défaut pour les vidéos.

J’ai deux hypothèses par rapport à ce souci, aucune n’étant satisfaisante :

  • le système a été modifié pour n’autoriser que certaines applications pré-installées à être en plein écran. Cela m’étonnerait un peu qu’un fabricant de tablette s’amuse à ce genre de choses, mais bon...
  • il faut signer l’application avec une « vraie » clé et pas la clé debug générée par le SDK. Mais je n’ai rien vu à ce sujet dans mes recherches...

Changer d’orientation, pas si facile

Encore une fois la problématique semblait simple : il fallait afficher une page HTML, avec images, JavaScript [4] et animation Flash.

Le JavaScript ajuste la CSS selon la largeur afin de bien afficher une colonne de droite escamotable. Et afin d’aider à la navigation, deux boutons ont été codés en JavaScript, via un addJavascriptInterface, pour fermer l’activité et revenir en arrière.

Afin de garder la position dans la page en cas de changement d’orientation, logiquement, des appels à WebView.saveState et WebView.restoreState.

Et là évidemment des bugs... Quand l’orientation changeait, la page s’affichait bizarrement, pas toujours avec la bonne CSS. Et les boutons ne fonctionnaient pas toujours.

Bien sûr retirer tous les saveState et restoreState faisait qu’un changement d’orientation perdait totalement la position dans la page.

Après de nombreuses tentatives, quelques cheveux arrachés, la solution était très simple :

  • supprimer tous les appels à saveState et restoreState
  • rajouter android:configChanges="orientation|keyboardHidden" à la définition de l’activity dans le AndroidManifest.xml

Et là magique, tout fonctionne, la page tourne bien, la position de défilement est gardée, les boutons sont actifs.

Erreurs JavaScript

Une dernière petite chose bizarre, il ne semble pas y avoir moyen de récupérer les erreurs JavaScript des WebView.

Elles ne semblent pas non plus apparaître dans adb logcat.

Et pourtant, elles apparaissent bien dans les journaux affichés par dkms...

Ce qui peut toujours être utile.


[1On peut tricher en tapant le style dans un layout pour avoir les valeurs, puis copier-coller sur le style, bien sûr.

[2Après avoir renommé pour éviter les conflits de package.

[3En remontant la partition racine en lecture-écriture puis en rendant root propriétaire du package.

[4jQuery notamment

Messages

  • Au sujet du plein écran, ça n’est ni un bug obscur, ni même un truc mal documenté...

    Selon la documentation du SDK :


    The SYSTEM_UI_FLAG_LOW_PROFILE flag replaces the STATUS_BAR_HIDDEN flag. When set, this flag enables “low profile" mode for the system bar or navigation bar. Navigation buttons dim and other elements in the system bar also hide. Enabling this is useful for creating more immersive games without distraction for the system navigation buttons. (...)
    The SYSTEM_UI_FLAG_HIDE_NAVIGATION is a new flag that requests the navigation bar hide completely. Be aware that this works only for the navigation bar used by some handsets (it does not hide the system bar on tablets). The navigation bar returns to view as soon as the system receives user input. As such, this mode is useful primarily for video playback or other cases in which the whole screen is needed but user input is not required.

    Ton problème vient d’une confusion entre les notions de system bar, status bar, et navigation bar. Alors pour faire simple :

    - Status Bar = la zone dans laquelle le système affiche les infos de batterie, les icônes d’état, etc. C’est, en gros, l’équivalent du systray de Windows ;

    - System Bar = Une barre qui reprend les fonctions de la status bar et y ajoute les contrôles de navigation système sur tablette : boutons back, home, etc.

    - Navigation bar = Une barre qui contient les contrôles de navigation, mais ne duplique pas la status bar.

    À la grosse louche, on peut généralement estimer que, sous Android 4 :

    - Un GSM qui comporte des touches physiques de navigation aura une Status Bar, pas de Navigation Bar, et pas de System Bar ;

    - Un GSM sans touches physiques de navigation aura une Navigation Bar (en bas) et une Status Bar (en haut) ;

    - Une tablette (généralement sans touches physiques de navigation) comportera une System Bar, et pas de Navigation Bar ni de Status Bar.

    Bon, alors pour le plein écran ?

    Sur tablette, c’est simple : le vrai plein écran n’existe pas (du moins sur une tablette non-rootée). La barre système ne peut jamais être éliminée. Il faut utiliser le flag SYSTEM_UI_FLAG_LOW_PROFILE, lequel "réduit" ladite barre, mais sans jamais la supprimer. C’est ce que fait le lecteur Google Vidéo généralement pré-installé.

    Sur GSM, SYSTEM_UI_FLAG_HIDE_NAVIGATION cachera la barre de navigation si elle existe (et seulement elle), et seulement jusqu’au premier événement tactile. La documentation n’implique pas que ce flag cache aussi la barre de status - Il faut donc utiliser SYSTEM_UI_FLAG_LOW_PROFILE en conjonction pour garantir un écran aussi "propre" que possible.

    Donc, à mon avis, le code suivant est le bon moyen d’obtenir un mode aussi proche que possible du plein écran :

    requestWindowFeature(Window.FEATURE_NO_TITLE) ;
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
    WindowManager.LayoutParams.FLAG_FULLSCREEN) ;
    getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) ;
    getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE) ;

    • Effectivement, j’ai peut-être une certaine confusion sur les différentes barres :)

      Cependant je maintiens que la tablette fait du plein écran, sans aucun bouton :)

      D’où ma conclusion que le système a été modifié pour autoriser cela - mais modifié comment, dur à dire...

    • Pour la petite histoire, quand même :)

      Nous allons finalement utiliser une révision plus récente de la tablette, 2 si j’en crois la numérotation.

      Et bien la barre apparaît en surexposition de l’application toute seule gentiment du moment qu’on demande le plein écran avec

      getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
      WindowManager.LayoutParams.FLAG_FULLSCREEN) ;

      Donc le contenu prend tout l’écran, une partie étant recouvert par la barre transparente.

      Et l’utilisateur peut escamoter cette barre avec un petit bouton magique, qui affiche une astuce « faites glisser depuis le bas pour réafficher ».

      Évidemment en surexposition c’est gênant pour cliquer sur le bouton de l’application elle-même qui se trouve juste dessous (quel manque de chance !), donc je me contente finalement du

      requestWindowFeature(Window.FEATURE_NO_TITLE) ;

      et la barre est présente, ou absente si l’utilisateur la cache (auquel cas il ne peut plus faire « retour », bien sûr).

      C’est fort quand même :)