0% ont trouvé ce document utile (0 vote)
8 vues41 pages

Développement d'applications Android

Transféré par

Warda Ait Cheikh
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
8 vues41 pages

Développement d'applications Android

Transféré par

Warda Ait Cheikh
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Développement Mobile

Parti 1: Android
Systeme

Module GIM21S04
Technologie XML et Technologie Mobile

4ème Année Génie informatique

REDOUANE EZZAHIR

2015/2016

DERNIER MIS-À-JOURS 2020


Table de matière

Introduction 4
Historique des versions 4
Les éléments d'une application 7
Le Manifest de l'application 7
Les ressources 8
Les chaines 8
Internationalisation 9
Autres valeurs simples 9
Autres ressources 9
Les activités 9
Cycle de vie d' une activité 10
Sauvegarde des interfaces d'activité 11
Démonstration 11
Interfaces graphiques 12
Propriétés de l'écran (unités de mesure) 12
Attributs XML obligatoires 12
Inclusions de gabarits 15
Positionnement avancé 15
ListView 16
Menu et Action bar 20
Menu : déclaration dans un fichier XML 20
Menu Contextuel 22
Résumé sur menus et menus contextuels 23
Action Bar 23
Les Intents 25
Principe des Intents 25
Résultat d'une activité 26
partager des informations via les intents 27
Types d'Intent: actions 27
Catégories d'Intent 28
Recevoir et filtrer les Intents 28
Filtrage d'un Intent par l'activité 29
Utilisation de catégories pour le filtrage 29
Réception par un BroadcastReceiver 30
Les messages natifs 30
Persistance des données 31
Différentes persistances 31
Préférences partagées 32
Activité d'édition de préférences 32
Attributs des préférences 33
Préférences: toggle et texte 33
Préférences: listes 33
Les fichiers 34
BDD SQLite 35
Lecture / Ecriture dans la BDD 35
Programmation concurrente 36
Processus 36
Cycle de Vie des processus 36
Threads 37
Services 37
Threads et interface graphique 37
Services 38
Démarrer / Arrêter un service 38
Auto démarrage d'un service 39
Service dans le thread principal 40
Références: 41
Introduction

Il est important de prendre la mesure des choses. A l'heure actuelle (November 2016):
• juillet 2011: 550 000 activations par jour
• décembre 2011: 700 000 activations par jour
• sept. 2012: 1.3 millions d'activations par jour (Wikipedia)
• avril 2013: 1.5 millions d'activations par jour (Wikipedia) Il y aurait donc un parc
de 400 millions d'appareils Android.

Historique des versions

Le nombre de release est impressionnant (Version):

Nom Version Date


Android 1.0 09/2008
Petit Four 1.1 02/2009
Cupcake 1.5 04/2009
Donut 1.6 09/2009
Gingerbread 2.3 12/2010
Honeycomb 3.0 02/2011
Ice Cream Sandwich 4.0.1 10/2011
Jelly Bean 4.1 07/2012
KitKat 4.4 10/2013
Lollipop 5.0 10/2014

Marshmallow 6.0 05/2015


Nougat 7.0 09/2016

La plateforme Android
La plate-forme Android est une pile logicielle qui a été conçu principalement, mais
pas exclusivement pour le soutien des appareils mobiles tels que les téléphones et
les tablettes. Cette pile a plusieurs couches :
• Un système d'exploitation open-source
• Un intergiciel (middleware)
• et des applications clés.
Le système Android est également livré avec un kit de développement, ou SDK, qui
fournit des outils et des APIs nécessaires pour le développement d’applications Android
en utilisant le langage de programmation Java.

Android est en fait un système de la famille des Linux, pour une fois sans les outils
GNU. L'OS s'appuie sur:
• un noyau Linux (et ses drivers): S’appuyant sur le noyau Linux 2.6 pour les services
système de base. Elle Offre une architecture d’autorisations Mémoire et processus de
gestion des Fichier, et d'IO réseau.

• Pilotes : de Gestion du matériel (écran clavier ...) et de Gestion des capteurs (appareil
photo, GPS, accéléromètre …). assurent la sécurité et fournissent une couche
d'abstraction entre le Hardware et le reste de la pile logiciel
• un couche d'abstraction pour l'accès aux capteurs (HAL)
• une machine virtuelle: Dalvik Virtual Machine (avant Lollipop)
• un compilateur de bytecode vers le natif Android Runtime (pour Lollipop)
• des applications (navigateur, gestion des contacts, application de téléphonie )
• des bibliothèques (SSL, SQLite, OpenGL ES, etc )
• des API d'accès aux services Google Anatomie d'un déploiement: Dalvik et ART

Dalvik et ART
[Dalvik] est le nom de la machine virtuelle open-source utilisée sur les systèmes Android.
Cette machine virtuelle exécute des fichiers .dex, plus ramassés que les .class classiques.
Ce format évite par exemple la duplication des String constantes. La machine virtuelle
utilise elle-même moins d'espace mémoire et l'adressage des constantes se fait par un
pointeur de 32 bits. [Dalvik] n'est pas compatible avec une JVM du type Java SE ou
même Java ME. La librairie d'accès est donc redéfinie entièrement par Google.

A partir de Lollipop, Android dispose d' qui compile l'application au moment du


déploiement (Ahead-of-time compilation).

Application Framework
Fonction Rôle
Utilisé pour construire une application, y compris les listes, grilles, texte, Boîtes,
View System
des boutons, et le navigateur Web intégré
Permettant aux applications d'accéder aux données d'autres applications ou pour
Content Provider
partager leurs propres données
Resource Fournir un accès à des ressources non-code (chaînes localisées, les graphiques et
Manager les fichiers de mise en page)
Notification Permettre à toutes les applications d’afficher des alertes de clients dans la barre

Manager d'état
Gestion du cycle de vie des applications en fournissant une navigation
Activity Manager
commune de "pile de retour (back stack).
Les éléments d'une application

Une application Android peut être composée des éléments suivants:


• Activity : il s'agit d'une partie de l'application présentant une vue à l'utilisateur
• Service : il s'agit d'une activité tâche de fond sans vue associée
• des fournisseurs de contenus ([Link]): permet le partage
d'informations au sein ou entre applications
• des widgets ([Link].*): une vue accrochée au Bureau d'Android
• des Intents ([Link]): permet d'envoyer un message pour un composant
externe sans le nommer explicitement
• des récepteurs d'Intents ([Link]): permet de déclarer être
capable de répondre à des Intents
• des notifications (.Notifications): permet de notifier l'utilisateur de la survenue
d’événements

Le Manifest de l'application
Le fichier déclare l'ensemble des éléments de l'application.
<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android=";" package="" android:versionCode="1" android:versionName="1.0">

<application android:icon="@drawable/icon" android:label="@string/app_name">

<activity android:name=".Main" android:label="@string/app_name">

<intent-filter>

<action android:name="" />

<category android:name="[Link]"/>

</intent-filter>

</activity>

<service></service>

<receiver></receiver>

<provider></provider>

</application>

</manifest>
Les ressources

Les ressources de l'applications sont utilisées dans le code au travers de la classe


statique R. ADT re-génère automatiquement la classe statique R à chaque changement
dans le projet. Toutes les ressources sont accessibles au travers de R, dés qu'elles sont
déclarées dans le fichier XML ou que le fichier associé est déposé dans le répertoire
adéquat. Les ressources sont utilisées de la manière suivante:
android.R.type_ressource.nom_ressource
qui est de type int. Il s'agit en fait de l'identifiant de la ressource. On peut alors utiliser cet
identifiant ou récupérer l'instance de la ressource en utilisant la classe Resources:
Resources res = getResources();
String hw = [Link]([Link]);
XXX o = [Link](id);
Une méthode spécifique pour les objets graphiques permet de les récupérer à partir de
leur id, ce qui permet d'agir sur ces instances même si elles ont été créées via leur
définition XML:
TextView texte = (TextView)findViewById([Link].le_texte); [Link]("Here we go ! ");

Les chaines

Les chaines constantes de l'application sont situées dans l’externalisation des chaines
permettra de réaliser l'internationalisation de l'application. Voici un exemple:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Hello Hello JFL !</string>
<string name="app_name">AndroJF</string>
</resources>

La récupération de la chaine se fait via le code:


Resources res = getResources();
String hw = [Link]([Link]);
Internationalisation
Le système de ressources permet de gérer très facilement l'internationalisation d'une
application. Il suffit de créer des répertoires values-XX où XX est le code de la langue que
l'on souhaite implanter. On place alors dans ce sous répertoire le fichier xml contenant les
chaines traduites associées aux même clefs que dans . On obtient par exemple pour les
langues es et fr l'arborescence:
MyProject/ res/ values/ values-es/ values-fr/
Android chargera le fichier de resources approprié en fonction de la langue du système.

Autres valeurs simples


Plusieurs fichies xml peuvent être placés dans res/values. Cela permet de définit des
chaines, des couleurs, des tableaux. L'assistant de création permet de créer de nouveaux
fichiers de ressources contenant des valeurs simples, comme par exemple un tableau de
chaines:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="test">
<item>it1</item>
<item>it2</item>
</string-array>
</resources>

Autres ressources
D'autres ressources sont spécifiantes dans le dossier res: telles que les menus, les
images ([Link]), des dimensions ([Link]) et des couleurs ([Link])

Les activités
Une application Android étant hebergée sur un système embarqué, le cycle de vie d'une
application ressemble à celle d'une application Java ME. L'activité peut passer des états:
• démarrage -> actif: détient le focus et est démarré
• actif -> suspendue: ne détient plus le focus
• suspendue -> actif:
• suspendue -> détruit:
Le nombre de méthodes à surcharger et même plus important que ces états:
Cycle de vie d' une activité

public class Main extends Activity {


public void onCreate(Bundle
savedInstanceState) {

[Link](savedInstanceState);
setContentView([Link]);
}
protected void onDestroy() {
[Link]();
}
protected void onPause() {
[Link]();
}
protected void onResume() {
[Link]();
}
protected void onStart() {
[Link]();
}
protected void onStop() {
[Link]();
}
}

onCreate() / onDestroy(): permet de gérer les opérations à faire avant l'affichage de


l'activité, et lorsqu'on détruit complètement l'activité de la mémoire. On met en général peu
de code dans onCreate() afin d'afficher l'activité le plus rapidement possible.
onStart() / onStop(): ces méthodes sont appelées quand l'activité devient visible/invisible
pour l'utilisateur.
onPause() / onResume(): une activité peut rester visible mais être mise en pause par le
fait qu'une autre activité est en train de démarrer, par exemple B. onPause() ne doit pas
être trop long, car B ne sera pas créé tant que onPause() n'a pas fini son exécution.
onRestart(): cette méthode supplémentaire est appelée quand on relance une activité qui
est passée par onStrop(). Puis onStart() est aussi appelée. Cela permet de différencier le
premier lancement d'un relancement.
Le cycle de vie des applications est très bien décrit sur la page qui concerne les Activity.
Sauvegarde des interfaces d'activité
L'objet Bundle passé en paramètre de la méthode onCreate permet de restaurer les
valeurs des interfaces d'une activité qui a été déchargée de la mémoire. En effet, lorsque
l'on appuie par exemple sur la touche Home, en revenant sur le bureau, Android peut-être
amené à déchargé les éléments graphiques de la mémoire pour gagner des ressources.
Si l'on rebascule sur l'application (appui long sur Home), l'application peut avoir perdu les
valeurs saisies dans les zones de texte.
Pour forcer Android à décharger les valeurs, il est possible d'aller dans "Development tools
> Development Settings" et de cocher "Immediately destroy activities".
Si une zone de texte n'a pas d'identifiant, Android ne pourra pas la sauver et elle ne pourra
pas être restaurée à partir de l'objet Bundle.

Si l'application est complètement détruite (tuée), rien n'est restauré.


Le code suivant permet de visualiser le déclenchement des sauvegardes:
protected void onSaveInstanceState(Bundle outState) {
[Link](outState);
[Link](this, "Sauvegarde !", Toast.LENGTH_LONG).show();
}

Démonstration
Videos:

parti1

parti2
Interfaces graphiques
Les éléments graphiques héritent de la classe View. On peut regrouper des éléments
graphiques dans une ViewGroup. Des ViewGroup particuliers sont prédéfinis: ce sont
des gabarits (layout) qui proposent une prédispositions des objets graphiques:
• LinearLayout: dispose les éléments de gauche à droite ou du haut vers le bas
• RelativeLayout: les éléments enfants sont placés les uns par rapport aux autres
• TableLayout: disposition matricielle
• FrameLayout: disposition en haut à gauche en empilant les éléments
• GridLayout: disposition matricielle avec N colonnes et un nombre infini de lignes

Propriétés de l'écran (unités de mesure)


Pour le développement d'application Android, il y en a 6 unités de mesure :
dp : Density Independent Pixel ( Densité de pixels indépendant) - Unité abstraite qui est
basés sur la densité physique de l'écran. Cette unité est égale à 160 DPI (Points par
pouce) par écran. Cette dimension sera utilisé pour la mise en page des éléments. Elle
permet aux applications de se ressemblent sur différents écrans et résolutions.
sp : Scale independent Pixel (Echelle de pixels indépendant) - Utilisé pour les tailles de
polices. On pourrait comparé cette unité aux em du développement web. La police peut
être plus ou moins grosse suivant les préférences utilisateurs.
pt : Point - 1/72 of an inch. basé sur la taille physique de l'écran.
px : Pixels - Corresponds aux pixels réels de l'écran. Cette unité de mesure n'est pas
recommandées car le rendu sur les différents types d'écran peut être différents. Le
nombre de pixels par pouce peut varier suivant les appareils.
mm : Millimètre - basée sur la taille physique de l'écran
in : Inches (Pouces) - basée sur la taille physique de l'écran.

Attributs XML obligatoires


Les déclarations se font principalement en XML, ce qui évite de passer par les
instanciations Java.
Les attributs des gabarits permettent de spécifier des attributs supplémentaires. Les plus
importants sont:
• android:layout_width et android:layout_height:
• ="match_parent": l'élément remplit tout l'élément parent
• ="wrap_content": prend la place minimum nécessaire à l'affichage
• ="fill_parent": comme match_parent (deprecated, API<8)
• android:orientation: définit l'orientation d'empilement
• android:gravity: définit l'alignement des éléments
les valeurs absolues sont en: dip, px, mm, in, ...

Voici un exemple de LinearLayout:


<LinearLayout xmlns:android="; android:orientation="vertical"
android:layout_width="match_parent" android:layout_height="match_parent"
android:gravity="center" android:id="@+id/accueilid" >
</LinearLayout>
Un ViewGroup contient des éléments graphiques. Pour construire un gabarit linéaire on
fera:

<LinearLayout xmlns:android="; android:layout_width="match_parent"


android:layout_height="match_parent" android:gravity="center" android:id="@+id/
accueilid" >
<TextView android:id="@+id/le_texte" android:text="@string/hello" /
><TextView android:id="@+id/le_texte2" android:text="@string/hello2" />
</LinearLayout>
Alors que pour empiler une image et un texte:

<FrameLayout xmlns:android="; android:layout_width="match_parent"


android:layout_height="match_parent" android:id="@+id/accueilid">
<ImageView ndroid:layout_width="match_parent"
android:layout_height="match_parent" android:src="@drawable/mon_image" />
<TextView android:id="@+id/le_texte" android:text="@string/hello" />
</FrameLayout
Une interface graphique définie en XML sera aussi générée comme une ressource dans la
classe statique R. Le nom du fichier xml, par exemple permet de retrouver le layout dans
le code java au travers de [Link].
Ainsi, pour associer la première vue graphique à l'activité principale de l'application, il faut
faire:
public void onCreate(Bundle savedInstanceState)
{ [Link](savedInstanceState);
setContentView([Link]);
}
Le layout reste modifiable au travers du code, comme tous les autres objets graphiques.
Pour cela, il est important de spécifier un id dans la définition XML du gabarit
(android:id="@+id/accueilid"). Le "+" signifie que cet id est nouveau et doit être généré
dans la classe R. Un id sans "+" signifie que l'on fait référence à un objet déjà existant.
En ayant généré un id, on peut accéder à cet élément et agir dessus au travers du code
Java:
LinearLayout l = (LinearLayout)findViewById([Link]);
[Link]([Link]);
En XML:
<TextView android:id="@+id/le_texte" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="@string/hello"
android:layout_gravity="center" />
Par la programmation:

public class Activity2 extends Activity { public void onCreate(Bundle


savedInstanceState) { [Link](savedInstanceState); LinearLayout gabarit
= new LinearLayout(this); [Link]([Link]); // centrer les éléments
graphiques [Link]([Link]); // empiler vers le bas !
TextView texte = new TextView(this);
[Link]("Programming creation of interface !"); [Link](texte);
setContentView(gabarit);
}}
En XML:
<EditText android:text="" android:id="@+id/EditText01"
android:layout_width="match_parent" android:layout_height="wrap_content">
</EditText>
Par la programmation:
EditText edit = new EditText(this); [Link]("Edit me"); [Link](edit);
Interception d'événements:
[Link](new TextWatcher() { @Override public void
onTextChanged(CharSequence s, int start, int before, int count) { // do something here
}
);
En XML:
<ImageView android:id="@+id/logoEnsi" android:src="@drawable/ensi"
android:layout_width="100px" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" ></ImageView>
Par la programmation:
ImageView image = new ImageView(this); [Link]();
[Link](image);
En XML:
<Button android:text="Go !" android:id="@+id/Button01"
android:layout_width="wrap_content" android:layout_height="wrap_content">
</Button>
La gestion des événements de click se font par l'intermédiaire d'un listener:
Button b = (Button)findViewById([Link].Button01);
[Link](new OnClickListener() {
@Override
public void onClick(View v) {
[Link]([Link](), "Stop !", Toast.LENGTH_LONG).show();
} });
}

Inclusions de gabarits

Les interfaces peuvent aussi inclure d'autres interfaces, permettant de factoriser des
morceaux d'interface. On utilise dans ce cas le mot clef include:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="; android:orientation="vertical"
android:layout_width="match_parent" android:layout_height="match_parent" >
<include android:id="@+id/include01"
android:layout_width="wrap_content" android:layout_height="wrap_content"
layout="@layout/acceuil" >
</include>
</LinearLayout>
Si le gabarit correspondant à LinearLayout contient lui aussi un LinearLayout, on risque
d'avoir deux imbrications de LinearLayout inutiles car redondant:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=";>
<LinearLayout xmlns:android=";>
<TextView/><TextView/>
</LinearLayout> </LinearLayout>

Positionnement avancé

Le problème précédent est dû au fait qu'un layout doit contenir un unique root element, du
type View ou ViewGroup. On peut pas mettre une série de TextView sans root element.
Pour résoudre ce problème, on peut utiliser le tag **merge**. Si l'on réécrit le layout
comme cela:
<merge xmlns:android=";>
<TextView /> <TextView /> </merge>
L'inclusion de celui-ci dans un layout linéaire transformera:
<LinearLayout xmlns:android=";><include android:id="@+id/include01" layout="@layout/
acceuil"></include> </LinearLayout>
en:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=";>
<TextView/><TextView/> </LinearLayout>
Pour obtenir une interface agréable, il est souvent nécessaire de réaliser correctement le
positionnement des éléments graphiques. La difficulté est d'arriver à programmer un
placement qui n'est pas dépendant de l'orientation ou de la taille de l'écran.
Dans [VL], on trouve une explication pour réaliser un placement simple: un texte à gauche
et une image à droite de l'écran, alignée avec le texte. Cela peut être particulièrement utile
si par exemple on réalise une liste d'item qui contient à chaque fois un texte et une icône.
Le principe réside dans l'imbrication de LinearLayout:
• Un premier layout contiendra l'ensemble des éléments. Son orientation doit
être horizontal et sa gravité (gravity) center. On inclut ensuite le texte.
• Puis, l'image étant censée être à droite, il faut créer un LinearLayout consécutif au
texte et préciser que la gravité (gravity) est right. Pour aligner les éléments, il faut préciser
que la gravité du layout (layout_gravity) est center.
Le layout décrit ci-avant ressemble à:
<LinearLayout xmlns:android="; android:layout_height="match_parent"
android:orientation="horizontal" android:layout_width="match_parent"
android:gravity="center">
<TextView ></TextView>
<LinearLayout android:layout_height="wrap_content"
android:orientation="horizontal" android:layout_width="match_parent"
android:gravity="right" android:layout_gravity="center">
<Image />
</LinearLayout></LinearLayout>

ListView
Au sein d'un gabarit, on peut implanter une liste que l'on pourra dérouler si le nombre
d'éléments est important. Si l'on souhaite faire une liste plein écran, il suffit juste de poser
un layout linéaire et d'y implanter une ListView. Le XML du gabarit est donc:
<LinearLayout>
<ListView android:id="@+id/listView1" >
</ListView></LinearLayout>
La classe ListView est de type ViewGroup et constituée de plusieurs lignes, chaque ligne
est une vue enfant du ViewGroup qui prend en charge l'affichage des données d'un
élément de la liste. Les données à afficher sont de provenance divers
la liste utilise un ListAdpater qui lie la vue aux données.
Android fournit plusieurs ListAdapter ArrayAdapter<T> pour les données de type tableau
CursorAdapter pour les données stockées en base SimpleCursorAdapter pour les
données de type String ou image stockées en base etc.

Etant donné qu'une liste peut contenir des éléments graphiques divers et variés, les
éléments de la liste doivent être insérés dans un ListAdapter et il faut aussi définir le
gabarit qui sera utilisé pour afficher chaque élément du ListAdapter.

Prenons un exemple simple: une liste de chaine de caractères. Dans ce cas, on créé un
nouveau gabarit montexte et on ajoute dynamiquement un ArrayAdapter à la
liste listView1.

Le code de l'application qui créé la liste peut être:


ListView list = (ListView)findViewById([Link].listView1);
ArrayAdapter<String> tableau =
new ArrayAdapter<String>([Link](), [Link]);
for (int i=0; i<40; i++) { ("coucou " + i); }
[Link](tableau);

Lorsque les listes contiennent un layout plus complexe qu'un texte, il faut utiliser un autre
constructeur de ArrayAdapter (ci-dessous) où resource est l'id du layout à appliquer à
chaque ligne et textViewResourceId est l'id de la zone de texte inclu dans ce layout
complexe. A chaque entrée de la liste, la vue générée utilisera le layout complexe et la
zone de texte contiendra la string passée en argument à la méthode add.
ArrayAdapter (Context context, int resource, int textViewResourceId)
Le code de l'exemple précédent doit être adapté comme ceci:
ListView list = (ListView)findViewById([Link]); ArrayAdapter<String> tableau
= new ArrayAdapter<String>( [Link](), [Link],
[Link]); for (int i=0; i<40; i++) {

("coucou " + i); } [Link](tableau);


Avec le layout de liste suivant :
<LinearLayout>
<TextView android:id="@+id/monTexte"/>
<LinearLayout> <ImageView /> </LinearLayout>
</LinearLayout>
Une autre solution consiste à hériter de la classe **BaseAdapter**, ce qui oblige à coder la
méthode getView() mais évite la complexité du constructeur de ArrayAdapter.

Lorsque chaque item de la liste contient plusieurs données dynamiques, il faut recoder la
classe ArrayAdapter, comme expliqué dans LVAA. Si l'on suppose par exemple que la
donnée est:
public class User { public String name; public String hometown; }
Et que le layout d'un item est:
<LinearLayout >
<TextView android:id="@+id/tvName" />
<TextView android:id="@+id/tvHome" /> <LinearLayout/>
On doit alors écrire une méthode getView d'une classe UsersAdapter héritant
de ArrayAdapter qui va renvoyer l'élément graphique correspondant à une item (donné
ci-après). Pour utiliser cette ArrayAdapter on fait comme précédemment:
// Construct the data source
ArrayList<User> arrayOfUsers = new ArrayList<User>();
// Create the adapter to convert the array to views
UsersAdapter adapter = new UsersAdapter(this, arrayOfUsers); ListView listView =
(ListView) findViewById([Link]); [Link](adapter) (new User( ..));

public class UsersAdapter extends ArrayAdapter<User>


