En el primer tutorial sobre el widget WebView, vimos que este componente encapsula el motor html/JavaScript WebKit , y cómo utilizarlo para implementar un sencillo navegador. En este tutorial se va a mostrar de forma muy simple cómo utilizar WebView para diseñar las interfaces gráficas (o algunas partes de ellas) de nuestras aplicaciones Android utilizando las tecnologías web soportadas por WebKit (HTML 5, JavaScript, CSS 3…) haciendo que estas interfaces puedan interactuar con nuestras Activities.
La principal ventaja de la utilización de tecnologías web para el desarrollo de aplicaciones móviles, conocidas como apps híbridas, es que permite a los programadores web la creación de apps sin necesitar apenas conocimientos de programación Android o incluso Java. Esto aumenta exponencialmente el número de programadores que pueden trabajar en la plataforma. Asimismo, otra gran ventaja es que este tipo de aplicaciones son relativamente fáciles de portar entre plataformas ya que las apps no son ejecutadas directamente por el SO sino por el componente WebView en Android o UIWebView en iOS por ejemplo y , gracias a Cordova, la integración de estas aplicaciones con las funcionalidades del SO resulta también independiente de la plataforma.
Sin embargo, el desarrollo de este tipo de apps tiene un precio: una gran pérdida de rendimiento debido a ejecutar la aplicación sobre un WebView y no de forma nativa. No obstante esta penalización es cada vez menor debido al imparable y rápido aumento de la potencia de los dispositivos móviles y, el menos en el caso de Android, la mejora del propio WebView a partir de Android 4.4 Kit Kat.
El presente tutorial es muy básico y simplemente mostrará los conocimientos mínimos necesarios para integrar interfaces web contenidas en un WebView con Activities a través de JavaScript y los mecanismos proporcionados por Android. Para el desarrollo de apps completas con tecnologías web ya existe una miríada de frameworks muy completos como Ionic o Sencha Touch aunque en última instancia la base de su funcionamiento es lo que veremos en este tutorial.
Aplicación de pruebas
Como es habitual, vamos a crear un proyecto de pruebas para Eclipse ADT como base para el tutorial. En esta ocasión usaremos Android 4.2 (luego veremos por qué) aunque el target mínimo será Android 2.1. Tendremos una Activity y un layout que incluya un WebView como único widget, por lo que por eficiencia utilizaremos la etiqueta merge:
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" > <WebView android:id="@+id/webView" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </merge>
Nota: en esta web podemos saber qué versión de WebKit proporciona el WebView de una API de Android.
Este proyecto base, que he llamado «WebView UI Demo», se puede descargar aquí y tiene la siguiente estructura:
Invocar métodos Java desde el WebView
La interacción WebView-Activity se realiza mediante JavaScript. Vamos a probarlo con un ejemplo sencillo: un input y un botón que envía su contenido a un método Java que se encargará de mostrar el contenido recibido en un AlertDialog.
En primer lugar, realizamos la página xhtml y sus recursos asociados (.css.js, imágenes) y lo ubicamos todo dentro del directorio assets. El .xhtml es el siguiente:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>WebView UI demo</title> <link rel="stylesheet" type="text/css" href="resources/styles.css"/> </head> <body> <div id="title">WebView UI Demo</div> <div id="content1" class="content"> <div id = "form" class="center"> <input type="text" id="input" maxlength="50" /> <input type="button" class="button" value="Send to Activity" onclick="MainActivity.getFromWebView(document.getElementById('input').value)" /> </div> </div> </body> </html>
El .css con los estilos, dentro de assets/resources
body { margin:5px; -webkit-user-modify: read-write-plaintext-only; -webkit-tap-highlight-color:rgba(0,0,0,0); } .button { -webkit-border-radius:5px; } .button:hover { background-color: #e8e8e8; } #title { font-size:x-large; color: blue; } .content { border-radius: 10px; background-color: grey; } .center { margin:auto; width:70%; }
Nota: el «truco» para ocultar el resaltado o highlight por defecto de Android en los elementos dentro del WebView lo he copiado de aquí.
Como podemos ver, la función JavaScript que se invoca al pulsar el botón es una llamada a MainActivity.getFromWebView(value) ¿Qué es lo que está invocando? Pues una clase Java que hemos indicado al WebView ( addJavascriptInterface) que se usará en JavaScript con el alias «MainActivity» (puede ser el nombre que queramos) y que contiene un método llamado getFromWebView que recibe un String. La implementación de esta clase, que por comodidad la haremos como una clase interna de la Activity, tendrá el siguiente código:
class JavascriptManager { @JavascriptInterface public void getFromWebView(String value) { Builder builder = new Builder(MainActivity.this); builder.setIcon(android.R.drawable.ic_dialog_info); builder.setCancelable(false); builder.setNeutralButton(R.string.ok, null) builder.setTitle(R.string.info); builder.setCancelable(false); builder.setMessage(value); builder.create().show(); } }
Nota: si se usa como target de la aplicación la API 17 (Android 4.2) es obligatorio utilizar la anotación @JavascriptInterface (y obviamente compilar el proyecto contra esta versión de la API) y que el método sea público.
Por último, unimos todas las piezas. Tal y como se ha comentado, hay que indicarle al WebView que la clase JavascriptManager será accesible a través de JavaScript mediante el nombre MainActivity. El código completo del onCreate queda tal que así:
@SuppressLint("SetJavaScriptEnabled") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); webView = (WebView) findViewById(R.id.webView); webView.getSettings().setJavaScriptEnabled(true); webView.addJavascriptInterface(new JavascriptManager(), "MainActivity"); webView.loadUrl("file:///android_asset/ui.xhtml"); }
En este ejemplo, hemos visto lo sencillo que resulta invocar métodos Java y enviarle datos desde el WebView, pero ¿qué pasa si queremos recibir información? Pues es muy simple: basta con hacer que el método llamado devuelva un String con los datos deseados. Si estos presentan cierta estructura, la cadena devuelta desde Java podría ser por ejemplo un documento JSON que procesaremos en JavaScript.
Vamos a ver este concepto con un ejemplo sencillo añadiendo un botón que haga que se muestre en pantalla los nombres de nuestros contactos. Este botón mediante una función JavaScript llamará a un nuevo método que se va definir en la clase JavascriptManager y que devolverá los nombres listos para ser mostrados directamente como un listado en html. Recogeremos la cadena con estos datos y la mostraremos en pantalla.
El xhtml quedará así:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>WebView UI demo</title> <link rel="stylesheet" type="text/css" href="resources/styles.css"/> <script type="text/javascript"> <![CDATA[ function showContacts() { //here we are calling a Java method!! var data = MainActivity.getContacts(); document.getElementById('contact').innerHTML = data; } ]]> </script> </head> <body> <div id="title">WebView UI Demo</div> <div id="content1" class="content"> <div id = "form" class="center"> <input type="text" id="input" maxlength="50" /> <input type="button" class="button" value="Send to Activity" onclick="MainActivity.getFromWebView(document.getElementById('input').value)" /> </div> </div> <div id="content2" class="content" style="margin-top:10px"> <input type="button" class="button" value="Contacts" onclick="showContacts();" /> <div id ="contact"> </div> </div> </body> </html>
El nuevo método
@JavascriptInterface public String getContacts() { Cursor contacts = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null); StringBuffer data = new StringBuffer(); while(contacts.moveToNext()) { int nameFieldColumnIndex = contacts.getColumnIndex(PhoneLookup.DISPLAY_NAME); data.append(contacts.getString(nameFieldColumnIndex)+ "<br/>"); } contacts.close(); return data.toString(); }
Y no se nos puede olvidar añadir el permiso correspondiente al manifest
<uses-permission android:name="android.permission.READ_CONTACTS"/>
El resultado (al usar el emulador hay que tener en cuenta que habrá que añadir primero algunos contactos)
jQuery
Vamos a incluir jQuery en nuestra app y aplicar una animación ya que hoy en día es muy habitual utilizar librerías de JavaScript porque simplifican notablemente tanto la creación de interfaces interactivas como el desarrollo con JavaScript. Para ello, descargamos la última versión estable en el momento de escribirse este tutorial (en estos momentos la 1.9) y la ubicamos en resources dentro de assets. Vamos a utilizarla para animar la visualización de los contactos. Nuestro xhtml quedará finalmente así
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>WebView UI demo</title> <link rel="stylesheet" type="text/css" href="resources/styles.css"/> <script src="resources/jquery19.js"></script> <script type="text/javascript"> <![CDATA[ function showContacts() { //here we are calling a Java method!! var data = MainActivity.getContacts(); if (data != "") { $('#contacts').html(data); $('#contacts').slideDown(1000); } else { $('#contacts').html("no contacts found"); $('#contacts').css('display','block'); } } ]]> </script> </head> <body> <div id="title">WebView UI Demo</div> <div id="content1" class="content"> <div id = "form" class="center"> <input type="text" id="input" maxlength="50" /> <input class="button" type="button" value="Send to Activity" onclick="MainActivity.getFromWebView(document.getElementById('input').value)" /> </div> </div> <div id="content2" class="content" style="margin-top:10px"> <input class="button" type="button" value="Contacts" onclick="showContacts();" /> <div id ="contacts" style="display:none"> </div> </div> </body>
La demo completa en ICS
i18n
Advertencia: con los cambios de esta sección, la demo no funciona ni en el emulador de Gingerbread ni en el de Jelly Bean 4.2 (sólo para la imagen de Intel, en la de ARM funciona). Esto se debe a bugs en estas imágenes, en dispositivos reales funciona (al menos hasta donde he podido probar).
Gracias a una duda de un lector, actualizo el tutorial ofreciendo una posible solución para la utilización del mecanismo de i18n de Android dentro de interfaces web. Una posibilidad es enviar los textos localizados como parámetros al WebView pero me he decantado por hacer un «wrapper» para el método getString de la Activity que exponga este método al WebView a través de JavaScript.
Creamos el wrapper en la Activity. Las cadenas la obtendremos por su clave y no por su id (la constante de tipo entera generada en la clase R).
@JavascriptInterface public String getString(String key) { String text = ""; int id = getResources().getIdentifier(key, "string", getPackageName()); if (id > 0) { text = MainActivity.this.getString(id); } return text; }
Ahora la dificultad está en invocar este método y obtener los textos necesarios. Usando JQuery la interfaz quedará tal que así:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>WebView UI demo</title> <link rel="stylesheet" type="text/css" href="resources/styles.css"/> <script src="resources/jquery19.js"></script> <script type="text/javascript"> <![CDATA[ function showContacts() { //here we are calling a Java method!! var data = MainActivity.getContacts(); if (data != "") { $('#contacts').html(data); $('#contacts').slideDown(1000); } else { $('#contacts').html(new String(MainActivity.getString("nocontacts"))); $('#contacts').css('display','block'); } } $( document ).ready(function() { $('#b_contacts').prop('value', MainActivity.getString("contacts")); $('#b_send').prop('value',MainActivity.getString("send")); }); ]]> </script> </head> <body> <div id="title">WebView UI Demo</div> <div id="content1" class="content"> <div id = "form" class="center"> <input type="text" id="input" maxlength="50" /> <input id="b_send" class="button" type="button" onclick="MainActivity.getFromWebView(document.getElementById('input').value)" /> </div> </div> <div id="content2" class="content" style="margin-top:10px"> <input id="b_contacts" class="button" type="button" onclick="showContacts();" /> <div id ="contacts" style="display:none"> </div> </div> </body> </html>
El resultado final (no olvidad añadir los textos a los strings.xml!!)
Demo completa en GitHub
Se encuentra disponible en GitHub una demo completa de todo lo visto en el presente tutorial. Para más información sobre cómo utilizar GitHub, consultar este artículo.
Muy bueno, y completo
Hola, gracias por tus artículos sobre webview. He desarrollado una pagina que se refresca cada 1.5 segundos, cuando la abro desde mi navegador, el refresco no se nota, pero si la enlazo en una app mediante webview el refresco se nota ya que queda negra la pantalla un instante mientras recarga la pagina nuevamente. Sabes como evitar eso de alguna forma? Saludos y Gracias.
Estimado muy buen tutorial!!!
Una consulta, se pbauede usar php en vez de javascript???
Saludos!
WebView renderiza interfaces web (html + css + JavaScript), no ejecuta scripts en PHP.
Gran tutorial, pero tengo un problema, intento probar tu primer ejemplo y cuando pulso el botón me explota (VM aborting). Sabes por qué podría ser?
Saludos y gracias.