Android WebView: incrustar un navegador en nuestras apps


VERSIONES

  1. 19/05/2012 (Primera publicación)
  2. 28/10/2012:
    • Añadida nota a la introducción sobre la versión de WebKit
    • Tratamiento de favicon (sólo en el código de GitHub)
  3. 06/12/2012:
    • Integración del favicon con el código de ejemplo del tutorial
    • Comprobación de conectividad (anteriormente incluído en los comentarios)
    • Solucionado el problema que no mostraba el teclado virtual en los input
    • Gestión de rotaciones
    • Launcher icon personalizado
  4. 16/02/2013:
    • Descargas asíncronas
    • Activación de plugins
    • Actualización del entorno de pruebas
    • Enlaces a los tips Android relacionados
  5. 17/11/2013:
    • Código revisado y probado con Android 4.3 y 4.4
    • Mejorado historial (sólo en demo de GitHub)
    • Añadida descripción de novedades de WebView en Android 4.4
    • Sólo se ofrece como código de ejemplo la demo completa en GitHub para facilitar el mantenimeinto y actualización del artículo.

NOTA

Este artículo explica la mayor parte de una demo más completa publicada en GitHub.


android

Android SDK proporciona un widget denominado WebView para renderizar páginas web, ya sea obteniéndolas través de una URL o bien recibiendo el html directamente desde una Activity.
WebView está basado en el proyecto Open Source WebKit, que incluye un motor de renderizado de html y un intérprete de javascript. WebKit es utilizado en numerosas aplicaciones y es la base de navegadores como Safari, ChromiunGoogle Chrome o Midori.

En este artículo se va a exponer de forma práctica cómo incrustar y utilizar en una app un navegador web; WebView, además de renderizar html, es muy potente y altamente configurable y proporciona numerosos métodos para controlar la navegación, gestionar cookies, procesar los errores, etc. El uso que veremos será básico y orientado a la navegación web, ya que con WebView se podría incluso realizar las interfaces gráficas de nuestra app en html y utilizar javascript para ejecutar código Android al estilo de PhoneGap. Para este uso he escrito otro artículo.

Nota: Para este tutorial se usará la API 7 (Android 2.1), pero la versión de WebKit dependerá de la versión de Android del dispositivo en el que se ejecute la demo. Para conocerla, bastará con ver el user-agent con el que se identifica el widget WebView o, más fácil todavía, visitar http://jimbergman.net/webkit-version-in-android-version/ .

Entorno de pruebas:

Requisitos: Conocimientos muy básicos de Android y Eclipse ADT.

Ejemplo básico

No nos complicamos la vida y creamos un proyecto de tipo Android con el asistente. Voy a usar la API 7 (2.1) para conseguir la mayor compatibilidad posible y que es más que suficiente para el propósito de este mini-tutorial.

New Android Project

WebView se utiliza como cualquier otro widget, lo incluímos en el main.xml creado por el asistente (y eliminamos también el Hello World):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    
    <WebView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/webkit"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</LinearLayout>

En nuestra Activity, establecemos la página que queremos mostrar:

package com.danielme.blog.android.webviewdemo;

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;

public class WebViewdemoActivity extends Activity
{
	private WebView browser;

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		browser = (WebView)findViewById(R.id.webkit);

                //habilitamos javascript y el zoom
		browser.getSettings().setJavaScriptEnabled(true);
		browser.getSettings().setBuiltInZoomControls(true);

		//habilitamos los plugins (flash)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO)
		{
			webview.getSettings().setPluginState(PluginState.ON);
		}
		else
		{
			//IMPORTANTE!! este método ha sido eliminado en Android 4.3 
                        //por lo que si lo necesitamos para mantener la compatibilidad 
                        //hacia atrás hay que compilar el proyecto con Android 4.2 como máximo
			webview.getSettings().setPluginsEnabled(true);
		}
		

		browser.loadUrl("http://danielme.com");
	}
}

También necesitaremos en el AndroidManifest.xml solicitar permiso para la conexión a Internet.

<uses-permission android:name="android.permission.INTERNET"/>

Veamos que tal se renderiza WordPress en un móvil ejecutando el proyecto en el emulador de ADT para la API 7.

Si se pulsa un enlace, Android lo abrirá con la aplicación por defecto para tal fin, esto es, el propio navegador. Si queremos permanecer en nuestra app hay que especializar la clase WebViewClient y sobreescribir el método shouldOverrideUrlLoading para que devuelva false. Puesto que en este método se recibe como parámetro la url solicitada se podría utilizar también para filtrar las direcciones a las que se permite el acceso.

package com.danielme.blog.android.webviewdemo;

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;

public class WebViewdemoActivity extends Activity
{
	private WebView browser;

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		browser = (WebView)findViewById(R.id.webkit);
		
                //habilitamos javascript y el zoom
		browser.getSettings().setJavaScriptEnabled(true);
		browser.getSettings().setBuiltInZoomControls(true);
                
                //habilitamos los plugins (flash)
		browser.getSettings().setPluginsEnabled(true);
		
                browser.loadUrl("http://danielme.com");
		
		browser.setWebViewClient(new WebViewClient()
		{
                        // evita que los enlaces se abran fuera nuestra app en el navegador de android
			@Override
			public boolean shouldOverrideUrlLoading(WebView view, String url)
			{
				return false;
			}	
			
		});
	}	

}

Barra de progreso

En el ejemplo anterior, el usuario no puede saber si una página se está cargando hasta que WebView la muestre, creando la sensación de que el navegador no responde. Afortunadamente es fácil proporcionar una barra de progreso con el widget ProgressBar y haciendo uso de la clase WebChromeClient para saber la evolución del proceso de carga de la página. Esta barra la ocultaremos una vez haya finalizado la carga de la página para ahorrar espacio en pantalla.

Primero añadimos la barra a nuestra interfaz, por ejemplo justo arriba del navegador.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ProgressBar
        android:id="@+id/progressbar"
        style="@android:style/Widget.ProgressBar.Horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="3dp" 
        android:visibility="gone"/>

    <WebView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/webkit"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</LinearLayout>

Añadimos el código que gestionará la barra:

package com.danielme.blog.android.webviewdemo;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;

public class WebViewdemoActivity extends Activity
{
        private WebView browser;

	private ProgressBar progressBar;
	
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		browser = (WebView)findViewById(R.id.webkit);
		
                 //habilitamos javascript y el zoom
		browser.getSettings().setJavaScriptEnabled(true);
		browser.getSettings().setBuiltInZoomControls(true);

                //habilitamos los plugins (flash)
		browser.getSettings().setPluginsEnabled(true);      

		browser.loadUrl("http://danielme.com");
		
		browser.setWebViewClient(new WebViewClient()
		{
                        // evita que los enlaces se abran fuera nuestra app en el navegador de android
			@Override
			public boolean shouldOverrideUrlLoading(WebView view, String url)
			{
				return false;
			}	
			
		});

		progressBar = (ProgressBar) findViewById(R.id.progressbar);
		
		browser.setWebChromeClient(new WebChromeClient() 
		{
			@Override
		    public void onProgressChanged(WebView view, int progress) 
		    {				
			    progressBar.setProgress(0);
			    progressBar.setVisibility(View.VISIBLE);
		        WebViewdemoActivity.this.setProgress(progress * 1000);

		        progressBar.incrementProgressBy(progress);

		        if(progress == 100)
		        {
		        	progressBar.setVisibility(View.GONE);
		        }
		    }
		});
	}	

}

El resultado:

WebView y barra de progreso

Lamentablemente la barra de progreso no permite hacer demasiadas florituras, así que hay que ingeniárselas para personalizar y mejorar su aspecto. Personalmente yo no me complicaría tanto y superpondría sobre la barra un TextView, ubicando ambos widgets en un FrameLayout. Es la soución por la que he optado en el ejemplo que he publicado en GitHub.

También existe la posibilidad de utilizar la barra de progreso propia de Android ubicada debajo de la barra de título de la aplicación tal y como se hace en el navegador de Android. No obstante, esta solución es menos flexible y sólo es válida si nuestra app utiliza la barra de título por defecto, algo que hoy en día no es demasiado habitual.

“Title” de la página

