載入應用程式內容

您可以提供網頁式內容 (例如 HTML、JavaScript 和 CSS),讓應用程式使用靜態編譯至應用程式,而非透過網際網路擷取。

應用程式內容不需存取網際網路,也不需消耗使用者的頻寬。如果內容是專為 WebView 設計,也就是需要與原生應用程式通訊,使用者就不會意外在網路瀏覽器中載入內容。

不過,應用程式內容有一些缺點。如要更新網路內容,您必須傳送新的應用程式更新,如果使用者的應用程式版本過舊,網站上的內容和裝置內容可能就會不相符。

WebViewAssetLoader

WebViewAssetLoader 可讓您有彈性且有效率的方式,在 WebView 物件中載入應用程式內的內容。這個類別支援下列項目:

  • 使用 HTTP(S) 網址載入內容,以便與相同來源政策相容。
  • 載入 JavaScript、CSS、圖片和 iframe 等子資源。

在主要活動檔案中加入 WebViewAssetLoader。以下範例說明如何從資產資料夾載入簡易網頁內容:

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 程式庫套件 AssetsPathHandlerResourcesPathHandler 會分別載入 Android 資產和資源。

如要開始使用,請先為您的應用程式建立素材資源和資源。一般來說,您必須符合以下條件:

  • HTML、JavaScript 和 CSS 等文字檔案都屬於素材資源。
  • 圖片和其他二進位檔案屬於資源。

如要在專案中新增文字型網頁檔案,請按照下列步驟操作:

  1. 在 Android Studio 中,以滑鼠右鍵按一下「app」>「src」>「main」資料夾,然後依序選擇「New」>「Directory」
    顯示 Android Studio 建立目錄選單的圖片
    圖 1.為專案建立素材資源資料夾。
  2. 將資料夾命名為「assets」。
    顯示素材資源資料夾的圖片
    圖 2.為素材資源資料夾命名。
  3. assets 資料夾上按一下滑鼠右鍵,然後依序點選「新增」>「檔案」。 輸入 index.html,然後按下「Return」或「Enter」鍵。
  4. 重複上述步驟,為 stylesheet.css 建立空白檔案。
  5. 在接下來兩個程式碼範例中,填入您建立的空白檔案。
```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;
}
```

如要在專案中新增圖片式網路檔案,請按照下列步驟操作:

  1. Android_symbol_green_RGB.png 檔案下載至本機電腦。

  2. 將檔案重新命名為 android_robot.png

  3. 手動將檔案移至硬碟上專案的 main/res/drawable 目錄。

圖 4 顯示您新增的圖片,以及上述程式碼範例的文字,這些內容會在應用程式中算繪。

顯示應用程式轉譯輸出內容的圖片
圖 4. 在應用程式中轉譯的應用程式內 HTML 檔案和圖片檔。

如要完成應用程式,請按照下列步驟操作:

  1. 註冊處理常式並設定 AssetLoader,方法是將下列程式碼新增至 onCreate() 方法:

    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));
    
  2. 將下列程式碼新增至 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);

如需應用程式內 HTML 頁面擷取網頁代管 JSON 資料的範例,請參閱 GitHub 中的 WebView 示範

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 內容時做為依據的網址。這必須是 HTTP(S) 網址。
  • data:這是您要以字串形式顯示的 HTML 內容。
  • mimeType:這通常必須設為 text/html
  • encoding:當 baseUrl 是 HTTP(S) 網址時未使用,因此可設為 null
  • historyUrl:已設為與 baseUrl 相同的值。

我們強烈建議您使用 HTTP(S) 網址做為 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:// 網址和 data: 網址視為「不透明來源」,因此無法使用功能強大的網路 API,例如 fetch()XMLHttpRequestloadData() 內部會使用 data: 網址,因此建議改用 WebViewAssetLoaderloadDataWithBaseURL()
  • 雖然 WebSettings.setAllowFileAccessFromFileURLs()WebSettings.setAllowUniversalAccessFromFileURLs() 可以解決 file:// 網址的問題,我們仍建議不要將這些項目設為 true,以免應用程式容易遭受檔案攻擊。為獲得最佳安全性,建議您在所有 API 級別中明確設為 false
  • 基於相同理由,我們建議您避免使用 file://android_assets/file://android_res/ 網址。AssetsHandlerResourcesHandler 類別是用來取代替換作業。
  • 請避免使用 MIXED_CONTENT_ALWAYS_ALLOW。這項設定通常並非必要,因此會降低應用程式的安全性。建議您透過與網站資源相同的配置 (HTTP 或 HTTPS),載入應用程式內容,並視情況使用 MIXED_CONTENT_COMPATIBILITY_MODEMIXED_CONTENT_NEVER_ALLOW