Cómo crear apps web en WebView

Usa WebView para entregar una aplicación web o una página web como parte de una app cliente. La clase WebView es una extensión de la clase View de Android que te permite mostrar páginas web como parte del diseño de tu actividad. No incluye las funciones de un navegador web completamente desarrollado, como los controles de navegación o una barra de direcciones. De forma predeterminada, todo lo que hace WebView es mostrar una página web.

WebView puede ayudarte a proporcionar información en tu app que tal vez necesites actualizar, como un acuerdo de usuario final o una guía del usuario. En tu app para Android, puedes crear un Activity que contenga un WebView y, luego, usarlo para mostrar el documento alojado en línea.

WebView también puede ser útil cuando tu app proporciona datos al usuario que requieren una conexión a Internet para recuperar datos, como el correo electrónico. En este caso, tal vez te resulte más fácil compilar un WebView en tu app para Android que muestre una página web con todos los datos del usuario, en lugar de realizar una solicitud de red, analizar los datos y renderizarlos en un diseño de Android. En su lugar, puedes diseñar una página web adaptada para dispositivos con Android y, luego, implementar un WebView en tu app para Android que cargue la página web.

En este documento, se describe cómo comenzar a usar WebView, cómo vincular JavaScript de tu página web al código del cliente en tu app para Android, cómo controlar la navegación de páginas y cómo administrar ventanas cuando usas WebView.

Cómo trabajar con WebView en versiones anteriores de Android

Para usar de forma segura las funciones de WebView más recientes en el dispositivo en el que se ejecuta tu app, agrega la biblioteca de Webkit de Jetpack. Esta es una biblioteca estática que puedes agregar a tu aplicación para usar las APIs de android.webkit que no están disponibles para las versiones anteriores de la plataforma.

Agrégalo a tu archivo build.gradle de la siguiente manera:

Kotlin

dependencies {
    implementation("androidx.webkit:webkit:1.8.0")
}

Groovy

dependencies {
    implementation ("androidx.webkit:webkit:1.8.0")
}

Explora el ejemplo de WebView en GitHub para obtener más detalles.

Agrega un WebView a tu app

Para agregar un WebView a tu app, puedes incluir el elemento <WebView> en el diseño de tu actividad o establecer toda la ventana de Activity como un WebView en onCreate.

Agrega un WebView en el diseño de la actividad

Para agregar un WebView a tu app en el diseño, agrega el siguiente código al archivo XML de diseño de tu actividad:

<WebView
    android:id="@+id/webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

Para cargar una página web en WebView, usa loadUrl, como se muestra en el siguiente ejemplo:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.loadUrl("http://www.example.com")

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.loadUrl("http://www.example.com");

Agrega un WebView en onCreate()

Para agregar un WebView a tu app en el método onCreate() de una actividad, usa una lógica similar a la siguiente:

Kotlin

val myWebView = WebView(activityContext)
setContentView(myWebView)

Java

WebView myWebView = new WebView(activityContext);
setContentView(myWebView);

Luego, carga la página:

Kotlin

myWebView.loadUrl("http://www.example.com")

Java

myWebView.loadUrl("https://www.example.com");

O carga la URL desde una string HTML:

Kotlin

// Create an unencoded HTML string, then convert the unencoded HTML string into
// bytes. Encode it with base64 and load the data.
val unencodedHtml =
     "<html><body>'%23' is the percent code for ‘#‘ </body></html>";
val encodedHtml = Base64.encodeToString(unencodedHtml.toByteArray(), Base64.NO_PADDING)
myWebView.loadData(encodedHtml, "text/html", "base64")

Java

// Create an unencoded HTML string, then convert the unencoded HTML string into
// bytes. Encode it with base64 and load the data.
String unencodedHtml =
     "<html><body>'%23' is the percent code for ‘#‘ </body></html>";
String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(),
        Base64.NO_PADDING);
myWebView.loadData(encodedHtml, "text/html", "base64");

La app debe tener acceso a Internet. Para obtener acceso a Internet, solicita el permiso INTERNET en tu archivo de manifiesto, como se muestra en el siguiente ejemplo:

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