Con WebChromeClient es posible recuperar el title de la página que se esté cargando en cuanto este sea leído gracias al método onReceivedTitle. Esto permite, por ejemplo, mostrarlo en la barra de la aplicación al estilo de los navegadores “tradicionales”.


//la implementación de este método permite para mostrar el title de la página en la barra de título de la aplicación
			@Override
			public void onReceivedTitle(WebView view, String title)
			{
				WebViewdemoActivity.this.setTitle("WebView demo: " + WebViewdemoActivity.this.browser.getTitle());
			}

barra de título WebView

Barra de navegación

Sigamos completando nuestro navegador con una barra de navegación que constará de los siguientes elementos:

  • Campo de texto para introducir una dirección url
  • Botón “Ir” para cargar la url del cuadro de texto
  • Botón de navegación hacia atrás. Sólo se mostrará cuando haya una página anterior a la que volver.
  • Botón de navegación hacia adelante. Sólo se mostrará cuando haya al menos una página por delante en el historial.
  • Botón para cancelar la carga de la página. Habilitado mientras se esté cargando una página.

Usando un RelativeLayout y el editor visual de ADT añadimos los elementos anteriores a nuestro Layout. Excepto el botón de “Ir” que se mostrará siempre, todos los demás estarán por defecto deshabilitados y se controlarán programáticamente

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ProgressBar
        android:id="@+id/progressbar"
        style="@android:style/Widget.ProgressBar.Horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="3dp" 
        android:visibility="gone"/>

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <EditText
            android:id="@+id/url"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:layout_toLeftOf="@+id/buttonIr"
            android:ems="10"
            android:inputType="textUri" 
            android:selectAllOnFocus="true">
        </EditText>

        <Button
            android:id="@+id/buttonIr"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBaseline="@+id/url"
            android:layout_alignBottom="@+id/url"
            android:layout_alignParentRight="true"
            android:onClick="ir"
            android:text="@string/ir" />

        <Button
            android:id="@+id/buttonAnt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_below="@+id/url"
            android:enabled="false"
            android:onClick="anterior"
            android:text="@string/anterior" />

        <Button
            android:id="@+id/buttonSig"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/url"
            android:layout_toRightOf="@+id/buttonAnt"
            android:enabled="false"
            android:onClick="siguiente"
            android:text="@string/siguiente" />

        <Button
            android:id="@+id/buttonDetener"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/url"
            android:layout_toRightOf="@+id/buttonSig"
            android:enabled="false"
            android:onClick="detener"
            android:text="@string/detener" />
    </RelativeLayout>

    <WebView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/webkit"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</LinearLayout>

barra de navegación - layout

Los métodos asociados a los botones no tiene ninguna complicación, simplemente invocan métodos de WebView. En el caso del botón “Ir” he incluído el código para ocultar el teclado.

package com.danielme.blog.android.webviewdemo;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;

public class WebViewdemoActivity extends Activity
{
        private WebView browser;

	private ProgressBar progressBar;
	
	private EditText url;
	
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		browser = (WebView)findViewById(R.id.webkit);
		
                 //habilitamos javascript y el zoom
		browser.getSettings().setJavaScriptEnabled(true);
		browser.getSettings().setBuiltInZoomControls(true);

                //habilitamos los plugins (flash)
		browser.getSettings().setPluginsEnabled(true);

		browser.setWebViewClient(new WebViewClient()
		{
                        // evita que los enlaces se abran fuera nuestra app en el navegador de android
			@Override
			public boolean shouldOverrideUrlLoading(WebView view, String url)
			{
				return false;
			}	
			
		});

		url = (EditText) findViewById(R.id.url);
		url.setText("http://");
		
		progressBar = (ProgressBar) findViewById(R.id.progressbar);
		
		browser.setWebChromeClient(new WebChromeClient() 
		{
			@Override
		    public void onProgressChanged(WebView view, int progress) 
		    {				
			    progressBar.setProgress(0);
			    progressBar.setVisibility(View.VISIBLE);
		        WebViewdemoActivity.this.setProgress(progress * 1000);

		        progressBar.incrementProgressBy(progress);

		        if(progress == 100)
		        {
		        	progressBar.setVisibility(View.GONE);
		        }
		    }
			

			//la implementación de este método permite para mostrar el title de la página en la barra de título de la aplicación
			@Override
			public void onReceivedTitle(WebView view, String title)
			{
				WebViewdemoActivity.this.setTitle("WebView demo: " + WebViewdemoActivity.this.browser.getTitle());
			}


		});
	}	
	
	// //////////BOTONES DE NAVEGACIÓN /////////

	public void ir(View view)
	{
		// oculta el teclado al pulsar el botón
		InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
		inputMethodManager.hideSoftInputFromWindow(url.getWindowToken(), 0);

		// he observado que si se pulsa "Ir" sin modificarse la url no se
		// ejecuta el método onPageStarted, así que nos aseguramos
		// que siempre que se cargue una url, aunque sea la que se está
		// mostrando, se active el botón "detener"
		((Button) WebViewdemoActivity.this.findViewById(R.id.buttonDetener)).setEnabled(true);

		browser.loadUrl(url.getText().toString());

	}

	public void anterior(View view)
	{
		browser.goBack();
	}

	public void siguiente(View view)
	{
		browser.goForward();
	}

	public void detener(View view)
	{
		browser.stopLoading();
	}

}

Lo más interesante de la barra es cómo controlar la carga de la página para activar/desactivar los botones, y para ello implementaremos dos nuevos métodos en nuestro WebViewClient

  • onPageStarted: Se ejecuta al iniciarse la carga de una página, una vez por cada frame(el control de este último caso no se contempla en este tutorial) .Es aquí donde se habilitará siempre el botón “Detener”.Asimismo, en el cuadro de texto mostraremos la url que se está cargando ya que es posible que se trate de un link que el usuario haya pulsado.
    // gestión de los botones de navegación (inicio de carga de una página)
    			@Override
    			public void onPageStarted(WebView view, String url, Bitmap favicon)
    			{
    				// mostramos la url de los enlaces seleccionados
    				WebViewdemoActivity.this.url.setText(url);
    
    				((Button) WebViewdemoActivity.this.findViewById(R.id.buttonDetener)).setEnabled(true);
    			}
    
  • onPageFinished: Se invocará cuando haya terminado la carga de la página, momento en el que hay que deshabilitar el botón Detener al carecer de sentido. También se gestionarán aquí los botones Anterior y Siguiente.
  • 	// gestión de los botones de navegación (final de carga de una
    			// página)
    			@Override
    			public void onPageFinished(WebView view, String url)
    			{
    				((Button) WebViewdemoActivity.this.findViewById(R.id.buttonDetener)).setEnabled(false);
    				Button botonAnterior = (Button) WebViewdemoActivity.this.findViewById(R.id.buttonAnt);
    
    				if (view.canGoBack())
    				{
    					botonAnterior.setEnabled(true);
    				}
    				else 
    				{
    					botonAnterior.setEnabled(false);
    				}
    
    				Button botonSiguiente = (Button) WebViewdemoActivity.this.findViewById(R.id.buttonSig);
    
    				if (view.canGoForward())
    				{
    					botonSiguiente.setEnabled(true);
    				}
    				else
    				{
    					botonSiguiente.setEnabled(false);
    				}
    			}
    
¿Y el botón Back?

En nuestra demo al pulsar el botón “Back” del dispositivo finalizaremos la Activity, mientras que en el navegador por defecto de Android se vuelve a la página anterior y la Activity no se finaliza hasta que no pulsemos Back desde la primera página mostrada. Este comportamiento puede ser implementado fácilmente, aunque en el caso de que incrustemos el navegador en una app probablemente resulte más conveniente mostrar una barra de navegación y no modificar el comportamiento por defecto del botón Back.

//simula el comportamiento del botón Back en el navegador de Android
	@Override
	public void onBackPressed()
	{
		if (browser.canGoBack())
		{
			browser.goBack();
		}
		else
		{
			super.onBackPressed();
		}
	}

Gestión de errores

WebView mostrará en pantalla los errores que se produzcan, pero podemos recuperalos para actuar en consecuencia implementando el método onReceivedError de WebViewClient.

//gestión de errores
			@Override
			public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) 
			{
				AlertDialog.Builder builder = new AlertDialog.Builder(WebViewdemoActivity.this);
				builder.setMessage(description).setPositiveButton("Aceptar", null).setTitle("onReceivedError");				
				builder.show();
			 }

