Existen muchas circunstancias por las cuales alguna solución requiera utilizar información de mas de 1 fuente de datos de la cual no somos propietarios. Por ejemplo esto se da cuando queremos utilizar la información publicada en algún sitio web para el cual no contamos el acceso a la API y lo único que tenemos para interactuar es la misma pagina.
Esta situación poco deseada plantea varias posibles soluciones. Una de las primeras en venir a la mente del Desarrollador es crear una interfaz mediante un servicio web que entregue la información de la pagina web en algún formato como JSON. Sin embargo esto no siempre es posible de realizar por lo que la solución elegida termina siendo algo mas "simple": interactuar con la pagina y parsear el HTML.
Esta solución no es la mas elegante pero aun tiene algunas ventajas menores y creo que eso ya lo sabes por lo que sin mas cuestionamientos vayamos al código.
Requisitos
- Recomendado: Tener una instalación funcional de Android Studio 2.1 o superior
Paso 1. Creación del proyecto
Crearemos un proyecto nuevo para este ejercicio el cual llamaremos JSInterface eligiendo como versión mínima compatible de Android la numero 18 (API level 18, Android 4.3 Jelly Bean). Te recomiendo que si quieres ir directo al código para resolver el problema salta directo al paso 3.

Agregaremos una actividad en blanco con el layout por defecto que nos ofrece Android Studio.