{ public UsersAdapter(Context context, ArrayList<User> users) { super(context, 0,
users); } int getCount() { // TODO !!! IMPORTANT !!! REQUIRED !!! DO NOT
FORGET !!! }public View getView(int position, View convertView, ViewGroup parent)
{ // Get the data item for this position
User user = getItem(position); // Check if an existing view is being reused, otherwise
inflate the viewif (convertView == null) { convertView = (getContext())
.inflate([Link].item_user, parent, false);
}
// Lookup view for data population
TextView tvName = (TextView) [Link]([Link]);
TextView tvHome = (TextView) [Link]([Link]); // Populate the
data into the template view using the data object [Link]();
[Link]([Link]); // Return the completed view to render on screen
return convertView;
}}
On notera l'appel à la fonction inflate (getContext()).inflate qui permet
de gonfler l’élément graphique pour un item de liste à partir du XML décrivant
le layout d'un item. Sur cet élément gonflé, on peut chercher des éléments graphiques à
l'aide de findViewById. Enfin, ne pas oublier getCount() qui sera appelé pour savoir
combien d'éléments sont à afficher et donc déclenchera les appels à getView.
Dans les dernières version d'Android, un nouveau mécanisme permet de gérer des listes
volumineuses pour lesquelles on recycle les éléments graphiques dynamiquement lorsque
la liste est parcourue. Il s'agit de la classe .
Comme pour une ListView il faut un Adapter. Il faut en plus un layout manager qui va
gérer le recyclage des éléments graphiques. Après avoir déclaré un RecyclerView dans
votre gabarit:
<[Link] android:id=« @+id/rv"
android:layout_below=« @+id/button" android:layout_height="match_parent"
android:layout_width="match_parent">
</[Link]>
Il faut accrocher l'Adapter et le LayoutManager à ce composant graphique:
RecyclerView rv = (RecyclerView)findViewById();
[Link](new
LinearLayoutManager(this, [Link], false));
[Link](new RVAdapter());

Chaque item aura pour layout (id: [Link]):


<TextView xmlns:android="; android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/itemid" />
Le code de l'Adapter est plus complexe: il s'agit d'une classe utilisant un générique qui
doit créer un objet encapsulant l'objet graphique à afficher pour chaque item (dans
l'example StringHolder héritant de [Link]). L'Adapter à deux buts:
créer les objets graphiques ou changer le contenu d'un objet graphique (car la liste est
déroulée).
onCreateViewHolder(ViewGroup parent, int viewType): doit générer un objet
graphique, par exemple à partir de l'id d'un gabarit. Dans mon exemple, on génère
un TextView, accorché au ViewGroup parent. Il doit aussi générer un objet Holder qui
pourra être mis à jour plus tard pour chaque donnée. On stocke donc l'objet graphique
dans ce Holder (cf. le constructeur).
onBindViewHolder(StringHolder holder, int position) doit mettre à jour l'objet
graphique stocké dans ce Hodler en fonctoin de la position de la liste.
getItemCount(): doit dire combien d'éléments sont dans la liste.
Le code du Holder est assez simple:

public class StringHolder extends [Link] { private TextView v_;


public StringHolder(TextView v) { super(v); v_ = v; }
public void setText(String text) { v_.setText(text);
}}

Menu et Action bar

Menu : déclaration dans un fichier XML


La déclaration des menus est effectuée dans un fichier XML situé dans res/menu.
La syntaxe est simple: la racine menu accepte des balise item pour un élément de menu
et menu pour déclarer un sous-menu. Deux niveaux maximum : menu et sous-menu.
Les Principaux attributs de la balise item
- title: titre de l'élément de menu
- icon: icône de l'élément de menu
- onClick: méthode de l'activité à invoquer (depuis Android 3) ! checked: présélection
de l'élément du menu

Exemple

<menu xmlns:android=[Link]
<item android:id="@+id/agenda" android:icon="@drawable/agenda"
android:orderInCategory="100" android:title="Agenda">
<menu>
<item android:id="@+id/rdv" android:title="RDV"/>
<item android:id="@+id/rappel" android:title="Rappel"/>
</menu>
</item>
</menu

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate([Link], menu); return true;
}

Exemple de construction dynamique par la redéfinition de la fonction


onPrepareOptionsMenu:

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
[Link](0).setTitle("Agenda" + [Link]());
rSubMenu submenu = [Link](99, 777, 101, "Signets")
[Link](99, 778, 101, "AdrooidNote");
[Link](99, 779, 101, "JavaNote"));
[Link](0).setIcon([Link]);
return [Link](menu);
}

Resultat de construction dynamique de menu

Pour filtrer les clicks de l'utilisateur sur un des boutons de l'action bar, il faut utiliser la
méthode onOptionsItemSelected de votre activité:

@Override
public boolean onOptionsItemSelected(MenuItem item) { switch ([Link]()) {
case [Link]: [Link](this, "Agenda", Toast.LENGTH_SHORT).show(); break;
case [Link]: [Link](this, "RDV " + [Link](),
Toast.LENGTH_SHORT).show(); break;
case [Link] : [Link](this, "Rappel " + [Link](),
Toast.LENGTH_SHORT).show(); break;
case 777: [Link](this, "Signets", Toast.LENGTH_SHORT).show(); break; case 778:
[Link](this, "AdrooidNote", Toast.LENGTH_SHORT).show(); break;
case 779: [Link](this, "JavaNote", Toast.LENGTH_SHORT).show();break; }
return [Link](item);
}
Menu Contextuel
Un menu contextuel apparaît si l'utilisateur effectue un appui long sur l'écran

• Il faut donc que l'appui long soit géré par le widget possédant le menu contextuel
• appeler la méthode registerForContextMenu après setContentView, lorsque la
construction de la vue (dans onCreate(). la méthode reçoit l'identifiant du widget sur
lequel ajouter le menu contextuel
• La création du menu est effectuée dans la méthode
onCreateContextMenu
• La méthode onContextItemSelected est utilisée pour traiter le choix de l'utilisateur

RelativeLayout view;
@Override
protected void onCreate(Bundle savedInstanceState) {
[Link](savedInstanceState); setContentView([Link].activity_main);
view = ((RelativeLayout) findViewById([Link].view1));
; registerForContextMenu(view); }
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate([Link], menu); return true;
}

@Override
public void onCreateContextMenu(ContextMenu menu, View v,ContextMenuInfo menuInfo)
{
[Link](menu);
[Link](menu, v, menuInfo);
}

protected void finish() {


unregisterForContextMenu(view);
[Link]()
}

Evénement lors du choix dans le menu contextuel

@Override
public boolean onContextItemSelected(MenuItem item) { switch ([Link]()) {
case [Link]:
[Link](this, "Context Menu (Agenda)", Toast.LENGTH_SHORT).show();
break;
case 777:
[Link](this, "context Menu (Signets)", Toast.LENGTH_SHORT).show();
break;
}
return [Link](item); }

@Override
public boolean onLongClick(View v) { [Link]();
return false;
}

Résumé sur menus et menus contextuels

• onCreateOptionsMenu - est invoqué que lorsque le menu est affiché pour la première fois.
Crée un menu et n'est pas plus utilisé. Nous ajoutons des éléments de menu ici.
• onPrepareOptionsMenu - invoqué à chaque fois avant d'afficher le menu. Nous apportons
des changements au menu déjà existant si cela est nécessaire.
• onOptionsItemSelected(MenuItem) appelée lors d'un choix dans un menu de l'activité. Le
paramètre est le choix effectuéon
• onOptionsMenuClosed(Menu) appelée lors de la fermeture d'un menu de l'activité. Le
paramètre est le menu fermé.
• onCreateContextMenu(ContextMenu, View, [Link]) appelée
lorsqu'un menu contextuel est affiché. Le 1er paramètre est ce menu contextuel, le 2ème
est l'élément d'interface auquel il est associé, le dernier donne des informations sur le
contenu de l'élément d'interface qui a causé l'apparition du menu contextuel

• registerForContextMenu(View) associe un menu contextuel à l'élément passé en


paramètre
• unregisterForContextMenu(View) supprime l'association du menu contextuel à l'élément
passé en paramètre
• onContextItemSelected(MenuItem) appelée lors d'un choix dans un menu contextuel. Le
paramètre est le choix effectué.
• onContextMenuClosed(ContextMenu) appelée lors de la fermeture d'un menu
[Link] paramètre est le menu fermé.

Action Bar

Pour ajouter une action bar, il suffit d'utiliser un thème l'ayant prévu, par
exemple [Link] et de faire hériter votre activité
de AppCompatActivity. Dans votre manifest, il faut modifier l'attribut android:theme:
<activity android:theme="@style/[Link]" >
Le menu de l'action bar est controlé par la méthode onCreateOptionsMenu(Menu
menu) qui charge un fichier menu depuis les ressources.
L'attribut app:showAsAction="ifRoom" permet de demander d'ajouter cette action
comme un bouton dans la barre d'action, si la place est disponible.