WebView - OnReceivedError

Descargas

Otra interesante funcionalidad proporcionada por WebView es el listener DownloadListener que nos permitirá saber cuándo WebView considera una url solicitada como descarga. A continuación se expone una posible implementación de ejemplo que, con la aprobación del usuario, descarga el fichero en la tarjeta SD por lo que hay darle permisos de escritura a la aplicación. Por simplicidad los textos se han incluído en el código, en una aplicación real deberían estar en el strings.xml para su abstracción y una posible internacionalización. Para mayor información sobre la obtención de recursos web, consultar el tip #8.Para el uso del espacio de almacenamiento externo, ver el tip #5.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		browser = (WebView) findViewById(R.id.webkit);
		faviconImageView = (ImageView) findViewById(R.id.favicon);
		WebIconDatabase.getInstance().open(getDir("icons", MODE_PRIVATE).getPath());

		// habilitamos javascript y el zoom
		browser.getSettings().setJavaScriptEnabled(true);
		browser.getSettings().setBuiltInZoomControls(true);
		
		//habilitamos los plugins (flash)
		browser.getSettings().setPluginsEnabled(true);

		//implementa la acción a realizar cuando WebView considere que el enlace solicitado es una descarga
		browser.setDownloadListener(new DownloadListener()
		{
			public void onDownloadStart(final String url, String userAgent, String contentDisposition, String mimetype, long contentLength)
			{				
				AlertDialog.Builder builder = new AlertDialog.Builder(WebViewdemoActivity.this);
				builder.setTitle("Descarga");
				builder.setMessage("¿Desea guardar el fichero en su tarjeta SD?");
				builder.setCancelable(false).setPositiveButton("Aceptar", new DialogInterface.OnClickListener()
				{
					public void onClick(DialogInterface dialog, int id)
					{
						descargar(url);
					}
					
				}).setNegativeButton("Cancelar", new DialogInterface.OnClickListener()
				{
					public void onClick(DialogInterface dialog, int id)
					{
						dialog.cancel();
					}
				});
				builder.create().show();				
				
				
			}
			
			private void descargar(final String url)
			{
               String resultado ="";
				
               URL urlObject = null;
               InputStream inputStream = null;
               HttpURLConnection urlConnection = null;
               try
				{
					urlObject = new URL(url);
					urlConnection = (HttpURLConnection) urlObject.openConnection();
						
					inputStream = urlConnection.getInputStream();
	
					//se crea el fichero en el que se almacenará
					String fileName = Environment.getExternalStorageDirectory().getAbsolutePath() + "/webviewdemo";
					File directorio = new File(fileName);
					File file = new File(directorio, url.substring(url.lastIndexOf("/")));
					//asegura que el directorio exista
					directorio.mkdirs();					
					
					FileOutputStream fileOutputStream = new FileOutputStream(file);
					ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
	
					byte[] buffer = new byte[1024];
					
					int len = 0;
					while (inputStream.available() > 0 && (len = inputStream.read(buffer)) != -1)
					{
						byteArrayOutputStream.write(buffer, 0, len);
					}
					fileOutputStream.write(byteArrayOutputStream.toByteArray());
					fileOutputStream.flush();
					resultado = "guardado en : " + file.getAbsolutePath();
				}
				catch (Exception ex)
				{
					resultado = ex.getClass().getSimpleName() + " " + ex.getMessage();
				}
				finally
				{
					if (inputStream != null)
					{
						try
						{
							inputStream.close();
						}
						catch (IOException e)
						{
							
						}
					}
					if (urlConnection != null)
					{
						urlConnection.disconnect();
					}
				}
				
				AlertDialog.Builder builder = new AlertDialog.Builder(WebViewdemoActivity.this);
				builder.setMessage(resultado).setPositiveButton("Aceptar", null).setTitle("Descarga");
				builder.show();
			}

		});

...

diálogo aceptar descarga

diálogo descarga realizada

Sin embargo, esta aproximación al tratamiento de las descargas no es correcta ya que a partir de HoneyComb no se pueden realizar llamadas a url dentro del hilo principal de la aplicación; de lo contrario obtenemos esta bonita excepción (aunque la aplicación no se cierra):

NetworkOnMainThread