Puedes personalizar tu WebView de las siguientes maneras:

  • Habilita la compatibilidad con pantalla completa con WebChromeClient. También se llama a esta clase cuando un WebView necesita permiso para modificar la IU de la app host, como crear o cerrar ventanas, o enviar diálogos de JavaScript al usuario. Para obtener más información sobre la depuración en este contexto, consulta Cómo depurar apps web.
  • Controlar eventos que afectan la renderización del contenido, como errores en los envíos de formularios o la navegación con WebViewClient También puedes usar esta subclase para interceptar la carga de URLs.
  • Habilitar JavaScript modificando WebSettings
  • Usar JavaScript para acceder a objetos del framework de Android que insertaste en un WebView

Usa JavaScript en WebView

Si la página web que quieres cargar en tu WebView usa JavaScript, debes habilitar JavaScript para tu WebView. Después de habilitar JavaScript, puedes crear interfaces entre el código de tu app y el de JavaScript.

Habilitar JavaScript

De forma predeterminada, JavaScript está inhabilitado en la WebView. Puedes habilitarla a través de la WebSettings adjunta a tu WebView. Recupera WebSettings con getSettings y, luego, habilita JavaScript con setJavaScriptEnabled.

Consulta el siguiente ejemplo:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.settings.javaScriptEnabled = true

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);

WebSettings proporciona acceso a una variedad de otros parámetros de configuración que podrían resultarte útiles. Por ejemplo, si desarrollas una aplicación web diseñada específicamente para el WebView en tu app para Android, puedes definir una cadena de usuario-agente personalizada con setUserAgentString y, luego, consultar el usuario-agente personalizado en tu página web para verificar que el cliente que solicita tu página web sea tu app para Android.

Vincula el código JavaScript al código de Android

Cuando desarrollas una aplicación web diseñada específicamente para el WebView en tu app para Android, puedes crear interfaces entre tu código de JavaScript y el código de Android del cliente. Por ejemplo, tu código de JavaScript puede llamar a un método en tu código de Android para mostrar un Dialog, en lugar de usar la función alert() de JavaScript.

Para vincular una interfaz nueva entre tu código JavaScript y Android, llama a addJavascriptInterface y pásale una instancia de clase para vincularla a tu JavaScript y un nombre de interfaz al que tu JavaScript pueda llamar para acceder a la clase.

Para obtener más información sobre la comunicación entre JavaScript y el código nativo, incluidas las APIs más modernas y seguras, consulta Cómo acceder a las APIs nativas con JSBridge.

Por ejemplo, puedes incluir la siguiente clase en tu app para Android:

Kotlin

/** Instantiate the interface and set the context.  */
class WebAppInterface(private val mContext: Context) {

    /** Show a toast from the web page.  */
    @JavascriptInterface
    fun showToast(toast: String) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show()
    }
}

Java

public class WebAppInterface {
    Context mContext;

    /** Instantiate the interface and set the context. */
    WebAppInterface(Context c) {
        mContext = c;
    }

    /** Show a toast from the web page. */
    @JavascriptInterface
    public void showToast(String toast) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
    }
}

En este ejemplo, la clase WebAppInterface permite que la página web cree un mensaje Toast con el método showToast().

Puedes vincular esta clase al código JavaScript que se ejecuta en tu WebView con addJavascriptInterface(), como se muestra en el siguiente ejemplo:

Kotlin

val webView: WebView = findViewById(R.id.webview)
webView.addJavascriptInterface(WebAppInterface(this), "Android")

Java

WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");

Esto crea una interfaz llamada Android para JavaScript que se ejecuta en el WebView. En este punto, tu aplicación web tiene acceso a la clase WebAppInterface. Por ejemplo, aquí hay un código HTML y JavaScript que crea un mensaje de aviso con la nueva interfaz cuando el usuario presiona un botón:

<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />

<script type="text/javascript">
    function showAndroidToast(toast) {
        Android.showToast(toast);
    }
</script>

No es necesario inicializar la interfaz Android desde JavaScript. El WebView lo pone automáticamente a disposición de tu página web. Por lo tanto, cuando un usuario presiona el botón, la función showAndroidToast() usa la interfaz Android para llamar al método WebAppInterface.showToast().

Cómo controlar la navegación de páginas

Cuando el usuario presiona un vínculo de una página web en tu WebView, Android inicia, de forma predeterminada, una app que controla las URLs. Por lo general, se abre el navegador web predeterminado y se carga la URL de destino. Sin embargo, puedes anular este comportamiento para tu WebView de modo que los vínculos se abran dentro de tu WebView. Luego, puedes permitir que el usuario navegue hacia atrás y hacia adelante por el historial de páginas web que mantiene tu WebView.

Para abrir los vínculos que presiona el usuario, proporciona un WebViewClient para tu WebView con setWebViewClient. Todos los vínculos que presiona el usuario se cargan en tu WebView. Si quieres tener más control sobre dónde se carga un vínculo en el que se hizo clic, crea tu propio WebViewClient que anule el método shouldOverrideUrlLoading. En el siguiente ejemplo, se supone que MyWebViewClient es una clase interna de Activity.

Kotlin

private class MyWebViewClient : WebViewClient() {

    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
        if (Uri.parse(url).host == "www.example.com") {
            // This is your website, so don't override. Let your WebView load
            // the page.
            return false
        }
        // Otherwise, the link isn't for a page on your site, so launch another
        // Activity that handles URLs.
        Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
            startActivity(this)
        }
        return true
    }
}

Java

private class MyWebViewClient extends WebViewClient {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        if ("www.example.com".equals(request.getUrl().getHost())) {
      // This is your website, so don't override. Let your WebView load the
      // page.
      return false;
    }
    // Otherwise, the link isn't for a page on your site, so launch another
    // Activity that handles URLs.
    Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl());
    startActivity(intent);
    return true;
  }
}

Luego, crea una instancia de este nuevo WebViewClient para la WebView:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.webViewClient = MyWebViewClient()

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new MyWebViewClient());

Ahora, cuando el usuario presiona un vínculo, el sistema llama al método shouldOverrideUrlLoading, que verifica si el host de la URL coincide con un dominio específico, como se define en el ejemplo anterior. Si coincide, el método devuelve un valor falso y no anula la carga de la URL. Permite que el WebView cargue la URL como de costumbre. Si el host de la URL no coincide, se crea un Intent para iniciar el Activity predeterminado que controla las URLs, que se resuelve en el navegador web predeterminado del usuario.

Cómo controlar las URLs personalizadas

WebView aplica restricciones cuando se solicitan recursos y se resuelven vínculos que usan un esquema de URL personalizado. Por ejemplo, si implementas devoluciones de llamada, como shouldOverrideUrlLoading o shouldInterceptRequest, WebView las invoca solo para URLs válidas.

Por ejemplo, es posible que WebView no llame a tu método shouldOverrideUrlLoading para vínculos como este:

<a href="showProfile">Show Profile</a>

Las URLs no válidas, como la que se muestra en el ejemplo anterior, se controlan de forma incoherente en WebView, por lo que recomendamos usar una URL con el formato correcto. Puedes usar un esquema personalizado o una URL HTTPS para un dominio que controle tu organización.

En lugar de usar una cadena simple en un vínculo, como en el ejemplo anterior, puedes usar un esquema personalizado como el siguiente:

<a href="example-app:showProfile">Show Profile</a>

Luego, puedes controlar esta URL en tu método shouldOverrideUrlLoading de la siguiente manera:

Kotlin

// The URL scheme must be non-hierarchical, meaning no trailing slashes.
const val APP_SCHEME = "example-app:"

override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
    return if (url?.startsWith(APP_SCHEME) == true) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length), "UTF-8")
        respondToData(urlData)
        true
    } else {
        false
    }
}

Java

// The URL scheme must be non-hierarchical, meaning no trailing slashes.
private static final String APP_SCHEME = "example-app:";

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith(APP_SCHEME)) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length()), "UTF-8");
        respondToData(urlData);
        return true;
    }
    return false;
}

La API de shouldOverrideUrlLoading está diseñada principalmente para iniciar intents de URLs específicas. Cuando la implementes, asegúrate de devolver false para las URLs que controla WebView. Sin embargo, no estás limitado a iniciar intents. Puedes reemplazar los intents de inicio por cualquier comportamiento personalizado en las muestras de código anteriores.

