Cours Android
Cours Android
Jean-Francois Lalande - November 2012 - Version 2 Le but de ce cours est de dcouvrir la programmation sous Android, sa plate-forme de dveloppement et les spcificits du dveloppement embarqu sur tlphone mobile.
Ce cours est mis disposition par Jean-Franois Lalande selon les termes de la licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage l'Identique 3.0 non transpos.
1 Plan du module
1.1 Introduction
Il est important de prendre la mesure de choses. A l'heure actuelle (November 2012): juin 2012: 1 million d'activations par jour (Google IO 2012) sept. 2012: 1.3 million d'activations par jour (FRAndroid) Il y aurait donc un parc de 400 millions d'appareils Android. Vous pouvez visionner de la propagande ici et l.
2 / 118
2 Le SDK Android
2 Le SDK Android
2.1 Android L'Operating System Projet ADT Les lments d'une application Le Manifest de l'application 2.2 Les ressources Les chaines Internationalisation Autres valeurs simples Autres ressources 2.3 Les activits Sauvegarde des interfaces d'activit Dmonstration 3 3 4 4 4 5 5 6 6 7 7 8 8
2.1 Android
L'ecosystme d'Android s'appuie sur deux piliers: le langage Java le SDK qui permet d'avoir un environnement de dveloppement facilitant la tche du dveloppeur Le kit de dveloppement donne accs des exemples, de la documentation mais surtout l'API de programmation du systme et un mulateur pour tester ses applications. Stratgiquement, Google utilise la licence Apache pour Android ce qui permet la redistribution du code sous forme libre ou non et d'en faire un usage commercial. Le plugin Android Development Tool permet d'intgrer les fonctionnalits du SDK Eclipse. Il faut l'installer comme un plugin classique en prcisant l'URL du plugin. Ensuite, il faut renseigner l'emplacement du SDK (pralablement tlcharg et dcompress) dans les prfrences du plugin ADT.
L'Operating System
Android est en fait un systme de la famille des Linux, pour une fois sans les outils GNU. L'OS s'appuie sur: un noyau Linux (et ses drivers) une machine virtuelle: Dalvik Virtual Machine des applications (navigateur, gestion contact, application de tlphonie...) des bibliothques (SSL, SQLite, OpenGL ES, etc...) [Dalvik] est le nom de la machine virtuelle open-source utilise sur les systmes Android. Cette machine virtuelle excute des fichiers .dex, plus ramasss que les .class classiques. Ce format vite par exemple la duplication des String constantes. La machine virtuelle utilise elle-mme moins d'espace mmoire et l'adressage des constantes se fait par un pointeur de 32 bits.
3 / 118
Projet ADT
[Dalvik] n'est pas compatible avec une JVM du type Java SE ou mme Java ME. La librairie d'accs est donc redfinie entirement par Google.
Projet ADT
Un projet bas sur le plugin ADT est dcompos de la manire suivante: src/: les sources Java du projet libs/: bibliothques tierces res/: res/drawable: ressources images res/layout: description des IHMs en XML res/values: chaines de caractres et dimensions gen/: les ressources auto gnres par ADT assets/: ressources brutes (raw bytes) bin/: bin/classes: les classes compiles en .class bin/classes.dex: excutable pour la JVM Dalvik bin/myapp.zip: les ressources de l'application bin/myapp.apk: application empaquete avec ses ressource et prte pour le dploiement
Le Manifest de l'application
Le fichier AndroidManifest.xml dclare l'ensemble des lments de l'application. <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="andro.jf"
4 / 118
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="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service>...</service> <receiver>...</receiver> <provider>...</provider> </application> </manifest>
Les chaines
Les chaines constantes de l'application sont situes dans res/values/strings.xml. L'externalisation des chaines permettra de raliser l'internationalisation de l'application. Voici un exemple: <?xml version="1.0" encoding="utf-8"?> <resources>
5 / 118
Internationalisation
<string name="hello">Hello Hello JFL !</string> <string name="app_name">AndroJF</string> </resources> La rcupration de la chaine se fait via le code: Resources res = getResources(); String hw = res.getString(R.string.hello);
Internationalisation
Le systme de ressources permet de grer trs facilement l'internationalisation d'une application. Il suffit de crer des rpertoires values-XX o XX est le code de la langue que l'on souhaite implanter. On place alors dans ce sous rpertoire le fichier xml strings.xml contenant les chaines traduites associes aux mme clefs que dans values/strings.xml. On obtient par exemple pour les langues es et fr l'arborescence: MyProject/ res/ values/ strings.xml values-es/ strings.xml values-fr/ strings.xml Android chargera le fichier de resources appropri en fonction de la langue du systme.
6 / 118
Autres ressources
</string-array> </resources>
Autres ressources
D'autres ressources sont spcifiables dans res: les menus les images (R.drawable) des dimensions (R.dimen) des couleurs (R.color)
7 / 118
super.onStop(); } }
Dmonstration
8 / 118
3 Interfaces graphiques
3 Interfaces graphiques
3.1 Vues et gabarits Attributs des gabarits L'interface comme ressource 3.2 Les lments graphiques Les labels de texte Les zones de texte Les images Les boutons Interface rsultat Dmonstration 3.3 Positionnement avanc Preview du positionnement 3.4 Les listes Dmonstration Liste de layouts plus complexes Interface rsultat 3.5 Les onglets Activit et onglets Crer les onglets Dmonstration 9 9 10 10 11 11 12 12 12 13 13 13 14 14 14 15 16 17 17 18
9 / 118
="wrap_content": prend la place ncessaire l'affichage android:orientation: dfinit l'orientation d'empilement android:gravity: dfinit l'alignement des lments Voici un exemple de LinearLayout: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" android:id="@+id/accueilid" > </LinearLayout>
10 / 118
11 / 118
Les images
edit.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // do something here } );
Les images
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); image.setImageResource(R.drawable.ensi); gabarit.addView(image);
Les boutons
En XML: <Button android:text="Go !" android:id="@+id/Button01" android:layout_width="wrap_content" android:layout_height="wrap_content"> </Button> La gestion des vnements de click se font par l'intermdiaire d'un listener: Button b = (Button)findViewById(R.id.Button01); b.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(v.getContext(), "Stop !", Toast.LENGTH_LONG).show(); } }); }
Interface rsultat
12 / 118
Dmonstration
Ce screenshot montre une interface contenant des TextView, EditText, ImageView, et un bouton (cf. ANX_Interfaces-graphiques).
Dmonstration
Preview du positionnement
Le layout dcrit ci-avant ressemble : <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" android:orientation="horizontal" android:layout_width="fill_parent" android:gravity="center"> <TextView ...></TextView> <LinearLayout android:layout_height="wrap_content"
13 / 118
android:orientation="horizontal" android:layout_width="fill_parent" android:gravity="right" android:layout_gravity="center"> <Image .../> </LinearLayout></LinearLayout> Ce qui produit dans l'interface de preview, en orientation portrait:
Dmonstration
(cf ANX_Listes-pour-des-items-texte)
14 / 118
Interface rsultat
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 entre de la liste, la vue gnre utilisera le layout complexe et la zone de texte contiendra la string passe en argument la mthode add. ArrayAdapter (Context context, int resource, int textViewResourceId) Le code de l'exemple prcdent doit tre adapt comme ceci: ListView list = (ListView)findViewById(R.id.maliste); ArrayAdapter<String> tableau = new ArrayAdapter<String>( list.getContext(), R.layout.ligne, R.id.monTexte); for (int i=0; i<40; i++) { tableau.add("coucou " + i); } list.setAdapter(tableau); Avec le layout de liste suivant (ligne.xml): <LinearLayout ...> <TextView ... android:id="@+id/monTexte"/> <LinearLayout> <ImageView /> </LinearLayout> </LinearLayout>
Interface rsultat
(cf ANX_Listes-de-layouts-complexes)
15 / 118
Certains ids sont imposs lorsqu'on utilise des onglets: TabHost : android:id= "@android:id/tabhost" TabWidget : android:id= "@android:id/tabs" FrameLayout : android:id= "@android:id/tabcontent"
16 / 118
Activit et onglets
Activit et onglets
L'activit qui gre l'ensemble des onglets est une activit spciale hritant de TabActivity. Le gabarit prcdents est donc associ une classe dfinir hritant de TabActivity: public class AndroTabs2Activity extends TabActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ... Chaque onglet contient, dans sa partie du bas, une activit qu'il convient de crer. Pour chaque activit, il faut donc dfinir une classe d'activit et lui associer son gabarit: public class ActivityOnglet1 extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.onglet1); }}
<LinearLayout ...> <!-- onglet1.xml --> <RatingBar android:id="@+id/ratingBar1" android:layout_width="wrap_content" android:layout_height="wrap_content"></RatingBar> <SeekBar android:layout_height="wrap_content" android:id="@+id/seekBar1" android:layout_width="fill_parent"></SeekBar> </LinearLayout>
17 / 118
Dmonstration
Dmonstration
(cf ANX_Onglets)
18 / 118
4 Les Intents
4 Les Intents
4.1 Principe des Intents 4.2 Intents pour une nouvelle activit Retour d'une activit Rsultat d'une activit Principe de la Dmonstration Dmonstration 4.3 Ajouter d'informations 4.4 Types d'Intent: actions Types d'Intent: catgories 4.5 Broadcaster des informations 4.6 Recevoir et filtrer les Intents Filtrage d'un Intent par l'activit Utilisation de catgories pour le filtrage Rception par un BroadcastReceiver Rcepteur d'Intent dynamique Les messages natifs Principe de la Dmonstration Dmonstration 4.7 Contrler les actions permises Exemples de permissions Echec de permissions Dmonstration 19 20 20 21 21 22 22 22 23 23 23 24 24 24 25 25 26 26 26 27 27 28
19 / 118
On peut envoyer des Intents informatifs pour faire passer des messages. Mais on peut aussi envoyer des Intents servant lancer une nouvelle activit.
20 / 118
Principe de la Dmonstration
(cf ANX_Intents)
21 / 118
Dmonstration
Dmonstration
22 / 118
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Test"); emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, "Message"); emailIntent.setType("text/plain"); startActivity(Intent.createChooser(emailIntent, "Send mail...")); finish(); Pour dfinir une action personelle, il suffit de crer une chaine unique: Intent monIntent = new Intent("andro.jf.nom_du_message");
23 / 118
data: filtre sur les donnes du message par exemple en utilisant android:host pour filtrer un nom de domaine particulier
24 / 118
tre insr 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="andro.jf.broadcast" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver> </application> La classe hritant de BroadcastReceiver: public final class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Bundle extra = intent.getExtras(); if (extra != null) { String val = extra.getString("extra"); Toast.makeText(context, "Broadcast message received: " + val, Toast.LENGTH_SHORT).show(); }}}
25 / 118
Principe de la Dmonstration
ACTION_SCREEN_ON / OFF: allumage / exctinction de l'cran ACTION_POWER_CONNECTED / DISCONNECTED: connexion / perte de l'alimentation ACTION_TIME_TICK: une notification envoye toutes les minutes ACTION_USER_PRESENT: notification recue lorsque l'utilisateur dlock son tlphone ... Tous les messages de broadcast se trouvent dans la documentation des Intents. D'autres actions permettent de lancer des applications tierces pour dlguer un traitement: ACTION_CALL (ANSWER, DIAL): passer/rceptionner/afficher un appel ACTION_SEND: envoyer des donnes par SMS ou E-mail ACTION_WEB_SEARCH: rechercher sur internet
Principe de la Dmonstration
Dmonstration
(cf ANX_Receveur-de-broadcasts)
26 / 118
Exemples de permissions
<uses-permission android:name="android.permission.RECEIVE_SMS"/> Attention, si vous n'avez pas prcis la cration de projet de numro de version minimum, les permissions READ_PHONE_STATE et WRITE_EXTERNAL_STORAGE sont accordes par dfaut [SO]. Pour avoir le nombre minimal de permissions, il faut ajouter au Manifest: <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="4" /> La liste compltes des permissions permet de contrler finement les droits allous l'application. Attention ne pas confondre permissions et dlgation: ne pas donner la permission de passer un appel n'empche pas l'application de dlguer, via un Intent, le passage d'un appel. Par contre, un appel direct depuis l'application n'est pas possible.
Exemples de permissions
Ces exemples montrent deux applications n'ayant pas les mmes permissions:
Echec de permissions
Sans aucun test utilisateur, ne pas donner une permission provoque la leve d'une exception dans l'application, comme pour la permission CALL_PHONE dans cet exemple:
27 / 118
Dmonstration
Dmonstration
28 / 118
29 / 118
La mthode getPreferences(int) appelle en fait getPreferences(String, int) partir du nom de la classe de l'activit courante. Le mode MODE_PRIVATE restreint l'accs au fichier cr l'application. Les modes d'accs MODE_WORLD_READABLE et MODE_WORLD_WRITABLE permettent aux autres applications de lire/crire ce fichier. L'intrt d'utiliser le systme de prfrences prvu par Android rside dans le fait que l'interface graphique associ la modification des prfrences est dj programm: pas besoin de crer l'interface de l'activit pour cela. L'interface sera gnre automatiquement par Android et peut ressembler par exemple cela:
30 / 118
Des attributs spcifiques certains types de prfrences peuvent tre utiliss, par exemple android:summaryOn pour les cases cocher qui donne la chaine afficher lorsque la prfrence est coche. On peut faire dpendre une prfrence d'une autre, l'aide de l'attribut android:dependency. Par exemple, on peut spcifier dans cet attribut le nom de la clef d'une prfrence de type case cocher: <CheckBoxPreference android:key="wifi" ... /> <EditTextPreference android:dependency="wifi" ... />
31 / 118
Prfrences: listes
Prfrences: listes
Une entre de prfrence peut tre lie une liste de paires de clef-valeur dans les ressources: <resources> <array name="key"> <!-- Petite=1, Moyenne=5, Grande=20 --> <item>"Petite"</item> <item>"Moyenne"</item> <item>"Grande"</item> </array> <array name="value"> <item>"1"</item> <item>"5"</item> <item>"20"</item> </array> </resources> qui se dclare dans le menu de prfrences: <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 prfrence "Petite", la prfrence vitesse est associe "1".
Ecriture de prfrences
Il est aussi possible d'craser des prfrences par le code, par exemple si une action fait changer le paramtrage de l'application ou si l'on recoit le paramtrage par le rseau. L'criture de prfrences est plus complexe: elle passe au travers d'un diteur qui doit raliser un commit des modifications (commit atomique, pour viter un mix entre plusieurs critures simultanes): SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); Editor editor = prefs.edit(); editor.putString("login", "jf"); editor.commit(); Il est mme possible de ragir un changement de prfrences en installant un couteur sur celles-ci: prefs.registerOnSharedPreferenceChangeListener( new OnSharedPreferenceChanged () { ... });
Dmonstration
(cf ANX_Gestion-des-prfrences)
32 / 118
33 / 118
L'objet de type Cursor permet de traiter la rponse (en lecture ou criture), par exemple: getCount(): nombre de lignes de la rponse moveToFirst(): dplace le curseur de rponse la premire ligne getInt(int columnIndex): retourne la valeur (int) de la colonne passe en paramtre getString(int columnIndex): retourne la valeur (String) de la colonne passe en paramtre moveToNext(): avance la ligne suivante getColumnName(int): donne le nom de la colonne dsigne par l'index ...
5.5 XML
[XML] Sans surprise, Android fournit plusieurs parsers XML (Pull parser, Document parser, Push parser). SAX et DOM sont disponibles. L'API de streaming (StAX) n'est pas disponible (mais pourrait le devenir). Cependant, une librairie quivalente est disponible: XmlPullParser. Voici un exemple simple: String s = new String("<plop><blup attr=\"45\">Coucou !</blup></plop>"); InputStream f = new ByteArrayInputStream(s.getBytes()); XmlPullParser parser = Xml.newPullParser(); try { // auto-detect the encoding from the stream parser.setInput(f, null); parser.next(); Toast.makeText(this, parser.getName(), Toast.LENGTH_LONG).show(); parser.next(); Toast.makeText(this, parser.getName(), Toast.LENGTH_LONG).show(); Toast.makeText(this, parser.getAttributeValue(null, "attr"), Toast.LENGTH_LONG).show(); parser.nextText(); Toast.makeText(this, parser.getText(), Toast.LENGTH_LONG).show(); } ...
SAX parser
SAX s'utilise trs classiquement, comme en Java SE. Voici un exemple attaquant du XML au travers d'une connexion http:
34 / 118
DOM parser
URI u = new URI("https://www.site.com/api/xml/list?apikey=845ef"); DefaultHttpClient httpclient = new DefaultHttpClient(); HttpGet httpget = new HttpGet(u); HttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); InputStream stream = entity.getContent(); InputSource source = new InputSource(stream); SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); XMLReader xr = sp.getXMLReader(); DefaultHandler handler = new DefaultHandler() { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { Vector monitors = new Vector(); if (localName.equals("monitor")) { monitors.add(attributes.getValue("displayname")); }} }; xr.setContentHandler(handler); xr.parse(source);
DOM parser
Enfin, le paser DOM permet de naviguer assez facilement dans la reprsentation arborescente du document XML. L'exemple suivant permet de chercher tous les tags "monitor" dans les sous-tags de la racine. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder builder = factory.newDocumentBuilder(); Document dom = builder.parse(source); Element root = dom.getDocumentElement(); // Rcupre tous les tags du descendant de la racine s'appelant monitor NodeList items = root.getElementsByTagName("monitor"); for (int i=0;i<items.getLength();i++){ Node item = items.item(i); // Traitement: // item.getNodeName() // item.getTextContent() // item.getAttributes() ... } } catch (...)
35 / 118
6 Programmation concurrente
6 Programmation concurrente
6.1 Composants d'une application 6.2 Processus Vie des processus 6.3 Threads Threads et interface graphique 6.4 Services 6.5 Dmarrer / Arrter un service Auto dmarrage d'un service onStartCommand() Service dans le thread principal Service dans un processus indpendant Dmonstration 6.6 Tche concurrentes Tche rgulire Dmonstration Tches asynchrones La classe BitmapDownloaderTask Mthodes de BitmapDownloaderTask Mthodes de BitmapDownloaderTask Dmonstation IntentService 6.7 Bilan: processus et threads 6.8 Coopration service/activit Lier activit et service: IBinder Lier activit et service: ServiceConnexion Dmonstration Service au travers d'IPC AIDL Service exposant l'interface 6.9 Etude de cas Service oprant dans onStart() Service oprant dans onStart() + processus Service oprant dans une tche asynchrone Service oprant dans onStart() + processus Service oprant dans onStart() + processus 37 37 37 38 38 39 39 40 40 41 41 41 42 42 42 42 43 43 44 44 44 44 45 45 46 46 46 47 48 48 48 48 49 49
36 / 118
6.2 Processus
[PT] Par dfaut, une application android s'excute dans un processus unique, le processus dit "principal". L'interface graphique s'excute elle aussi au travers de ce processus principal. Ainsi, plus une application ralisera de traitement, plus la gestion de la programmation devient dlicate car on peut arriver trs rapidement des problmes de blocage de l'interface graphique par exemple. Il est possible de sparer les composants d'une application en plusieurs processus. Ces composants sont les codes attachs aux tags <activity>, <service>, <receiver>, et <provider> de l'application. Par dfaut, l'application (tag <application>) s'excute dans le processus principal portant le mme nom que le package de l'application. Bien que cela soit inutile, on peut le prciser dans le Manifest l'aide de l'attribut android:process, par exemple: <application android:process="andro.jf"> Les sous-tags du manifest, c'est--dire les composants de l'application hritent de cet attribut et s'excutent donc dans le mme processus. Si l'on souhaite crer un nouveau processus indpendant, on peut utiliser l'attribut android:process en prfixant le nom du processus par ":", par exemple: <service android:name=".AndroService" android:process=":p2"> Pour cet exemple, le service et l'application sont indpendants. L'interface graphique pourra s'afficher indpendament.
37 / 118
6.3 Threads
4. Processus tche de fond (activit non visible (onStop() a t appel) 5. Processus vides (ne comporte plus de composants actifs, gards pour des raisons de cache) Ainsi, il faut prfrer l'utilisation d'un service la cration d'un thread pour accomplir une tche longue, par exemple l'upload d'une image. On garantit ainsi d'avoir le niveau 3 pour cette opration, mme si l'utilisateur quitte l'application ayant initi l'upload.
6.3 Threads
Dans le processus principal, le systme cr un thread d'excution pour l'application: le thread principal. Il est, entre autre, responsable de l'interface graphique et des messages/notifications/vnements entre composants graphiques. Par exemple, l'vnement gnrant l'excution de onKeyDown() s'excute 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 oprations sur l'interface graphique en dehors du thread principal (aussi appel UI thread), ce qui se rsume dans la documentation sur les threads par deux rgles: 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 imprvisible car le toolkit graphique n'est pas thread-safe. public void onClick(View v) { // DO NOT DO THIS ! new Thread(new Runnable() { public void run() { Bitmap b = loadImageFromNetwork("http://example.com/image.png"); mImageView.setImageBitmap(b); } }).start(); }
38 / 118
6.4 Services
La documentation donne les quelques mthodes utiles pour ces cas dlicats: Activity.runOnUiThread(Runnable) View.post(Runnable) View.postDelayed(Runnable, long)
6.4 Services
La structure d'une classe de service ressemble une activit. Pour raliser un service, on hrite de la classe Service et on implmente les mthodes de cration/dmarrage/arrt du service. La nouvelle mthode qu'il faut implmenter ici est onBind qui permet aux IPC de faire des appels des mthodes distantes. public class MyService extends Service { public void onCreate() { // Cration du service } public void onDestroy() { // Destruction du service } @deprecated // utile pour les versions antrieures d'Android 2.0 public void onStart(Intent intent, int startId) { // Dmarrage du service } public int onStartCommand(Intent intent, int flags, int startId) { // Dmarrage du service return START_STICKY; } public IBinder onBind(Intent arg0) { return null; }} Le service se dclare dans le Manifest dans le tag application: <service android:name=".MyService"/>
39 / 118
Button b = (Button) findViewById(R.id.button1); b.setOnClickListener(new OnClickListener() { public void onClick(View v) { Intent startService = new Intent("andro.jf.manageServiceAction"); startService(startService); } });
onStartCommand()
Un problme subsiste aprs que l'application ait dmarr le service. Typiquement, le service dmarr excute onCreate() puis onStart() qui va par exemple lancer un Thread pour raliser la tche de fond. Si le service doit tre dtruit par la plate-forme (pour rcuprer de la mmoire), puis est rcr, seule la mthode onCreate() est appele [API-service]. Afin de rsoudre le problme prcdent, une nouvelle mthode a fait son apparition dans les versions ultrieures la version 5 de l'API. La mthode onStartCommand() est trs similaire onStart() mais cette mthode renvoie un entier qui permet d'indiquer au systme ce qu'il faut faire au moment de la r-instanciation du service, si celui-ci a d tre arrt. La mthode peut renvoyer (cf [API-service]): START_STICKY: le comportement est similaire aux API<5. Si le service est tu, il est ensuite redmarr. Cependant, le systme prend soin d'appeler nouveau la mthode onStartCommand(Intent intent) avec un Intent null, ce qui permet au service qu'il vient d'tre dmarr aprs un kill du systme.
40 / 118
START_NOT_STICKY: le service n'est pas redmarr en cas de kill du systme. C'est utile lorsque le service ragit 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 ultrieures l'API 5, la commande onStart() se comporte comment onStartCommand() avec un r-appel de la mthode avec un Intent null. Pour conserver le comportement obsolte de l'API (pas d'appel onStart()), on peut utiliser le flag START_STICKY_COMPATIBILITY.
Dmonstration
41 / 118
Dans cette dmonstration, le service, qui comporte une boucle infinie, s'excute dans un processus indpendant de l'interface principale. Android va tuer le service qui occupe trop de temps processeur, ce qui n'affectera pas l'application et son interface (cf ANX_Processus-indpendants).
Tche rgulire
S'il s'agit de faire une tche rptitive qui ne consomme que trs peu de temps CPU interfals rgulier, une solution ne ncessitant pas de processus part consiste programmer une tche rptitive l'aide d'un TimerTask.
final Handler handler = new Handler(); task = new TimerTask() { public void run() { handler.post(new Runnable() { public void run() { Toast.makeText(AndroService.this, "plop !", Toast.LENGTH_SHORT).show(); } }); }}; timer.schedule(task, 0, 5000);
La cration du TimerTask est particulirement tarabiscote mais il s'agissait de rsoudre le problme du lancement du Toast. Pour des cas sans toast, un simple appel new TimerTask() { public void run() { code; } }; suffit. Attention bien dtruire la tche programme lorsqu'on dtruit le service ! (sinon, le thread associ la tche programm reste actif). public void onDestroy() { // Destruction du service timer.cancel(); }
Dmonstration
Dans cette dmonstration, le service local l'application est lanc: il affiche toutes les 5 secondes un toast l'aide d'un TimerTask. Il est visible dans la section running services. En cliquant une seconde fois sur le bouton start, deux services s'excutent en mme temps. Enfin, les deux services sont stopps. (cf ANX_Demarrage-de-services)
Tches asynchrones
Dans [AT], Gilles Debunne prsente comment grer le chargement d'image de faon asynchrone afin d'amliorer la fluidit d'une application. Dans le cas d'une liste d'image charge depuis le web, c'est mme obligatoire, sous peine de voir planter l'application.
42 / 118
La classe BitmapDownloaderTask
Pour raliser cela, il faut se baser sur la classe AsyncTask<U,V,W> base sur 3 types gnriques, cf [ATK]: U: le type du paramtre envoy l'excution V: le type de l'objet permettant de notifier de la progression de l'excution W: le type du rsultat de l'excution A partir de l'implmentation d'[AT], nous proposons une implmentation utilisant 3 paramtres: l'url de l'image charger, un entier reprsentant les tapes d'avancement de notre tche et la classe Bitmap renvoyant l'image quand celle-ci est charge, soit la classe AsyncTask<String, Integer, Bitmap>.
La classe BitmapDownloaderTask
Notre classe hritant de AsyncTask est donc ddie la gestion d'une tche qui va impacter l'interface graphique. Dans notre exemple, la classe doit charger une image, et donc modifier un ImageView. Elle doit aussi grer l'affichage de la progression de la tche, ce que nous proposons de faire dans un TextView. Le constructeur de la classe doit donc permettre d'accder ces deux lments graphiques: public class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> { private final WeakReference<ImageView> imageViewReference; private final WeakReference<TextView> textViewReference; public BitmapDownloaderTask(ImageView imageView, TextView textView) { imageViewReference = new WeakReference<ImageView>(imageView); textViewReference = new WeakReference<TextView>(textView); } On notera l'emploi de weak references qui permet au garbage collector de dtruire les objets graphiques mme si le tlchargement de l'image est encore en cours.
Mthodes de BitmapDownloaderTask
void onPreExecute(): invoque juste aprs que la tche soit dmarre. Elle permet de modifier l'interface graphique juste aprs le dmarrage de la tche. Cela permet de crer une barre de progression ou de charger un lment par dfaut. Cette mthode sera excute par l'UI thread. protected void onPreExecute() { if (imageViewReference != null) { ImageView imageView = imageViewReference.get(); if (imageView != null) { imageView.setImageResource(R.drawable.interro); }}} W doInBackground(U...): invoque dans le thread qui s'excute en tche de fond, une fois que onPreExecute() est termine. C'est cette mthode qui prendra un certain temps s'excuter. L'objet reu en paramtre est de type U et permet de grer la tche accomplir. Dans notre exemple, l'objet est l'url ou tlcharger l'image. A la fin, la tche doit renvoyer un objet de type W. protected Bitmap doInBackground(String... params) { String url = params[0]; publishProgress(new Integer(0));
43 / 118
Mthodes de BitmapDownloaderTask
AndroidHttpClient client = AndroidHttpClient.newInstance("Android"); HttpGet getRequest = new HttpGet(url); HttpResponse response = client.execute(getRequest); publishProgress(new Integer(1)); ... }
Mthodes de BitmapDownloaderTask
void onProgressUpdate(V...): invoque dans le thread qui gre l'UI, juste aprs que la mthode doInBackground(U...) ait appel publishProgress(V...). On reoit donc l'objet V qui contient les informations permettant de mettre jour l'UI pour notifier de la progression de la tche. protected void onProgressUpdate(Integer... values) { Integer step = values[0]; if (textViewReference != null) { textViewReference.get().setText("Step: " + step.toString()); }} onPostExecute(W): invoque quand la tche est termine et qu'il faut mettre jour l'UI avec le rsultat calcul dans l'objet de la classe W. protected void onPostExecute(Bitmap bitmap) { if (imageViewReference != null) { ImageView imageView = imageViewReference.get(); if (imageView != null) { imageView.setImageBitmap(bitmap); }}}
Dmonstation
(cf ANX_Tches-asynchrones) La dmonstration suivante montre la notification au travers de l'entier passant de 0 3. Une image par dfaut est charge avant que l'image soit compltement tlcharge depuis le web et affiche son tour.
IntentService
TODO
44 / 118
R. Ravichandran> I have a need to create a background service that starts up during the system boot up, and keeps running until the device is powered down. There is no UI or Activity associated with this. Dianne Hackborn> Mark answered how to do this, but please: think again about whether you really need to do this. Then think another time. And think once more. And if you are really really absolutely positively sure, this what you want to do, fine, but realize -On current Android devices, we can keep only a small handful of applications running at the same time. Having your application do this is going to going to take resources from other things that at any particular point in time would be better used elsewhere. And in fact, you can be guaranteed that your service will -not- stay running all of the time, because there is so much other stuff that wants to run (other background services that are only running when needed will be prioritized over yours), or needs more memory for what the user is actually doing (running the web browser on complicated web pages is a great way to kick background stuff out of memory). We have lots of facilities for implementing applications so they don't need to do this, such as alarms, and various broadcasts from events going on in the system. Please please please use them if at all possible. Having a service run forever is pretty close to the side of evil.
45 / 118
private int infoOfService = 0; // La donne transmettre l'activit private class MonServiceBinder extends Binder implements AndroServiceInterface { // Cette classe qui hrite de Binder implmente une mthode // dfinie dans l'interface AndroServiceInterface public int getInfo() { return infoOfService; }}
L'interface, dfinir, dclare les mthodes qui seront accessibles l'application: public interface AndroServiceInterface { public int getInfo(); }
Dmonstration
Dans cette dmonstration, un service lance un thread qui attendra 4 secondes avant d'enregistrer la valeur 12 comme rsultat du service. A chaque click du bouton, l'activit se connecte au service pour toaster le rsultat. (cf ANX_Binding-entre-service-et-activite-pour-un-meme-processus)
46 / 118
Le langage AIDL est trs proche de la dfinition d'interfaces Java EE (mais pas tout fait identique). Il s'agit de dfinir ce que les service est capable de faire et donc, quelles mthodes peuvent tre appeles. Un peu comme un web service, la dfinition de l'interface permettra l'outil AIDL de gnrer des stubs de communication pour les IPC. Ainsi, une autre application android pourra appeler le service au travers d'IPC. Une interface en AIDL peut par exemple ressembler : package andro.jf; interface AndroServiceInterface { int getInfo(); } L'outil aidl va gnrer les stubs correspondants l'interface dans un fichier .java portant le mme nom que le fichier aidl, dans le rpertoire gen.
// This file is auto-generated. DO NOT MODIFY. package andro.jf; public interface AndroServiceInterface extends android.os.IInterface { public static abstract class Stub extends android.os.Binder implements andro.jf.AndroServiceInterface { private static final java.lang.String DESCRIPTOR = "andro.jf.AndroServiceInterface"; ...
Il devient alors possible de dclarer le service comme appartenant un autre processus que celui de l'activit, i.e. android:process=":p2" (cf. Processus).
47 / 118
Cette solution n'est pas bonne: elle bloque l'activit principale, c'est--dire l'UI thread et donc l'interface utilisateur !
Cette solution est un peu mieux: l'interface de l'activit n'est plus bloque. Cependant, en isolant le processus du service de celui de l'activit, il sera difficile de rcuprer l'image.
48 / 118
Dans cette solution, la tche asynchrone aura tout loisir de charger l'image et de notifier l'activit principale. Cependant, si l'utilisateur quitte l'activit, l'upload s'arrte.
En introduisant un service entre l'activit et la tche synchronise on rsout le problme prcdemment voqu: la tche survivra l'activit. Dans une telle configuration, chaque upload sera gnr un nouveau service qui vivra le temps de la tche.
La manire la plus lgante d'oprer est d'utiliser un IntentService. L'IntentService va grer toutes les requtes d'upload dans une working queue qui sera dans un thread part inclu dans un service.
49 / 118
7 Connectivit
7 Connectivit
7.1 Tlphonie Passer ou filtrer un appel Envoyer et recevoir des SMS 7.2 Rseau Grer le rseau Wifi/Mobile 7.3 Bluetooth S'associer en bluetooth Utiliser le rseau 7.4 Localisation Coordonnes Alerte de proximit Carte google map La classe MapActivity Points d'intrt: cration Points d'intrt: coder le comportement Reverse Geocoding 7.5 Capteurs Hardware Lecture des donnes 7.6 Camra 50 51 51 52 52 53 53 54 54 54 55 55 56 56 57 57 58 58 59 59
7.1 Tlphonie
Les fonctions de tlphonie sont relativement simples utiliser. Elles permettent de rcuprer l'tat de la fonction de tlphonie (appel en cours, appel entrant, ...), d'tre notifi lors d'un changement d'tat, de passer des appels et de grer l'envoi et rception de SMS. L'tat de la tlphonie est gr par la classe TelephonyManager qui permet de rcuprer le nom de l'oprateur, du tlphone, et l'tat du tlphone. Pour lire ces informations, il est ncessaire de disposer de la permission android.permission.CALL_PHONE (cf Contrler les actions permises).
TelephonyManager tel = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); int etat = tel.getCallState(); if (etat == TelephonyManager.CALL_STATE_IDLE) // RAS if (etat == TelephonyManager.CALL_STATE_RINGING) // Le tlphone sonne String SIMnb = tel.getSimSerialNumber();
Il est aussi possible d'tre notifi d'un changement d'tat en utilisant un couteur:
50 / 118
public class Ecouteur extends PhoneStateListener { public void onCallStateChanged(int etat, String numero) { super.onCallStateChanaged(etat, numero) if (etat == TelephonyManager.CALL_STATE_OFFHOOK) // Le tlphone est utilis }
Uri telnumber = Uri.parse("tel:0248484000"); Intent call = new Intent(Intent.ACTION_DIAL, telnumber); startActivity(call); (TODO: revoir) Pour filtrer l'appel, dans le but de loguer une information ou grer l'appel, il faut poser un intent filter: <receiver android:name=".ClasseGerantLAppel"> <intent-filter> <action android:name="Intent.ACTION_CALL"/> </intent-filter> </receiver>
51 / 118
7.2 Rseau
public final class MyBroadcastReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(android.provider.Telephony.SMS_RECEIVED)) { String val = extra.getString("extra"); Object[] pdus = (Object[]) intent.getExtras().get("pdus"); SmsMessage[] messages = new SmsMessage[pdus.length]; for (int i=0; i < pdus.length; i++) messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); }}}
7.2 Rseau
(cf ANX_Rseau) Le rseau peut tre disponible ou indisponible, suivant que le tlphone utilise une connexion Wifi, 3G, bluetooth, etc. Si la permission android.permission;ACCESS_NETWORK_STATE est dclare, la classe NetworkInfo (depuis ConnectivityManager) permet de lire l'tat de la connexion rseau parmis les constantes de la classe State: CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED, SUSPENDED, UNKNOWN. ConnectivityManager manager = (ConnectivityManager)getSystemService(CONNECTIVITY_SERVICE); NetworkInfo net = manager.getActiveNetworkInfo(); if (net.getState().compareTo(State.CONNECTED) // Connect Il est possible de connatre le type de la connexion: int type = net.getType(); Le type est un entier correspondant, pour l'instant, au wifi ou une connexion de type mobile (GPRS, 3G, ...). ConnectivityManager.TYPE_MOBILE: connexion mobile ConnectivityManager.TYPE_WIFI: wifi
52 / 118
7.3 Bluetooth
Les caractristiques de la connexion Wifi sont accessibles par des appels statiques des mthodes de WifiManager: force du signal projet sur une chelle [0,levels]: WifiManager.calculateSignalLelvel(RSSI ?, levels) vitesse du lien rseau: info.getLinkSpeed() les points d'accs disponibles: List<ScanResult> pa = manager.getScanResults()
7.3 Bluetooth
Le bluetooth se gre au travers de principalement 3 classes: BluetoothAdapter: similaire au WifiManager, cette classe permet de grer les autres appareils bluetooth et d'initier les communications avec ceux-ci. BluetoothDevice: objet reprsentant l'appareil distant. BluetoothSocket et BluetoothServerSocket: gre une connexion tablie. Pour pouvoir utiliser les fonctionnalits bluetooth, il faut activer les persmissions android.permission.BLUETOOTH et android.permission.BLUETOOTH_ADMIN pour pouvoir chercher des appareils ou changer la configuration bluetooth du tlphone.
BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter(); if (!bluetooth.isEnabled()) { Intent launchBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivity(launchBluetooth); }
S'associer en bluetooth
Pour pouvoir associer deux appareils en bluetooth, il faut que l'un d'eux soit accessible (s'annonce) aux autres appareils. Pour cela, l'utilisateur doit autoris le mode "dcouverte". L'application doit donc le demander explicitement via un Intent:
Intent discoveryMode = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoveryMode.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 60); startActivity(discoveryMode);
A l'inverse, si un appareil externe diffuse une annonce de dcouverte, il faut capturer les intents recus en broadcast dans le mobile: public final class BluetoothBroadcastReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); BluetoothDevice appareil = null; if (action.equals(BluetoothDevice.ACTION_FOUND)) appareil = (BluetoothDevice)intent.getParcelableExtra( BluetoothDevice.EXTRA_DEVICE); } }
53 / 118
Utiliser le rseau
Utiliser le rseau
De nombreuses mthodes de dveloppement permettent d'exploiter le rseau. Elles ne sont pas rapelles en dtail ici (ce n'est pas l'objet du cours) et elles sont de toutes faons dj connues: HTTP: HttpClient, httpResponse SOAP: SoapObjet, SoapSerializationEnvelope REST: JSON: JSONObject XML: DocumentBuilder Sockets: Socket, ServerSocket Bluetooth: BluetoothSocket, BluetoothServerSocket
7.4 Localisation
(cf ANX_Localisation) Comme pour le rseau, Android permet d'utiliser plusieurs moyens de localisation. Cela permet de rendre transparent l'utilisation du GPS ou des antennes GSM ou des accs au Wifi. La classe LocationManger permet de grer ces diffrents fournisseurs de position. LocationManager.GPS_PROVIDER: fournisseur GPS LocationManager.NETWORK_PROVIDER: fournisseur bas rseau La liste de tous les fournisseurs s'obtient au travers de la mthode getAllProviders() ou getAllProviders(true) pour les fournisseurs activs:
LocationManager manager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); List<String> fournisseurs = manager.getAllProviders(); for (String f : fournisseurs) { Toast.makeText(getApplicationContext(), "" + f, Toast.LENGTH_SHORT).show(); if (f.equals.(LocationManager.GPS_PROVIDER)) ... }
Les permissions associes pour la localisation sont: android.permission.ACCESS_FINE_LOCATION via le GPS android.permission.ACCESS_COARSE_LOCATION via le rseau
Coordonnes
A partir du nom d'un fournisseur de position actif, il est possible d'interroger la dernire localisation en utilisant l'objet Location.
Location localisation = manager.getLastKnownLocation("gps"); Toast.makeText(getApplicationContext(), "Lattitude" + localisation.getLatitude(),
54 / 118
Alerte de proximit
Il est possible de ragir un changement de position en crant un couteur qui sera appel intervalles rguliers et pour une distante minimum donne: manager.requestLocationUpdates("gps", 6000, 100, new LocationListener() { public void onStatusChanged(String provider, int status, Bundle extras) { } public void onProviderEnabled(String provider) { } public void onProviderDisabled(String provider) { } public void onLocationChanged(Location location) { // TODO Auto-generated method stub } });
Alerte de proximit
Il est possible de prparer un vnement en vu de ragir la proximit du tlphone une zone. Pour cela, il faut utiliser la mthode addProximityAlert de LocationManager qui permet d'enregistrer un Intent qui sera envoy lorsque des conditions de localisation sont runies. Cette alerte possde une dure d'expiration qui la dsactive automatiquement. La signature de cette mthode est: addProximityAlert(double latitude, double longitude, float radius, long expiration, PendingIntent intent) Il faut ensuite filtrer l'intent prpar: IntentFilter filtre = new IntentFilter(PROXIMITY_ALERT); registerReceiver(new MyProximityAlertReceiver(), filtre); La classe grant l'alerte est alors: public class MyProximityAlertReceiver extends BroadcastReiceiver { public void onReceive(Context context, Intent intent) { String key = LocationManager.KEY_PROXIMITY_ENTERING; Boolean entering = intent.getBooleanExtra(key, false); }}
55 / 118
La classe MapActivity
On peut alors utiliser une nouvelle classe hritant de View, i.e. une MapView que l'on peut dclarer dans un gabarit: <com.google.android.maps.MapView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/mapview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:clickable="true" android:apiKey="Your Maps API Key goes here" /> La clef d'API est une clef gnrer sur le serveur de google grant le service des cartes partir de votre clef de signature d'application. Votre clef de signature de travail gnre par Eclipse suffit pour la phase de dveloppement.
La classe MapActivity
A partir de ce point, il faut crer une activit spciale hritant de MapActivity: public class HelloGoogleMaps extends MapActivity { @Override protected boolean isRouteDisplayed() { return false; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); MapView mapView = (MapView) findViewById(R.id.mapview); mapView.setBuiltInZoomControls(true); } } Trs peu de choses sont ncessaires dans cette classe: La mthode isRouteDisplayed() doit tre implmente car elle permet d'envoyer au serveur votre intention de dessiner ou non des chemins sur la carte en question. L'appel setBuiltInZoomControls() permet d'autoriser l'utilisateur zoomer dans la carte.
56 / 118
Drawable drawable = this.getResources().getDrawable(R.drawable.androidmarker); HelloItemizedOverlay itemizedoverlay = new HelloItemizedOverlay(drawable, this); GeoPoint point = new GeoPoint(19240000,-99120000); OverlayItem overlayitem = new OverlayItem(point, "Hola, Mundo!", "I'm in Mexico!");
Puis, on met l'objet de type OverlayItem dans l'objet de type HelloItemizedOverlay qui va ensuite dans la liste... itemizedoverlay.addOverlay(overlayitem); mapOverlays.add(itemizedoverlay); Il reste dfinir le comportement de ces points d'intrt en dfinissant la classe HelloItemizedOverlay.
Reverse Geocoding
Une autre classe intressante fournie par Android permet de retrouver une adresse partir d'une localisation. Il s'agit de la classe Geocoder qui permet d'interroger un service Google partir de coordonnes. Le code est assez simple mettre en oeuvre:
57 / 118
7.5 Capteurs
Geocoder geocoder = new Geocoder(context, Locale.getDefault()); List<Address> addresses = geocoder.getFromLocation(loc.getLatitude(), loc.getLongitude(), 1); String addressText = String.format("%s, %s, %s", address.getMaxAddressLineIndex() > 0 ? address.getAddressLine(0) : "", address.getLocality(), address.getCountryName()); Attention utiliser l'mulateur contenant les "Google APIs" pour pouvoir utiliser ce service. Pour savoir si l'OS dispose du backend permettant d'utiliser la mthode getFromLocation, on peut appeler la mthode isPresent() qui doit renvoyer *true* dans ce cas. Un exemple de code utilisant une tche asynchrone est donn dans ANX_Geocoding.
7.5 Capteurs
(cf ANX_Capteurs) Android introduit la gestion de multiples capteurs. Il peut s'agir de l'acclromtre, du gyroscope (position angulaire), de la luminosit ambiante, des champs magntiques, de la pression ou temprature, etc. En fonction, des capteurs prsents, la classe SensorManager permet d'accder aux capteurs disponibles. Par exemple, pour l'acclromtre:
SensorManager manager = (SensorManager)getSystemService(SENSOR_SERVICE); manager.registerListener( new SensorEventListener() { public void onSensorChanged(SensorEvent event) { // TODO method stub } public void onAccuracyChanged(Sensor sensor, int accuracy) { // TODO method stub }} , manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) , SensorManager.SENSOR_DELAY_UI ); }
Quand l'accelromtre n'est plus utilis, il doit tre dsenregistr l'aide de: manager.unregisterListener( pointeur ecouteur , manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) ); La frquence d'interrogation influe directement sur l'nergie consomme. Cela va du plus rapide possible pour le systme une frquence faible: SENSOR_DELAY_FASTEST < SENSOR_DELAY_GAME < SENSOR_DELAY_NORMAL < SENSOR_DELAY_UI.
Hardware
Pour dclarer l'utilisateur d'un capteur et avoir le droit d'accder aux valeurs lues par le hardware, il ne faut pas oublier d'ajouter dans le Manifest la dclaration adquat l'aide du tag uses-feature [UF]. Cela permet Google Play de filtrer les applications compatibles avec l'appareil de l'utilisateur. Par exemple, pour l'acclromtre:
<uses-feature android:name="android.hardware.sensor.accelerometer"></uses-feature>
La directive n'est cependant pas utilise lors de l'installation, ce qui signifie qu'il est possible d'installer une application utilisant le bluetooth sans possder de hardware bluetooth. Evidemment, il risque d'y avoir une exception ou des dysfonctionnements. Un boolen supplmentaire permet alors d'indiquer si ce hardware est indispensable au fonctionnement de l'application:
58 / 118
<uses-feature android:name="xxx" android:required="true"></uses-feature> L'outil aapt permet d'apprcier la faon dont Google Play va filtrer l'application, en donnant un dump des informations collectes: ./aapt dump badging ~/workspace/AndroSensors2/bin/AndroSensors2.apk package: name='jf.andro.as' versionCode='1' versionName='1.0' sdkVersion:'8' targetSdkVersion:'14' uses-feature:'android.hardware.sensor.acceleromedter'
7.6 Camra
La documentation propose par Google dans [CAM] permet de bien comprendre comment fonctionne la camra. Il est souvent ncessaire de construire une zone de preview de ce que la camra voit, avant par exemple de prendre une photo. Avant toute chose, il faut prvoir dans le manifest la permission ncessaire i.e. android.permission.CAMERA. Ensuite les grandes tapes sont les suivantes: Dans l'activit principale on ajoute une ViewGroup spciale et on ouvre la camra (arrire, d'id 0, frontale, d'id 1): Camera mCamera; Preview mPreview = new Preview(this);
59 / 118
setContentView(mPreview); Camera.open(id); mPreview.setCamera(mPreview); Avec une classe Preview hritant de ViewGroup et implmentant SurfaceHolder.Callback. Cela oblige recoder les mthodes surfaceChanged, surfaceCreated, surfaceDestroyed et onLayout qui seront appel quand la surface de rendu est effectivement cr. Notamment, il faut accrocher un objet SurfaceHolder l'objet Camera, puis dire l'objet SurfaceHolder que le callback est l'objet Preview. Plus de dtails sont observables en annexes [ANX_Camera].
60 / 118
8.1 Architectures
Les applications clientes interragissant avec un serveur distant, qu'il soit dans un cloud ou non, peuvent permettre de dporter des traitements sur le serveur. La plupart du temps, on dporte les traitements pour: garder et protger le savoir mtier rendre l'utilisateur captif amliorer les performances lors de traitements lourds conomiser les ressources du client Le prix payer est le temps de latence induit par le rseau, voire l'incapacit de l'application fonctionner correctement si l'utilisateur n'a plus de rseau ou un rseau dgrad (e.g. 2G). L'exprience utilisateur peut aussi tre largement impact. Losqu'un dveloppeur ralise une application cliente, il utilise presque de manire inconsciente le modle MVC. S'il ajoute la dimension rseau, il peut alors choisir de dporter des lements du modle MVC sur le serveur. En effet, il faut dcider quels lments du modle MVC sont du ct client ou du ct serveur.
Types d'applications
Lorsqu'on ralise une application client-serveur, on peut considrer que trois choix d'architectures sont possibles pour l'emplacement du modle MVC: application native: MVC sont cods en java, dans une machine virtuelle Dalvik application hybride: certains lments du Contrle sont cods en java, mais Modles et Vues sont cods par le serveur application web: tout est ralis sur le serveur Dans ce cours, jusqu' maintenant, il a toujours t prsent des applications natives. Une application hybride utilise le langage HTML5 et les capacits d'Android afficher des pages web pour intgrer des composants de visualisation dans une application native. Ainsi, une partie de l'application est
61 / 118
classiquement native, tandis que certaines parties de la vue sont des affichages de documents HTML calculs cot serveur.
Cependant, les liens web appelent tout de mme le navigateur, ce qui est assez gnant:
Surcharge du WebClient
Afin de ne pas lancer le navigateur par dfaut, il faut surcharger le client web afin de recoder la mthode agissant lorsqu'un lien est cliqu. La classe est trs simple: private class MyWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); Toast.makeText(getApplicationContext(), "A link has been clicked! ", Toast.LENGTH_SHORT).show(); return true; }} Du ct de l'activit, il faut remplacer le comportement du client web par dfaut. On peut aussi prvoir un bouton afin de pouvoir revenir en arrire car le bouton back du tlphone va tuer l'activit. wv.setWebViewClient(new MyWebViewClient()); Button back = (Button)findViewById(R.id.back); back.setOnClickListener(new OnClickListener() { public void onClick(View v) { wv.goBack(); } });
Dmonstration
62 / 118
JQueryMobile
Dans cet exemple (cf ANX_Applications-hybrides), on navigue sur une page web incluse dans une activit. Un bouton transparent "Back" permet de controler la WebView:
JQueryMobile
Evidemment, une page web classique n'est pas trs adapte un tlphone ou une tablette. Il existe des technologies pour calculer le rendu javascript d'une page web en fonction du navigateur, ce qui est particulirement pratique. On peut par exemple utiliser JQueryMobile pour cela, comme expliqu dans [JQM].
<!DOCTYPE html> <html> <head> <title>My Page</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" /> <script src="http://code.jquery.com/jquery-1.8.2.min.js"></script> <script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script> </head> <body> <div data-role="page"> <div data-role="header"> <h1>Jquery example</h1> </div> <div data-role="content"> <p>Hello world !</p> </div><!-- /content --> </div> </body> </html>
63 / 118
chaque requte est stateless c'est dire qu'elle est excutable du ct serveur sans que celui-ci ait besoin de retenir l'tat du client (son pass i.e. ses requtes passes) les rponses peuvent parfois tre mises en cache facilitant la monte en charge les ressources (services) du serveur sont clairement dfinis; l'tat du client est envoy par "morceaux" augmentant le nombre de requtes. Pour batir le service du ct serveur, un serveur PHP ou Tomcat peut faire l'affaire. Il pourra fournir un web service, des donnes au format XML ou JSON. Du ct client, il faut alors implmenter un parseur SAX ou d'objet JSON, comme montr dans [JSONREST].
64 / 118
PrintWriter out = response.getWriter(); // traitements } public void destroy() { } public String getServletInfo() { } } La servlet peut mme redlguer la partie "Vue" une JSP, en fin de traitement: <title> <!-- get a value that has been set by the servlet --> <%= request.getAttribute("title") %> </title> <jspx:foreach collection="<%= myVector %>"> <jspx:item ref="myItem" type="java.lang.Integer"> <element> <%= myItem %> </element> </jspx:item> </jspx:foreach>
65 / 118
Classe d'interface
La premire tape consiste raliser le code Java permettant de faire le pont entre votre activit et les mthodes natives. Dans l'exemple suivant, on dclare une mthode statique (en effet, votre programme C ne sera pas un objet), appelant une mthode native dont l'implmentation ralise l'addition de deux nombres:
66 / 118
Gnration du .h
package andro.jf.jni; public class NativeCodeInterface { public static native int calcul1(int x, int y); public static int add(int x, int y) { int somme; somme = calcul1(x,y); return somme; } static { System.loadLibrary("testmodule"); } } Un appel natif depuis une activit ressemblera : TextView text = (TextView)findViewById(R.id.texte); text.setText("5+7 = " + NativeCodeInterface.add(5, 7));
Gnration du .h
A partir de la classe prcdemment crite, il faut utiliser l'outil javah pour gnrer le fichier .h correspondant aux mthodes natives dclares dans le .java. En dehors du rpertoire src/ de votre projet, vous pouvez crer un rpertoire jni/ afin d'y placer les fichiers de travail de la partie C. On ralise donc la compilation, puis l'extraction du .h: javac javac -d ./jni ./src/andro/jf/jni/NativeCodeInterface.java cd ./jni javah -jni andro.jf.jni.NativeCodeInterface On obtient alors le fichier andro_jf_jni_NativeCodeInterface.h suivant: // Header for class andro_jf_jni_NativeCodeInterface #ifndef _Included_andro_jf_jni_NativeCodeInterface #define _Included_andro_jf_jni_NativeCodeInterface #ifdef __cplusplus extern "C" { #endif JNIEXPORT jint JNICALL Java_andro_jf_jni_NativeCodeInterface_calcul1 (JNIEnv *, jclass, jint, jint); #ifdef __cplusplus } #endif #endif
67 / 118
Ecriture du .c et compilation
Ecriture du .c et compilation
A partir du fichier .h, il faut maintenant crire le code de l'appel natif: #include "andro_jf_jni_NativeCodeInterface.h" JNIEXPORT jint JNICALL Java_andro_jf_jni_NativeCodeInterface_calcul1 (JNIEnv * je, jclass jc, jint a, jint b) { return a+b; } Il faut ensuite crer le Makefile appropri pour la compilation du fichier .c l'aide du script ndk-build. Le fichier de Makefile doit s'appler Android.mk: LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE LOCAL_CFLAGS LOCAL_SRC_FILES LOCAL_LDLIBS := := := := testmodule -Werror test.c -llog
include $(BUILD_SHARED_LIBRARY) Et la phase de compilation doit ressembler : /usr/local/android-ndk-r8b/ndk-build Compile thumb : testmodule <= test.c SharedLibrary : libtestmodule.so Install : libtestmodule.so => libs/armeabi/libtestmodule.so
Dmonstration
L'annexe [ANX_JNI] presente le code complet d'un appel natif une librairie C ralisant l'addition de deux nombres. Le rsultat obtenu est le suivant:
68 / 118
9.2 Dcompilation
9.2 Dcompilation
Les applications Android sont trs facilement dcompilables, cause de la nature mme des applications java qui tournent dans une machine virtuelle. Comme l'explique Godfrey Nolan dans [DA], les efforts pour protger le bytecode de la dcompilation sont un peu pass de mode car les technologies J2EE sont principalement utilises ct serveur. Cependant, avec la monte en puissance d'Android, ce problme revient sur le devant de la scne. Il est pour l'heure extremement simple de dcompiler une application package dans un .apk, par exemple en utilisant les android-apktool: java -jar /usr/local/apktool.jar decode AndroListsSimple.apk I: Baksmaling... I: Loading resource table... I: Loaded. I: Loading resource table from file: /home/jf/apktool/framework/1.apk I: Loaded. I: Decoding file-resources... I: Decoding values*/* XMLs... I: Done. I: Copying assets and libs...
Application dcompile:
cd AndroListsSimple/ ./AndroListsSimple$ tree sss AndroidManifest.xml sss apktool.yml sss res s sss drawable-hdpi
69 / 118
Smali
s s sss icon.png s sss drawable-ldpi s s sss icon.png s sss drawable-mdpi s s sss icon.png s sss layout s s sss main.xml s s sss montexte.xml s sss values s sss ids.xml s sss public.xml s sss strings.xml sss smali sss andro sss jf sss AndroListsSimpleActivity.smali sss R$attr.smali sss R$drawable.smali sss R$id.smali sss R$layout.smali sss R.smali sss R$string.smali
Smali
Smali et Baksmali sont des assembleurs et dsassembleurs de fichiers dex, intgrs dans les apktools. Le code obtenu lors du dsassemblage est proche du bytecode: il s'agit d'un fichier au format "Jasmin" du nom de l'outil originel Jasmin qui permet d'assembler du code Jasmin pour une machine virtuelle Java. La syntaxe Jasmin est assez explicite, comme le montre l'exemple suivant: .class public Landro/jf/AndroListsSimpleActivity; .super Landroid/app/Activity; .source "AndroListsSimpleActivity.java" # direct methods .method public constructor <init>()V .locals 0 .prologue .line 8 invoke-direct {p0}, Landroid/app/Activity;-><init>()V return-void .end method # virtual methods .method public onCreate(Landroid/os/Bundle;)V .locals 5
70 / 118
Cependant, le .apk gnr n'est pas encore prt pour tre dploy:
71 / 118
/usr/local/android-sdk-linux_x86/platform-tools/adb install AndroListsSimple2.apk 216 KB/s (11522 bytes in 0.052s) pkg: /data/local/tmp/AndroListsSimple2.apk Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES]
dex2jar
L'outil dex2jar, voqu dans [DA], permet de passer du format .dex au format .class. Cela permet de tirer partie des outils travaillant sur les fichiers classiques de Java. /usr/local/dex2jar-0.0.9.10/d2j-dex2jar.sh AndroListsSimple.apk dex2jar AndroListsSimple.apk -> AndroListsSimple-dex2jar.jar unzip AndroListsSimple-dex2jar.jar Archive: AndroListsSimple-dex2jar.jar inflating: andro/jf/AndroListsSimpleActivity.class inflating: andro/jf/R$attr.class inflating: andro/jf/R$drawable.class inflating: andro/jf/R$id.class inflating: andro/jf/R$layout.class
72 / 118
9.3 Obfuscation
inflating: andro/jf/R$string.class inflating: andro/jf/R.class Combin avec jd-gui, il suffit alors de dsassembler les .class pour retrouver le code source:
9.3 Obfuscation
Dans [DA], l'auteur prsente la vision de Collberg, Thomborson et Low propos des classes d'obfuscateurs. On peut en distingue trois grands types: obfuscateurs de reprsentation obfuscateurs de contrle obfuscateurs de donnes L'obfuscation de reprsentation consiste travailler sur les identifiants du programmes (variables, mthodes, classes) pour leur faire perdre leur sens en les anonymisant (a, b, c). C'est le process d'obfuscation le plus simple. L'obfuscation de contrle va travailler sur la structure du code en insrant du code inutile, en tendant ou en sparant des boucles, en ajoutant des oprations redondantes, en rordonnant les instructions, les boucles, ou expressions. L'obfuscation de donnes va travailler sur les variables et leurs contenus en sparant les contenus de variables, en cassant ou fusionnant les tableaux, en changeant d'encoding.
Rsultat: MySecretClass.java
public class MySecretClass { protected int color_; public int monCalcul(int temperature, int pression) { color_ = color_ + 1;
73 / 118
Rsultat: MainActivity.java
return temperature / pression; } public void setColor(int color) { color_ = color; } public int getColor() { return color_; } }
public class a { protected int a; public int a() { return this.a; } public int a(int paramInt1, int paramInt2) { this.a = (1 + this.a); return paramInt1 / paramInt2; } public void a(int paramInt) { this.a = paramInt; } }
Rsultat: MainActivity.java
public class MainActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MySecretClass secret = new MySecretClass(); secret.setColor(0); int volume = secret.monCalcul(25, 1000); System.out.println("Volume: " + volume + secret.getColor()); } public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true; } }
public class MainActivity extends Activity { public void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); setContentView(2130903040); a locala = new a(); locala.a(0); int i = locala.a(25, 1000); System.out.println("Volume: " + i + locala.a());
74 / 118
Rsultat: MainActivity.java
75 / 118
10 Annexes: outils
10 Annexes: outils
10.1 L'mulateur Android AVD: le gestionnaire d'appareils Accleration matrielle pour l'mulateur L'mulateur 10.2 ADB: Android Debug Bridge Debugging Tester sur son tlphone 10.3 Simuler des sensors Adaptation de votre application au simulateur 10.4 HierarchyViewer 76 76 76 77 77 78 78 78 79 79
76 / 118
L'mulateur
architecture x86: on gagne l'overhead d'mulation ! Pour se faire, il faut bien entendu disposer de kvm et installer depuis le SDK manager l'outil Intel x86 Atom System Image, disponible dans ICS. Pour utiliser l'acclration, il faut crer un nouvel appareil utilisant un CPU de type Intel Atom (x86), comme montr ici:
L'mulateur
[Emulator] Dans le rpertoire tools, l'excutable emulator permet de lancer l'mulateur Android en prcisant la configuration d'appareil l'aide de l'option -avd. La syntaxe de la ligne de commande est donc: emulator -avd <avd_name> [-<option> [<value>]] Par dfaut, la commande sans option dmarre l'mulateur. L'mulateur peut aussi tre paramtr en ligne de commande. Les options possibles permettent par exemple de: spcifier un serveur DNS (-dns-server <IP>) ralentir le CPU (-cpu-delay <value>) charger une partition de SDcard (-ramdisk <filepath>) effacer des donnes utilisateur (-wipe-data, -initdata) ...
77 / 118
Debugging
Debugging
Les commandes classiques System.out.xxx sont rediriges vers /dev/null. Il faut donc dbugguer autrement. Pour ce faire, le SDK fournit un moyen de visualiser les logs du systme et de les filtrer au travers de l'outils adb situ dans le rpertoire platform-tools du SDK. L'outil permet de se connecter l'mulateur et d'obtenir les messages de logs. On obtient par exemple pour une exception: ./adb logcat I/System.out( W/System.err(
W/System.err(
483): debugger has settled (1441) 483): java.io.FileNotFoundException: /test.xml (No such file or directory) ... 483): at andro.jfl.AndroJFLActivity.onCreate( Andro7x24Activity.java:38)
Il est aussi possible de gnrer des logs l'aide de la classe Log est des mthodes statiques .v (verbose), .d (debug), .e (error), .w (warning), etc. Par exemple: Log.w("myApp", "Mon message logguer");
78 / 118
Vous pouvez, pour commencer, tester la connexion entre l'outil externe et l'outil de configuration interne de Sensor Simulator. Dans l'mulateur, lancez Sensor Simulator et aprs avoir vrifi la concordance entre les adresses IP, vous pouvez connecter les deux outils depuis l'onglet Testing. Vous pouvez ensuite faire bouger le device virtuel dans le client java (tlphone en haut gauche).
10.4 HierarchyViewer
Android fournit un outil d'analyse live de la hirarchie des objets graphiques. Ainsi, il est possible de mieux comprendre sa propre application lors de la phase de dbugging. Pour l'utiliser, il faut lancer: ./hierarchyviewer. Dans l'exemple suivant, on voit deux FrameLayout qui sont enfants de l'lments de plus haut niveau LinearLayout. Le premier FrameLayout a t dcroch de l'lment root et remplac par un nouveau FrameLayout contenant l'lment Preview permettant de visualiser ce que voit la camra arrire (cf [ANX_Camera]). Les trois points de couleur reprsentent la vitesse de rendu. De gauche droite on a le temps utilis pour mesurer (calculer les dimensions), positionner (calculer la positiond des enfants) et dessiner . L'interprtation est: vert: 50% plus rapide que les autres lments graphiques, jaune: moins de 50%, rouge: le plus lent de la hirarchie.
79 / 118
80 / 118
Layout
b.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(v.getContext(), "Stop !", Toast.LENGTH_LONG).show(); } }); // Changing layout LinearLayout l = (LinearLayout)findViewById(R.id.accueilid); l.setBackgroundColor(Color.GRAY); } }
Layout
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" android:id="@+id/accueilid">
<EditText android:text="" android:id="@+id/EditText01" android:layout_width="fill_parent" android:layout_height="wrap_content"></EditText> <EditText android:text="" android:layout_width="fill_parent" android:layout_height="wrap_content"></EditText> <ImageView android:id="@+id/logoEnsi" android:src="@drawable/ensi" android:layout_width="100px" android:layout_height="wrap_content" android:layout_gravity="center_horizontal"></ImageView> <Button android:text="Go !" android:id="@+id/Button01" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> </LinearLayout>
Activity2.java
public class Activity2 extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout gabarit = new LinearLayout(this); gabarit.setOrientation(LinearLayout.VERTICAL); gabarit.setGravity(Gravity.CENTER); // Label TextView texte = new TextView(this); texte.setText("Programming creation of interface !"); gabarit.addView(texte); // Zone de texte EditText edit = new EditText(this); edit.setText("Edit me"); gabarit.addView(edit);
81 / 118
Layout
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ListView android:layout_height="fill_parent" android:id="@+id/maliste" android:layout_width="fill_parent"></ListView> </LinearLayout>
82 / 118
Layout
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ListView android:layout_height="fill_parent" android:id="@+id/maliste" android:layout_width="fill_parent"></ListView> </LinearLayout>
11.4 Onglets
Dveloppement sous Android - J.-F. Lalande 83 / 118
AndroTabs2Activity.java
AndroTabs2Activity.java
public class AndroTabs2Activity extends TabActivity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); TabHost tabHost = getTabHost(); TabHost.TabSpec spec; Intent intent; // Cration de l'intent lancant l'activit de l'onglet intent = new Intent().setClass(this, ActivityOnglet1.class); // Cration dynamique d'une configuration pour l'onglet 1 spec = tabHost.newTabSpec("Onglet 1"); spec.setContent(intent); spec.setIndicator("Onglet 1"); tabHost.addTab(spec);
intent = new Intent().setClass(this, ActivityOnglet2.class); spec = tabHost.newTabSpec("Onglet 2"); spec.setContent(intent); spec.setIndicator("Onglet 2"); tabHost.addTab(spec); // Choisir l'onglet par dfaut tabHost.setCurrentTab(0); } }
Layout du Main
<?xml version="1.0" encoding="utf-8"?> <TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/tabhost" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:id="@+id/linearLayout1" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <TabWidget android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@android:id/tabs"> </TabWidget> <FrameLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@android:id/tabcontent"> </FrameLayout> </LinearLayout> </TabHost>
Onglet 1
84 / 118
Layout de l'onglet 1
public class ActivityOnglet1 extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.onglet1); } }
Layout de l'onglet 1
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <RatingBar android:id="@+id/ratingBar1" android:layout_width="wrap_content" android:layout_height="wrap_content"></RatingBar> <SeekBar android:layout_height="wrap_content" android:id="@+id/seekBar1" android:layout_width="fill_parent"></SeekBar> <TextView android:text="TextView" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView> </LinearLayout>
Onglet 2
public class ActivityOnglet2 extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.onglet2); } }
Layout de l'onglet 2
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ProgressBar android:layout_height="wrap_content" style="?android:attr/progressBarStyleLarge" android:layout_width="wrap_content" android:id="@+id/progressBar1"></ProgressBar> <QuickContactBadge android:id="@+id/quickContactBadge1" android:layout_width="wrap_content" android:layout_height="wrap_content"></QuickContactBadge> <TextView android:textAppearance="?android:attr/textAppearanceMedium" android:text="TextView" android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/textView1"></TextView> </LinearLayout>
11.5 Intents
Main.java
85 / 118
Layout du Main
public class Main extends Activity { private MyBroadcastReceiverDyn myreceiver; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button call = (Button)findViewById(R.id.call); call.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent login = new Intent(getApplicationContext(), GivePhoneNumber.class); startActivity(login); }}); Button info = (Button)findViewById(R.id.info); info.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent login = new Intent(getApplicationContext(), GivePhoneNumber.class); startActivityForResult(login,48); }}); // Broadcast receiver dynamique myreceiver = new MyBroadcastReceiverDyn(); IntentFilter filtre = new IntentFilter("andro.jf.broadcast"); registerReceiver(myreceiver, filtre); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 48) Toast.makeText(this, "Code de requte rcupr (je sais d'ou je viens)", Toast.LENGTH_LONG).show(); if (resultCode == 50) Toast.makeText(this, "Code de retour ok (on m'a renvoy le bon code)", Toast.LENGTH_LONG).show(); } }
Layout du Main
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Main Activity:" /> <Button android:text="Call" android:id="@+id/call" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> <Button android:text="Call with result" android:id="@+id/info" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> </LinearLayout>
Seconde activit
public class GivePhoneNumber extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
86 / 118
setContentView(R.layout.givephonenumber); // Bouton ok Button b = (Button)findViewById(R.id.ok); b.setOnClickListener(new OnClickListener() { public void onClick(View v) { EditText number = (EditText)findViewById(R.id.number); Uri telnumber = Uri.parse("tel:" + number.getText().toString()); Intent call = new Intent(Intent.ACTION_DIAL, telnumber); startActivity(call); } }); // Bouton finish Button finish = (Button)findViewById(R.id.finish); finish.setOnClickListener(new OnClickListener() { public void onClick(View v) { setResult(50); finish(); } }); // Bouton appel direct Button direct = (Button)findViewById(R.id.direct); direct.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { EditText number = (EditText)findViewById(R.id.number); Uri telnumber = Uri.parse("tel:" + number.getText().toString()); Intent call = new Intent(Intent.ACTION_CALL, telnumber); startActivity(call); }}); // Bouton broadcast Button broad = (Button)findViewById(R.id.broadcast); broad.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent broadcast = new Intent("andro.jf.broadcast"); broadcast.putExtra("extra", "test"); sendBroadcast(broadcast); }}); // Bouton pour s'auto appeler Button autoinvoc = (Button)findViewById(R.id.autoinvoc); autoinvoc.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent call = new Intent("andro.jf.nom_du_message"); call.putExtra("extra", "test"); startActivity(call); }}); } }
87 / 118
android:layout_width="wrap_content" android:layout_height="wrap_content" xmlns:android="http://schemas.android.com/apk/res/android"></TextView> <EditText android:id="@+id/number" android:layout_width="fill_parent" android:layout_height="wrap_content"></EditText> <Button android:text="Ok" android:id="@+id/ok" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> <Button android:text="Finish" android:id="@+id/finish" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> <Button android:text="Direct Call" android:id="@+id/direct" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> <Button android:text="Broadcast" android:id="@+id/broadcast" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> <Button android:text="Auto call" android:id="@+id/autoinvoc" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> </LinearLayout>
Manifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="andro.jf" 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="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter> <action android:name="andro.jf.nom_du_message" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
88 / 118
<activity android:name=".GivePhoneNumber" android:label="Renseigner le login"/> <receiver android:name="MyBroadcastReceiver"> <intent-filter> <action android:name="andro.jf.broadcast" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver> </application> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.CALL_PHONE"/> <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="4" />
</manifest>
MesPreferences.java
89 / 118
Preferences XML
public class MesPreferences extends PreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.prefs); } }
Preferences XML
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:key="prefs">
<CheckBoxPreference android:title="Diffusion du login" android:summaryOff="Ne pas diffuser" android:summaryOn="Diffuser" android:key="diffuser"></CheckBoxPreference> <EditTextPreference android:summary="login d'authentification" android:title="Login" android:key="login" android:dependency="diffuser"></EditTextPreference> <EditTextPreference android:title="Password" android:key="password" android:dependency="diffuser"></EditTextPreference> <ListPreference android:title="Vitesse" android:key="vitesse" android:entries="@array/key" android:entryValues="@array/value" android:dialogTitle="Choisir la vitesse:" android:persistent="true"></ListPreference> </PreferenceScreen>
Manifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="andro.jf" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="10" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".LocalStartUI" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" />
90 / 118
LocalStartUI.java
<category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".AndroService"> <intent-filter> <action android:name="andro.jf.manageServiceAction" /> </intent-filter> </service> <receiver android:name=".AutoStart"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> </application> </manifest>
LocalStartUI.java
public class LocalStartUI extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.main); Button b = (Button) findViewById(R.id.button1); b.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent startService = new Intent("andro.jf.manageServiceAction"); startService(startService); } }); Button b2 = (Button) findViewById(R.id.button2); b2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent stopService = new Intent("andro.jf.manageServiceAction"); stopService(stopService); } }); }
AndroService.java
91 / 118
public void onCreate() { // Cration du service Toast.makeText(this, "Service Created", Toast.LENGTH_SHORT).show(); timer = new Timer(); } public void onDestroy() { // Destruction du service Toast.makeText(this, "Destruction du service", Toast.LENGTH_SHORT).show(); timer.cancel(); } public int onStartCommand(Intent intent, int flags, int startId) { // Dmarrage du service Toast.makeText(this, "Dmarrage du service", Toast.LENGTH_SHORT).show(); /* boolean blop = true; // Vraiment pas une bonne ide ! while (blop == true) ; */ final Handler handler = new Handler(); task = new TimerTask() { public void run() { handler.post(new Runnable() { public void run() { Toast.makeText(AndroService.this, "plop !", Toast.LENGTH_SHORT).show(); } }); } }; timer.schedule(task, 0, 5000); return START_STICKY; } public IBinder onBind(Intent arg0) { return null; } }
// La donne transmettre l'activit MonServiceBinder(); // L'implmentation de l'objet de binding extends Binder implements AndroServiceInterface { de Binder implmente une mthode dfinie dans l'interface
92 / 118
AndroServiceInterface.java
} /* Service stuff */ public void onCreate() { // Cration du service Toast.makeText(this, "Service Created", Toast.LENGTH_SHORT).show(); } public void onDestroy() { // Destruction du service } public int onStartCommand(Intent intent, int flags, int startId) { // Dmarrage du service Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } infoOfService = 12; } }); t.start(); return START_STICKY; } }
AndroServiceInterface.java
package andro.jf; /* C'est l'interface utilise de part et d'autre (activit / service). * Le service va crer un objet de type Binder qui implmente cette interface. * L'activit va recevoir cet objet (sans connaitre son type) mais sait qu'il * implmentera cette interface. */ public interface AndroServiceInterface { public int getInfo(); }
AndroServiceBindActivity.java
93 / 118
Manifest
public class AndroServiceBindActivity extends Activity { /** Called when the activity is first created. */ private int infoFromService = 0; /* Code disponible lorsque l'activit sera connecte au service */ private ServiceConnection maConnexion = new ServiceConnection() { /* Code excut lorsque la connexion est tablie: */ public void onServiceConnected(ComponentName name, IBinder service) { AndroServiceInterface myBinder = (AndroServiceInterface)service; infoFromService = myBinder.getInfo(); // stockage de l'information provevant du service } public void onServiceDisconnected(ComponentName name) { } };
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Intent serv = new Intent(this,AndroService.class); startService(serv); Button b = (Button)findViewById(R.id.button1); b.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { /* Intent pour la connexion au service */ Intent intentAssociation = new Intent(AndroServiceBindActivity.this, AndroService.class); /* Bind au service: refaire chaque appui sur le bouton pour rafraichir la valeur */ bindService(intentAssociation, maConnexion, Context.BIND_AUTO_CREATE); Toast.makeText(getApplicationContext(), "Info lue dans le service: " + infoFromService, Toast.LENGTH_SHORT).show(); /* On se dconnecte du service */ unbindService(maConnexion); } });
} }
Manifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="andro.jf" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="10" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".AndroServiceBindActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".AndroService" /> <!-- not possible: android:process=":p2" --> </application> </manifest>
AndroService.java
AndroService.java
public class AndroService extends Service { /* For binding this service */ private int infoOfService = 0; // La donne transmettre l'activit private AndroServiceInterface.Stub ib = new AndroServiceInterface.Stub() { @Override public int getInfo() throws RemoteException { // Cette classe qui hrite de Binder implmente une mthode dfinie dans l'interface return infoOfService; } }; public IBinder onBind(Intent arg0) { return ib; } /* Service stuff */ public void onCreate() { // Cration du service Toast.makeText(this, "Service Created", Toast.LENGTH_SHORT).show(); } public void onDestroy() { // Destruction du service } public int onStartCommand(Intent intent, int flags, int startId) { // Dmarrage du service Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } infoOfService = 12; } }); t.start(); return START_STICKY; } }
AndroServiceInterface.aidl
package andro.jf; interface AndroServiceInterface { int getInfo(); }
AndroServiceInterface.java gnr
95 / 118
AndroService.java
/* * This file is auto-generated. DO NOT MODIFY. * Original file: /home/jf/workspace/AndroServiceBind2/src/andro/jf/AndroServiceInterface.aidl */ package andro.jf; public interface AndroServiceInterface extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements andro.jf.AndroServiceInterface { private static final java.lang.String DESCRIPTOR = "andro.jf.AndroServiceInterface"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an andro.jf.AndroServiceInterface interface, * generating a proxy if needed. */ public static andro.jf.AndroServiceInterface asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof andro.jf.AndroServiceInterface))) { return ((andro.jf.AndroServiceInterface)iin); } return new andro.jf.AndroServiceInterface.Stub.Proxy(obj); } public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getInfo: { data.enforceInterface(DESCRIPTOR); int _result = this.getInfo(); reply.writeNoException(); reply.writeInt(_result); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements andro.jf.AndroServiceInterface { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } public int getInfo() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getInfo, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } } static final int TRANSACTION_getInfo = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); } public int getInfo() throws android.os.RemoteException; }
96 / 118
AndroServiceBind2Activity.java
AndroServiceBind2Activity.java
public class AndroServiceBind2Activity extends Activity { private int infoFromService = 0; /* Code disponible lorsque l'activit sera connecte au service */ private ServiceConnection maConnexion = new ServiceConnection() { /* Code excut lorsque la connexion est tablie: */ public void onServiceConnected(ComponentName name, IBinder service) { AndroServiceInterface myBinder = AndroServiceInterface.Stub.asInterface(service); try { // stockage de l'information provevant du service infoFromService = myBinder.getInfo(); } catch (RemoteException e) { e.printStackTrace(); } } public void onServiceDisconnected(ComponentName name) { } };
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Intent serv = new Intent(this,AndroService.class); startService(serv); Button b = (Button)findViewById(R.id.button1); b.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { /* Intent pour la connexion au service */ Intent intentAssociation = new Intent(AndroServiceBind2Activity.this, AndroService.class); /* Bind au service: refaire chaque appui sur le bouton pour rafraichir la valeur */ bindService(intentAssociation, maConnexion, Context.BIND_AUTO_CREATE); Toast.makeText(getApplicationContext(), "Info lue dans le service: " + infoFromService, Toast.LENGTH_SHORT).show(); /* On se dconnecte du service */ unbindService(maConnexion); } }); } }
Manifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="andro.jf" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="10" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".AndroServiceBind2Activity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".AndroService" android:process=":p2" /> <!-- Now possible --> </application> </manifest>
97 / 118
AndroService.java
public class AndroService extends Service { public void onCreate() { } public void onDestroy() { } public int onStartCommand(Intent intent, int flags, int startId) { // Dmarrage du service boolean b = true; while (b) ; return START_STICKY; } public IBinder onBind(Intent arg0) { return null; } }
98 / 118
BitmapDownloaderTask
TextView txt = (TextView) findViewById(R.id.textView1); BitmapDownloaderTask task = new BitmapDownloaderTask(img, txt); task.execute("http://www.univ-orleans.fr/lifo/Members/Jean-Francois.Lalande/enseignement/android/images/androids.png"); } }
BitmapDownloaderTask
public class BitmapDownloaderTask extends AsyncTask<String, Integer, Bitmap> { private final WeakReference<ImageView> imageViewReference; private final WeakReference<TextView> textViewReference; public BitmapDownloaderTask(ImageView imageView, TextView textView) { imageViewReference = new WeakReference<ImageView>(imageView); textViewReference = new WeakReference<TextView>(textView); } // Actual download method, run in the task thread protected Bitmap doInBackground(String... params) { // params comes from the execute() call: params[0] is the url. String url = params[0]; publishProgress(new Integer(0)); AndroidHttpClient client = AndroidHttpClient.newInstance("Android"); HttpGet getRequest = new HttpGet(url); try { Thread.sleep(2000); // To simulate a slow downloading rate HttpResponse response = client.execute(getRequest); publishProgress(new Integer(1)); Thread.sleep(1000); // To simulate a slow downloading rate final int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url); return null; } HttpEntity entity = response.getEntity(); if (entity != null) { InputStream inputStream = null; try { inputStream = entity.getContent(); Bitmap bitmap = BitmapFactory.decodeStream(inputStream); publishProgress(new Integer(3)); return bitmap; } finally { if (inputStream != null) { inputStream.close(); } entity.consumeContent();
99 / 118
11.13 Rseau
} } } catch (Exception e) { // Could provide a more explicit error message // for IOException or IllegalStateException getRequest.abort(); Log.w("ImageDownloader", "Error while retrieving bitmap from " + url); } finally { if (client != null) { client.close(); } } return null; } @Override protected void onProgressUpdate(Integer... values) { Integer step = values[0]; if (textViewReference != null) { textViewReference.get().setText("Step: " + step.toString()); } } @Override // Once the image is downloaded, associates it to the imageView protected void onPostExecute(Bitmap bitmap) { if (isCancelled()) { bitmap = null; } if (imageViewReference != null) { ImageView imageView = imageViewReference.get(); if (imageView != null) { imageView.setImageBitmap(bitmap); } } } @Override protected void onPreExecute() { if (imageViewReference != null) { ImageView imageView = imageViewReference.get(); if (imageView != null) { imageView.setImageResource(R.drawable.interro); } } } }
11.13 Rseau
Main.java
100 / 118
Manifest
public class Main extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Rseau ConnectivityManager manager = (ConnectivityManager)getSystemService(CONNECTIVITY_SERVICE); NetworkInfo net = manager.getActiveNetworkInfo(); Toast.makeText(getApplicationContext(), "" + net.getType(), Toast.LENGTH_LONG).show(); // Wifi WifiManager wifi = (WifiManager)getSystemService(Context.WIFI_SERVICE); if (!wifi.isWifiEnabled()) wifi.setWifiEnabled(true); manager.setNetworkPreference(ConnectivityManager.TYPE_WIFI); // Bluetooth BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter(); if (!bluetooth.isEnabled()){ Intent launchBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivity(launchBluetooth); } Intent discoveryMode = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoveryMode.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 60); startActivity(discoveryMode); Set<BluetoothDevice> s = bluetooth.getBondedDevices(); for (BluetoothDevice ap : s) Toast.makeText(getApplicationContext(), "" + ap.getName(), Toast.LENGTH_LONG).show(); } }
Manifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="andro.jf" 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="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <receiver android:name=".ClasseGerantLAppel"> <intent-filter> <action android:name="Intent.ACTION_CALL"/> </intent-filter> </receiver> </activity> </application> <uses-sdk android:minSdkVersion="5" />
101 / 118
Bluetooth
Bluetooth
public class BluetoothBroadcastReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); BluetoothDevice appareil = null; if (action.equals(BluetoothDevice.ACTION_FOUND)) appareil = (BluetoothDevice)intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); } }
11.14 Localisation
Main.java
public class Main extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Fournisseurs de service LocationManager manager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); List<String> fournisseurs = manager.getAllProviders(); for (String f : fournisseurs) Toast.makeText(getApplicationContext(), "" + f, Toast.LENGTH_SHORT).show(); // Rcupration de la localisation Location localisation = manager.getLastKnownLocation("gps"); Toast.makeText(getApplicationContext(), "Lattitude" + localisation.getLatitude(), Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), "Longitude" + localisation.getLongitude(), Toast.LENGTH_SHORT).show(); // Ecouteur de changement de localisation manager.requestLocationUpdates("gps", 6000, 100, new LocationListener() { public void onStatusChanged(String provider, int status, Bundle extras) { } public void onProviderEnabled(String provider) { } public void onProviderDisabled(String provider) { } public void onLocationChanged(Location location) { // TODO Auto-generated method stub } });
} }
Manifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="andro.jf" 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="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
102 / 118
11.15 Geocoding
11.15 Geocoding
AndroReverseGeocodingActivity.java
public class AndroReverseGeocodingActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); TextView address = (TextView)findViewById(R.id.address); ReverseGeocodingTask rgt = new ReverseGeocodingTask(this, address); Location ensib = new Location("JFL provider"); ensib.setLatitude(47.082687); ensib.setLongitude(2.415916); rgt.execute(new Location[] {ensib}); } }
ReverseGeocodingTask.java
public class ReverseGeocodingTask extends AsyncTask<Location, Void, String> { Context mContext; TextView addresseResult; protected void onPostExecute(String result) { addresseResult.setText(result); } public ReverseGeocodingTask(Context context, TextView result) { super(); mContext = context; this.addresseResult = result; } @Override protected String doInBackground(Location... params) { String addressText = null; Geocoder geocoder = new Geocoder(mContext, Locale.getDefault()); System.out.println("Geocoder is present ? " + geocoder.isPresent()); Location loc = params[0]; List<Address> addresses = null; try {
103 / 118
// Call the synchronous getFromLocation() method by passing in the lat/long values. addresses = geocoder.getFromLocation(loc.getLatitude(), loc.getLongitude(), 1); } catch (IOException e) { e.printStackTrace(); System.out.println("Erreur: " + e.toString()); } System.out.println("Adresses: " + addresses); if (addresses != null && addresses.size() > 0) { Address address = addresses.get(0); // Format the first line of address (if available), city, and country name. addressText = String.format("%s, %s, %s", address.getMaxAddressLineIndex() > 0 ? address.getAddressLine(0) : "", address.getLocality(), address.getCountryName()); System.out.println("Adresse: " + addressText); } return addressText; } }
11.17 Capteurs
104 / 118
Main.java
Main.java
public class Main extends Activity { @Override protected void onStop() { // Surtout, ne pas oublier de dtacher l'couteur ! manager.unregisterListener(myListener); super.onStop(); } SensorManagerSimulator manager; SensorEventListener myListener; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //SensorManager manager = (SensorManager)getSystemService(SENSOR_SERVICE); manager = SensorManagerSimulator.getSystemService(this, SENSOR_SERVICE); manager.connectSimulator(); // Cration d'un couteur qui ragit aux vnements de l'acclromtre myListener = new SensorEventListener() { public void onSensorChanged(SensorEvent event) { if (event.type == Sensor.TYPE_ACCELEROMETER) { float x,y,z; x = event.values[0]; y = event.values[1]; z = event.values[2]; TextView t = (TextView)findViewById(R.id.text1); t.setText("x = " + x); } } public void onAccuracyChanged(Sensor sensor, int accuracy) { // TODO Auto-generated method stub } }; // Ajout d'un couteur pour l'acceleromtre manager.registerListener(myListener , manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) , SensorManager.SENSOR_DELAY_UI); } }
11.18 Camera
MainActivity.java
public class MainActivity extends Activity { Camera mCamera; Preview mPreview;
105 / 118
Preview.java
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPreview = new Preview(this); setContentView(mPreview); // A faire dans un thread part en principe boolean ok = safeCameraOpen(0); if (ok) mPreview.setCamera(mCamera); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true; } private boolean safeCameraOpen(int id) { boolean qOpened = false; try { releaseCameraAndPreview(); mCamera = Camera.open(id); qOpened = (mCamera != null); } catch (Exception e) { Log.e(getString(R.string.app_name), "failed to open Camera"); e.printStackTrace(); } return qOpened; } private void releaseCameraAndPreview() { mPreview.setCamera(null); if (mCamera != null) { mCamera.release(); mCamera = null; } } }
Preview.java
package jf.andro.ac; import java.io.IOException; import java.util.List; import android.content.Context;
106 / 118
Preview.java
class Preview extends ViewGroup implements SurfaceHolder.Callback { SurfaceView mSurfaceView; SurfaceHolder mHolder; Camera mCamera; List<Size> mSupportedPreviewSizes; Size mPreviewSize; Preview(Context context) { super(context); mSurfaceView = new SurfaceView(context); addView(mSurfaceView); // Install a SurfaceHolder.Callback so we get notified when the // underlying surface is created and destroyed. mHolder = mSurfaceView.getHolder(); mHolder.addCallback(this); mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } public void setCamera(Camera camera) { if (mCamera == camera) { return; } stopPreviewAndFreeCamera(); mCamera = camera; if (mCamera != null) { List<Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes(); mSupportedPreviewSizes = localSizes; requestLayout(); try { mCamera.setPreviewDisplay(mHolder); } catch (IOException e) { e.printStackTrace(); } /* Important: Call startPreview() to start updating the preview surface. Preview must be started before you can take a picture. */ mCamera.startPreview();
} } /** * When this function returns, mCamera will be null. */ private void stopPreviewAndFreeCamera() { if (mCamera != null) {
107 / 118
Preview.java
/* Call stopPreview() to stop updating the preview surface. */ mCamera.stopPreview(); /* Important: Call release() to release the camera for use by other applications. Applications should release the camera immediately in onPause() (and re-open() it in onResume()). */ mCamera.release(); mCamera = null; } } @Override public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { // TODO Auto-generated method stub } @Override public void surfaceCreated(SurfaceHolder arg0) { // The Surface has been created, acquire the camera and tell it where // to draw. try { if (mCamera != null) { mCamera.setPreviewDisplay(mHolder); } } catch (IOException exception) { Log.e("W", "IOException caused by setPreviewDisplay()", exception); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { // Surface will be destroyed when we return, so stop the preview. if (mCamera != null) { /* Call stopPreview() to stop updating the preview surface. */ mCamera.stopPreview(); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (changed && getChildCount() > 0) { final View child = getChildAt(0); final int width = r - l;
final int height = b - t; int previewWidth = width; int previewHeight = height; if (mPreviewSize != null) { previewWidth = mPreviewSize.width; previewHeight = mPreviewSize.height; }
108 / 118
11.19 JNI
// Center the child SurfaceView within the parent. if (width * previewHeight > height * previewWidth) { final int scaledChildWidth = previewWidth * height / previewHeight; child.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height); } else { final int scaledChildHeight = previewHeight * width / previewWidth; child.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2); } } } }
11.19 JNI
MainActivity.java
public class MainActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView text = (TextView)findViewById(R.id.texte); text.setText("5+7 = " + NativeCodeInterface.add(5, 7)); } public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true; } }
NativeCodeInterface.java
public class NativeCodeInterface { public static native int calcul1(int x, int y); public static int add(int x, int y) { int somme; somme = calcul1(x,y); return somme; } static { System.loadLibrary("testmodule");
109 / 118
test.c
} }
test.c
#include "andro_jf_jni_NativeCodeInterface.h" JNIEXPORT jint JNICALL Java_andro_jf_jni_NativeCodeInterface_calcul1 (JNIEnv * je, jclass jc, jint a, jint b) { return a+b; }
andro_jf_jni_NativeCodeInterface.h
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class andro_jf_jni_NativeCodeInterface */ #ifndef _Included_andro_jf_jni_NativeCodeInterface #define _Included_andro_jf_jni_NativeCodeInterface #ifdef __cplusplus extern "C" { #endif /* * Class: andro_jf_jni_NativeCodeInterface * Method: calcul1 * Signature: (II)I */ JNIEXPORT jint JNICALL Java_andro_jf_jni_NativeCodeInterface_calcul1 (JNIEnv *, jclass, jint, jint); #ifdef __cplusplus } #endif #endif
110 / 118
12 Bibliographie
12 Bibliographie
PA DA Cookbook Dalvik E-mail SO SD API-Service XML VL BAS MARA AT HWM JSONREST JNI ON PT DBSQL SS Emulator ATK JQM UF CAM SIGN LSAS KEK
Programmation Android, de la conception au dploiement avec le SDK Google Android, Damien Guignard, Julien Chable, Emmanuel Robles, Eyrolles, 2009. Decompiling Android, Godfrey Nolan, Apress, 2012. Android Cookbook, Ian F. Darwin, O'Reilly Media, decembre 2011. Article Wikipedia, from Wikipedia, the free encyclopedia. Email sending in Android, Oleg Mazurashu, The Developer's Info, October 22, 2009. Android permissions: Phone Calls: read phone state and identity, stackoverflow. Utiliser les services sous Android, Nicolas Druet, Developpez.com, Fvrier 2010. Service API changes starting with Android 2.0, Dianne Hackborn, 11 Fvrier 2010. Working with XML on Android, Michael Galpin, IBM, 23 Jun 2009. How to Position Views Properly in Layouts, jwei512, Think Android, Janvier 2010. Basics of Android : Part III Android Services, Android Competency Center, Janvier 2009. Implementing Remote Interface Using AIDL, Marko Gargenta, novembre 2009. Multithreading For Performance, Gilles Debunne, 19 juillet 2010. MapView Tutorial, 20 aout 2012. How-to: Android as a RESTful Client, Cansin, 11 janvier 2009. Tutorial: Android JNI, Edwards Research Group, CC-BY-SA, Avril 2012. Tab Layout, Android developers. Processes and Threads, Android developers. Data storage: Using Databases, Android developers, Novembre 2010. Sensor Simulator. Android Emulator. AsyncTask. Getting Started with jQuery Mobile. <uses-feature>. Controlling the Camera. Signing Your Applications. [android-developers] Launching a Service at the startup, Archives googlegroups, R Ravichandran and Dianne Hackborn, Juillet 2009. Comment on fait un jeu pour smartphone ?, Kek, 2011.
111 / 118