Um seletor de arquivos que abre na hora no Chrome fica inerte dentro de um WebView. O WebView do Android cancela cada pedido de arquivo por padrão, a menos que o app implemente WebChromeClient.onShowFileChooser (e o antigo openFileChooser em versões velhas); esqueça o callback, a permissão em runtime ou o resultado da activity, e o toque não faz nada — sem erro, sem seletor. No iOS, o WKWebView rebaixa silenciosamente capture=camera para a biblioteca de fotos dentro dos navegadores integrados, e um comportamento de longa data do WebKit reinicia o FileList para um item a cada captura. Essas são funções do lado do app, não do lado web, então o upload funciona é uma propriedade do invólucro, não da página. Um runtime próprio do host oferece um único fluxo unificado de arquivos e câmera que se comporta igual em cada dispositivo e versão de sistema.

O toque que não faz nada

No Android, um <input type="file"> não abre um seletor sozinho. A plataforma o encaminha para WebChromeClient.onShowFileChooser, documentado como o callback chamado quando a página web pediu para enviar ou salvar um arquivo. A documentação é franca sobre o padrão: o WebView cancelará todos os pedidos de arquivo. Assim, num invólucro que não implementou o callback, o input renderiza normalmente, o usuário toca, e nada acontece. Sem erro, sem seletor — a página parece perfeitamente saudável por fora. No Android 4.x o antigo openFileChooser é outro override de API oculta, e esquecer de chamar o callback quando o seletor é cancelado deixa o input morto até recarregar a página.

Três portas antes de um arquivo

Mesmo com o callback, um único upload precisa de mais três coisas que a página não pode dar: a permissão do manifest (READ_EXTERNAL_STORAGE ou câmera), a permissão em runtime do Android (API 23+), e o tratamento correto do resultado da activity mais o armazenamento com escopo (Android 10+). Falta uma e a falha é silenciosa — tela preta, lista de arquivos vazia, ou uma URL blob que a página não consegue ler. É por isso que o upload não funciona no meu WebView é uma das perguntas de suporte mais repetidas: o controle parece um elemento HTML padrão e falha como se faltasse uma função nativa.

iOS: capture mente e o FileList de uma foto

No iOS, o WKWebView sim lida com inputs de arquivo, mas o atributo capture é pouco confiável dentro de navegadores integrados como WeChat, TikTok e Instagram — capture=camera é ignorado e o usuário cai na biblioteca de fotos. Pior ainda, no iOS o seletor do sistema substitui todo o FileList a cada sessão, então uma web nunca consegue acumular várias fotos de câmera entre toques; os próprios fóruns de desenvolvedores da Apple confirmam que o comportamento persiste até o iOS 26.4. O acesso a câmera e microfone via getUserMedia (WebRTC) não era suportado no WKWebView antes do iOS 14.3, e mesmo depois exige um delegate, um entitlement e um contexto seguro.

O que você perde sem uma camada de E/S unificada

  • Uploads consistentes. O mesmo <input type="file"> funciona no Chrome e falha no seu app, porque o seletor é código do lado do app.
  • Captura de câmera. capture=camera significa coisas diferentes em WebViews diferentes; os navegadores integrados a rebaixam silenciosamente.
  • Fluxos de vários arquivos. A multi-seleção do Android e a captura acumulativa do iOS precisam de código sob medida que a página não pode dar.
  • Downloads. Um WebView não salva arquivos sozinho; sem setDownloadListener um link de PDF ou CSV não faz nada.
  • Uma superfície de teste real. Funciona no Safari não prova nada, porque cada invólucro é uma implementação diferente do mesmo HTML.

A solução do runtime

Um runtime próprio do host oferece um único fluxo de arquivos e mídia abaixo da página. Ele implementa o seletor uma vez para cada sistema e dispositivo, mapeia o atributo accept e as intenções de câmera ou biblioteca de forma uniforme, trata a multi-seleção e a captura acumulativa para que o FileList se comporte como a spec determina, faz a ponte dos downloads para um armazenamento gerenciado, e aplica as regras de contexto seguro e permissões de forma centralizada. A página chama o <input type="file"> e o capture padrão; o runtime garante o resultado. Sem código repetitivo por app, sem inputs mortos em silêncio, sem surpresas de funciona no Safari.

DimensãoWebViewRuntime próprio do host
Seletor de arquivosO app deve implementar onShowFileChooser; padrão cancelaSeletor unificado, uma implementação
Captura de câmeracapture=camera ignorado em navegadores integradosIntenção de captura uniforme, sem rebaixamento
Vários arquivosMulti-seleção Android e acumulativa iOS exigem código sob medidaMulti-seleção nativa e captura acumulativa
Portas de permissãoManifest, runtime, armazenamento com escopo — tudo manualCentralizado, aplicado uma vez
DownloadsExige setDownloadListener ou não acontece nadaArmazenamento gerenciado, automático
Superfície de testeComportamento por invólucro, funciona no Safari menteUm runtime, um comportamento

O input de arquivos é o controle mais comum da web e o mais propenso a quebrar em silêncio dentro de um WebView. Ele quebra não porque a página esteja errada, mas porque o seletor vive no app, não no navegador. Um runtime devolve essa capacidade para baixo da página, então um toque que deveria abrir um seletor sempre abre um seletor.

Fontes