Tạo ứng dụng web trong WebView

Sử dụng WebView để phân phối một ứng dụng web hoặc một trang web trong ứng dụng khách. Lớp WebView là một tiện ích của lớp View của Android, cho phép bạn hiển thị các trang web như một phần của bố cục hoạt động. Nó không bao gồm các tính năng của một trình duyệt web được phát triển đầy đủ, chẳng hạn như các nút điều hướng hoặc thanh địa chỉ. Theo mặc định, tất cả những gì WebView làm là hiển thị một trang web.

WebView có thể giúp bạn cung cấp thông tin trong ứng dụng mà bạn có thể cần cập nhật, chẳng hạn như thoả thuận với người dùng cuối hoặc hướng dẫn sử dụng. Trong ứng dụng Android, bạn có thể tạo một Activity chứa một WebView, sau đó dùng đối tượng này để hiển thị tài liệu được lưu trữ trực tuyến.

WebView cũng có thể giúp ích khi ứng dụng của bạn cung cấp dữ liệu cho người dùng mà cần có kết nối Internet để truy xuất dữ liệu, chẳng hạn như email. Trong trường hợp này, bạn có thể thấy rằng việc tạo một WebView trong ứng dụng Android hiển thị một trang web có tất cả dữ liệu người dùng sẽ dễ dàng hơn so với việc thực hiện một yêu cầu mạng, sau đó phân tích cú pháp dữ liệu và hiển thị dữ liệu đó trong bố cục Android. Thay vào đó, bạn có thể thiết kế một trang web phù hợp với các thiết bị chạy Android, sau đó triển khai một WebView trong ứng dụng Android để tải trang web đó.

Tài liệu này mô tả cách bắt đầu sử dụng WebView, cách liên kết JavaScript từ trang web của bạn với mã phía máy khách trong ứng dụng Android, cách xử lý thao tác điều hướng trang và cách quản lý các cửa sổ khi sử dụng WebView.

Làm việc với WebView trên các phiên bản Android cũ

Để sử dụng an toàn các chức năng WebView gần đây hơn trên thiết bị mà ứng dụng của bạn đang chạy, hãy thêm thư viện AndroidX Webkit. Đây là một thư viện tĩnh mà bạn có thể thêm vào ứng dụng của mình để sử dụng các API android.webkit không có sẵn cho các phiên bản nền tảng cũ hơn.

Thêm đoạn mã này vào tệp build.gradle như sau:

Kotlin

dependencies {
    implementation("androidx.webkit:webkit:1.8.0")
}

Groovy

dependencies {
    implementation ("androidx.webkit:webkit:1.8.0")
}

Khám phá ví dụ WebView trên GitHub để biết thêm thông tin chi tiết.

Thêm WebView vào ứng dụng

Để thêm một WebView vào ứng dụng, bạn có thể thêm phần tử <WebView> vào bố cục hoạt động hoặc đặt toàn bộ cửa sổ Activity làm WebView trong onCreate().

Thêm WebView vào bố cục hoạt động

Để thêm một WebView vào ứng dụng trong bố cục, hãy thêm mã sau vào tệp XML bố cục của hoạt động:

<WebView
    android:id="@+id/webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

Để tải một trang web trong WebView, hãy sử dụng loadUrl(), như trong ví dụ sau:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.loadUrl("http://www.example.com")

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.loadUrl("http://www.example.com");

Thêm WebView trong onCreate()

Để thêm WebView vào ứng dụng trong phương thức onCreate() của một hoạt động, hãy sử dụng logic tương tự như sau:

Kotlin

val myWebView = WebView(activityContext)
setContentView(myWebView)

Java

WebView myWebView = new WebView(activityContext);
setContentView(myWebView);

Sau đó, tải trang:

Kotlin

myWebView.loadUrl("http://www.example.com")

Java

myWebView.loadUrl("https://www.example.com");

Hoặc tải URL từ một chuỗi HTML:

Kotlin

// Create an unencoded HTML string, then convert the unencoded HTML string into
// bytes. Encode it with base64 and load the data.
val unencodedHtml =
     "<html><body>'%23' is the percent code for ‘#‘ </body></html>";
val encodedHtml = Base64.encodeToString(unencodedHtml.toByteArray(), Base64.NO_PADDING)
myWebView.loadData(encodedHtml, "text/html", "base64")

Java

// Create an unencoded HTML string, then convert the unencoded HTML string into
// bytes. Encode it with base64 and load the data.
String unencodedHtml =
     "<html><body>'%23' is the percent code for ‘#‘ </body></html>";
String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(),
        Base64.NO_PADDING);