Y finalizamos el asistente.
Paso 2. Agregar una WebView
En la estructura del proyecto buscaremos el layout "activity_main" y lo modificaremos para que se vea como el siguiente código XML:
"Archivo: activity_main.xml"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.innercode.jsinterface.MainActivity"> <WebView android:id="@+id/act_main_wv" android:layout_width="match_parent" android:layout_height="match_parent"> </WebView> </RelativeLayout> |
Paso 3. JavaScript
La primera solución que vamos a abordar será con JavaScript y para ello debemos realizar unos ajustes antes de poder utilizarlo de manera correcta. A continuación buscaremos el archivo MainActivity.java y lo editaremos para que se vea de la siguiente forma.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package com.innercode.jsinterface; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.webkit.WebView; public class MainActivity extends AppCompatActivity { private WebView mWebView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mWebView = (WebView) findViewById(R.id.act_main_wv); mWebView.getSettings().setJavaScriptEnabled(true); mWebView.getSettings().setDomStorageEnabled(true); mWebView.addJavascriptInterface(new MyJavaScriptInterface(), "Interface"); mWebView.setWebViewClient(new MyWebViewClient()); mWebView.loadUrl("https://www.google.com.mx");} } |
Lo destacable de este código es lo siguiente:
- mWebView.getSettings().setJavaScriptEnabled(true): como podrás imaginar este linea de código es necesaria para que nuestra WebView pueda reconocer y ejecutar el código JavaScript
- mWebView.getSettings().setDomStorageEnabled(true): esta linea de código permitirá que podamos inyectar código JavaScript y manipular el DOM siendo esta la manera como interactuaremos con la pagina web.
- mWebView.addJavascriptInterface(new MyJavaScriptInterface(), "Interface"): esta linea de codigo hará que Android inyecte un objeto llamado "Interface" dentro de la pagina web y que desde allá podamos ejecutar con JavaScript métodos de Java definidos dentro de MyJavaScriptInterface.java.
- mWebView.setWebViewClient(new MyWebViewClient()): con esta instrucción podremos sobreescribir algunos métodos con los cuales modificaremos el comportamiento o ejecutaremos sentencias adicionales para incorporar nuestra lógica.
A continuación veamos la implementación de MyJavaScriptInterface.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package com.innercode.jsinterface; import android.util.Log; import android.webkit.JavascriptInterface; /** * Created by Carlos Cano on 12/05/2016. */ public class MyJavaScriptInterface { @JavascriptInterface public void logHtml() { Log.d("=====>", "Java from Android"); } } |
Lo que debemos resaltar de esta clase es lo siguiente:
- import android.webkit.JavascriptInterface: si en nuestra configuración el targetSDKVersion es igual o superior a la API 17 (Android 4.2) será necesario agregar la anotación @JavascriptInterface a los métodos que queramos que sean accesibles desde la interface JavaScript, de lo contrario obtendremos un mensaje en consola del tipo "Uncaught Type Error: Object [object Object] has no method 'logHtml'".
A continuación veamos la implementación de MyWebViewClient.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package com.innercode.jsinterface; import android.webkit.WebView; import android.webkit.WebViewClient; /** * Created by Carlos Cano on 16/05/2016. */ public class MyWebViewClient extends WebViewClient { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); injectJS(view); } private void injectJS(WebView webview) { try { webview.loadUrl("javascript:(function() {" + "document.getElementById('lst-ib').value='Obtener contenido HTML de una WebView en Android';" + "document.getElementsByName('btnK')[0].click();" + "})()"); } catch (Exception e) { e.printStackTrace(); } } } |
Vamos a comentar lo mas importante de esta clase:
- onPageFinished(Webview view, String url): este método se ejecuta justo antes de que la pagina sea mostrada al usuario, los recursos (excepto algunos multimedia) han sido cargados exitosamente y están listos para ser mostrados. Este es el lugar donde procedemos a inyectar nuestro código JavaScript.
- injectJS(WebView webview): este método inyecta básicamente dos instrucciones, seguramente habrás notado que la pagina que utilizaremos para este proyecto es la pagina https://www.google.com.mx/ en ella con la primera linea de código JavaScript seleccionamos el cuadro de texto y asignamos un valor, mientras tanto con la segunda linea ejecutamos el método click() simulado la interacción.
Paso 4. Ejecutando y resolviendo posibles errores
Cuando corremos esta aplicación en un Dispositivo Físico o en un Dispositivo Virtual (AVD) por lo general obtenemos el resultado esperado viendo al cargarse la pagina de Google por un breve momento esta cambia y muestra los resultados de la busqueda. Pero en otras circunstancias esto no sucede asi, veamos una imagen del resultado esperado y posteriormente continuaremos con algunas recomendaciones para resolver algunos de los posibles problemas que estés experimentando.
Recomendaciones
- Si por algún motivo decidiste inyectar tu interfaz JavaScript en alguno de los eventos de tu cliente, puede que este sea el motivo por el cual obtienes un error. Se recomienda que lo hagas fuera del cliente y antes de que la pagina ejecute la instrucción loadUrl o alguna de sus variantes ya que con ello Android decidirá el momento oportuno de inyectar la interfaz.
- Si eres de los que te gusta tunear tu ambiente de desarrollo, revisa que ProGuard no este modificando tu código @JavascriptInterface, de hecho la plantilla por defecto de proguard tienen algunos comentarios sobre esta situación. Modifica y agrega al final de tu proguard-rules.pro estas instrucciones para que se vea de la siguiente manera:
1 2 3 4 5 6 7 8 9 10 11 | -keepclassmembers class * { @android.webkit.JavascriptInterface <methods>; } -keepattributes JavascriptInterface -keep public class com.mypackage.MyClass$MyJavaScriptInterface -keep public class * implements com.mypackage.MyClass$MyJavaScriptInterface -keepclassmembers class com.mypackage.MyClass$MyJavaScriptInterface { <methods>; } -keepattributes *Annotation*
|
- Revisa que tu dispositivo te permita escribir en consola con Log.d. Este es un caso especial donde el dispositivo por alguna modificación o motivo extraño ya no muestra la información con esta instrucción. Intenta utilizar Log.i o alguna otra variante y verifica que este funcionando correctamente. He visto que también que algunos comandos sobre el abd corrigen esta situación pero esto se encuentra fuera del ámbito de este post por lo que recomiendo investigar.
- Verifica que tengas el permiso para conectarte a internet, parece obvio pero puede llegar a ocurrir.
1 | <uses-permission android:name="android.permission.INTERNET" /> |
- ¿Y si todo lo anterior no surte efecto? Bien a continuación verás una segunda forma de obtener el contenido HTML de tu WebView.
WebChromeClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package com.innercode.jsinterface; import android.util.Log; import android.webkit.ConsoleMessage; import android.webkit.WebChromeClient; /** * Created by Carlos Cano on 16/05/2016. */ public class MyWebChromeClient extends WebChromeClient { @Override public boolean onConsoleMessage(ConsoleMessage cm) { Log.i("=====>", cm.message()); return true; } } |
La clase que acabas de ver te permitirá obtener lar porción del contenido HTML que tu quieras siempre y cuando mediante el JavaScript inyectado que vimos anteriormente mandes a consola el HTML. Para agregar este nuevo cliente solo basta agregar una linea de código a nuestro MainActivity.
1 2 3 | //Antes de cargar la URL mWebView.setWebChromeClient(new MyWebChromeClient()); mWebView.loadUrl("https://www.google.com.mx"); |