El motivo está bien fundamentado: estamos realizando operaciones que pueden tomar bastante tiempo y no podemos dejar bloqueada la interfaz de usuario. Por ello es necesario realizar el proceso de descarga en segundo plano en otro hilo, usando por ejemplo Thread/handler o AsyncTask, estrategias comentadas en la sección Tips Android (#2 y #3 respectivamente). Usando AsyncTask para la descarga, el código quedará tal que así (también se validará la disponibilidad de almacenamiento externo):

 class DownloadAsyncTask extends AsyncTask<String, Void, String>
		{
			@Override
			protected String doInBackground(String... arg0)
			{
				String result = null;
				String url = arg0[0];
				
				if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
				{
					URL urlObject = null;
					InputStream inputStream = null;
					HttpURLConnection urlConnection = null;
					try
					{ 
						urlObject = new URL(url);
						urlConnection = (HttpURLConnection) urlObject.openConnection();
						inputStream = urlConnection.getInputStream();
	
						String fileName = Environment.getExternalStorageDirectory().getAbsolutePath() + "/webviewdemo";
						File directory = new File(fileName);
						File file = new File(directory, url.substring(url.lastIndexOf("/")));
						directory.mkdirs();
	
				
						FileOutputStream fileOutputStream = new FileOutputStream(file);
						ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
						byte[] buffer = new byte[1024];
						int len = 0;
	
						while (inputStream.available() > 0	&& (len = inputStream.read(buffer)) != -1)
						{
							byteArrayOutputStream.write(buffer, 0, len);
						}
	
						fileOutputStream.write(byteArrayOutputStream.toByteArray());
						fileOutputStream.flush();
						result = "guardado en : " + file.getAbsolutePath();
					} 
					catch (Exception ex)
					{
						result = ex.getClass().getSimpleName() + " " + ex.getMessage();
					} 
					finally
					{
						if (inputStream != null)
						{
							try
							{
								inputStream.close();
							} 
							catch (IOException ex)
							{
			                    result = ex.getClass().getSimpleName() + " " + ex.getMessage();
							}
						}
						if (urlConnection != null)
						{
							urlConnection.disconnect();
						}
					}
				}
				else
				{
					result = "Almacenamiento no disponible";
				}

				return result;
			}

			@Override
			protected void onPostExecute(String result)
			{
				 AlertDialog.Builder builder = new AlertDialog.Builder(WebViewdemoActivity.this);
				 builder.setMessage(result).setPositiveButton("Aceptar", null).setTitle("Descarga");
				 builder.show();
			}

		}

Ahora, en lugar de llamar al método descargar(url) lanzamos la AsyncTask:

builder.setCancelable(false).setPositiveButton("Aceptar", new DialogInterface.OnClickListener()
{
	public void onClick(DialogInterface dialog, int id)
	{
		(new DownloadAsyncTask()).execute(url);
	}
...

En Gingerbread se introdujo el servicio DownloadManager diseñado para realizar grandes descargas en segundo plano. Hay un pequeño ejemplo de uso en este tip.

Favicon

En principio utilizar el favicon de las páginas visitadas por WebView parece sencillo ya que el método onPageStarted recibe un Bitmap llamado favicon. Sin embargo, este parámetro siempre es nulo ya que según la documentación la imagen sólo se recibe si ya existe previamente en una base de datos empleada por WebView (*): WebIconDatabase.

(*) Según la documentación oficial a partir de Android 4.3 ya no es necesaria utilizar esta clase por lo que ha sido “deprecada”. Sin embargo, he probado a no usarla y no se reciben los íconos.
WebIconDatabase

Siguiendo las instrucciones del segundo post de este hilo podemos incluir el procesamiento del favicon de forma muy simple y mostrarlo junto a la url:

 
  <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">  
        
      <ImageView
            android:id="@+id/favicon"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_alignBottom="@+id/url"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:src="@drawable/favicon_default" />   

        <EditText
            android:id="@+id/url"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_toLeftOf="@+id/buttonIr"
            android:layout_toRightOf="@+id/favicon"
            android:ems="10"
            android:inputType="textUri" >
     

El icono por defecto que he usado pertenece a esta versión de la colección oficial de gráficos de Google, se encuentra en el el tema “Holo Dark” y se llama originalmente “7-location-web-site.png”. Está disponible en varias densidades.

Ahora para mostrar el favicon adecuado:

  1. Añadir el widget a la Activity
     
    private ImageView faviconImageView;
    
  2. Iniciar WebIconDatabase en el onCreate de la activity:
     
       faviconImageView = (ImageView) findViewById(R.id.favicon);
       WebIconDatabase.getInstance().open(getDir("icons", MODE_PRIVATE).getPath());
    
  3. Recibir el favicon en cuanto sea obtenido por WebView gracias al método onReceivedIcon.Si no se inicia WebIconDatabase este método nunca se invoca.
    @Override
    public void onReceivedIcon(WebView view, Bitmap icon)
    {				
       faviconImageView.setImageBitmap(icon);				
    }
    

El resultado final con el favicon junto a la dirección:

url con favicon - android

Conectividad

Siempre que en una app necesitemos conectividad, es una buena práctica comprobar que la tenemos disponible antes de solicitar cualquier operación y esperar a recibir un timeout o error similar. Para ello hay que hacer uso de ConnectivityManager y NetworkInfo , yo siempre utilizo el siguiente snippet:

private boolean checkConnectivity()
		{
			boolean enabled = true;
	
			ConnectivityManager connectivityManager = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
			NetworkInfo info = connectivityManager.getActiveNetworkInfo();
			
			if ((info == null || !info.isConnected() || !info.isAvailable()))
			{
				enabled = false;
				Builder builder = new Builder(this);
				builder.setIcon(android.R.drawable.ic_dialog_alert);
				builder.setMessage(getString(R.string.noconnection));
				builder.setCancelable(false);
				builder.setNeutralButton(R.string.ok, null);
				builder.setTitle(getString(R.string.error));
				builder.create().show();		
			}
			return enabled;			
		}

Nota: El código anterior es muy básico, y si fuera necesario se puede obtener más información sobre la conexión activa a través del método getType(). Si necesitamos realizar un uso intensivo de la red, puede ser por ejemplo conveniente avisar al usuario si utiliza una conexión de datos móvil en lugar de wifi.

Obviamente necesitamos los nuevos textos en strings.xml:

<string name="noconnection">La conexión a internet no se encuentra disponible en estos momentos</string>
<string name="ok">Aceptar</string>
<string name="error">Error</string>  

También necesitamos añadir al manifest el permiso para acceder al estado de la red, con el de acceso a Internet que ya teníamos no es suficiente:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  

Para cubrir tanto las peticiones realizadas el escribir manualmente la url como los links pulsados, añadimos la validación a los métodos onPageStarted y los de los botones de navegación:

@Override
			public void onPageStarted(WebView view, String url, Bitmap favicon)
			{
				if (checkConnectivity())
				{
					// mostramos la url de los enlaces seleccionados
					WebViewdemoActivity.this.url.setText(url);				

					((Button) WebViewdemoActivity.this.findViewById(R.id.buttonDetener)).setEnabled(true);
				}
				else
				{
					((Button) WebViewdemoActivity.this.findViewById(R.id.buttonDetener)).setEnabled(false);
				}
			}
	
	public void ir(View view)
	{
		// oculta el teclado al pulsar el botón
		InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
		inputMethodManager.hideSoftInputFromWindow(url.getWindowToken(), 0);
		
		if(checkConnectivity())
		{
			// he observado que si se pulsa "Ir" sin modificarse la url no se
			// ejecuta el método onPageStarted, así que nos aseguramos
			// que siempre que se cargue una url, aunque sea la que se está
			// mostrando, se active el botón "detener"
			((Button) WebViewdemoActivity.this.findViewById(R.id.buttonDetener)).setEnabled(true);		
			browser.loadUrl(url.getText().toString());
		}
		
	}

	public void anterior(View view)
	{
		if(checkConnectivity())
		{
			browser.goBack();
		}
	}

	public void siguiente(View view)
	{
		if(checkConnectivity())
		{
			browser.goForward();
		}
	}
			
	

ConnectivityManager

Mostrar el teclado virtual en los input

En algunas pruebas he encontrado un error muy molesto:al hacer click en un input text de una web no se mostraba el teclado y, por lo tanto, no podía hacer uso de él. Parece ser que esto se debe al siguiente bug, la “solución”, simplemente copio y pego, es añadir al onCreate el siguiente código:

browser.setOnTouchListener(new View.OnTouchListener() 
		{ 
			@Override
			public boolean onTouch(View v, MotionEvent event) 
			{
			           switch (event.getAction()) 
			           { 
			               case MotionEvent.ACTION_DOWN: 
			               case MotionEvent.ACTION_UP: 
			                   if (!v.hasFocus()) 
			                   { 
			                       v.requestFocus(); 
			                   }  
			                   break; 
			           } 
			           return false; 
			        }		
			});

Gestionar las rotaciones

Uno de mis últimos tutoriales estuvo dedicado a la gestión de rotaciones en Android. En el navegador que estamos implementando, si rotamos la pantalla la Activity se reinicia tal y como se analizó en ese artículo y volvemos a la pantalla en blanco de inicio, perdiendo lo que tuviéramos en pantalla. Para evitar esto, vamos a ir a la solución sencilla ya que en este caso no tenemos un layout para el modo landscape, y simplemente deshabilitamos la gestión de rotaciones por defecto de Android configurando nuestra única Activity de la siguiente forma:

 <activity
            android:name=".WebViewdemoActivity"
            android:label="@string/app_name" 
            android:configChanges="orientation|keyboardHidden">

Para más detalles, remito al lector al mencionado artículo.

Ejemplo más completo en GitHub

Aunque la demo cumple con lo que quería mostrar cuando me planteé la elaboración de este tutorial (que por otra parte es consecuencia de haber tenido que utilizar WebView en uno de mis proyectos), le he dedicado algo más de tiempo para implementar algunas mejoras:

  • Página de inicio en html
  • Información en la barra de progreso
  • Internacionalización
  • Historial

Esta nueva demo está publicada publicada en GitHub y en la sección de demos del blog. Para más información sobre cómo utilizar GitHub, consultar este artículo.

En el fondo la construcción del navegador no tiene mucho sentido como fin, pero sí como medio y divertimento para seguir aprendiendo a desarrollar en Android. Espero que mi trabajo os haya sido de ayuda ;)

Lo nuevo en Android 4.4 Kit Kat

Una de las grandes novedades de la última versión de Android en el momento de escribir estas líneas es el “nuevo” WebView ya que a partir de esta versión se basa directamente en el motor de Chromium, concretamente en su versión 30. Chromium es el navegador Open Source en el que se basa Google Chrome y según Google con este cambio podemos esperar una notable mejora del rendimiento en la ejecución de javascript gracias a su motor y de CSS gracias a la aceleración hardware. También se mejora el soporte de HTML 5. Este cambio debería repercutir en una importante mejora de las aplicaciones nativas basadas en tecnologías Web como las desarrolladas con PhoneGap. Por otra parte, la API del propio WebView que hemos visto en este artículo no ha sufrido cambios importantes.

Aquí se puede encontrar una pequeña guía de migración para las aplicaciones basadas en WebView.

107 Responses to Android WebView: incrustar un navegador en nuestras apps

  1. alan dice:

    Muchas gracias,me ha sido de gran ayuda

  2. willy dice:

    Muchas gracias, muy útil.

  3. yeahman dice:

    y como se introduciria una pagina html en la app?

    • danielme.com dice:

      Pon el .html en el directorio assets y muéstralo en un WebView:

      webview.loadUrl(“file:///android_asset/pagina.html”);

      También puedes incluir dentro de assets los recursos que quieras (.css, .js, imágenes) y referenciarlos dentro del html del mismo que lo harías en un sitio web estándar.

  4. vanwicho dice:

    Tengo un error en public void onPageStarted(WebView view, String url, Bitmap favicon) me dice Bitmap cannot be resolved to a type

  5. vanwicho dice:

    Hola muchas gracias….

    oye tengo un problema en el parte de
    public void onPageStarted(WebView view, String url, Bitmap favicon)
    me dice:
    Bitmap cannot be resolved to a type
    que puedo hacer ahi???

    • danielme.com dice:

      ¿Tienes el import “import android.graphics.Bitmap;”? La clase Bitmap siempre ha estado en la API de Android

      • vanwicho dice:

        La importe, pero al importarla me marca un error en toda la linea de codigo donde sale Bitmap favicon

      • danielme.com dice:

        Pues no sé qué decirte, me he descargado el proyecto del tutorial y la demo de GitHub y no he tenido el más mínimo problema, quizás Eclipse esté haciendo de la suyas. Al menos he aprovechado y he actualizado el artículo con el uso del favicon.

  6. dklobucaric dice:

    Can you check for internet connection?
    For instance i know there is some errorhandling diference between GPRS(HSPDA…) and the normal WIFI. Is it?

    • danielme.com dice:

      You can check internet connection with ConnectivityManager before loading an url, It ´s a good practice. I always use this snippet:
      ConnectivityManager connectivityManager = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
      NetworkInfo info = connectivityManager.getActiveNetworkInfo();
      if (info == null || !info.isConnected() || !info.isAvailable())
      {
      //no connection
      }
      (you need this permission: <uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” > )

      You can also check the device connection type
      info.getType()–>ConnectivityManager.TYPE_MOBILE, ConnectivityManager.TYPE_WIFI, ConnectivityManager.TYPE_WIMAX…

      If type is mobile, you can even check the subtype
      info.getSubtype()->TelephonyManager.NETWORK_TYPE_UMTS, TelephonyManager.NETWORK_TYPE_LTE, TelephonyManager.NETWORK_TYPE_GPRS …

      WebView uses the active network and you don´t have to worry about it. I don´t know if there are any differences in error handling when you are using WIFI, UMTS,GPRS…but you can always control the connection type, specially if your app sends/downloads a lot of data:

      if (info.getType() != ConnectivityManager.TYPE_WIFI)
      {
      alertdialog: this app only uses wifi
      }

      Hope this helps :)

  7. Marcelo dice:

    Muchisimas gracias!, te agradezco el aporte!

  8. uNbAs dice:

    Buenos dias!

    Estoy mirando el tema del webview para crear una aplicacion que se conecte a un dropbox y que la gente se pueda descargar cosas de dicho dropbox, la aplicación va destinada a unas ROM que recompilo, pero tengo problemas a la hora de generar la descarga y guardarla en un apartado de la SDCARD.

    He descargado tus ejemplos pero tengo el mismo problema que con mi codigo y no se muy bien por que pasa. Te dejo una imagen con el error a ver si me puedes echar un cable

    http://img339.imageshack.us/img339/5606/screenshot2012121103575.png

  9. netqwark dice:

    Hola Daniel, muchísimas gracias por tu aporte, me ha sido de muchísima ayuda y he construido un webview que muestra mi web.

    El único problema que tengo es que al intentar abrir link que apunta un archivo multimedia no salta el reproductor de android.
    Si la web la veo desde el navegador de android el reproductor se abre sin problemas.
    Parece como si la aplicación o no tuviera permisos para abrir el reproductor de medios o no supiera como hacerlo.

    Alguna idea?

    Muchas gracias!

    • danielme.com dice:

      Lo normal es que intentara descargar el archivo. Como todos los links al pulsarlos pasan por shouldOverrideUrlLoading, puedes tratar en ese método cierto tipo de url como por ejemplo los archivos multimedia. Con el siguiente código en el ejemplo de GitHub:
      public boolean shouldOverrideUrlLoading(WebView view, String url)
      {
      if (url.endsWith(“.mp3″))
      {
      Intent intent = new Intent(Intent.ACTION_VIEW);
      intent.setDataAndType(Uri.parse(url),”audio/mpeg”);
      startActivity(intent);
      return true;
      }
      return false;
      }
      si abro un mp3 de una web (he probado con searchmp3.mobi) en mi Nexus 7 puedo seleccionar una aplicación para escuchar el mp3 (y al menos con Google Music se escucha):

      selecionar reproductor nexus 7

      Espero que te sirva. Incluiré este ejemplo en una próxima versión del artículo ;)

  10. Minino dice:

    Muy bien explicado, gracias ^_^

  11. Torrejoncillu dice:

    Muy buen tutorial, pero tengo un pequeño poblema, he echo una apliciación, que muestra una pagina, con una barra de progreso, pero me queda mucha cache como la borro al salir de la aplicación, no he puesto botones tendria que ser con el boton back.

  12. Otra preguntita, en mi blog hay videos de youtube y no se ven como prodria hacer, para poder verlos, en los navegadores de mi movil si se ven normalmente.

  13. Valu dice:

    Muy buen tutorial, pero tengo un pequeño poblema, he echo una apliciación, que muestra una pagina online, con una barra de progreso, pero al intentar descargar un archivo de un link me salta el error NetworkOnMainThreadExceptionnull. Te agradecería si me podes ayudar. Saludos

    • danielme.com dice:

      El problema es que a partir de HoneyComb en el hilo principal de la aplicación (UITHread) no se pueden realizar llamadas a url para evitar que la pantalla se quede bloqueada mientras se realiza la operación ya que ésta puede tardar bastante. Para solucionarlo estas operaciones hay que hacerlas de forma asíncrona, por ejemplo con AsyncTask, de hecho en la demo de GitHub la descarga está implementada así. He aprovechado tu comentario y he actualizado el artículo con esta casuística, espero que ahora no tengas problemas.

  14. Valu dice:

    Solucione el problema de NetworkOnMainThreadExceptionnull y la aplicacion anda barbaro, pero surgio otro problema, al realizar una descarga no salta el gestor de descargas de android, y voy al directorio de descarga de la app y esta el archivo pero con 0 KB. Que puede ser? me vole la cabeza tratando de solucionarlo.

  15. Valu dice:

    Hay alguna posibilidad de gestionar las descargas tal cual lo hace google chrome? que diga iniciando descarga.. y que pase a segundo plano en la barra de notificaciones. Digo alomejor es mucho pedir. Saludos, muy buen tuto a pesar de que todavía no puedo resolver el tema de las descargas.

  16. Mrkz Mtz dice:

    Haber si entendi esto es como una aplicacion nativa web? Por ejemplo tengo sitio x y si lo quiero tener digamos como nativa para android esto me sirve? o ando demasiado perdido!

    • danielme.com dice:

      WebView es un componente gráfico o widget de Android que permite mostrar y ejecutar dentro de sí mismo html/javascript/css. En este artículo se muestra sus posibilidades para usarlo como navegador web mostrando urls de la web, pero en el último que he escrito muestro lo que comentas: cómo usarlo para mostrar una interfaz web incluída dentro de una aplicación nativa y no una web externa.

      Para crear un sitio web y “empaquetarlo” dentro de una aplicación nativa lo mejor es usar un framework como PhoneGap o JQuery Mobile que lo que hacen es ayudarte a crear interfaces web para móviles y que, en el caso de Android, al final terminan usando un WebView para mostrar las pantallas.

  17. Un articulo genial. Muchisimas gracias

  18. Valu dice:

    Prueba con el DownloadManager, aquí tienes un ejemplo muy completo: http://www.mysamplecode.com/2012/09/android-downloadmanager-example.html

    Estuve viendo el ejemplo que me pasaste y me cuesta mucho desarmarlo, si me ayudas te lo agradeceria mucho, lo que quiero es algo mas simple, que al hacer click en un link de Descarga inicie con el DownloadManager sin Dialogo ni nada que Descargue direcamente. Gracias Espero tu respuesta. SI me podes ayudar mandame un mail con el ejemplo xxxxxx@xxxxxxxxx

  19. julioacuna dice:

    Buenas noches estimado. Lo felicito por su tutorial. para mi que no se nada de android es bastante completo. Yo programo en php hace muchos años y será por esto ver código aunque sea de otro lenguaje no me asusta.
    Mi duda es seguramente una pavada para ud. Es la primero vez que estoy en esto de las app para celulares. Estoy haciendo una aplicacion para celular pero en base web. En realidad si se entra por web anda muy bien en cualquier celular. Lo que yo quería es saber como hacer una app para que se la bajen en el celu y al abrirla levante como si fuera un navegador pero sin barra de navegación, etc. Embeber mi web en una aplicacion android. Muchas gracias.

    • danielme.com dice:

      Lo que buscas es más o menos lo que se expone en mi artículo Diseño Android: Interfaces web con WebView. La idea es crear la interfaz gráfica de una aplicación Android con html, css, ect cuyos ficheros se guardan en el directorio assets como si fueran un “minisite”. Estas interfaces tendrías que mostrarlas a través de un WebView y además tienes la ventaja añadida con respecto a una aplicación web de que con javascript puedes ejecutar métodos de la Activity por lo que una aplicación diseñada así puede hacer lo mismo que una nativa.

      Si te interesa este tipo de desarrollo échale un vistazo a jQuery Mobile, lo puedes usar para crear un sitio web adaptable a dispositivos móviles y además es relativamente fácil “empaquetar” este sitio en una aplicación para Android, iOS, Blackberry… ya sea “a mano” o usando por ejemplo PhoneGap.

      • julioacuna dice:

        Maestro, gracias por su aporte, te comento que yo lo hice con jquery todo el sistema móvil, anda muy bien en un celular, lo que yo quería era embeberlo para que se bajen la aplicación y la instalen en su celular y no tener que entrar al browser de este, voy a seguir leyendo a ver si logro entender como es to de las aplicaciones , celulares y web móviles.
        Gracias

      • danielme.com dice:

        Tendrás que crear una aplicación .apk, echa un vistazo a PhoneGap para “empaquetar” tu sitio.

  20. Valu dice:

    Al implemetar el Codigo de Descarga me aparece el siguiente error:
    FileNotFoundExceptiom /mnt/sdcard/Download: open failed: EISDIR (Is a directory)

    Alguna sugerencia?

  21. ivan dice:

    Hola muchísimas gracias por el GRAN aporte, he seguido todo a la perfección y me va todo de maravillas pero tengo un pequeño inconveniente, mi aplicación esta basada en mostrar una red social y al intentar subir fotos no pasa nada es decir pulso sobre el botón de subir foto y no se abre la galería para poder seleccionar la foto (al hacerlo desde el navegador del celular se abre la galería y suben las fotos normalmente), Saludos y muchas gracias

    • danielme.com dice:

      Tienes que abrir la galería para poder seleccionar una foto con startActivityForResult e implementar en tu Activity el método onActivityResult para recibir la ubicación del fichero seleccionado. Aquí tienes el código.

  22. Jose Espitia dice:

    Hola Daniel, alguna vez has intentado incrustar un navegador pero con motor de renderizado gecko en lugar del webkit?

  23. Hola, ante nada, gracias por toda la información, es muy útil para mi.
    Con respecto a la “conectividad” y el ejemplo que propones
    En onPageStarted checkeamos con checkConnectivity() ¿ok?
    Pero me gustaria mostrar un noconnect.html cuando el estado es False.
    El tema es, que como se vuelve a producir onPageStarted, se generan 2 mensajes “La conexión a internet no se encuentra disponible en estos momentos” ¿me entiendes?
    ¿como puedo solucionar esto?
    Grcias por tus respuestas.

    • danielme.com dice:

      Comprueba en el onPageStarted si vas a mostrar esa página a partir de la url y en ese caso no hagas nada

      • Algo asi como
        public void onPageStarted(WebView view, String url, Bitmap favicon)
        {
        if (url != “file:///android_asset/noconnect.html”) { checkConnectivity() }
        }

      • Disculpa.. te respondi rapido y no dije Gracias…

      • No logro que funcione!

        onPageStarted en onCreate

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon)
        {
        if (url != “file:///android_asset/noconnect.html”) {
        if (checkConnectivity()== false){
        browser.loadUrl(“file:///android_asset/noconnect.html”);
        }
        }

        }

        Continua enviandome 2 mensajes de “sin coneccion”

        ¿que estoy haciendo mal?

      • Lo solucione eliminando checkConnectivity()

        y agregando

        public void onReceivedError (WebView view, int errorCode, String description, String failingUrl)
        {

        browser.loadUrl(“file:///android_asset/noconnect.html”);

        }

        El problema esta resuelto, pero sigo con la intriga que hice mal en el ejemplo previo.
        Saludos y gracias

  24. Andres Felipe dice:

    Es posble poner una opcion de cerrar la aplicacion ademas con confirmacion de cierre. “¿Esta seguro de cerrar la aplicacion?”

    • danielme.com dice:

      Las aplicaciones Android no se deben cerrar, el ciclo de vida de las activities es gestionado por el SO. Es una característica muy peculiar de Android que hay que tener en cuenta.
      Lo que puedes hacer es que cuando una pulsación del botón back implique “salir” de la aplicación, esto es, volver atrás desde la primera Activity en la que entraste, mostrar ese AlertDialog. Si quieres “matar” explícitamente una Activity, que no toda la aplicación, tendrás que invocar el método finish(). También puedes simular el cierre de una aplicación “minimizándola” con el método moveTaskToBack(true).

      • Andres Felipe dice:

        Gracias por responder. Esas opciones se pueden agregar al boton inferior izquierdo del celular(galaxy ace en mi caso) como a manera de menu?

      • danielme.com dice:

        Los menús se definen en un xml y se gestionan en la Activity sobreescribiendo los métodos onCreateOptionsMenu y onOptionsItemSelected. En realidad, cuando trabajes con ActionBar verás que lo que haces en realidad es definir acciones:un botón–>un acción.

  25. Hola Daniel. nuevamente gracias por compartir tu conocimiento… comparto algo en modo de pregunta:
    Vos ejecutas la función checkConnectivity() en onPageStarted(), y si es false, muestra el mensaje mientras el WebView carga la pagina de “error en conexion” tipica de los navegadores.
    Que pasa si llamas a checkConnectivity() en shouldOverrideUrlLoading(), intercepto la url antes de comenzar la carga y si no hay conexión llamas a stopLoading() o a un html personalizado dentro de file:///android_asset.
    Yo lo hice asi y me quedo bien, pero estoy buscando errores. ¿que opinas?
    saludos

    • danielme.com dice:

      Ten en cuenta que ese método sólo se invoca cuando se pulsa un enlace dentro la página web por lo que no abarca todas las posibilidades.

      • Gracias por responder!
        Cuales son los casos que quedan afuera del evento?
        Entiendo que intercepta todas las url antes de la carga, lo que veo logico para chequear conectividad.
        Porfavor, comentame más lo que te parece!
        Gracias

      • danielme.com dice:

        Botón atrás, siguiente, ir… si sólo muestras un WebView sin controles externos el shouldOverrideUrlLoading() puede ser suficiente. Lo mejor es que pongas unos breakpoints y compruebes qué se ejecuta en cada caso.

  26. Jorge David dice:

    Disculpa tu webview puede ver videos de youtube?? Es que yo desarrolle un webview de otro tutorial pero no puedo ver videos de youtube, tu sabras porque no los puedo reproducir??

    • danielme.com dice:

      Puedes ver videos de Youtube si el dispositivo soporta flash, lo tienes instalado y habilitas los plugin en el WebView. Con la Nexus 7 al menos es así.

      Por otra parte, si lo que quieres es reproducir videos de Youtube hay una alternativa mucho mejor que consiste utilizar la APi nativa de Google que te permite incrustar en tu app el reproductor de videos de Youtube (para ello deberás tener instalada la aplicación oficial de Youtube). Es fácil de utilizar y tiene un proyecto de ejemplo bastante completo. En breve escribiré un tip android sobre este tema ya que he utilizado esta funcionalidad en una aplicación que estoy desarrollando.

  27. Yesid dice:

    Como esta? Me ha servido mucho el tutorial. Que cree que se puede hacer para liberar la cpu? Veo que la aplicacion en mi celular sube el cpu hasta el 40% y lo baja solo hasta maximo el 20%. Existe algun metodo para liberar la cpu y la memoria? Gracias

    • danielme.com dice:

      La memoria se gestiona automáticamente en Java y en Android. En Java puedes solicitar la ejecución del recolector de basura pero eso no te garantiza que se ejecute en ese momento.
      Si te refieres al teléfono en sí, según la versión de Android y dispositivo hay una opción de liberar memoria.

      • Yesid dice:

        Gracias por responder, yo me refiero al cpu que consume la apk de ejemplo que se descarga aqui. Yo segui los pasos del tutorial y tambien se obtiene el mismo problema. Al ejecutar la apk, se va uno a revisar los recursos y se puede ver como la memoria y el cpu estan altos.

  28. Duvan dice:

    Hola, tengo una aplicación con WebVIew y me gustaría reproducir el vídeo de una cara IP, tu sabes algún método para hacerlo?

  29. emmanuel bautista dice:

    hola daniel, encontre tu ejemplo y estoy viendo k cuando no tienes internet te muestra la advertencia de que no tienes conexion, y me gustaria saber como le indico que me muestre una pagina vacia, en lugar de la pagina web no disponible… url y sugerencias jejeje…. en mi aplicacion estoy atorado en ese puntoook

  30. Una pregunta,
    Como hago para que, por ejemplo: determinada url no se agrege a la Pila.
    Aver si me explico

    browser.loadUrl(“url_1″);
    …..
    browser.loadUrl(“url_2″);
    …..
    browser.loadUrl(“url_3″); // que esta url no se guarde
    …..
    browser.goBack(); /// Volveria a url_1

    Saludos

    • danielme.com dice:

      Tienes que invocar el método clearHistory() del WebView después de haberse cargado la página (onPageFinished) si lo que quieres es que el historial esté siempre vacío. De todos modos, puedes capturar el evento back sobreescribiedno método onBackPressed y controlar dónde quieras que vaya el usuario, y esto es válido para cualquier Activity y aplicación.

  31. hola amigo oye una pregunta, en tu aplicación veo k manipulas un modulo de historial, la pregunta seria.. ¿como puedo acortar las url o solo poner el nombre del titulo como si fuera una simple liga en php?, no soy tan bueno es esto pero he estado investigando como hacerlo y todo menciona que he leido me dice k lo hago como si fuera un php, y no lo he podido sacar espero me ayudes…

    saludos amigo, k estes bien y tienes una buena aplicación, Felicidades.. :D

    • danielme.com dice:

      Si te refieres al historial, habría que añadir un nuevo campo a la clase Link para almacenar cuando se reciba el título cuando se reciba en onReceivedTitle y en el Dialog con el historial mostrar el title en lugar de la url siempre y cuando lo tengas.

      Tengo pendiente darle un buen repaso al proyecto de Github, cuando salga Kit Kat le daré un lavado de cara y refactorización y quizás añada esta función que estoy comentando.

      • hola gracias por tu respuesta, lo hice todo funciona muy bien, pero el problema esta cuando voy a la parte de historial o favoritos tengo las paginas visitadas por titulo de cada una, pero ala hora de darle clik a cualquiera no puedo redireccionarlo ala url, por que en si busca la pagina con ese titulo pero nunca la encuentra.

        ya le moví barias veces pero no se que mas hacerle, espero me des una idea o una ayuda.

        gracias, que estes bien :D

      • danielme.com dice:

        Tienes que guardar tanto la url como el title de la página, mostra en el historial el title pero al hacer click abrir en el WebView la url, no el title. Espero tener tiempo y añadir esta mejora a la demo de GitHub cuando salga Android KitKat y vaya revisando las demos.

  32. Danilo dice:

    hola, primero que nada, agradecerte el tutorial, me ha servido muchisimo !
    me tomo el atrevimiento de hacerte una consulta, hice una aplicacion con Webview en la cual despliego un html (index). Al final del mismo hay un enlace al siguiente html (pasos.html), pero al abrir este, aparece en el final del html, como si se hubiera hecho scroll hacia abajo.
    Y a partir de ahi, cualquier otro html que abre lo hace de la misma manera.
    Hay algo que me falta programar en el Webview ?

    muchisimas gracias !!

    • danielme.com dice:

      Lo siento pero nunca me ha pasado lo que comentas. Los enlaces en el WebView se abren en navegador del sistema salvo que sobreescribas el método shouldOverrideUrlLoading . Ahora bien, si vas a construir la interfaz de tu aplicación utilizando tecnología web, te recomiendo que eches un vistazo a mi otro artículo sobre WebView donde se muestra un ejemplo sencillo sobre cómo llamar desde el javascript de un WebView a un método de una Activity, quizás eso te sirva para navegar entre distintas pantallas en html de tu aplicación que deduzco es lo que quieres hacer.

      Lamento no poder ser de más ayuda.

  33. ozkr dice:

    esta genial amig@ muchas gracias por el tuto

  34. Chema dice:

    Hola,
    Gracias por el tutorial y por el de DownloadManager también. Una consulta he hecho una aplicación en la que poner un user y password, al pulsar un botón abro un webview y mediante javascript relleno los campos de acceso a un webmail. El problema es la descarga de archivos (adjuntos en los emails), no puedo usar el navegador normal porque tendría que hacer login, he intentado un par de cosas sin éxito, la última a través de un DownloadManager en el webview, la descarga comienza pero no se finaliza (se descargan unos pocos KB) y se reintenta constantemente (eso creo que es normal por el downloadmanager). Este es mi código del webview:

    public class WebViewActivity extends Activity {

    private WebView webView;

    //Version 3
    private DownloadManager downloadmanager;
    private long enqueue;

    //Version 2
    public void onBackPressed()
    {

    if (webView.canGoBack())
    {
    webView.goBack();
    }
    else
    {
    super.onBackPressed();
    }
    }

    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.webview);

    webView = (WebView) findViewById(R.id.webView1);

    final Bundle bundle = getIntent().getExtras();

    webView.getSettings().setJavaScriptEnabled(true);
    webView.getSettings().setBuiltInZoomControls(true);//Version 2
    webView.loadUrl(“https://xxx.xxxxxxx.com”);

    webView.setWebViewClient(new WebViewClient() {
    public void onPageFinished(WebView view, String url){

    String us = bundle.getString(“username”);
    String ps = bundle.getString(“password”);

    webView.loadUrl(“javascript: {” +
    “document.getElementById(‘username’).value = ‘”+us+”‘;” +
    “document.getElementById(‘password’).value = ‘”+ps+”‘;” +
    “var frms = document.getElementsByName(‘logonForm’);” +
    “frms[0].submit(); };”);

    //Version 3

    webView.setDownloadListener(new DownloadListener() {
    public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength)

    {

    BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent i) {
    String action = i.getAction();
    if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
    long downloadId = i.getLongExtra(
    DownloadManager.EXTRA_DOWNLOAD_ID, 0);
    Query query = new Query();
    query.setFilterById(enqueue);
    Cursor c = downloadmanager.query(query);
    if (c.moveToFirst()) {
    int columnIndex = c
    .getColumnIndex(DownloadManager.COLUMN_STATUS);
    if (DownloadManager.STATUS_SUCCESSFUL == c
    .getInt(columnIndex)) {

    }
    }
    }
    }
    };

    registerReceiver(receiver, new IntentFilter(
    DownloadManager.ACTION_DOWNLOAD_COMPLETE));

    downloadmanager = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
    DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
    DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));

    request.setDescription(“Download”);
    request.setTitle(url);
    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, System.currentTimeMillis()+”Download”);

    enqueue = downloadmanager.enqueue(request);

    }

    public void showDownload(View view)
    {
    Intent i = new Intent();
    i.setAction(DownloadManager.ACTION_VIEW_DOWNLOADS);
    startActivity(i);
    }

    });

    }
    });
    }
    }

    Alguna idea?

    • Chema dice:

      Perdona hay una línea de sobra (DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));)….Gracias de antemano!

  35. erika1390 dice:

    Como puedo descargar un video

  36. Hola que tal, gracias por ese super aporte….tengo una duda, quiero desgar un archivo de un directorio al momento de descargarlo con al app me guarda un index.php con la key del producto y no el archivo en si…como pordria solucionar este problema para que me descarge el archivo no el enlace php con que llamo al archivo…gracias….

  37. omar dice:

    k tal.. oye una pregunta esto me crea un link a una pajina de internet o le da la interface de la web k se crea,, porque yo desarrolle una web pero nececito meterla en una aplicacion.. xfa ayuda necesito k se vea aun sin tener acceso a internet,..

  38. JUAN dice:

    hola ante todo gracias por tutoriales como este, decir que soy nuevo en android, tengo en mente una app y necesito saber hacer esto bien, he copiado tal y como pones tu el codigo para que te muestre una web y me manda un error en el webview(solo con esa palabra) me pone esto (webview cannot be resolved), te lo copio por si me puedes ayudar

    package com.prueba2;

    import android.os.Build;
    import android.os.Bundle;
    import android.app.Activity;
    import android.view.Menu;
    import android.webkit.WebSettings.PluginState;
    import android.webkit.WebView;

    public class MainActivity extends Activity {

    private WebView browser;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    browser = (WebView)findViewById(R.id.webkit);

    //habilitamos javascript y el zoom
    browser.getSettings().setJavaScriptEnabled(true);
    browser.getSettings().setBuiltInZoomControls(true);

    //habilitamos los plugins (flash)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO)
    {
    webview.getSettings().setPluginState(PluginState.ON);
    }
    else
    {
    //IMPORTANTE!! este método ha sido eliminado en Android 4.3
    //por lo que si lo necesitamos para mantener la compatibilidad
    //hacia atrás hay que compilar el proyecto con Android 4.2 como máximo
    webview.getSettings().setPluginsEnabled(true);
    }

    browser.loadUrl(“http://danielme.com”);
    }
    }

  39. Gracias por tu aporte, tengo un problema al momento de descargar un archivo, me descarga un archivo index.php que contiene la key del producto, lo que quisiera es que me descargue el .exe, no se como se podría solucionarlo….

  40. Eduardo dice:

    Hola! Primero que nada quiero agradecer este tuto es magnifico me apoye en el u cree una app con un webview que muestra mi pagina de facebook y esta todo genial solo quiero saber si puedes ayudarme con un par de cosas:

    1- Como hago que los usuarios de mi app puedan publicar fotos en mi pag? Osea a la hora de comentar no logro hacer que el boton de subir foto me lance las opciones de galeria y eso!

    2- Como hago que al mantener precionada una foto me lance la opcion de descargarla?

    De antemano muchas gracias ojala puedas ayudarme o si alguien mas sabe de esto se lo agradeceria igual’:)

  41. Blaydd dice:

    Tan genial el artículo como las respuestas que aportas.

    Tan sólo una duda. Has añadido al artículo que a partir de Kit Kat “Una de las grandes novedades de la última versión de Android en el momento de escribir estas líneas es el “nuevo” WebView ya que a partir de esta versión se basa directamente en el motor de Chromium”. Antes de eso usa el de sístema (según entiendo yo). Mi problema es que mi aplicación web usa algunos componentes de html5 que el navegador de sístema no se “come”. ¿Habría alguna forma de hacer que use chrome o firefox en lugar de el de sístema?

    Un saludo y gracias de antemano por la contestación.

  42. Hola muy buen tutorial felicidades, mi problema es como llamar a los strings desde un html que tengo en assets y visualizo a través de un WebView, preciso que este html se pueda ver en la app con los mismos idiomas que estoy usando para los XML, como puedo hacer?, gracias de antemano.

  43. Hola, creo que ya me solucionaste el problema, yo estaba probando con un código mío, después de mi primer comentario he probado el código que tienes en github y ya he visto la solución, // Welcome page loaded from assets directory (linea 207) de la activity WebViewDemoActivity, me sirve perfectamente, de todas maneras ya solo por curiosidad, hay alguna manera de llamar a un string en concreto desde HTML?, muchas gracias, muy útil el aporte de código en github.

  44. cuetox dice:

    Gracias muy bien explicado

  45. Felipe dice:

    danielme.com, muchas gracias por este articulo, he aprendido bastante. Tengo una pregunta, es posible ocultar/quitar la seccion donde se pone el titulo de la aplicación? Para tener un espacion más grande para el sitio web?

    • danielme.com dice:

      Puedes aplicar a la Activity un tema incluido en Android para utilizar la pantalla completa:
      @android:style/Theme.NoTitleBar.Fullscreen –>No muestra ni el título/ActionBar ni la barra de notificaciones
      @android:style/Theme.NoTitleBar –>No muestra el título/ActionBar pero sí la barra de notificaciones

  46. David dice:

    NO existe algun codigo el cual consume menos kilobyte a la hora de navegar con el webView

  47. Jorismos dice:

    No queda clara la importante parte de comprobar la conectividad antes de abrir el navegador. ¿Donde van los fragmentos de código?. Por cierto, este el mejor manual en español para esta función. Gracias

  48. Jorismos dice:

    Voy a tratar de contribuir a este buen tutorial, explicando una manera detallada de comprobar la CONEXIÓN a internet que a mi me ha funcionado:

    1.- En AndroidManifest.xml ponemos:

    2.- En MainActivity.java inmediatamente después de la estiqueta “public class MainActivity extends Activity {” ponemos:

    public static boolean verificaConexion(Context ctx) {
    boolean bConectado = false;
    ConnectivityManager connec = (ConnectivityManager) ctx
    .getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo[] redes = connec.getAllNetworkInfo();
    for (int i = 0; i < 2; i++) {
    if (redes[i].getState() == NetworkInfo.State.CONNECTED) {
    bConectado = true;
    }
    }
    return bConectado;
    }

    3.- En MainActivity.java inmediatamente después de la etiqueta "super.onCreate(savedInstanceState);" ponemos:

    if (!verificaConexion(this)) {
    Toast.makeText(getBaseContext(),
    "Imposible conectar. Comprueba tu conexión a Internet.", Toast.LENGTH_LONG)
    .show();
    this.finish();
    }

    Esto comprobará la conexión de tu dispositivo (wifi o sim) que, si no existe conexión, mostrará un mensaje de información y tras un par de segundos saldrá de la aplicación.

    Espero haber podido ayudar, a mi me ha servido.

    saludos

    • Jorismos dice:

      Arriba en el punto 1 no salió escrito el código.

      Se trata de

      uses-permission android:name=”android.permission.INTERNET”
      uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE”

      pero con los corchetes

  49. Alex dice:

    Hola , muchas gracias me sirvió de mucho , te comento unas cosas , cuando giro el movil / tablet la aplicación en vez de permanecer abierta , carga otra vez y no se queda fija , sabes algún codigo para esto o como arreglarlo ? luego el tema de abrir links , estoy mirando de como configurarlo , si hago que las paginas se abran en la app , otro tipo de enlaces que no es http .. no me funciona , osea intenta abrirlo en la misma app pero como no es http no me lo abre , en cambio si quito “WebViewClient” me funcionan todo bien pero los enlaces http me los abre fuera de la app , el tema seria que los http o enlaces los abriera en la misma app y otros enlaces no intente abrirlo en el navegtador, no se si me a explicado jeje , bueno gracias por todo un slaudo

  50. cuetox dice:

    gracias

  51. Jessie dice:

    Hola disculpa mi código me marca mal “navegador.getSettings().setPluginState(PluginState.ON);” ya intente de varias maneras, mas aun asi me sique marcando error y tachando el .setPluginState.
    Aquie el codigo:
    public class MainActivity extends Activity {
    private WebView navegador;
    private ProgressBar barraProgreso;
    private EditText url;

    @SuppressWarnings(“deprecation”)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    navegador = (WebView)findViewById(R.id.webView1);

    WebView webView1 = new WebView(this);

    //habilitamos javascript y el zoom
    navegador.getSettings().setJavaScriptEnabled(true);
    navegador.getSettings().setBuiltInZoomControls(true);

    //habilitamos los plugins (flash)
    if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.FROYO){
    navegador.getSettings().setPluginState(PluginState.ON);
    }
    Gracias:D

  52. Monik dice:

    hola, porfavor si alguien me pudiera ayudar se los agradeciería…
    necesito hacer un buscador en xamarin, se supone que debo ingresar una direccion local, osea de la pc que estoy utilizando y que me abra los archivos, imagenes, musica, fotos, etc.. incluso que los reprodusca o los abra.
    porfavor es urgente. gracias.

  53. Andres dice:

    Me ayudaste resto!!! ;) gracias!!!

  54. alex dice:

    ola..mira cuando hago correr mi aplicación la pagina no se acomoda , me sale mas grande que la pantalla.
    que estoy haciendo mal, ?

    • danielme.com dice:

      Si estás haciendo una web con responsive design debes incluir en el header de todas las páginas lo siguiente para que se tome como ancho el tamaño de la pantalla en un navegador de móvil:

Deja un comentario

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 38 seguidores

%d personas les gusta esto: