Android WebView: cómo incrustar un navegador en nuestras apps

Última actualización: 13/09/2015

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 pero este widget es la base de las aplicaciones híbridas basadas en tecnologías web (HTML5, JavaScript, CSS) y frameworks como Ionic que se ejecutan «dentro» de un WebView. Para una introducción a este uso del WebView (sin framework) consultar este tutorial.

Versiones de WebView: La versión de WebKit depende de la versión de Android del dispositivo en el que se ejecute la app. 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/. A partir de Android 4.4 KitKat Google ha cambiado la política de implementación de este widget. Al final de este tutorial se puede encontrar más información al respecto.

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, aunque a día de hoy (2015) para aplicaciones nuevas no merece ya la pena contemplar Android 2.

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.

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 a partir de Android 4.4 Kit Kat

A partir de esta versión de Android WebView 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 proporciona una importante mejora de rendimiento en las aplicaciones híbridas. Por otra parte, la API del propio WebView que hemos visto en este artículo no ha sufrido cambios importantes.

En Lollipop y superior el componente WebView es actualizable vía Google Play lo que permite a Google mantener actualizado este componete en todos los dispositivos modernos para solucionar problemas de seguridad y mejorar el rendimiento.

Proyecto de ejemplo 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 en GitHub . Para más información sobre cómo utilizar GitHub, consultar este artículo.

android browser with webview

131 comentarios sobre “Android WebView: cómo incrustar un navegador en nuestras apps

    1. 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.

      1. Buenas tardes, tengo un problema con mi table, en la pantalla sale ( se detuvo la aplicación de android system webview)
        (Aceptar) y presiono aceptar pero no pasa nada, sigue en lo mismo y no puedo entrar a ninguna función. Q puedo hacer, por favor y gracias.

  1. 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???

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

      2. 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.

    1. 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 🙂

  2. 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

  3. 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!

    1. 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 😉

  4. 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.

  5. 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

    1. 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.

  6. 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.

  7. 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.

  8. 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!

    1. 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.

  9. 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

  10. 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.

    1. 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.

      1. 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

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

    Alguna sugerencia?

  12. 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

    1. 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.

  13. 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.

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

      2. 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?

      3. 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

  14. Es posble poner una opcion de cerrar la aplicacion ademas con confirmacion de cierre. «¿Esta seguro de cerrar la aplicacion?»

    1. 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).

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

      2. 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.

  15. 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

      1. 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

      2. 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.

  16. 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??

    1. 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.

  17. 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

    1. 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.

      1. 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.

  18. 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?

  19. 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

  20. 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

    1. 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.

  21. 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.. 😀

    1. 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.

      1. 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 😀

      2. 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.

  22. 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 !!

    1. 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.

  23. 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?

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

  24. 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….

  25. 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,..

  26. 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»);
    }
    }

  27. 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….

  28. 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’:)

  29. 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.

  30. 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.

  31. 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.

  32. 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?

    1. 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

  33. 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

  34. 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

    1. 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

  35. 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

  36. 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

  37. 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.

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

    1. 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:

  39. gracias por el aporte daniel, pero mi pregunta es la siguiente, ya una vez entrado en una aplicación que para algunos es necesario tener conexión a internet, necesito abrir un contenido en donde me lleve a otra página, y de repente el internet se cae cuando carga, cómo puedo detectar el time_out, diciendo «problemas de conexion a internet», me gustaria que se lanzara ese mensaje como toast, como puedo hacerlo????

  40. hola daniel muy buen tutorial lo felicito mi pregunta es como hago para que mi aplicacion abra los enlaces internos dentro de la la APP y abra los enlaces externos en el navegador de la aplicacion porque lo estoy haciendo para una pagina en especial y tiene varios enlaces y me gustaria que se cargaran en la misma aplicacion.

  41. Gracias daniel pero lo hice asi y me sigue sin funcionar los enlaces alli tengo dos clase

    PRIMERA CLASE MyActivity

    package movilnet.movilnet.com.movilnet;

    import android.support.v7.app.ActionBarActivity;
    import android.os.Bundle;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.webkit.WebSettings;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;

    import static movilnet.movilnet.com.movilnet.R.string.*;

    public class MyActivity extends ActionBarActivity {

    // INI AGREGADO
    private WebView mWebView;
    // FIN AGREGADO

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_my);

    // INI AGREGADO
    mWebView = (WebView) findViewById(R.id.activity_main_webview);
    // Activamos Javascript
    WebSettings webSettings = mWebView.getSettings();
    webSettings.setJavaScriptEnabled(true);
    // Url que carga la app (webview)
    mWebView.loadUrl(«http://www.movilnet.com.ve/»);

    // Forzamos el webview para que abra los enlaces internos dentro de la la APP
    mWebView.setWebViewClient(new WebViewClient());

    // Forzamos el webview para que abra los enlaces externos en el navegador

    mWebView.setWebViewClient(new MyAppWebViewClient());

    // FIN AGREGADO

    }

    // INI AGREGADO
    @Override
    // Detectar cuando se presiona el botón de retroceso
    public void onBackPressed() {
    if(mWebView.canGoBack()) {
    mWebView.goBack();
    } else {
    super.onBackPressed();
    }
    }
    // FIN AGREGADO

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.my, menu);
    return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    if (id == R.id.action_settings) {
    return true;
    }
    return super.onOptionsItemSelected(item);
    }
    }

    SEGUNDA CLASE MyAppWebViewClient

    package movilnet.movilnet.com.movilnet;

    import android.content.Intent;
    import android.net.Uri;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;

    // INI AGREGADO
    public class MyAppWebViewClient extends WebViewClient {

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url)
    {
    if (url.endsWith(«.action»)){

    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(Uri.parse(url), «text/html»);
    startActivity(intent);
    return true;

    }
    return false;
    }

    private void startActivity(Intent intent) {

    }
    }

  42. Hola daniel gracias pero lo hice asi y todavia me sigue sin funcionar los enlaces

    public class MyActivity extends ActionBarActivity {

    ProgressDialog progressBar;
    private int progressBarStatus = 0;
    private Handler progressBarHandler = new Handler();
    EditText txtUrl = null;

    public final String HOME = «http://www.movilnet.com.ve/sitio/»;

    private class MiWebViewClient extends WebViewClient {

    private void iniciaCarga(WebView v) {
    if(progressBar==null){
    progressBar = new ProgressDialog(v.getContext());
    }
    if (progressBar != null && !progressBar.isShowing()) {
    progressBar.setTitle(«Espere…»);
    progressBar.setMessage(«Cargando página…»);
    progressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER);
    progressBar.setCancelable(false);
    pbShow();
    progressBarStatus = 0;
    runOnUiThread(new Runnable() {

    @Override
    public void run() {
    progressBarHandler.post(new Runnable() {
    public void run() {
    try {
    progressBar
    .setProgress(progressBarStatus++);
    } catch (Exception e) {
    }
    }
    });
    }
    });
    }
    }

    private void pbCancel() {
    try {
    progressBar.cancel();
    } catch (Exception e) {
    Log.e(«ERROR», e.getMessage());
    }
    }

    private void pbShow() {
    try {
    progressBar.show();
    } catch (Exception e) {
    Log.e(«ERROR», e.getMessage());
    }
    }

    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
    iniciaCarga(view);
    if (txtUrl != null) {
    txtUrl.setText(url);
    }
    super.onPageStarted(view, url, favicon);
    }

    // the current WebView will handle the url
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url)
    {
    if (url.endsWith(«.action») || url.endsWith(«.jsp»))
    {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(Uri.parse(url),»text/html»);
    startActivity(intent);
    return true;
    }
    return false;
    }
    @Override
    public void onPageFinished(WebView view, String url) {
    super.onPageFinished(view, url);
    pbCancel();
    }
    }

    private class MiChromeWebViewClient extends WebChromeClient {

    }

    WebView wv_pagina;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_my);
    wv_pagina = (WebView) findViewById(R.id.wv_pagina);
    txtUrl = (EditText) findViewById(R.id.txtUrl);
    wv_pagina.clearHistory();
    wv_pagina.clearCache(true);
    wv_pagina.setWebViewClient(new MiWebViewClient());
    wv_pagina.setWebChromeClient(new MiChromeWebViewClient());
    wv_pagina.getSettings().setJavaScriptEnabled(true);
    runOnUiThread(new Runnable() {

    @Override
    public void run() {
    wv_pagina.loadUrl(HOME);
    }
    });

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.my, menu);
    return true;
    }
    // INI AGREGADO
    @Override
    // Detectar cuando se presiona el botón de retroceso
    public void onBackPressed() {
    if(wv_pagina.canGoBack()) {
    wv_pagina.goBack();
    } else {
    super.onBackPressed();
    }
    }
    // FIN AGREGADO

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    if (wv_pagina != null) {
    switch (item.getItemId()) {
    case R.id.menu_back:
    if (wv_pagina.canGoBack()) {
    wv_pagina.goBack();
    }
    break;
    case R.id.menu_forward:
    if (wv_pagina.canGoForward()) {
    wv_pagina.goForward();
    }
    break;
    }
    return true;
    } else {
    return super.onOptionsItemSelected(item);
    }
    }

    }

  43. MI AMIGO DANIEL, GRACIAS POR EL POST ESTA MUY BUENO, PERO TENGO UN PEQUEñO PROBLEMA, COLOQUE UNA PAGINA DE YOUTUBE PERO QUISIERA QUE CUANDO PULSE PARA VER EL VIDEO ME ABRA LA APLICACION DE YOUTUBE, ESPERO ME PUEDAS AYUDAR SALUDOS DESDE VENEZUELA!!

  44. hola daniel excelente material, quiero que me ayudes, quiero hacer un webview que me premita abrir los videos de youtube?

  45. Hola, quisiera saber ¿como conectar un webview de forma local a un toast? Es decir, cuando mi webview habrá la pag y de clic en alguna parte de ella, se muestre un toast!

  46. Me da bastantes problemas 😦

    AlertDialog cannot be resolved to a type

    Unexpected namespace prefix «xmlns» found for tag RelativeLayout

    Unexpected namespace prefix «xmlns» found for tag WebView

    Bitmap cannot be resolved to a type

    The method onBackPressed() of type new WebViewClient(){} must override or implement a supertype method

    The method onBackPressed() is undefined for the type WebViewClient

    AlertDialog cannot be resolved to a type

    Build cannot be resolved to a variable

    Build cannot be resolved to a variable

    The method onReceivedTitle(WebView, String) of type new WebViewClient(){} must override or implement a supertype method

    PluginState cannot be resolved to a variable

    main cannot be resolved or is not a field

  47. Hola buenas tardes tengo un webview y le he asociado la pagina, todo responde bien, el problema es que cuando realizo alguna modificación no se refleja en la pagina del webview y me he percatado que cuando elimino los datos que guarda la aplicacion se reflejan los cambios. ¿Existe alguna forma de borrar los datos de una forma que no sea manual, o alguna forma de solucionar este problema?

  48. Mi problema es que cargo una web dentro de este webview, la cual cuenta con bastante información, pero al querer leer pdf’s que cuelgan de varios enlaces no me va a ningún sitio. ¿Como puedo solucionarlo?

    Muchas gracias por este magnífico artículo!!

  49. Muy buenos los cambios, pero te agradecería que me dijeras como puedo parsear un enlace con un pdf que leo desde mi base de datos y se abriera, hasta ahora solo consigo que no funcione el enlace.

    Gracias y sigue así

  50. Quisiera descargar aplicaciones con esta opción de descarga que nos brindas, pero me salen cobertor en el paquete de análisis,. Que puedo hacer en ese caso?

Replica a denissesanoja Cancelar la respuesta

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.