Cuando tu WebView anula la carga de URL, acumula automáticamente un historial de páginas web visitadas. Puedes navegar hacia atrás y hacia adelante por el historial con goBack y goForward.

Por ejemplo, a continuación, se muestra cómo tu Activity puede usar el botón Atrás del dispositivo para navegar hacia atrás:

Kotlin

override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    // Check whether the key event is the Back button and if there's history.
    if (keyCode == KeyEvent.KEYCODE_BACK && myWebView.canGoBack()) {
        myWebView.goBack()
        return true
    }
    // If it isn't the Back button or there isn't web page history, bubble up to
    // the default system behavior. Probably exit the activity.
    return super.onKeyDown(keyCode, event)
}

Java

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    // Check whether the key event is the Back button and if there's history.
    if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
        myWebView.goBack();
        return true;
    }
    // If it isn't the Back button or there's no web page history, bubble up to
    // the default system behavior. Probably exit the activity.
    return super.onKeyDown(keyCode, event);
}

Si tu app usa Jetpack AppCompat 1.6.0 o una versión posterior, puedes simplificar aún más el fragmento anterior:

Kotlin

onBackPressedDispatcher.addCallback {
    // Check whether there's history.
    if (myWebView.canGoBack()) {
        myWebView.goBack()
    }
}

Java

onBackPressedDispatcher.addCallback {
    // Check whether there's history.
    if (myWebView.canGoBack()) {
        myWebView.goBack();
    }
}

El método canGoBack devuelve verdadero si hay un historial de páginas web para que el usuario visite. Del mismo modo, puedes usar canGoForward para verificar si hay un historial de avance. Si no realizas esta verificación, después de que el usuario llegue al final del historial, goBack y goForward no harán nada.

Para conservar el historial de navegación durante la recreación de la actividad o la recuperación del proceso en segundo plano, usa saveState para serializar el historial de navegación de WebView en un Bundle y restoreState para restablecerlo. Para obtener orientación sobre cómo administrar de forma segura los límites de tamaño del estado guardado y evitar TransactionTooLargeException, consulta Cómo administrar el estado de WebView de manera eficiente.

Cómo controlar los cambios en la configuración del dispositivo

Durante el tiempo de ejecución, los cambios de estado de la actividad se producen cuando cambia la configuración de un dispositivo, por ejemplo, cuando los usuarios rotan el dispositivo o descartan un editor de método de entrada (IME). Estos cambios hacen que se destruya la actividad de un objeto WebView y se cree una nueva, lo que también crea un nuevo objeto WebView que carga la URL del objeto destruido. Puedes usar saveState dentro de onSaveInstanceState() y restoreState en el momento de la recreación para restablecer el contexto de navegación del usuario. Para conocer las estrategias que te permiten administrar el tamaño del estado de forma segura, consulta Administra el estado de WebView de manera eficiente.

Para modificar el comportamiento predeterminado de tu actividad, puedes cambiar la forma en que controla los cambios de orientation en tu manifiesto. Para obtener más información sobre cómo administrar los cambios de configuración durante el tiempo de ejecución, consulta Cómo administrar los cambios en la configuración.

Administrar ventanas

De forma predeterminada, las solicitudes para abrir ventanas nuevas se ignoran. Esto se aplica independientemente de si se abren con JavaScript o con el atributo de destino en un vínculo. Puedes personalizar tu WebChromeClient para proporcionar tu propio comportamiento para abrir varias ventanas.

Para que tu app sea más segura, es mejor evitar que se abran ventanas emergentes y ventanas nuevas. La forma más segura de implementar este comportamiento es pasar "true" a setSupportMultipleWindows, pero no anular el método onCreateWindow, del que depende setSupportMultipleWindows(). Esta lógica evita que se cargue cualquier página que use target="_blank" en sus vínculos.

Administra la memoria y el rendimiento

Debido a que WebView se ejecuta en un motor basado en Chromium con procesos independientes y código nativo, su uso de memoria de referencia es mayor que el de las vistas de Android estándar. Reutilizar las instancias de WebView y destruirlas de forma correcta es fundamental para mantener una app estable.

Para obtener más información sobre cómo diagnosticar y optimizar el espacio en memoria de tu app cuando usas contenido web, consulta Cómo administrar y diagnosticar la memoria de WebView.