Un selector de archivos que se abre al instante en Chrome queda inerte dentro de un WebView. El WebView de Android cancela cada solicitud de archivo por defecto a menos que la app implemente WebChromeClient.onShowFileChooser (y el antiguo openFileChooser en versiones viejas); olvida el callback, el permiso en tiempo de ejecución o el resultado de la actividad, y el toque no hace nada — sin error, sin selector. En iOS, WKWebView degrada en silencio capture=camera a la biblioteca de fotos dentro de los navegadores integrados, y un comportamiento de WebKit de larga data reinicia el FileList a un elemento en cada captura. Estas son funciones del lado de la app, no del lado web, así que la subida funciona es una propiedad del contenedor, no de la página. Un runtime propio del host ofrece un único flujo unificado de archivos y cámara que se comporta igual en cada dispositivo y versión de sistema.
El toque que no hace nada
En Android, un <input type="file"> no abre un selector por sí solo. La plataforma lo enruta a WebChromeClient.onShowFileChooser, documentado como el callback invocado cuando la página web ha solicitado subir o guardar un archivo. La documentación es franca sobre el valor por defecto: WebView cancelará todas las solicitudes de archivo. Así que en un contenedor que no implementó el callback, el input se renderiza con normalidad, el usuario toca, y no pasa nada. Sin error, sin selector — la página se ve perfectamente sana por fuera. En Android 4.x el antiguo openFileChooser es otro override de API oculta, y olvidar llamar al callback cuando se cancela el selector deja el input muerto hasta recargar la página.
Tres puertas antes de un archivo
Aun con el callback, una sola subida necesita tres cosas más que la página no puede dar: el permiso del manifest (READ_EXTERNAL_STORAGE o cámara), el permiso en tiempo de ejecución de Android (API 23+), y el manejo correcto del resultado de la actividad más el almacenamiento con alcance (Android 10+). Falta una y la falla es silenciosa — pantalla negra, lista de archivos vacía, o una URL blob que la página no puede leer. Por eso la subida no funciona en mi WebView es una de las preguntas de soporte más repetidas: el control parece un elemento HTML estándar y falla como si faltara una función nativa.
iOS: capture miente y el FileList de una foto
En iOS, WKWebView sí maneja los inputs de archivo, pero el atributo capture es poco fiable dentro de navegadores integrados como WeChat, TikTok e Instagram — capture=camera se ignora y el usuario cae en la biblioteca de fotos. Peor aún, en iOS el selector del sistema reemplaza todo el FileList en cada sesión, así que una web nunca puede acumular varias fotos de cámara entre toques; los propios foros de desarrolladores de Apple confirman que el comportamiento persiste hasta iOS 26.4. El acceso a cámara y micrófono vía getUserMedia (WebRTC) no se soportó en WKWebView antes de iOS 14.3, y aun después requiere un delegate, un entitlement y un contexto seguro.
Qué pierdes sin una capa de E/S unificada
- Subidas consistentes. El mismo
<input type="file">funciona en Chrome y falla en tu app, porque el selector es código del lado de la app. - Captura de cámara. capture=camera significa cosas distintas en distintos WebView; los navegadores integrados la degradan en silencio.
- Flujos de varios archivos. La multi-selección de Android y la captura acumulativa de iOS necesitan código a medida que la página no puede dar.
- Descargas. Un WebView no guarda archivos por sí solo; sin setDownloadListener un enlace a PDF o CSV no hace nada.
- Una superficie de prueba real. Funciona en Safari no prueba nada, porque cada contenedor es una implementación distinta del mismo HTML.
La solución del runtime
Un runtime propio del host ofrece un único flujo de archivos y medios por debajo de la página. Implementa el selector una vez para cada sistema y dispositivo, mapea el atributo accept y las intenciones de cámara o biblioteca de forma uniforme, maneja la multi-selección y la captura acumulativa para que el FileList se comporte como dicta la spec, puentea las descargas a un almacén gestionado, y aplica las reglas de contexto seguro y permisos de forma centralizada. La página llama al <input type="file"> y a capture estándar; el runtime garantiza el resultado. Sin código repetitivo por app, sin inputs muertos en silencio, sin sorpresas de funciona en Safari.
| Dimensión | WebView | Runtime propio del host |
|---|---|---|
| Selector de archivos | La app debe implementar onShowFileChooser; por defecto cancela | Selector unificado, una implementación |
| Captura de cámara | capture=camera ignorado en navegadores integrados | Intención de captura uniforme, sin degradación |
| Varios archivos | Multi-selección Android y acumulativa iOS requieren código a medida | Multi-selección nativa y captura acumulativa |
| Puertas de permisos | Manifest, runtime, almacenamiento con alcance — todo manual | Centralizado, aplicado una vez |
| Descargas | Requiere setDownloadListener o no pasa nada | Almacén gestionado, automático |
| Superficie de prueba | Comportamiento por contenedor, funciona en Safari miente | Un runtime, un comportamiento |
El input de archivos es el control más ordinario de la web y el más probable a romperse en silencio dentro de un WebView. Se rompe no porque la página esté mal, sino porque el selector vive en la app, no en el navegador. Un runtime devuelve esa capacidad por debajo de la página, así un toque que debiera abrir un selector siempre abre un selector.
Fuentes
- Android Developers — WebChromeClient.onShowFileChooser: el comportamiento por defecto es que WebView cancelará todas las solicitudes de archivo — la razón documentada por la que un input de archivo en WebView puro está inerte.
- Android Developers — Build web apps in WebView: acceso a archivos, permisos y el requisito de onShowFileChooser — el conjunto completo de puertas que la app debe abrir.
- Apple Developer Forums — iOS WKWebView reinicia el FileList a un elemento en cada captura (persiste hasta iOS 26.4) — una limitación de WebKit confirmada y multi-versión.
- web.dev — Web on Android: WebView delega el comportamiento sensible a la app contenedora — por qué subir y descargar son funciones del lado de la app.
- Stack Overflow — Android WebView input type=file requiere cuatro métodos callback ocultos separados — el alcance real del código repetitivo.