1. 项目概述:当WebView遇上本地文件
在Android开发里,WebView是个让人又爱又恨的组件。爱它,是因为它能轻松地把网页内容嵌入到原生应用里,实现混合开发,快速迭代;恨它,往往就恨在加载本地内容这一环上。特别是当你的H5页面需要访问应用沙盒内的图片、PDF,或者加载一个本地的HTML模板时, file:// 和 content:// 这两个协议就成了绕不开的坎。
我见过太多项目,前期为了图省事,直接一个 webView.loadUrl(“file:///android_asset/index.html”) 就搞定了。开发机上跑得飞快,测试也一切正常。结果一到线上,各种稀奇古怪的问题就冒出来了:在Android 7.0 (Nougat) 及以上版本的设备上,页面直接白屏,控制台报着一堆 net::ERR_ACCESS_DENIED ;好不容易用 FileProvider 把 file:// 换成了 content:// ,页面是能打开了,里面的JavaScript却像被掐住了脖子,跨域请求发不出去,本地资源加载失败,整个页面功能半残。
这背后的水,远比想象中深。它不仅仅是改个协议那么简单,而是涉及Android沙盒安全模型的演进、 FileProvider 的精细配置、WebView对 content:// 协议的支持度,以及最棘手的—— 同源策略(Same-Origin Policy)在 content:// 协议下的诡异表现 。网上那些零散的帖子,要么只讲 FileProvider 怎么配,要么只提一句要开 setAllowFileAccess ,但真正把这一整套链条,尤其是JavaScript跨域访问这个“深坑”讲透的,少之又少。
今天,我就结合自己踩过的无数个坑,把 WebView 安全加载 content:// 和 file:// 协议的完整方案,从原理、配置到避坑,给你彻底拆解清楚。无论你是正在处理本地H5离线包、需要渲染用户下载的文档,还是构建一个混合开发框架,这篇文章都能帮你省下大量排查问题的时间。
2. 核心安全模型演变与协议选择
要理解为什么不能简单粗暴地用 file:// ,得先看看Android系统权限墙是怎么一步步砌高的。这决定了我们技术方案的根本选型。
2.1 从 file:// 到 content:// :被迫的升级
在Android 7.0之前,应用访问自己的私有目录( /data/data/包名/ )或者申请了 READ_EXTERNAL_STORAGE 权限后访问外部存储,使用 file:// 协议是通行无阻的。WebView通过 file:// 路径可以直接访问这些文件。
但是,从Android 7.0 (API 24) 开始,Google引入了“StrictMode”的严格文件权限策略。核心变化是: 禁止应用向外部公开 file:// URI 。如果一个应用尝试通过 Intent 携带 file:// URI传递给另一个应用,系统会直接抛出 FileUriExposedException 。
这个限制很快也影响到了WebView。虽然WebView和你的应用在同一个进程内,但系统认为通过 file:// 加载应用私有目录之外(甚至是 android_asset 和 android_res 在某些情况下)的文件,是一种潜在的不安全行为。因此,当你尝试 webView.loadUrl(“file:///storage/emulated/0/Download/my.html”) 时,在API 24+的设备上,很可能会失败。
解决方案就是使用 ContentProvider 来安全地共享文件。 FileProvider 是Android系统提供的一个特殊的 ContentProvider 子类,它能够为你生成一个形如 content://com.yourapp.fileprovider/external_files/Download/my.html 的URI。这个URI是临时的、受权限控制的,替代了原始的 file:// 路径。
注意 :这里有个关键误区。很多人以为只有跨应用分享文件才需要用
FileProvider。实际上, 即使是在你自己的应用内部,只要WebView加载的file://路径指向了应用私有目录(getFilesDir(),getCacheDir())以外的位置,在Android 7.0+上就强烈建议使用FileProvider来获取content://URI进行加载 ,以确保最大的兼容性。对于android_asset和android_res目录,情况稍特殊,后文会详述。
2.2 content:// 协议的同源困境
换用 content:// 协议,WebView能加载页面了,但真正的“坑”才刚刚开始——同源策略。
同源策略是浏览器安全的基石,它规定:来自不同“源”(协议、域名、端口三者完全相同)的脚本,不能互相访问对方的DOM、Cookie、LocalStorage等资源。在Web上,“源”通常是 https://www.example.com:443 。
那么, content://com.yourapp.fileprovider/external_files/Download/ 这个URI,它的“源”是什么? 答案是:整个 content:// 协议本身,通常被视为一个不透明的、特殊的源。更麻烦的是, FileProvider 生成的每次URI都可能因为路径、授权等因素略有不同,但它们之间很可能也被视为不同源 。
这就导致了灾难性的后果:
- 页面内AJAX请求失败 :你的HTML页面通过
<script src=“app.js”></script>能加载同目录的JS,但JS里如果用fetch(‘./data.json’)发起请求,浏览器会因跨域而阻止。 - 访问本地资源失败 :在CSS中设置
background-image: url(‘./bg.png’),图片可能无法加载。 - iframe通信中断 :如果页面内嵌了另一个本地HTML的
iframe,父子页面之间的postMessage可能无法正常工作。
浏览器控制台会抛出类似这样的错误:
Access to fetch at ‘content://com.yourapp.fileprovider/external_files/Download/data.json’ from origin ‘null’ has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
或者
Uncaught DOMException: Failed to read the ‘localStorage’ property fro
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/weixin_29192365/article/details/163119132