myWebView.loadData(encodedHtml, "text/html", "base64");

Ứng dụng của bạn phải có quyền truy cập vào Internet. Để có quyền truy cập vào Internet, hãy yêu cầu quyền INTERNET trong tệp kê khai, như trong ví dụ sau:

<manifest ... >
    <uses-permission android:name="android.permission.INTERNET" />
    ...
</manifest>

Bạn có thể tuỳ chỉnh WebView bằng cách làm theo một trong những cách sau:

  • Bật chế độ hỗ trợ toàn màn hình bằng cách sử dụng WebChromeClient. Lớp này cũng được gọi khi WebView cần có quyền thay đổi giao diện người dùng của ứng dụng lưu trữ, chẳng hạn như tạo hoặc đóng cửa sổ hoặc gửi hộp thoại JavaScript cho người dùng. Để tìm hiểu thêm về cách gỡ lỗi trong bối cảnh này, hãy đọc bài viết Gỡ lỗi ứng dụng web.
  • Xử lý các sự kiện ảnh hưởng đến việc hiển thị nội dung, chẳng hạn như lỗi khi gửi biểu mẫu hoặc điều hướng bằng WebViewClient. Bạn cũng có thể dùng lớp con này để chặn quá trình tải URL.
  • Bật JavaScript bằng cách sửa đổi WebSettings.
  • Sử dụng JavaScript để truy cập vào các đối tượng khung Android mà bạn đã chèn vào một WebView.

Sử dụng JavaScript trong WebView

Nếu trang web mà bạn muốn tải trong WebView sử dụng JavaScript, thì bạn phải bật JavaScript cho WebView. Sau khi bật JavaScript, bạn có thể tạo các giao diện giữa mã ứng dụng và mã JavaScript.

Bật JavaScript

Theo mặc định, JavaScript sẽ bị tắt trong WebView. Bạn có thể bật tính năng này thông qua WebSettings được đính kèm vào WebView. Truy xuất WebSettings bằng getSettings(), sau đó bật JavaScript bằng setJavaScriptEnabled().

Hãy xem ví dụ sau:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.settings.javaScriptEnabled = true

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);

WebSettings cung cấp quyền truy cập vào nhiều chế độ cài đặt khác mà bạn có thể thấy hữu ích. Ví dụ: nếu đang phát triển một ứng dụng web được thiết kế riêng cho WebView trong ứng dụng Android, thì bạn có thể xác định một chuỗi tác nhân người dùng tuỳ chỉnh bằng setUserAgentString(), sau đó truy vấn tác nhân người dùng tuỳ chỉnh trong trang web để xác minh rằng ứng dụng Android của bạn là ứng dụng yêu cầu trang web.

Liên kết mã JavaScript với mã Android

Khi phát triển một ứng dụng web được thiết kế dành riêng cho WebView trong ứng dụng Android, bạn có thể tạo các giao diện giữa mã JavaScript và mã Android phía máy khách. Ví dụ: mã JavaScript của bạn có thể gọi một phương thức trong mã Android để hiển thị Dialog, thay vì sử dụng hàm alert() của JavaScript.

Để liên kết một giao diện mới giữa JavaScript và mã Android, hãy gọi addJavascriptInterface(), truyền cho giao diện đó một thực thể lớp để liên kết với JavaScript và tên giao diện mà JavaScript có thể gọi để truy cập vào lớp.

Ví dụ: bạn có thể thêm lớp sau vào ứng dụng Android:

Kotlin

/** Instantiate the interface and set the context.  */
class WebAppInterface(private val mContext: Context) {

    /** Show a toast from the web page.  */
    @JavascriptInterface
    fun showToast(toast: String) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show()
    }
}

Java

public class WebAppInterface {
    Context mContext;

    /** Instantiate the interface and set the context. */
    WebAppInterface(Context c) {
        mContext = c;
    }

    /** Show a toast from the web page. */
    @JavascriptInterface
    public void showToast(String toast) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
    }
}

Trong ví dụ này, lớp WebAppInterface cho phép trang web tạo thông báo Toast bằng phương thức showToast().

Bạn có thể liên kết lớp này với JavaScript chạy trong WebView bằng addJavascriptInterface(), như minh hoạ trong ví dụ sau:

Kotlin

val webView: WebView = findViewById(R.id.webview)
webView.addJavascriptInterface(WebAppInterface(this), "Android")

Java

WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");

Thao tác này sẽ tạo một giao diện có tên là Android cho JavaScript đang chạy trong WebView. Tại thời điểm này, ứng dụng web của bạn có quyền truy cập vào lớp WebAppInterface. Ví dụ: sau đây là một số mã HTML và JavaScript tạo thông báo tạm thời bằng giao diện mới khi người dùng nhấn vào một nút:

<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />

<script type="text/javascript">
    function showAndroidToast(toast) {
        Android.showToast(toast);
    }
</script>

Bạn không cần khởi chạy giao diện Android từ JavaScript. WebView sẽ tự động cung cấp thông tin này cho trang web của bạn. Vì vậy, khi người dùng nhấn vào nút này, hàm showAndroidToast() sẽ sử dụng giao diện Android để gọi phương thức WebAppInterface.showToast().

Xử lý thao tác điều hướng trang

Khi người dùng nhấn vào một đường liên kết trên trang web trong WebView, theo mặc định, Android sẽ khởi chạy một ứng dụng xử lý URL. Thông thường, trình duyệt web mặc định sẽ mở và tải URL đích. Tuy nhiên, bạn có thể ghi đè hành vi này cho WebView để các đường liên kết mở trong WebView. Sau đó, bạn có thể cho phép người dùng di chuyển ngược lại và tiến lên thông qua nhật ký trang web do WebView duy trì.

Để mở các đường liên kết mà người dùng nhấn vào, hãy cung cấp một WebViewClient cho WebView bằng cách sử dụng setWebViewClient(). Tất cả các đường liên kết mà người dùng nhấn vào đều tải trong WebView. Nếu bạn muốn có nhiều quyền kiểm soát hơn đối với vị trí tải một đường liên kết được nhấp, hãy tạo WebViewClient của riêng bạn để ghi đè phương thức shouldOverrideUrlLoading(). Ví dụ sau đây giả định rằng MyWebViewClient là một lớp bên trong của Activity.

Kotlin

private class MyWebViewClient : WebViewClient() {

    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
        if (Uri.parse(url).host == "www.example.com") {
            // This is your website, so don't override. Let your WebView load
            // the page.
            return false
        }
        // Otherwise, the link isn't for a page on your site, so launch another
        // Activity that handles URLs.
        Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
            startActivity(this)
        }
        return true
    }
}

Java

private class MyWebViewClient extends WebViewClient {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        if ("www.example.com".equals(request.getUrl().getHost())) {
      // This is your website, so don't override. Let your WebView load the
      // page.
      return false;
    }
    // Otherwise, the link isn't for a page on your site, so launch another
    // Activity that handles URLs.
    Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl());
    startActivity(intent);
    return true;
  }
}

Sau đó, hãy tạo một phiên bản của WebViewClient mới này cho WebView:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.webViewClient = MyWebViewClient()

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new MyWebViewClient());

Giờ đây, khi người dùng nhấn vào một đường liên kết, hệ thống sẽ gọi phương thức shouldOverrideUrlLoading(). Phương thức này sẽ kiểm tra xem máy chủ lưu trữ URL có khớp với một miền cụ thể hay không, như được xác định trong ví dụ trước. Nếu khớp, phương thức sẽ trả về giá trị false và không ghi đè quá trình tải URL. Thao tác này cho phép WebView tải URL như bình thường. Nếu máy chủ URL không khớp, thì một Intent sẽ được tạo để chạy Activity mặc định nhằm xử lý các URL. Activity này sẽ phân giải thành trình duyệt web mặc định của người dùng.

URL tuỳ chỉnh dựa trên tên người dùng

WebView áp dụng các quy định hạn chế khi yêu cầu tài nguyên và phân giải các đường liên kết sử dụng một lược đồ URL tuỳ chỉnh. Ví dụ: nếu bạn triển khai các lệnh gọi lại như shouldOverrideUrlLoading() hoặc shouldInterceptRequest(), thì WebView chỉ gọi các lệnh gọi lại đó cho những URL hợp lệ.

Ví dụ: WebView có thể không gọi phương thức shouldOverrideUrlLoading() của bạn cho các đường liên kết như sau:

<a href="showProfile">Show Profile</a>

Các URL không hợp lệ, chẳng hạn như URL trong ví dụ trước, được xử lý không nhất quán trong WebView, vì vậy, bạn nên sử dụng một URL có định dạng phù hợp. Bạn có thể sử dụng một lược đồ tuỳ chỉnh hoặc URL HTTPS cho một miền mà tổ chức của bạn kiểm soát.

Thay vì sử dụng một chuỗi đơn giản trong đường liên kết, như trong ví dụ trước, bạn có thể sử dụng một lược đồ tuỳ chỉnh như sau:

<a href="example-app:showProfile">Show Profile</a>

Sau đó, bạn có thể xử lý URL này trong phương thức shouldOverrideUrlLoading() như sau:

Kotlin

