Tải nội dung trong ứng dụng

Bạn có thể cung cấp nội dung dựa trên nền tảng web (chẳng hạn như HTML, JavaScript và CSS) cho ứng dụng của mình bằng cách biên dịch tĩnh vào ứng dụng thay vì tìm nạp qua Internet.

Nội dung trong ứng dụng không yêu cầu kết nối Internet hoặc tiêu thụ băng thông của người dùng. Nếu nội dung được thiết kế riêng cho WebView (nghĩa là nội dung phụ thuộc vào việc giao tiếp với ứng dụng gốc), thì người dùng không thể vô tình tải nội dung đó trong trình duyệt web.

Tuy nhiên, nội dung trong ứng dụng có một số hạn chế. Để cập nhật nội dung dựa trên nền tảng web, bạn cần phải chuyển một bản cập nhật ứng dụng mới. Do đó, nếu người dùng sử dụng phiên bản ứng dụng lỗi thời, thì nội dung trên trang web có thể không khớp với nội dung trong ứng dụng.

WebViewAssetLoader

WebViewAssetLoader là một cách linh hoạt và hiệu quả để tải nội dung trong ứng dụng trong đối tượng WebView. Lớp này hỗ trợ các mục sau:

  • Tải nội dung bằng một URL HTTP(S) để đảm bảo khả năng tương thích với chính sách cùng nguồn gốc.
  • Đang tải các tài nguyên phụ như JavaScript, CSS, hình ảnh và iframe.

Đưa WebViewAssetLoader vào tệp hoạt động chính. Sau đây là ví dụ về cách tải nội dung web đơn giản từ thư mục thành phần:

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));
    }
}

Ứng dụng của bạn phải định cấu hình một thực thể WebViewAssetLoader cho phù hợp với nhu cầu của ứng dụng. Phần tiếp theo có một ví dụ.

Tạo thành phần và tài nguyên trong ứng dụng

WebViewAssetLoader dựa vào các thực thể PathHandler để tải các tài nguyên tương ứng với một đường dẫn tài nguyên nhất định. Mặc dù bạn có thể triển khai giao diện này để truy xuất tài nguyên theo nhu cầu của ứng dụng, nhưng thư viện Webkit sẽ gói AssetsPathHandlerResourcesPathHandler tương ứng để tải thành phần và tài nguyên Android.

Để bắt đầu, hãy tạo các thành phần và tài nguyên cho ứng dụng của bạn. Nhìn chung, những điều sau sẽ áp dụng:

  • Các tệp văn bản như HTML, JavaScript và CSS nằm trong thành phần.
  • Hình ảnh và các tệp nhị phân khác thuộc về tài nguyên.

Để thêm các tệp web dựa trên văn bản vào một dự án, hãy làm như sau:

  1. Trong Android Studio, hãy nhấp chuột phải vào thư mục app > src > main (ứng dụng > src > chính) rồi chọn New > Directory (Mới > Thư mục).
    Hình ảnh minh hoạ các trình đơn thư mục tạo của Android Studio
    Hình 1. Tạo thư mục tài sản cho dự án của bạn.
  2. Đặt tên thư mục là "Assets".
    Hình ảnh cho thấy thư mục thành phần
    Hình 2. Đặt tên cho thư mục thành phần.
  3. Nhấp chuột phải vào thư mục Assets (tài sản), rồi nhấp vào New > File (Mới > Tệp). Nhập index.html rồi nhấn phím Return hoặc Enter.
  4. Lặp lại bước trước đó để tạo một tệp trống cho stylesheet.css.
  5. Điền vào các tệp trống mà bạn đã tạo bằng nội dung trong 2 mã mẫu tiếp theo.
```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;
}
```

Để thêm tệp web dựa trên hình ảnh vào dự án, hãy làm như sau:

  1. Tải tệp Android_symbol_green_RGB.png xuống máy cục bộ.

  2. Đổi tên tệp thành android_robot.png.

  3. Di chuyển tệp theo cách thủ công vào thư mục main/res/drawable của dự án trên ổ đĩa cứng.

Hình 4 cho thấy hình ảnh bạn đã thêm và văn bản của các mã mẫu trước đó hiển thị trong ứng dụng.

Hình ảnh cho thấy kết quả được hiển thị trên ứng dụng
Hình 4. Tệp HTML trong ứng dụng và tệp hình ảnh hiển thị trong một ứng dụng.