Il est possible d'élaborer des action bar avec des actions plus complexes:
• un champ de recherche: avec
l'attribut app:actionViewClass="[Link]"
• une action presonalisée:
• ShareActionProvider: pour partager un élément dans l'écosystème Android
• une action personalisée: on doit construit l'objet View soit même.
Pour aller plus, se référer à la documentation de l'Action bar.
Certaines classes helpers permettent d'organiser l'espace de l'écran ou de réaliser des
animations pour chabger l'état de l'écran.
• L'animation des gabarits
• Les onglets: une combinaison de l'action bar et de fragments
• Le défilement à gauche ou à droite: en utilisant ViewPager, PagerAdapter et des
fragments Et d'autres animations que je ne détaille pas par la suite:
• fondu enchainé de deux activités
• animation de retournement de carte
• effet de zoom sur un élément
L'animation d'un gabarit est simple à mettre en
oeuvre: il suffit par exemple d'ajouter l'attribut
android:animateLayoutChanges="true" à un LinearLayout pour que l'animation soit
jouée quand on ajoute un élément à ce gabarit:
[Link](newView, 0);

La réalisation d'onglets permet de mieux utiliser l'espace réduit de l'écran. Pour réaliser
les onglets, les choses se sont considérablement simplifiées depuis l'API 11. Dans
l'activité principale, il faut ajouter à l'action bar existante des onglet des objets de
type Tab ayant obligatoirement un listener de type TabListener, comme présenté dans la
documentation sur les onglets:
final ActionBar actionBar = getActionBar();
// Specify that tabs should be displayed in the action
bar. [Link](ActionBar.NAVIGATION_MODE_TABS); // Create a
tab listener that is called when the user changes tabs. [Link] tabListener
= new [Link]() { public void onTabSelected( tab,
FragmentTransaction ft) { // show the given tab } public void
onTabUnselected( tab, FragmentTransaction ft) { // hide the given tab }
public void onTabReselected( tab, FragmentTransaction ft) { // probably ignore
this event
}}; // Add 3 tabs, specifying the tab's text and TabListenerfor (int i = 0; i < 3; i++)
{ Tab t = [Link]().setText("Tab " + (i + 1));
[Link](tabListener); [Link](t);
}

Il faut ensuite coder le changement d'écran lorsque l'évenement onTabSelected survient.


On utilise pour cela l'objet FragmentTransaction pour lequel on passe l'id du composant
graphique à remplacer par un objet de type Tab:
public void onTabSelected( tab, FragmentTransaction ft) { FragmentTab newft
= new FragmentTab(); if ([Link]().equals("Tab 2")) { [Link](true); }
[Link]([Link], newft); }
La classe FragmentTab hérite de Fragment: c'est une sous partie d'activité qui a son
propre cycle de vie. Dans cet exemple, au lieu de faire 2 fragments différents, on
surcharge la méthode onCreateView pour cocher la case à cocher:

public class FragmentTab extends Fragment {


public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
View view = [Link]([Link].fragment1, container, false); CheckBox cb =
(CheckBox) [Link]([Link].checkBox1); [Link]([Link]);
return view;
}}
Le gabarit de l'activité, est donc:
<RelativeLayout>
<TextView android:id="@+id/textView1" android:text="@string/hello_world" />
<FrameLayout android:id="@+id/fragmentContainer" />
</FrameLayout>
</RelativeLayout>

Et celui du fragment :
<LinearLayout
<CheckBox android:id="@+id/checkBox1" />
</LinearLayout>

Les Intents

Principe des Intents


Les Intents permettent de gérer l'envoi et la réception de messages afin de faire coopérer
les applications. Le but des Intents est de déléguer une action à un autre composant, une
autre application ou une autre activité de l'application courante.
Un objet Intent contient les information suivantes:
• le nom du composant ciblé (facultatif)
• l'action à réaliser, sous forme de chaine de caractères
• les données: contenu MIME et URI
• des données supplémentaires sous forme de paires clef/valeur
• une catégorie pour cibler un type d'application
• des drapeaux (informations supplémentaires)
On peut envoyer des Intents informatifs pour faire passer des messages. Mais on peut
aussi envoyer des Intents servant à lancer une nouvelle activité.

Intents pour une nouvelle activité


Il y a plusieurs façons de créer l'objet de type Intent qui permettra de lancer une nouvelle
activité. Si l'on passe la main à une activité interne à l'application, on peut créer l'Intent et
passer la classe de l'activité ciblée par l'Intent:
Intent login = new Intent(getApplicationContext(), [Link]); startActivity(login);
Le premier paramètre de construction de l'Intent est en fait le contexte de l'application.
Dans certain cas, il ne faut pas mettre this mais faire appel à getApplicationContext() si
l'objet manipulant l'Intent n'hériste pas de Context.
S'il s'agit de passer la main à une autre application, on donne au constructeur de l'Intent
les données et l'URI cible: l'OS est chargé de trouver une application pouvant répondre à
l'Intent.
Button b = (Button)findViewById([Link].Button01);
[Link](new OnClickListener() { public void onClick(View v) {
Uri telnumber = [Link]("[Link]
Intent call = new Intent(Intent.ACTION_DIAL, telnumber); startActivity(call); }
});
Sans oublier de déclarer la nouvelle activité dans le Manifest.
Retour d'une activité

Lorsque le bouton retour est pressé, l'activité courante prend fin et revient à l'activité
précédente. Cela permet par exemple de terminer son appel téléphonique et de revenir à
l'activité ayant initié l'appel.
Au sein d'une application, une activité peut vouloir récupérer un code de retour de l'activité
"enfant". On utilise pour cela la méthode startActivityForResult qui envoie un code de
retour à l'activité enfant. Lorsque l'activité parent reprend la main, il devient possible de
filtrer le code de retour dans la méthode onActivityResult pour savoir si l'on revient ou
pas de l'activité enfant.
L'appel d'un Intent devient donc:
public void onCreate(Bundle savedInstanceState) {
Intent login = new Intent(getApplicationContext(),
[Link]); startActivityForResult(login,48); }
Le filtrage dans la classe parente permet de savoir qui avait appelé cette activité enfant:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 48)
[Link](this, "Code de requête récupéré (je sais d'ou je viens)",
Toast.LENGTH_LONG).show();
}

Résultat d'une activité


Il est aussi possible de définir un résultat d'activité, avant d'appeler explicitement la fin
d'une activité avec la méthode finish(). Dans ce cas, la méthode setResult permet
d'enregistrer un code de retour qu'il sera aussi possible de filtrer dans l'activité parente.
Dans l'activité enfant, on met donc:
Button finish = (Button)findViewById([Link]);
[Link](new OnClickListener() {
@Override
public void onClick(View v) {
setResult(50); finish();
}});

Et la classe parente peut filtrer ainsi:


protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 48)
[Link](this, "Code de requête récupéré (je sais d'ou je viens)",
Toast.LENGTH_LONG).show();
if (resultCode == 50)
[Link](this, "Code de retour ok (on m'a renvoyé le bon code)",
Toast.LENGTH_LONG).show();
}

partager des informations via les intents

Les Intent permettent de transporter des informations à destination de l'activité cible. On


appelle ces informations des Extra: les méthodes permettant de les manipuler
sont getExtra et putExtra. Lorsqu'on prépare un Intent et que l'on souhaite ajouter une
information de type "clef -> valeur" on procéde ainsi:
Intent callactivity2 = new Intent(getApplicationContext(), [Link]);
[Link]("login", "jfl"); startActivity(callactivity2);
Du côté de l'activité recevant l'Intent, on récupère l'information de la manière suivante:
Bundle extras = getIntent().getExtras();
String s = new String([Link]("login"));

Types d'Intent: actions


Le premier paramètre de construction de l'Intent est le type de l'action véhiculé par cet
Intent. Ces types d'actions peuvent être les actions natives du système ou des actions
définies par le développeur.
Plusieurs actions natives existent par défaut sur Android. La plus courante est
l'action Intent.ACTION_VIEW qui permet d'appeler une application pour visualiser un
contenu dont on donne l'URI.
Voici un exemple d'envoi d'email (issu de [E-mail]). Dans ce cas ou l'on utilise l'action
native, il faut ajouter des informations supplémentaires à l'aide de putExtra:
Intent emailIntent = new Intent([Link].ACTION_SEND);
String[] recipients = new String[]{"", "",};
[Link]([Link].EXTRA_EMAIL, recipients);
[Link]([Link].EXTRA_SUBJECT, "Test");
[Link]([Link].EXTRA_TEXT, "Message");
[Link]("text/plain"); startActivity([Link](emailIntent,
"Send mail "));
finish();

