viernes, 20 de mayo de 2016

Obtener contenido HTML de una WebView con JavaScript en Android

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");



miércoles, 20 de agosto de 2014

Mac OSX Yosemite BETA password




Tratando de mantenerme al día, hoy les comparto mi experiencia al instalar la ultima versión de MAC OSX Yosemite al día de esta publicación. Se trata de un error en la instalación que dejará frustrado al usuario intentando colocar su password una y otra vez sin poder iniciar sesión con éxito diciéndole que el password es incorrecto, incluso si el usuario no tenia asignado ningún password.

En principio uno puede alarmarse preguntándose ¿Que pasará con toda mi información? o ¿Como podré continuar? pero este tipo de cosas suelen suceder con los BETA. Afortunadamente la solución es sencilla, vamos decirle al instalador que nos cree un nuevo usuario Administrador para poder ingresar y recuperar nuestra antigua cuenta o simplemente usar la nueva, los pasos son:


  1. Reiniciar la Mac
  2. Tan pronto escuchemos que encienda oprimir el botón de "cmd" (también conocido como botón de Apple)
  3. Se abrirá una terminal y deberas esperar unos momentos hasta que se detenga
  4. Ejecuta los siguientes comandos
    1. mount -uw /
    2. rm /var/db/.AppleSetupDone
    3. shutdown -h now
  5. Después de esto veras que tu Mac enciende correctamente y continua con los pasos faltantes de la instalación. También te permitirá crear un nuevo usuario Administrador con el cual podrás hacer dos cosas.
    1. Continuar alegremente con tu prueba de Yosemite e ignorar la información perdida
    2. Utilizar tu nueva cuenta de Administrador para cambiar el password de tu antigua cuenta y recuperar tu acceso. Esto se logra siguiendo estos pasos 


¡Disfruta la nueva BETA!

martes, 28 de agosto de 2012

Extras de la cámara disponible en México

Por fin ha aparecido la App Extras de la Camara en el Nokia Collection del Marketplace. Al menos para  los Lumia 710 con Windows Phone 7.5 Tango (8737) de Telcel en Mexico. Simplemente hay que hacer  una de las cosas siguientes:


  • Buscarla en el Marketplace como "extras de la camara"
  • Entrar en el Nokia Collection, seleccionar cualquier App, en los detalles tocar la opción "Mas Aplicaciones de Nokia Corporation" y hasta el final deberia aparecer.

O la opción mas fácil de todas...
  • Oprimir el botón físico de la Lupa de nuestro Windows Phone, tocar el icono del Vision (el ojo) y apuntar a la siguiente imagen:


lunes, 23 de julio de 2012

Como crear una ventana de EULA, términos o disclaimer en Android

Después de muchas horas de arduo trabajo llega el ansiado momento de liberar la primera versión de nuestra aplicación al publico. Es en ese momento donde muchos desarrolladores concienzudos se plantean el agregar un mensaje para al usuario final el cual le comunique todos los detalles relacionados al uso de su aplicación.

Sin entrar en detalles acerca de los distintos tipos de Licencias de Software también conocidos como End User License Agreement (EULA), hoy les quiero compartir unas porciones de código que les ayudarán a implementar de manera fácil este tipo de "ventanas" que se muestran al inicio de nuestra aplicación y que solo aparecen una sola vez... claro si el usuario esta de acuerdo con todo.

Partiendo del supuesto de que ya se tiene una aplicación terminada, les compartiré únicamente las porciones necesarias que deberán añadir a su proyecto. Aquellos que quieran probarlo tambien lo pueden hacer, pero tendrán que crearse un proyecto con dos Activities al menos.

Paso uno

Antes de todo, deberán estar de acuerdo en que al usar las porciones de código aquí compartidas y todos los pasos que ahora llamaremos "El contenido", lo aceptan "COMO ES" y "SIN NINGUNA GARANTÍA" de que funcione, que quiza me falto algun import, o me falto aclarar alguna instrucción, variable... =D bueno ya, mejor vamos al paso uno.