// The URL scheme must be non-hierarchical, meaning no trailing slashes.
const val APP_SCHEME = "example-app:"

override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
    return if (url?.startsWith(APP_SCHEME) == true) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length), "UTF-8")
        respondToData(urlData)
        true
    } else {
        false
    }
}

Java

// The URL scheme must be non-hierarchical, meaning no trailing slashes.
private static final String APP_SCHEME = "example-app:";

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith(APP_SCHEME)) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length()), "UTF-8");
        respondToData(urlData);
        return true;
    }
    return false;
}

API shouldOverrideUrlLoading() chủ yếu dùng để chạy các ý định cho những URL cụ thể. Khi triển khai API này, hãy nhớ trả về false cho những URL mà WebView xử lý. Tuy nhiên, bạn không bị giới hạn ở việc khởi chạy các ý định. Bạn có thể thay thế các ý định khởi chạy bằng bất kỳ hành vi tuỳ chỉnh nào trong các mẫu mã trước đó.

Khi WebView ghi đè quá trình tải URL, nó sẽ tự động tích luỹ nhật ký các trang web đã truy cập. Bạn có thể di chuyển qua lại trong nhật ký bằng goBack()goForward().

Ví dụ: sau đây cho thấy cách Activity có thể dùng nút Quay lại trên thiết bị để quay lại:

Kotlin

override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    // Check whether the key event is the Back button and if there's history.
    if (keyCode == KeyEvent.KEYCODE_BACK && myWebView.canGoBack()) {
        myWebView.goBack()
        return true
    }
    // If it isn't the Back button or there isn't web page history, bubble up to
    // the default system behavior. Probably exit the activity.
    return super.onKeyDown(keyCode, event)
}

Java

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    // Check whether the key event is the Back button and if there's history.
    if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
        myWebView.goBack();
        return true;
    }
    // If it isn't the Back button or there's no web page history, bubble up to
    // the default system behavior. Probably exit the activity.
    return super.onKeyDown(keyCode, event);
}

Nếu ứng dụng của bạn dùng AndroidX AppCompat 1.6.0 trở lên, bạn có thể đơn giản hoá đoạn mã trước đó hơn nữa:

Kotlin

onBackPressedDispatcher.addCallback {
    // Check whether there's history.
    if (myWebView.canGoBack()) {
        myWebView.goBack()
    }
}

Java

onBackPressedDispatcher.addCallback {
    // Check whether there's history.
    if (myWebView.canGoBack()) {
        myWebView.goBack();
    }
}

Phương thức canGoBack() sẽ trả về giá trị true nếu người dùng có nhật ký trang web để truy cập. Tương tự, bạn có thể dùng canGoForward() để kiểm tra xem có nhật ký chuyển tiếp hay không. Nếu bạn không thực hiện bước kiểm tra này, thì sau khi người dùng xem hết nhật ký, goBack()goForward() sẽ không làm gì cả.

Xử lý các thay đổi về cấu hình thiết bị

Trong thời gian chạy, các thay đổi về trạng thái hoạt động xảy ra khi cấu hình của thiết bị thay đổi, chẳng hạn như khi người dùng xoay thiết bị hoặc đóng trình chỉnh sửa phương thức nhập (IME). Những thay đổi này khiến hoạt động của đối tượng WebView bị huỷ và một hoạt động mới được tạo, hoạt động này cũng tạo ra một đối tượng WebView mới tải URL của đối tượng đã huỷ. Để sửa đổi hành vi mặc định của hoạt động, bạn có thể thay đổi cách hoạt động này xử lý các thay đổi về orientation trong tệp kê khai. Để tìm hiểu thêm về cách xử lý các thay đổi về cấu hình trong thời gian chạy, hãy đọc bài viết Xử lý các thay đổi về cấu hình.

Quản lý cửa sổ

Theo mặc định, các yêu cầu mở cửa sổ mới sẽ bị bỏ qua. Điều này đúng cho dù các cửa sổ đó được mở bằng JavaScript hay bằng thuộc tính đích trong một đường liên kết. Bạn có thể tuỳ chỉnh WebChromeClient để cung cấp hành vi riêng cho việc mở nhiều cửa sổ.

Để giữ cho ứng dụng của bạn an toàn hơn, tốt nhất là bạn nên ngăn cửa sổ bật lên và cửa sổ mới mở ra. Cách an toàn nhất để triển khai hành vi này là truyền "true" vào setSupportMultipleWindows() nhưng không ghi đè phương thức onCreateWindow()setSupportMultipleWindows() phụ thuộc vào. Logic này ngăn mọi trang sử dụng target="_blank" trong các đường liên kết của trang đó tải.