Pour définir une action personnelle, il suffit de créer une chaine unique:
Intent monIntent = new Intent(".nom_du_message");

Catégories d'Intent
Les catégories d'Intent permettent de grouper les applications par grands types de
fonctionnalités (clients emails, navigateurs, players de musique, etc ). Par exemple, on
trouve les catégories suivantes qui permettent de lancer:
• DEFAULT: catégorie par défaut
• BROWSABLE: une activité qui peut être invoquée depuis un clic sur un navigateur web,
ce qui permet d'implémenter des nouveaux types de lien, e.g. foo://truc
• APP_MARKET: une activité qui permet de parcourir le market de télécharger des
applications • APP_MUSIC: une activité qui permet de parcourir et jouer de la musique

Broadcaster des informations


Il est aussi possible d'utiliser un objet Intent pour broadcaster un message à but informatif.
Ainsi, toutes les applications pourront capturer ce message et récupérer l'information.
Intent broadcast = new Intent(".broadcast"); [Link]("extra", "test");
sendBroadcast(broadcast);
La méthode putExtra permet d'enregistrer une paire clef/valeur dans l'Intent. On peut
récupérer les données à l'aide de la méthode getExtras dans l'objet Bundle qui est dans
l'Intent:
Bundle extra = [Link]();
String val = [Link]("extra");

Recevoir et filtrer les Intents


Etant donné la multitude de messages véhiculés par des Intent, chaque application doit
pouvoir facilement "écouter" les Intents dont elle a besoin. Android dispose d'un système
de filtres déclaratifs permettant de définir dans le Manifest des filtres.
Un filtre peut utiliser plusieurs niveaux de filtrage:
• action: identifie le nom de l'Intent. Pour éviter les collisions, il faut utiliser la convention
de nommage de Java
• category: permet de filtrer une catégorie d'action (DEFAULT, BROWSABLE, )
• data: filtre sur les données du message par exemple en utilisant android:host pour filtrer
un nom de domaine particulier

Filtrage d'un Intent par l'activité


En déclarant un filtre au niveau du tag activity, l'application déclare les types de
messsage qu'elle sait gérer et qui l'invoquent.
<activity android:name=".Main" android:label="@string/app_name">
<intent-filter>
<action android:name=".nom_du_message" />
<category android:name="[Link]" />
</intent-filter>
</activity>

Ainsi, l'application répond à la sollicitation des Intents envoyés par:


Button autoinvoc = (Button)findViewById([Link]);
[Link](new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(".nom_du_message"); startActivity(intent);
}});

Utilisation de catégories pour le filtrage


Les catégories d'Intent évoquées précédemment permettent de répondre à des
évènements dans un contexte particulier. L'exemple le plus utilisé est l'interception d'un
clic lors d'une navigation sur le web ou un protocole particulier est prévu. Par exemple, les
liens vers le market sont de la forme market://.
Pour par exemple répondre à un clic sur un lien , il faut construire un filtre d'Intent utilisant
la catégorie définissant ce qui est "navigable" tout en combinant cette catégorie avec un
type d'action signifiant que l'on souhaite "voir" la ressource. Il faut en plus utiliser le
tag data dans la construction du filtre:
<intent-filter>
<action android:name="" />
<category android:name="[Link]" />
<category android:name="[Link]" />
<data android:scheme="foo" android:host=""/>
</intent-filter>

L'attribut host définit l'URI de l'autorité (on évite ainsi le fishing). L'attribut scheme définit la
partie de gauche de l'URI. On peut ainsi lancer une activité lors d'un clic sur un lien:
<a href=";>lien</a>
Réception par un BroadcastReceiver
Les messages broadcastés par les applications sont réceptionnées par une classe héritant
de BroadcastReceiver. Cette classe doit surcharger la méthode onReceive. Dans le
Manifest, un filtre doit être inséré dans un tag receiver qui pointe vers la classe se
chargeant des messages.
Dans le Manifest:
<application android:icon="@drawable/icon" android:label="@string/
app_name"><receiver android:name="MyBroadcastReceiver">
<intent-filter> <action android:name=".broadcast" />

<category android:name="[Link]" />


</intent-filter> </receiver> </application>

La classe héritant de BroadcastReceiver:

public final class MyBroadcastReceiver extends BroadcastReceiver { @Override


public void onReceive(Context context, Intent intent) { Bundle extra =
[Link](); if (extra != null)
{
String val = [Link]("extra");
[Link](context, "Broadcast message received: " + val,
Toast.LENGTH_SHORT).show();
}}}

Récepteur d'Intent dynamique


Il est possible de créer un récepteur et d'installer le filtre correspondant dynamiquement.

private MyBroadcastReceiverDyn myreceiver;


public void onCreate(Bundle savedInstanceState) { // Broadcast receiver dynamique
myreceiver = new MyBroadcastReceiverDyn(); IntentFilter filtre
= new IntentFilter(".broadcast"); registerReceiver(myreceiver, filtre);
}
Lorsque ce receiver n'est plus utile, il faut le désenregistrer pour ainsi libérer les
ressources:
unregisterReceiver(myreceiver);
C'est particulièrement important puisque un filtre de réception reste actif même si
l'application est éteinte (elle peut être relancée si un message la concernant survient).

Les messages natifs


Un certain nombre de messages sont diffusés par l'OS:
• ACTION_BOOT_COMPLETED: diffusé lorsque le système a fini son boot
• ACTION_SHUTDOWN: diffusé lorsque le système est en cours d'extinction
• ACTION_SCREEN_ON / OFF: allumage / exctinction de l'écran
• ACTION_POWER_CONNECTED / DISCONNECTED: connexion / perte de
l'alimentation
• ACTION_TIME_TICK: une notification envoyée toutes les minutes
• ACTION_USER_PRESENT: notification recue lorsque l'utilisateur délock son téléphone

Tous les messages de broadcast se trouvent dans la documentation des Intents.
D'autres actions permettent de lancer des applications tierces pour déléguer un traitement:
• ACTION_CALL (ANSWER, DIAL): passer/réceptionner/afficher un appel
• ACTION_SEND: envoyer des données par SMS ou E-mail
• ACTION_WEB_SEARCH: rechercher sur internet

Démonstration : voir TP N° 1

Persistance des données

Différentes persistances
Android fournit plusieurs méthodes pour faire persister les données applicatives:
• la persistance des données de l'activité (cf Le SDK Android)
• un mécanisme de sauvegarde clé/valeur, utilisé pour les fichiers de préférences (appelé
préférences partagées)
• des entrées sorties de type fichier
• une base de donnée basé sur SQLite
La persistance des données des activités est géré par l'objet Bundle qui permet de
restaurer les View qui possède un id. S'il est nécessaire de réaliser une sauvegarde plus
personnalisée, il suffit de recoder les méthodes onSaveInstanceState et onCreate et
d'utiliser les méthodes qui permettent de lire/écrire des données sur l'objet Bundle.
Android fournit aussi automatiquement, la persistance du chemin de navigation de
l'utilisateur, ce qui le renvoie à la bonne activité lorsqu'il appuie sur la touche Retour. La
navigation vers le parent (bouton "up") n'est pas automatique car c'est le concepteur qui
doit décider vers quelle activitée l'application doit retourner quand on appuie sur "up". Elle
peut être programmée dans le Manifest avec l'attribut android:parentActivityName.

Préférences partagées
La classe SharedPreferences permet de gérer des paires de clé/valeurs associées à une
activité. On récupère un tel objet par l'appel à getPreferences:
SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
String nom = [Link]("login", null);
Long nom = [Link]("taille", null);
La méthode getPreferences(int) appelle en fait getPreferences(String, int) à partir du
nom de la classe de l'activité courante. Le mode MODE_PRIVATE restreint l'accès au
fichier créé à l'application. Les modes
d'accès MODE_WORLD_READABLE et MODE_WORLD_WRITABLE permettent aux
autres applications de lire/écrire ce fichier.
L'intérêt d'utiliser le système de préférences prévu par Android réside dans le fait que
l'interface graphique associé à la modification des préférences est déjà programmé: pas
besoin de créer l'interface de l'activité pour cela. L'interface sera générée
automatiquement par Android et peut ressembler par exemple à cela:
Représentation XML d'un menu de préférences

