您可以为应用提供基于 Web 的内容(例如 HTML、JavaScript 和 CSS),这些内容是静态编译到应用中,而不是通过互联网获取的。
应用内内容不需要互联网访问权限,也不会消耗用户的带宽。如果内容是专门为 WebView 设计的(也就是说,它依赖于与原生应用的通信),那么用户就不会意外地在 Web 浏览器中加载它。
然而,应用内内容也有一些缺点。更新基于 Web 的内容需要发布新的应用更新,而且如果用户使用的是过时的应用版本,可能会导致设备上的内容与网站上的内容不匹配。
WebViewAssetLoader
WebViewAssetLoader 是一种在 WebView 对象中加载应用内内容的灵活且高性能的方式。此类支持以下功能:
- 使用 HTTP(S) URL 加载内容,以兼容同源策略。
- 加载子资源,例如 JavaScript、CSS、图像和 iframe。
在您的主 Activity 文件中包含 WebViewAssetLoader。以下是从 assets 文件夹加载简单 Web 内容的示例:
Kotlin
private class LocalContentWebViewClient(private val assetLoader: WebViewAssetLoader) : WebViewClientCompat() { @RequiresApi(21) override fun shouldInterceptRequest( view: WebView, request: WebResourceRequest ): WebResourceResponse? { return assetLoader.shouldInterceptRequest(request.url) } // To support API < 21. override fun shouldInterceptRequest( view: WebView, url: String ): WebResourceResponse? { return assetLoader.shouldInterceptRequest(Uri.parse(url)) } }
Java
private static class LocalContentWebViewClient extends WebViewClientCompat { private final WebViewAssetLoader mAssetLoader; LocalContentWebViewClient(WebViewAssetLoader assetLoader) { mAssetLoader = assetLoader; } @Override @RequiresApi(21) public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { return mAssetLoader.shouldInterceptRequest(request.getUrl()); } @Override @SuppressWarnings("deprecation") // To support API < 21. public WebResourceResponse shouldInterceptRequest(WebView view, String url) { return mAssetLoader.shouldInterceptRequest(Uri.parse(url)); } }
您的应用必须配置一个 WebViewAssetLoader 实例以满足其需求。下一节提供了示例。
创建应用内资产和资源
WebViewAssetLoader 依赖于 PathHandler 实例来加载与给定资源路径相对应的资源。虽然您可以实现此接口来根据需要检索应用资源,但 Webkit 库捆绑了 AssetsPathHandler 和 ResourcesPathHandler,分别用于加载 Android 资产 (assets) 和资源 (resources)。
首先,为您的应用创建资产和资源。通常适用以下规则:
- HTML、JavaScript 和 CSS 等文本文件属于资产 (assets)。
- 图像和其他二进制文件属于资源 (resources)。
要将基于文本的 Web 文件添加到项目中,请执行以下操作:
- 在 Android Studio 中,右键点击 app > src > main 文件夹,然后选择 New > Directory。
图 1. 为您的项目创建一个 assets 文件夹。 - 将文件夹命名为 "assets"。
图 2. 命名 assets 文件夹。 - 右键点击 assets 文件夹,然后点击 New > File。输入
index.html并按下 Return 或 Enter 键。
图 3. 创建 index.html文件。 - 重复上一步,创建一个名为
stylesheet.css的空文件。 - 使用下方的两个代码示例中的内容填充您创建的空文件。
```html
<!-- index.html content -->
<html>
<head>
<!-- Tip: Use relative URLs when referring to other in-app content to give
your app code the flexibility to change the scheme or domain as
necessary. -->
<link rel="stylesheet" href="/assets/stylesheet.css">
</head>
<body>
<p>This file is loaded from in-app content.</p>
<p><img src="/res/drawable/android_robot.png" alt="Android robot" width="100"></p>
</body>
</html>
```
```css
<!-- stylesheet.css content -->
body {
background-color: lightblue;
}
```
要将基于图像的 Web 文件添加到您的项目中,请执行以下操作:
将
Android_symbol_green_RGB.png文件下载到您的本地计算机。将文件重命名为
android_robot.png。手动将文件移动到硬盘上的项目
main/res/drawable目录中。
图 4 显示了您添加的图像以及在应用中呈现的前述代码示例中的文本。
要完成应用,请执行以下操作:
通过将以下代码添加到
onCreate()方法中,注册处理程序并配置AssetLoader。Kotlin
val assetLoader = WebViewAssetLoader.Builder() .addPathHandler("/assets/", AssetsPathHandler(this)) .addPathHandler("/res/", ResourcesPathHandler(this)) .build() webView.webViewClient = LocalContentWebViewClient(assetLoader)
Java
final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder() .addPathHandler("/assets/", new WebViewAssetLoader.AssetsPathHandler(this)) .addPathHandler("/res/", new WebViewAssetLoader.ResourcesPathHandler(this)) .build(); mWebView.setWebViewClient(new LocalContentWebViewClient(assetLoader));
通过将以下代码添加到
onCreate()方法中,加载内容。Kotlin
webView.loadUrl("https://appassets.androidplatform.net/assets/index.html")
Java
mWebView.loadUrl("https://appassets.androidplatform.net/assets/index.html");
混合使用应用内内容与网站资源
您的应用可能需要同时加载应用内内容和来自互联网的内容,例如由您网站 CSS 设置样式的应用内 HTML 页面。WebViewAssetLoader 支持此用例。如果没有任何已注册的 PathHandler 实例可以找到给定路径的资源,WebView 将回退到从互联网加载内容。如果您将应用内内容与来自网站的资源混合使用,请为应用内资源保留目录路径,例如 /assets/ 或 /resources/。请避免在这些位置存储任何来自您网站的资源。
Kotlin
val assetLoader = WebViewAssetLoader.Builder() .setDomain("example.com") // Replace this with your website's domain. .addPathHandler("/assets/", AssetsPathHandler(this)) .build() webView.webViewClient = LocalContentWebViewClient(assetLoader) val inAppHtmlUrl = "https://example.com/assets/index.html" webView.loadUrl(inAppHtmlUrl) val websiteUrl = "https://example.com/website/data.json" // JavaScript code to fetch() content from the same origin. val jsCode = "fetch('$websiteUrl')" + ".then(resp => resp.json())" + ".then(data => console.log(data));" webView.evaluateJavascript(jsCode, null)
Java
final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder() .setDomain("example.com") // Replace this with your website's domain. .addPathHandler("/assets/", new AssetsPathHandler(this)) .build(); mWebView.setWebViewClient(new LocalContentWebViewClient(assetLoader)); String inAppHtmlUrl = "https://example.com/assets/index.html"; mWebView.loadUrl(inAppHtmlUrl); String websiteUrl = "https://example.com/website/data.json"; // JavaScript code to fetch() content from the same origin. String jsCode = "fetch('" + websiteUrl + "')" + ".then(resp => resp.json())" + ".then(data => console.log(data));"; mWebView.evaluateJavascript(jsCode, null);
请参阅 GitHub 上的 WebView 演示,了解获取 Web 托管 JSON 数据的应用内 HTML 页面示例。
loadDataWithBaseURL
当您的应用只需要加载一个 HTML 页面且不需要拦截子资源时,请考虑使用 loadDataWithBaseURL(),它不需要应用资产。您可以按照以下代码示例使用它:
Kotlin
val html = "<html><body><p>Hello world</p></body></html>" val baseUrl = "https://example.com/" webView.loadDataWithBaseURL(baseUrl, html, "text/html", null, baseUrl)
Java
String html = "<html><body><p>Hello world</p></body></html>"; String baseUrl = "https://example.com/"; mWebView.loadDataWithBaseURL(baseUrl, html, "text/html", null, baseUrl);
请谨慎选择参数值。请考虑以下事项:
baseUrl:这是您的 HTML 内容加载时的 URL。这必须是一个 HTTP(S) URL。data:这是您要显示的 HTML 内容(以字符串形式)。mimeType:通常必须设置为text/html。encoding:当baseUrl为 HTTP(S) URL 时,此参数未使用,因此可以设置为null。historyUrl:这设置为与baseUrl相同的值。
我们强烈建议将 HTTP(S) URL 用作 baseUrl,因为这有助于确保您的应用符合同源策略。
如果您无法为您的内容找到合适的 baseUrl 并倾向于使用 loadData(),则必须使用百分比编码或 Base64 编码来对内容进行编码。我们强烈建议选择 Base64 编码并使用 Android API 以编程方式对其进行编码,如以下代码示例所示:
Kotlin
val encodedHtml: String = Base64.encodeToString(html.toByteArray(), Base64.NO_PADDING) webView.loadData(encodedHtml, mimeType, "base64")
Java
String encodedHtml = Base64.encodeToString(html.getBytes(), Base64.NO_PADDING); mWebView.loadData(encodedHtml, mimeType, "base64");
应避免的事项
还有其他几种加载应用内内容的方法,但我们强烈建议不要使用它们:
file://URL 和data:URL 被视为不透明来源 (opaque origins),这意味着它们无法利用fetch()或XMLHttpRequest等强大的 Web API。loadData()在内部使用data:URL,因此我们建议改用WebViewAssetLoader或loadDataWithBaseURL()。- 虽然
WebSettings.setAllowFileAccessFromFileURLs()和WebSettings.setAllowUniversalAccessFromFileURLs()可以解决file://URL 的问题,但我们建议不要将它们设置为true,因为这样做会使您的应用容易受到基于文件的攻击。为了最强的安全性,我们建议在所有 API 级别上显式地将这些设置为false。 - 出于同样的原因,我们不建议使用
file://android_assets/和file://android_res/URL。AssetsHandler和ResourcesHandler类旨在作为直接替换方案。 - 避免使用
MIXED_CONTENT_ALWAYS_ALLOW。此设置通常是不必要的,会削弱应用的安全性。我们建议使用与您网站资源相同的协议(HTTP 或 HTTPS)加载应用内内容,并根据需要使用MIXED_CONTENT_COMPATIBILITY_MODE或MIXED_CONTENT_NEVER_ALLOW。