Nous avons créé un premier projet appelé Hello Android afin d’installer notre environnement de développement. Nous avons :
- installer Android Studio
- Créé un projet Android Studio appelé Hello Android
- Et utiliser GitHub pour archiver notre projet Android Studio.
Nous allons maintenant commencé la partie utile de notre développement en créant la base d’un projet Hybride. Un projet Hybride Android utilise :
- le code java pour les accès matériels du terminal : lecteur Code barre, disque local, …
- des pages HTML pour la couche de présentation et l’interface utilisateur.
Cet article reprend les bases pour la création d’une application mobile hybride avec Android Studio.
WebView, des pages Html pour la création d’une application mobile hybride Android Studio
WebView est un composant graphique. Ce composant intègre la visualisation des pages html avec le css et le javascript. WebView est basé sur le navigateur webkit.
Création d’un projet Empty Activity pour Hello WebView
Nous créons un nouveau projet dans Android Studio.
Nous choisissons [New Project](1).
Android Studio contient des profils d’activités différents.
Pour ce projet, nous prenons Empty Activity.
Nous allons fournir la description du projet ainsi que son répertoire :
Le code de l’application minimale est générée :
L’application générée est fonctionnelle. Nous pouvons l’activer dans le simulateur :
Modification du code pour ajouter le composant WebView
Nous allons modifier le fichier activity_main.xml pour remplacer la partie <TextView…/> par un <WebView… /> :
<WebView android:id="@+id/webViewG" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:layout_alignParentEnd="true" android:layout_alignParentBottom="true" />
La modification dans Android Studio :
Notons l’id du WebView @+id/webViewG. Cet id sera utilisé pour chargé le composant dans le code MainActivity.java.
Nous devons définir une référence à notre composant graphique webview, afin de pouvoir agir sur son contenu. Nous allons aussi définir un WebViewClient qui nous permettra d’interagir avec la composant webview.
La classe MainActivity.java est modifiée comme suit.
Présentation de la classe MainActivity.java :
public class MainActivity extends AppCompatActivity { public static WebView webView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //on récupère la référence à notre webview à patir de l'id utilisé dans activity_main.xml webView = (WebView)findViewById(R.id.webViewG); webView.setWebViewClient(new AppWebViewClient(getApplicationContext())); webView.loadUrl("http://127.0.0.2/index.html"); } }
La ligne 10 permet de retrouver le webview défini dans activity_main.xml et de créer la version java de ce composant.
la ligne 11 enregistrer une classe client personnalisée qui permettra de filtre les requêtes http.
La ligne 13 charge la page index dans le webview. Nous prenons une adresse locale 127.0.02 pour identifier nos requêtes.
Nous allons créer la classe AppWebViewClient qui va permettre d’attraper les requêtes émises par le WebView afin de les traiter en local.
Présentation de la classe AppWebViewClient.java :
public class AppWebViewClient extends WebViewClient { private Context currentContext = null; public AppWebViewClient(Context applicationContext) { currentContext = applicationContext; } @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { String urlString=request.getUrl().toString(); WebResourceResponse response = null; try { URL url = new URL(urlString); if( url.getProtocol().startsWith("http") && url.getHost().equalsIgnoreCase("127.0.0.2")) { AppWebResponse webResponse = AppLocalResource.LocalFileCall(url.getPath().substring(1), currentContext); response = new WebResourceResponse(webResponse.contentType, null, new ByteArrayInputStream(webResponse.bodyData)); } } catch (Exception e) { // nothing } return(response); } }
Nous interceptons les requêtes, et nous demandons à AppLocalResource de trouver le fichier en local sur le disque du terminal.
La ligne 9 donne la méthode à surcharger pour intercepter la requête
La ligne 14 contrôle la requête afin de vérifier si c’est bien une requête à traiter en local
La ligne 15 délègue à AppLocalResource la lecture du fichier sur le disque
La ligne 16 construit la réponse au format Android WebResourceResponse.
La classe AppLocalResource va lire le fichier et le transmettre dans la réponse AppWebResponse.
Attention, lors des copies d’écran, la classe AppLocalResource.java est nommée AppLocalProxy.java, Le renommage de la classe AppLocalProxy en AppLocalResource a été réalisée après les copies d’écran.
Présentation de la classe AppLocalResource.java :
public class AppLocalResource { public static AppWebResponse LocalFileCall(final String filePath, final Context currentContext) { try { String filePath2 = filePath; if (filePath2.startsWith(".")) { filePath2 = filePath2.substring(1); } InputStream fileData = null; try { System.out.println("AppLog.AppLocalProxy.LocalFileCall : filePath2="+filePath2); fileData = currentContext.getAssets().open(filePath2); } catch(IOException e) { throw e; } int responseLength = fileData.available(); byte[] fileDataArray = new byte[responseLength]; do { responseLength -= fileData.read(fileDataArray); } while(responseLength > 0); String mimeType = "text/html"; if(filePath2.endsWith(".js")) { mimeType = "text/javascript"; } else if(filePath2.endsWith(".css")) { mimeType = "text/css"; } else if(filePath2.endsWith(".png")) { mimeType = "image/png"; } else if(filePath2.endsWith(".svg")) { mimeType = "image/svg+xml"; } return(new AppWebResponse(200, mimeType, fileDataArray, "")); } catch (Exception e) { return(new AppWebResponse(404, "", null, "")); } } }
La classe contient une méthode consistant à trouver dans l’url le nom du fichier et à lire le fichier sur le disque. Nous complétons la réponse afin de traiter les erreurs selon le protocole http. Le retour contient le mimeType ainsi que le statut, soit 200 pour une réponse conforme, et 404 en cas d’erreur.
La classe AppWebResponse défini la structure d’une réponse type.
Présentation de la classe AppWebResponse.java :
public class AppWebResponse { public int statusCode; public String contentType; public byte[] bodyData; public String headersString; public AppWebResponse(int statusCode, String contentType, byte[] bodyData, String headersString) { this.statusCode = statusCode; this.contentType = contentType; this.bodyData = bodyData; this.headersString = headersString; } }
Nous avons fini la réalisation du code permettant de traiter en locale les url demandées par le WebView.
Nous allons maintenant créé un fichier index.html très simple de type « Hello WebView ! »
Présentation du fichier index.html :
<!DOCTYPE html> <html> <head> <title>Page hello webview</title> </head> <body> Hello WebView Android! </body> </html>
La structure de notre projet dans Android Studio est maintenant la suivante :
Nous pouvons compiler l’application et l’exécuter dans le simulateur.
Comment ajouter les pages html pour la création de notre application mobile hybride dans Android Studio
Pour ajouter des ressources assets, nous devons créer un répertoire assets. Nous nous plaçons à la racine du projet sur [app], puis dans le menu fichier on choisi [New], [Folder] et [Assets Folder].
Une boite s’ouvre permettant de configurer le dossier Assets. En particulier on note la phrase « create a source root for assets which will be include innthe APK » (crée un dossier pour les documents à insérer dans l’APK). C’est bien notre souhait, nous voulons que les fichiers html soient embarqués avec l’application dans l’APK que nous distribuons.
Pour créer un fichier html dans Assets, nous nous plaçons sur assets, et nous choisissons [New] puis [File].
Une boite de saisie permet de donner le nom du fichier à créer. Nous devons renseigner l’extension de notre fichier pour définir son type. Ici nous indiquons index.html.
Une page d’edition en mode html s’ouvre.
Nous copions-codons le code html pour index.html.
Nous avons montré la création d’une application mobile hybride avec Android Studio. Notre application contient la première page index.html. Nous allons ajouter d’autres pages.
Utilisation du WebView pour la création de plusieurs pages HTML dans notre application mobile hybride avec Android Studio
Nous venons de voir comment implémenter un Proxy Local pour gérer dans l’application java les requêtes http de WebView et fournir le fichier HTML présent sur le disque du terminal. Nous allons mettre en place maintenant la navigation d’une page à l’autre.
Pour ce faire, nous allons ajouter deux autres pages, et ajouter des liens et des boutons dans ces pages afin de passer d’une page à l’autre. Ces pages seront enregistrées comme la page index.html dans le dossier Assets, en local sur le disque du terminal.
Description de la page 2 :
<!DOCTYPE html> <html> <head> <title>Page 2</title> </head> <body> Nous sommes sur la page 2 !<br> <a href="/index.html">Retour à la page index</a> </body> </html>
Description de la page 3 :
<!DOCTYPE html> <html> <head> <title>Page 3</title> </head> <body> Voilà la page 3 !<br> <a href="/index.html">Retour à la page index</a><br> <button type="button" onclick="location.href='page2.html'"> Nous préférons la page 2 </button> </body> </html>
Nous modifions la page index :
<!DOCTYPE html> <html> <head> <title>Page hello webview</title> </head> <body> Hello WebView Android!<br> <a href="/page2.html">Nous allons vers la page 2</a><br> <button type="button" onclick="location.href='page3.html'"> Nous demandons la page 3 </button> </body> </html>
Nous remarquons que le lien fonctionne mais pas le boutton. En effet, nous avons configuré notre WebView avec le paramétrage minimum.
Nous devons ajouter en ligne 12 la propriété setJavaScriptEnabled(true);
public class MainActivity extends AppCompatActivity { public static WebView webView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //on récupère la référence à notre webview à patir de l'id utilisé dans activity_main.xml webView = (WebView)findViewById(R.id.webViewG); webView.setWebViewClient(new AppWebViewClient(getApplicationContext())); webView.getSettings().setJavaScriptEnabled(true); webView.loadUrl("http://127.0.0.2/index.html"); } }
Nous voyons dans le simulateur les différentes pages.
Nous avons activé la trace logcat (3) dans Android Studio. Le message console System.out.println de la ligne 18 dans AppLocalResource (1) affiche les différentes url demandées. Pour éviter de surcharger la trace, nous commençons notre message par AppLog, et nous indiquons AppLog dans le filtre des message (2).
Remarque, la copie d’écran indique AppLocalProxy. Nous avons changé le nom de cette classe en AppLocalReource.
Nous avons maintenant la création d’une application mobile hybride avec Android Studio. Nous avons plusieurs page et nous pouvons passer d’une page à l’autre.
Les appels de page fonctionnent selon la méthode get de Http. Voyons maintenant comment utiliser la méthode post de http avec WebView.
Comment envoyer des données par la méthode POST depuis un WebView Android
Actuellement WebView d’Android ne permet pas de gérer les données transmises par un post. La classe Google WebResourceRequest ne fournit aucun moyen de récupérer le corps de la requête dans le cas de méthode post.
Nous devons donc traiter autrement l’envoie de données du client vers le serveur (ici vers notre AppLocalResource).
Pour ce faire, nous allons utiliser la communication entre le javascript des pages html et les objets java de l’application Android. Cela se fait en utilisant javascriptInterface.
Nous créons une page html identification.html. Dans cette page nous créons 2 champs de saisie : nom et prénom, ainsi qu’un bouton [enregistrer]. Lorsque nous utilisons le bouton [enregistrer] un code javascript récupère les valeurs des champs nom et prénom et contact l’objet Utilisateur.java à travers la méthode enregistrerUtilisateur(nom, prénom). la méthode donne en retour le texte « l’utilisateur prénom nom est enregistré ». que nous affichons dans un champs message.
Création de la page identification.html
<!DOCTYPE html> <html> <head> <title>Page identification</title> <script> //enregistre l'utilisateur function enregistrer(){ let prenom = document.getElementById("prenom").value; let nom = document.getElementById("nom").value; let messageText= "l'utilisateur " + prenom + " " + nom + " est enregistré"; document.getElementById("messageZone").textContent = messageText; } </script> </head> <body> Identifiez-vous :<br> <label for="prenom">Prénom: </label> <input type="text" id="prenom"/> <br> <label for="nom">Nom: </label> <input type="text" id="nom"/> <br> <button type="button" onclick="enregistrer()"> Enregistrer </button> <button type="button" onclick="location.href='index.html'"> Retour accueil </button> <br> <span id="messageZone"></span> </body> </html>
Pour appeler la page identification, nous modifions la page index.html
<!DOCTYPE html> <html> <head> <title>Page hello webview</title> </head> <body> Hello WebView Android!<br> <a href="/page2.html">Nous allons vers la page 2</a><br> <button type="button" onclick="location.href='page3.html'"> Nous demandons la page 3 </button> <button type="button" onclick="location.href='identification.html'"> Identification </button> </body> </html>
Nous avons ajouté le bouton Identification créant l’appel à la page identification.html.
Nous vérifions le bon fonctionnement de la page. Puis nous ajouterons le code de communication avec l’application java Android.
Création de la classe Utilisateur.java
public class Utilisateur { Context context; Utilisateur(Context context){ this.context=context; } @JavascriptInterface public String enregistrer(String nom, String prenom) { System.out.println("AppLog.Utilisateur.enregistrer : nom="+nom+", prenom="+prenom); return "OK"; } }
Notez bien au dessus de la méthode enregistrer(String nom, String prenom), l’annotation @JavascriptInterface. Toutes les méthodes qui doivent pouvoir être appelées depuis javascript doivent être annotées avec @JavascriptInterface.
Nous devons maintenant déclarer notre classe à WebView pour qu’il puisse l’utiliser.
public class MainActivity extends AppCompatActivity { public static WebView webView; private Utilisateur utilisateur; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); utilisateur = new Utilisateur(this); //on récupère la référence à notre webview à patir de l'id utilisé dans activity_main.xml webView = (WebView)findViewById(R.id.webViewG); webView.setWebViewClient(new AppWebViewClient(getApplicationContext())); webView.getSettings().setJavaScriptEnabled(true); webView.addJavascriptInterface(utilisateur, "Utilisateur"); webView.loadUrl("http://127.0.0.2/index.html"); } }
Nous avons ajouté :
Ligne 3 : on garde un lien vers l’objet utilisateur
Ligne 9 : on crée l’objet utilisateur
Ligne 15 : on ajoute l’interface entre le WebView et l’objet utilisateur avec addJavascriptInterface.
Le second parametre de addJavascriptInterface peut être n’importe quel nom. Nous avons gardé « Utilisateur » par simplicité. Il est interessant de mettre une majuscule car cela simplifie la lecture du code javascript dans la page identification.html.
Nous ajoutons maintenant dans la page identification.html l’appel à Utilisateur.java
Modification de la page identification.html :
<!DOCTYPE html> <html> <head> <title>Page identification</title> <script> //efface la note function enregistrer(){ let prenom = document.getElementById("prenom").value; let nom = document.getElementById("nom").value; let messageRetour = Utilisateur.enregistrer(nom, prenom); let messageText= "l'utilisateur " + prenom + " " + nom + " est enregistré"; document.getElementById("messageZone").textContent = messageText+" : "+messageRetour; } </script> </head> <body> Identifiez-vous :<br> <label for="prenom">Prénom: </label> <input type="text" id="prenom"/> <br> <label for="nom">Nom: </label> <input type="text" id="nom"/> <br> <button type="button" onclick="enregistrer()"> Enregistrer </button> <button type="button" onclick="location.href='index.html'"> Retour accueil </button> <br> <span id="messageZone"></span> </body> </html>
Nous avons ajouter :
Ligne 10 : l’appel à la méthode enregistrer de l’objet Utilisateur. Le nom utilisé « Utilisateur » est le nom passer en second paramètre de addJavascriptInterface, dans MainActivity.java
Ligne 12 : nous complétons avec la valeur de retour messageRetour le message affiché dans messageZone.
Nous voyons à travers cette approche que la page html permet en javascript de transférer des informations au code java Android, et d’obtenir un texte en retour.
Le résultat dans le simulateur Android Studio :
Le simulateur sur la page identification.html avec le retour de Utilisateur.java
Notez dans la zone Logcat tout en bas de l’image, le texte affiché par la méthode enregistrer de Utilisateur.java. Cela confirme que les données ont bien été reçues par java dans Android.
Lorsque nous souhaitons un retour avec du texte plus structuré, nous passons par un objet json. La communication se fait en transformant le json en String du côté java, et en parsant la valeur reçu au format texte vers un objet json javascript. Nous montrerons cela dans un prochain article.
Nous avons montré la création de la communication entre la partie Web de notre application mobile hybride et la partie java d’Android Studio.
Comment envoyer des données depuis java Android vers une page html de WebView
Nous avons plusieurs moyens pour exécuter du code dans le WebView à l’initiative de l’application java Android :
- demander l’évaluation de code javascript par la méthode :
evaluateJavascript(String fonctionJavascript, ValueCallback<String> resultCallBack) - poster un message par :
postWebMessage(WebMessage message, Uri targetOrigin)
La description de ces méthodes WebView se trouve dans la documentation android
Nous allons mettre en œuvre un exemple de code pour activer un message depuis le code java.
Nous allons utiliser la méthode post(Runnable action) héritée de android.view.View et qui permet d’ajouter le Runnable à la queue des messages (messageQueue) de WebView. Comme le Runnable sera lancé par le WebView, celui-ci sera exécuté dans le UI thread de WebView.
Pour notre exemple d’utilisation, nous allons voir comment transférer le message des touches Android vers la page de WebView.
Ce code est à ajouter dans MainActivity.java, et surcharge la méthode onBackPressed :
@Override public void onBackPressed() { System.out.println("AppLog.MainActivity.onBackPressed"); webView.post(new Runnable() { @Override public void run() { String jsonString = null; try { jsonString = new JSONObject() .put("keyCode", KeyEvent.KEYCODE_BACK) .toString(); } catch (JSONException e) { // nothing } MainActivity.webView.postWebMessage(new WebMessage(jsonString), Uri.parse("http://127.0.0.2/device")); } }); }
Le message arrive dans la page en cours. Nous devons mettre le code javascript de réception des messages :
window.addEventListener(« message », messageReceiver);
Tous les messages arriverons dans messageReceiver et les traitements des messages peuvent être personnalisés dans la page.
Nous ajoutons le code suivant dans la page identification.html :
window.addEventListener("message", messageReceiver); function messageReceiver(e) { console.log("messageReceiver",e); try { let jsonString = JSON.parse(e.data); if (jsonString.keyCode) { keyCodeEvent(jsonString.keyCode); } } catch (e){ //nothing } } function keyCodeEvent(keyCode){ console.log("keyCodeEvent",keyCode); if(keycode==4){//KEYCODE_BACK let messageText="keycodeEvent : KEYCODE_BACK"; document.getElementById("messageZone").textContent = messageText; } }
Nous avons ajouter des traces console.log afin de visualiser l’arrivée du message dans la page.
Nous avons le résultat attendu. Nous avons cliqué sur le bouton retour du simulateur (1). Le message s’est affiché dans la page (2). Nous voyons la trace dans la partie Logcat (3). Nous allons maintenant regarder la trace console.log (4).
Nous disposons maintenant de la création de la communication entre la partie java et la partie web de notre application mobile hybride dans Android Studio.
Comment debugguer notre projet lors de la création d’une application mobile hybride avec Android Studio
Google fournit la moyen d’analyser la page web qui s’affiche dans notre simulateur. Pour ce faire, nous utilisons Inspect des outils Chrome Développeur.
Nous ouvrons Chrome et dans l’url nous saisissons chrome://inspect.
Une fenêtre s’ouvre et après une petite attente, nous avons la partie Remote Target quis’affiche. Nous cliquons sur inspect (1) pour activer l’ouverture d’une page Chrome DevTool. La page active de notre simulateur est présentée. Nous pouvons analyser notre page. En particulier, nous voyons les traces console.log() :
Nous voyons que nous avons un EventListener (1). Nous retrouvons notre donnée dans messageEvent.data (2). Notre message est bien reçu par la méthode keyCodeEvent (3).
Nous avons vu comment utilisé les outils de débogages pour la création d’une application mobile hybride avec Android Studio.
En résumé
Nous avons montré comment réaliser une application hybride qui utilise tout la puissance de java et des technologie web (html, css, javascript) pour créé une application mobile.
Nous aons montré comment réaliser les appels depuis javascript d’une page vers le code java, ce qui permet d’accéder aux fonctionnalités du matériel.
Nous avons montré comment générer des évènements depuis java vers le WebVieuw grâce à post et postMessage.
Nous sommes maintenant équiper pour la création d’une application mobile hybride avec Android Studio.
Merci pour cet article très intéressant.
Merci.
Dans le prochain article, nous allons montrer comment créer l’interface utilisateur de notre application mobile avec des web-components réutilisables !
Je ne connaissais pas cette manière de faire, très interessant !
Merci. Dans la suite nous allons voir comment utiliser les web composants…