Lo primero que haremos es sobrescribir el método onResume de la Activity donde queramos que se muestre el EULA, que generalmente es la primera o una de las primeras en mostrase al usuario. Para aquellos que no están familiarizados con dicho método solo les puedo decir que es parte del ciclo de vida de las aplicaciones Android y que se llama cuando nuestra App se vuelve la aplicación activa del usuario. Si ya lo tienen reescrito solo agreguen la linea que les falta.



Paso dos

Dado que no todos tenemos la facilidad y los conocimientos para escribir un EULA, les comparto la siguiente platilla EULA a la que solo le deberán poner el nombre de ustedes donde están los corchetes.

Plantilla EULA: Descargar
Pegar en:


Paso tres

Gracias al proyecto de código abierto de WordPress para android, he podido extraer la siguiente clase que nos permitirá crear ventanas (Dialogs) de forma muy sencilla. Tiene varios métodos útiles de los cuales solo usaremos uno. Quiza al integrar esta clase en su proyecto les resulte mejor modificar el package que tiene (com.blogger.innercode.myapp.helpers) por uno mas adecuado.

Clase AlertUtil.java: Descargar

Paso cuatro

Ahora si vamos a implementar el método processEULA(). A groso modo lo que hace este método es invocar otro método que le permitirá saber si debe mostrar el EULA, si es así entonces genera la ventana (Dialog) y le asigna el comportamiento a los botones de "Aceptar" y "Declinar".



Paso cinco

Para finalizar y evitar que nuestro IDE siga mostrandonos mas errores, es necesario implementar dos métodos mas que ya estamos usando en el código anterior.

Método: checkEULA
Descripción: Crea una tabla donde almacena una valor booleano que indica si se ha acetado el EULA o no.



Método: acceptEULA
Descripción: Actualiza el valor booleano de la tabla anterior para indicar que ya se ha aceptado el EULA.



¡Listo!

Ahora solo falta ejecutar. El resultado debe ser muy parecido al siguiente... se ve mas profesional nuestra app no?

miércoles, 2 de mayo de 2012

Windows Phone y JQuery Metro

El dia de hoy les traigo una pequeña prueba que he realizado con JQuery Metro. Se trata de unos archivos JavaScript y CSS3 que nos ayudan a dibujar una interfaz del tipo Metro, la cual es usada en Windows Phone y Windows 8. En esta prueba se va a tratar de imitar la aplicación del Hub de Contactos.

Archivos requeridos (descargar):


Primer paso

Lo que vamos a hacer es crear la siguiente estructura de archivos, donde agregaremos un archivo Index.html con la estructura básica y el body vacio. Ademas vease que ya agregamos los archivos que descargamos en una carpeta llamada lib.








Segundo paso

Ahora vamos a referenciar los archivos que hemos descargado añadiendo las correspondientes lineas de codigo en el area de head del Index.html.


Tercer paso

En el body agregaremos las siguientes instrucciones donde crearemos dos listas, una de Contactos y otra de Favoritos, cada una en un div diferente.


Resultado

Si seguimos los pasos correctamente veremos el siguiente resultado al abrir el archivo Index.html en nuestro navegador:



Hará falta que ajusten la ventana un poco para que se vea como en la imagen. Prueben dando click en Favoritos...

Este es un proyecto que se encuentra actualmente en desarrollo y que ya cuenta con mucha funcionalidad. Ademas provee la gran mayoría de los controles existentes para Windows Phone. Si no me creen vean el sitio del proyecto desde su Windows Phone (de preferencia) o navegador.

Aqui hay otro buen post acerca de lo mismo.

miércoles, 25 de abril de 2012

Cargar y mostrar imagen de la web en Android

El día de hoy les comparto dos formas de cargar una imagen desde la web. Ambas formas provienen de programadores que han querido compartir sus conocimientos, tiempo y experiencia, y que han compartido los codigos fuente de sus clases para cargar y mostrar imágenes desde la web en nuestro Android.

 Primera forma

Archivo: LoaderImageView.java
Autor: Blundell