Une activité spécifique a été programmée pour réaliser un écran d'édition de préférences.
Il s'agit de PreferenceActivity. A partir d'une description XML des préférences, la classe
permet d'afficher un écran composé de modificateurs pour chaque type de préférences
déclarées.
Voici un exemple de déclarations de préférences XML, à stocker dans :
<PreferenceScreen xmlns:android="; android:key="first_preferencescreen">
<CheckBoxPreference android:key="wifi enabled" android:title="WiFi" />
<PreferenceScreen android:key="second_preferencescreen"
android:title="WiFi settings">
<CheckBoxPreference android:key="prefer wifi" android:title="Prefer WiFi" />
</PreferenceScreen>
</PreferenceScreen>

Activité d'édition de préférences


Pour afficher l'écran d'édition des préférences correspondant à sa description XML, il faut
créer une nouvelle activité qui hérite de PreferenceActivity et simplement appeler la
méthode addPreferencesFromResource en donnant l'id de la description XML:
public class MyPrefs extends PreferenceActivity {
public void onCreate(Bundle savedInstanceState) {
[Link](savedInstanceState);
addPreferencesFromResource([Link]); }
}
Pour lancer cette activité, on crée un bouton et un Intent correspondant.

Attributs des préférences


Les attributs suivants sont utiles:
• android:title: La string apparaissant comme nom de la préférence
• android:summary: Une phrase permettant d'expliciter la préférence
• android:key: La clef pour l'enregistrement de la préférence
Pour accéder aux valeurs des préférences, on utiliser la
méthode getDefaultSharedPreferences sur la classe
PreferenceManager. C'est la clef spécifiée par l'attribut android:key qui est utilisée pour
récupérer la valeur choisie par l'utilisateur.
SharedPreferences prefs =
[Link]( getApplicationContext());
String login = [Link]("login","");

Des attributs spécifiques à certains types de préférences peuvent être utilisés, par
exemple android:summaryOn pour les cases à cocher qui donne la chaine à afficher
lorsque la préférence est cochée. On peut faire dépendre une préférence d'une autre, à
l'aide de l'attribut android:dependency. Par exemple, on peut spécifier dans cet attribut le
nom de la clef d'une préférence de type case à cocher:
<CheckBoxPreference android:key="wifi" />
<EditTextPreference android:dependency="wifi" />

Préférences: toggle et texte


Une case à cocher se fait à l'aide de CheckBoxPreference:
<CheckBoxPreference android:key="wifi" android:title="Utiliser le wifi"
android:summary="Synchronise l'application via le wifi."
android:summaryOn="L'application se synchronise via le wifi."
android:summaryOff="L'application ne se synchronise pas." />
Un champs texte est saisi via EditTextPreference:
<EditTextPreference android:key="login&" android:title="Login utilisateur"
android:summary="Renseigner son login d'authentification."
android:dialogTitle="Veuillez saisir votre login" />

Préférences: listes
Une entrée de préférence peut être liée à une liste de paires de clef-valeur dans les
ressources:
qui se déclare dans le menu de préférences:
<ListPreference android:title="Vitesse" android:key="vitesse"
android:entries=" @array/key"
android:entryValues="@array/value" android:dialogTitle="Choisir la vitesse:"
android:persistent="true">
</ListPreference>
Lorsque l'on choisit la valeur "Petite", la préférence vitesse est associée à "1".
Ecriture de préférences

Il est aussi possible d'écraser des préférences par le code, par exemple si une action fait
changer le paramétrage de l'application ou si l'on recoit le paramétrage par le réseau.
L'écriture de préférences est plus complexe: elle passe au travers d'un éditeur qui doit
réaliser un commit des modifications (commit atomique, pour éviter un mix entre plusieurs
écritures simultanées):
SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); Editor editor = ();
[Link]("login", "jf"); [Link]();
Il est même possible de réagir à un changement de préférences en installant un écouteur
sur celles-ci:
[Link]( new OnSharedPreferenceChanged () {});

Les fichiers
Android fournit aussi un accès classique au système de fichier pour tous les cas qui ne
sont pas couverts par les préférences ou la persistance des activités, c'est à dire de
nombreux cas. Le choix de Google est de s'appuyer sur les classes classiques de Java
EE tout en simplifiant la gestion des permissions et des fichiers embarqués dans
l'application.
Pour la gestion des permissions, on retrouve comme pour les préférences les
constantes MODE__PRIVATE et MODE_WORLD_READABLE/WRITABLE à passer en
paramètre de l'ouverture du fichier. En utilisant ces constantes comme un masque, on
peut ajouter | MODE_APPEND pour ajouter des données.
try { FileOutputStream out = openFileOutputStream("fichier", MODE_PRIVATE);
} catch (FileNotFoundException e) { }
Les ressources permettent aussi de récupérer un fichier embarqué dans l'application:
Resources res = getResources();
InputStream is = [Link]([Link]);
A noter: les fichier sont à éviter. Mieux vaut alléger l'application au maximum et prévoir le
téléchargement de ressources nécessaires et l'utilisation de fournisseurs de contenu.

BDD SQLite
Android dispose d'une base de donnée relationelle basée sur SQLite. Même si la base
doit être utilisée avec parcimonie, cela fournit un moyen efficace de gérer une petite
quantité de données.
[DBSQL] présente un exemple de création de base de données, ce qui se fait en héritant
de SQLiteOpenHelper:

public class DictionaryOpenHelper extends SQLiteOpenHelper {


private static final int DATABASE_VERSION = 2; privatestaticfinal String
DICTIONARY_TABLE_NAME = "dictionary"; privatestaticfinal String
DICTIONARY_TABLE_CREATE = "CREATE TABLE " +
DICTIONARY_TABLE_NAME + " (" +
KEY_WORD + " TEXT, " +
KEY_DEFINITION + " TEXT);";
DictionaryOpenHelper(Context context) { super(context, DATABASE_NAME, null,
DATABASE_VERSION); }
@Override public void onCreate(SQLiteDatabase db)
{ [Link](DICTIONARY_TABLE_CREATE); }
}

Lecture / Ecriture dans la BDD


Pour réaliser des écritures ou lectures, on utilise les méthodes getWritableDatabase()
et getReadableDatabase() qui renvoient une instance de SQLiteDatabase. Sur cet objet,
une requête peut être exécutée au travers de la méthode query():
public Cursor query (boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit)
L'objet de type Cursor permet de traiter la réponse (en lecture ou écriture), par exemple:
• getCount(): nombre de lignes de la réponse
• moveToFirst(): déplace le curseur de réponse à la première ligne
• getInt(int columnIndex): retourne la valeur (int) de la colonne passée en paramètre
• getString(int columnIndex): retourne la valeur (String) de la colonne passée en
paramètre
• moveToNext(): avance à la ligne suivante
• getColumnName(int): donne le nom de la colonne désignée par l'index
Programmation concurrente

Processus

• processus "lourd" appelé par la suite "processus"


• processus "léger" appelé par la suite thread
Une fois ces deux notions présentées, nous verrons comment réaliser des tâches
particulières qui sont souvent concurrente à l'activité principale. Ces tâches s'appuient
principalement sur les threads, mais aussi sur un nouveau composant d'une application
appelé "service".
[PT] Par défaut, une application android s'exécute dans un processus unique, le
processus dit "principal". L'interface graphique s'exécute elle aussi au travers de ce
processus principal. Ainsi, plus une application réalisera de traitement, plus la gestion de
la programmation devient délicate car on peut arriver très rapidement à des problèmes de
blocage de l'interface graphique par exemple.
Il est possible de séparer les composants d'une application en plusieurs processus. Ces
composants sont les codes attachés aux tags <activity>, <service>, <receiver>, et
<provider> de l'application. Par défaut, l'application (tag <application>) s'exécute dans le
processus principal portant le même nom que le package de l'application. Bien que cela
soit inutile, on peut le préciser dans le Manifest à l'aide de l'attribut android:process, par
exemple:
<application android:process="">
Les sous-tags du manifest, c'est-à-dire les composants de l'application héritent de cet
attribut et s'exécutent donc dans le même processus. Si l'on souhaite créer un nouveau
processus indépendant, on peut utiliser l'attribut android:process en préfixant le nom du
processus par ":", par exemple:
<service android:name=".AndroService" android:process=":p2">
Pour cet exemple, le service et l'application sont indépendants. L'interface graphique
pourra s'afficher indépendament.

