Na web aberta, um link <a download> ou um endpoint de PDF/CSV salva um arquivo no dispositivo. Dentro de um WebView, esse mesmo toque é um no-op silencioso. O Android só expõe downloads por meio de um DownloadListener do host, e o iOS WKWebView não traz gerenciador de downloads. Um runtime próprio do host faz a ponte do download: uma API que salva um arquivo real em cada motor.

Android: os downloads precisam de um listener do host

A documentação do Android e a comunidade concordam: um WebView padrão não baixa nada sozinho. Quando o usuário toca um link de download, o motor entrega os parâmetros —url, userAgent, contentDisposition, mimeType, contentLength— ao DownloadListener que você registra com setDownloadListener, ou nada acontece. Sem esse listener, um botão "Baixar fatura PDF" que funciona no Chrome não faz absolutamente nada dentro do WebView do app. Conectá-lo significa possuir a instância, extrair o nome do arquivo de Content-Disposition, solicitar WRITE_EXTERNAL_STORAGE (ou delegar ao DownloadManager) e mostrar progresso. Downloads blob e <a download> são a mesma história: a página não pode gravar o arquivo; só o host pode.

iOS: WKWebView não tem gerenciador de downloads

O WebView da Apple não tem um pipeline de download integrado. Textos sobre as limitações do WKWebView dizem claro: "Não há gerenciador de downloads, então os downloads falham ou não fazem nada em silêncio". Antes do iOS 15, a única via era interceptar a requisição e reimplementar o salvamento. O iOS 15 adicionou WKDownloadDelegate, mas o host precisa implementá-lo: o atributo <a download> apenas sinaliza intenção, e a execução vive no app. O comportamento padrão é pior que um no-op: o arquivo frequentemente abre dentro do frame web e quebra o histórico de navegação, ou o toque é engolido sem feedback.

O que quebra quando o download morre

  • Faturas e extratos não chegam. Um botão "Baixar PDF" que funciona no Safari não faz nada no WebView.
  • Exportações CSV e relatórios somem. Painéis SaaS e financeiros que exportam dados perdem a única saída.
  • Atualizações e APKs travam. Fluxos de atualização que empurram um APK ou pacote falham dentro do navegador integrado.
  • Navegadores integrados pioram. WeChat, Douyin e similares raramente expõem uma superfície de download, então a falha se esconde no QA de desktop.

A solução do runtime

Um runtime próprio do host faz a ponte do download. A página chama uma API uniforme —runtime.download(url, options) ou runtime.saveFile(blob)— e o host salva com DownloadManager no Android e WKDownloadDelegate no iOS. Nome, destino, progresso e permissão de armazenamento são tratados de forma nativa e consistente. Uma integração, cada motor, um arquivo salvo.

DimensãoWebViewRuntime próprio do host
Disparador<a download> é no-opruntime.download() uniforme
Caminho AndroidHost liga DownloadListenerDownloadManager nativo
Caminho iOSSem gerenciador de downloadsWKDownloadDelegate nativo
Salvar blobNão a partir da páginaUma API
PermissõesManual por appPonte nativa
ConsistênciaPor motorUniforme

Um app nativo salva um arquivo em cada dispositivo. Um WebView padrão quase em nenhum. Um runtime dá à página uma ação de salvar — e um arquivo real no fim.

Fontes