Lo único que tenemos que hacer es


  1. Copiar el archivo LoaderImageView en nuestro proyecto o copiar el codigo y crear uno con el mismo nombre.
  2. Cambiar el "package" que apunta a uno que el autor definió como  package diablo.douban.common;  por el que corresponda a nuestra estructura de packages/carpetas. Con esto ya no marcará error.
  3. En la actividad donde deseamos cargar y mostrar la imagen agregamos las siguientes lineas de codigo




Segunda forma

La segunda forma es similar pero usando una tres clases muy bien hechas y que se recomienda sean utilizadas si se va a crear una aplicación para la Android Market que ahora se llama Google Play. Este grupo de clases trabajan en conjunto para cargar de manera asincrona una imagen, guardarla en cache/BD cierto tiempo y recuperarla de ahi hasta que expire el tiempo especificado (un dia, una semana, infinito, ect.) Este es uno de esos aportes que uno realmente agradece dada la calidad y esfuerzo requerido.

Archivos:

UrlImageViewHelper.java
UrlImageCache.java
SoftReferenceHashTable.java

Autor: Koushik Dutta

Descargar codigo fuente

Los pasos para hacer funcionar estas clases son prácticamente los mismo que el ejemplo anterior, la diferencia es que aquí necesitamos tres archivos de código y corregir los tres en su sección del package. Para usarlo hacemos lo siguiente:






Listo!

martes, 10 de abril de 2012

Crear conexion https selfsigned en Android

Una de las tareas mas laboriosas de investigacion y desarrollo es precisamente crear una conexion https en Android con un certificado Selfsigned donde a menudo cuando persiste el problema veremos la excepcion "javax.net.ssl.SSLException: Not trusted server certificate". Hay mucha información en foros pero lo cierto es que no siempre funcionan para todos las soluciones que se plantean. A continuacion muestro como lo he hecho yo.

Este post esta basado en: Este otro post de stackoverflow

Primer paso:

Descargar BouncyCastleProvider

Segundo paso

Pegar el archivo en la carpeta donde se encuentra el JavaRuntime "Java\jre6\bin" o el compilador de Java . Lo que interesa es que se encuentre el archivo keytool.jar que viene por defecto con Java.

Tercer paso

Abrir tu navegador preferido (Chrome, IE, Firefox, etc) y pegar en la barra de direcciones la URL a la que quieres conectarte (https). Da click ante cualquier botón de Aceptar/Continuar que te aparezca. Ahora veras que en la barra de direcciones a la izquierda o derecha (dependiendo del navegador) hay un candado o cerrojo.

Dar un click sobre el candado y luego dar click en la opción Datos del Certificado/Mostrar mas detalles.

Aparecerá una ventana y dependiendo del OS (Windows, Mac, Linux, etc) tendremos que buscar la opción que nos permita guardar el certificado. En el caso de Windows este botón se encuentra en la pestaña "Detalles" y el botón dice "Copiar en archivo...".

La siguiente serie de pasos de puede resumir en "Siguiente... Siguiente" hasta que les de la opcion de elegir el lugar donde se va a guardar el certificado.
Lo vamos a guardar en el mismo lugar donde pusimos el archivo de BouncyCastleProvider y le pondremos el nombre "certificate.cer" al guardarlo.

Cuarto paso

Abrir la consola/terminal y colocarnos en la carpeta donde guardamos el certificate.cer y el BouncyCastleProvider. Luego escribir o copiar (cuidado de no copiar los Enter porque no funcionará) la siguiente instruccion:



Si todo salio bien en la carpeta donde hemos guardado los anteriores archivos deberia existir ahora un archivo llamado "mykeystore.bks".

Quinto paso

Copiar el archivo y pegarlo en la carpeta "res/raw" de su proyecto Android. Si la carpeta raw no existe hay que crearla.

Sexto paso

Ahora lo que tenemos que hacer es que en nuestra aplicación, al crear la conexión se deberá cargar el archivo que hemos creado. A continuación proporciono una clase simple que deberán copiar a su proyecto.



Septimo paso

El paso final es que en su actividad hagan uso de la clase que acabamos de crear con las siguientes instrucciones:



Esto ha funcionado para mi y espero funcione para ti. Otra forma es usando EasySSLSocketFactory y aqui hay un post muy bueno acerca de como hacerlo funcionar Ver el post!