Cycle de Vie des processus


Android peut devoir arrêter un processus à cause d'autres processus qui requièrent plus
d'importance. Par exemple, un service peut être détruit car une application gourmande en
ressources (navigateur web) occupe de plus en plus de mémoire. Plus classiquement, le
processus affecté à une activité qui n'est plus visible a de grandes chances d'être détruit.
Ainsi, une hiérarchie permet de classer le niveau d'importance des processus:
1. Processus en avant plan (activité en interaction utilisateur, service attaché à cette
activité, BroadCastReceiver exécutant onReceive())
2. Processus visible: il n'interagit pas avec l'utilisateur mais peut influer sur ce que l'on voit
à l'écran (activité ayant affiché une boite de dialogue (onPause() a été appelé), service lié
à ces activité "visibles").
3. Processus de service
4. Processus tâche de fond (activité non visible (onStop() a été appelé))
5. Processus vide (ne comporte plus de composants actifs, gardé pour des raisons
de cache)
Ainsi, il faut préférer l'utilisation d'un service à la création d'un thread pour accomplir une
tâche longue, par exemple l'upload d'une image. On garantit ainsi d'avoir le niveau 3 pour
cette opération, même si l'utilisateur quitte l'application ayant initié l'upload.

Threads
Dans le processus principal, le système crée un thread d'exécution pour l'application:
le thread principal. Il est, entre autre, responsable de l'interface graphique et des
messages/notifications/événements entre composants graphiques. Par exemple,
l'événement générant l'exécution de onKeyDown() s'exécute dans ce thread.
Ainsi, si l'application doit effectuer des traitements longs, elle doit éviter de les faire dans
ce thread. Cependant, il est interdit d'effectuer des opérations sur l'interface graphique en
dehors du thread principal (aussi appelé UI thread), ce qui se résume dans la
documentation sur les threads par deux règles:
• Do not block the UI thread
• Do not access the Android UI toolkit from outside the UI thread
L'exemple à ne pas faire est donné dans la documentation et recopié ci-dessous. Le
comportement est imprévisible car le toolkit graphique n'est pas thread-safe.

Services
public void onClick(View v) { // DO NOT DO THIS !
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork("");
[Link](b); } }).start(); }
}}
Une exception peut parfois (pas toujours) être levée quand cela est
détecté: CalledFromWrongThreadException.

Threads et interface graphique


Android fournit des méthodes pour résoudre le probème précédemment évoqué. Il s'agit
de créer des objets exécutables dont la partie affectant l'interface graphique n'est pas
exécutée mais déléguée à l'UI thread pour exécution ultérieure.
Par exemple, un appel à la méthode (Runnable) permet de réaliser cela et donne, pour
l'exemple précédent:
public void onClick(View v) { new Thread(new Runnable() {
public void run() {
final Bitmap bitmap = loadImageFromNetwork("");
[Link](bitmap); }
});
}).start(); }
La documentation donne les quelques méthodes utiles pour ces cas délicats:
• [Link](Runnable)
• (Runnable)
• [Link](Runnable, long)

Services
La structure d'une classe de service ressemble à une activité. Pour réaliser un service, on
hérite de la classe Service et on implémente les méthodes de création/démarrage/arrêt du
service. La nouvelle méthode qu'il faut implémenter ici est onBind qui permet aux IPC de
faire des appels à des méthodes distantes.

public class MyService extends Service { public void onCreate() { // Création du


service }
public void onDestroy() { // Destruction du service
} @deprecated // utile pour les versions antérieures d'Android 2.0public void
onStart(Intent intent, int startId) { // Démarrage du service } public int
onStartCommand(Intent intent, int flags, int startId) { // Démarrage du
servicereturn START_STICKY; } public IBinder onBind(Intent arg0) { returnnull;
}}
Le service se déclare dans le Manifest dans le tag application:
<service android:name=".MyService"/>

Démarrer / Arrêter un service


Les Intents fournissent un moyen de démarrer/arrêter un service en utilisant le nom de la
classe du service ou un identifiant d'action:
startService(new Intent(this, [Link]));
stopService(new Intent(this, [Link]));
startService(new Intent(".manageServiceAction"));
Ces méthodes sont à invoquer dans l'activité de l'application développée. On dit alors que
le service est local à l'application: il va même s'exécuter dans le thread de l'application. Un
bouton de démarrage d'un service local est simple à coder:
Button b = (Button) findViewById([Link].button1);
[Link](new OnClickListener() {
public void onClick(View v) {
Intent startService = new Intent(".manageServiceAction");
startService(startService); }
});

Auto démarrage d'un service


Il est aussi possible de faire démarrer un service au démarrage du système. Pour cela, il
faut créer un BroadcastReceiver qui va réagir à l'action BOOT_COMPLETED et lancer
l'Intent au travers de la méthode startService.
Le Manifest contient donc:
<application android:icon="@drawable/icon" android:label="@string/app_name">
<service android:name=".AndroService"></service>
<receiver android:name=".AutoStart">
<intent-filter>
<action android:name="[Link].BOOT_COMPLETED" />
</intent-filter> </receiver> </application>

Et la classe AutoStart doit être:


public class AutoStart extends BroadcastReceiver { public void onReceive(Context
context, Intent intent) { Intent startServiceIntent = new Intent(context,
[Link]); [Link](startServiceIntent); }
}
onStartCommand()

Un problème subsiste après que l'application ait démarré le service. Typiquement, le


service démarré exécute onCreate() puis onStart() qui va par exemple lancer
un Thread pour réaliser la tâche de fond. Si le service doit être détruit par la plate-forme
(pour récupérer de la mémoire), puis est récréé, seule la méthode onCreate() est appelée
[API-service].
Afin de résoudre le problème précédent, une nouvelle méthode a fait son apparition dans
les versions ultérieures à la version 5 de l'API. La méthode onStartCommand() est très
similaire à onStart() mais cette méthode renvoie un entier qui permet d'indiquer au
système ce qu'il faut faire au moment de la ré-instanciation du service, si celui-ci a dû être
arrêté. La méthode peut renvoyer (cf [API-service]):
• START_STICKY: le comportement est similaire aux API<5. Si le service est tué, il est
ensuite redémarré. Cependant, le système prend soin d'appeler à nouveau la
méthode onStartCommand(Intent intent) avec un Intent null, ce qui permet au service
qu'il vient d'être démarré après un kill du système.

• START_NOT_STICKY: le service n'est pas redémarré en cas de kill du système. C'est


utile lorsque le service réagit à l'envoi d'un Intent unique, par exemple une alarme qui
envoie un Intent toutes les 15 minutes.
• START_REDELIVER_INTENT: similaire à START_NOT_STICKY avec en plus le rejoue
d'un Intent si le service n'a pas pu finir de le traiter et d'appeler stopSelf().
Dans les versions ultérieures à l'API 5, la commande onStart() se comporte
comment onStartCommand() avec un ré-appel de la méthode avec un Intent null. Pour
conserver le comportement obsolète de l'API (pas d'appel à onStart()), on peut utiliser le
flag START_STICKY_COMPATIBILITY.

Service dans le thread principal


Le service invoqué par l'Intent de l'application s'exécute dans le thread de l'application.
Cela peut être particulièrement ennuyeux si les traitements à effectuer sont couteux, par
exemple si l'on souhaite faire:
boolean blop = true; while (blop == true) ;

On obtient:
Service dans un processus indépendant

Une manière de résoudre le problème précédent est de demander à Android d'exécuter le


service dans un processus indépendant en le déclarant dans le Manifest:
<service android:name=".AndroService" android:process=":p2">
Cependant, cela n'autorise pas le service à réaliser un traitement long dans la
méthode onStart() (comme une boucle infinie). La méthode onStart() sert à créer des
threads, des tâches asynchrones, des tâches programmées, qui vont s'exécuter en tâche
de fond.
En fait, isoler le service dans un processus à part rend la programmation plus difficile, car
la programmation de la communication entre l'activité principale et le service devient plus
difficile (il faut utiliser AIDL). Il est donc plus simple de laisser le service dans le processus
principal de l'application et de lui faire lancer des threads ou tâches asynchrones.
Références:
[Link]

[Link]
title=An_Overview_of_the_Android_Architecture&mobileaction=toggle_view_desktop

[Link]
enjeux-et-pratique/download?chk=8764161b42e7c18cad66c63b5d1c7cd8

Vous aimerez peut-être aussi