Để hoàn tất ứng dụng, hãy làm như sau:

  1. Đăng ký các trình xử lý và định cấu hình AssetLoader bằng cách thêm mã sau vào phương thức 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. Tải nội dung bằng cách thêm mã sau vào phương thức onCreate():

    Kotlin

    webView.loadUrl("https://appassets.androidplatform.net/assets/index.html")
    

    Java

    mWebView.loadUrl("https://appassets.androidplatform.net/assets/index.html");
    

Kết hợp nội dung trong ứng dụng với tài nguyên từ trang web của bạn

Ứng dụng của bạn có thể cần tải kết hợp cả nội dung trong ứng dụng và nội dung từ Internet, chẳng hạn như trang HTML trong ứng dụng được tạo kiểu theo CSS của trang web. WebViewAssetLoader hỗ trợ trường hợp sử dụng này. Nếu không thực thể PathHandler nào đã đăng ký có thể tìm thấy tài nguyên cho đường dẫn đã cho, thì WebView sẽ quay lại sử dụng phương thức tải nội dung từ Internet. Nếu bạn kết hợp nội dung trong ứng dụng với tài nguyên trên trang web của mình, hãy đặt trước đường dẫn thư mục (chẳng hạn như /assets/ hoặc /resources/) cho tài nguyên trong ứng dụng. Tránh lưu trữ bất kỳ tài nguyên nào từ trang web của bạn ở những vị trí đó.

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);

Xem bản minh hoạ WebView trên GitHub để biết ví dụ về trang HTML trong ứng dụng tìm nạp dữ liệu JSON được lưu trữ trên web.

tảiDataWithBaseURL

Khi ứng dụng của bạn chỉ cần tải một trang HTML mà không cần chặn các tài nguyên phụ, hãy cân nhắc sử dụng loadDataWithBaseURL() mà không yêu cầu thành phần ứng dụng. Bạn có thể sử dụng mã này như trong mã mẫu sau:

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);

Chọn giá trị đối số một cách cẩn thận. Hãy cân nhắc thực hiện những bước sau:

  • baseUrl: đây là URL để tải nội dung HTML của bạn. Đây phải là một URL HTTP(S).
  • data: đây là nội dung HTML mà bạn muốn hiển thị dưới dạng chuỗi.
  • mimeType: giá trị này thường phải được đặt thành text/html.
  • encoding: thuộc tính này không được dùng khi baseUrl là một URL HTTP(S) nên có thể đặt thành null.
  • historyUrl: thuộc tính này được đặt thành cùng giá trị với baseUrl.

Bạn nên dùng URL loại HTTP(S) làm baseUrl, vì điều này giúp đảm bảo ứng dụng của bạn tuân thủ chính sách cùng nguồn gốc.

Nếu không tìm thấy baseUrl phù hợp với nội dung của mình và muốn sử dụng loadData(), bạn phải mã hoá nội dung bằng mã hoá phần trăm hoặc mã hoá Base64. Bạn nên chọn phương thức mã hoá Base64 và sử dụng các API Android để mã hoá phương thức này theo phương thức lập trình, như trong mã mẫu sau:

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");

Những điều nên tránh

Ngoài ra, còn có một số cách khác để tải nội dung trong ứng dụng, nhưng bạn nên làm như vậy:

  • Các URL file:// và URL data: được xem là nguồn gốc không rõ ràng, tức là chúng không thể tận dụng các API web mạnh mẽ như fetch() hoặc XMLHttpRequest. loadData() sử dụng nội bộ URL data:. Vì vậy, bạn nên sử dụng WebViewAssetLoader hoặc loadDataWithBaseURL().
  • Mặc dù WebSettings.setAllowFileAccessFromFileURLs()WebSettings.setAllowUniversalAccessFromFileURLs() có thể giải quyết các vấn đề với URL file://, nhưng bạn không nên đặt các URL này thành true vì làm như vậy sẽ khiến ứng dụng của bạn dễ bị khai thác dựa trên tệp. Bạn nên đặt rõ ràng các giá trị này thành false ở mọi cấp độ API để có mức bảo mật mạnh nhất.
  • Với lý do tương tự, bạn không nên dùng các URL file://android_assets/file://android_res/. Các lớp AssetsHandlerResourcesHandler là các lớp thay thế không thể dùng.
  • Tránh sử dụng MIXED_CONTENT_ALWAYS_ALLOW. Chế độ cài đặt này thường không cần thiết và làm giảm khả năng bảo mật của ứng dụng. Bạn nên tải nội dung trong ứng dụng qua cùng một giao thức – HTTP hoặc HTTPS – làm tài nguyên của trang web và sử dụng MIXED_CONTENT_COMPATIBILITY_MODE hoặc MIXED_CONTENT_NEVER_ALLOW (nếu phù hợp).