Android: Chuyển sang WebView trong Android 4.4

Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực

Android 4.4 (API level 19) giới thiệu phiên bản mới WebView dựa trên Chromium. Thay đổi này nâng cấp hiệu suất của WebView và tiêu chuẩn hỗ trợ cho HTML5, CSS3 và JavaScript để phù hợp với các trình duyệt web mới nhất. Bất kỳ ứng dụng nào sử dụng WebView sẽ kế thừa các nâng cấp này khi chạy trên Android 4.4 trở lên.

Bài viết này mô tả các thay đổi bổ sung WebView mà ta cần lưu ý nếu ta đặt targetSdkVersion thành "19" hoặc cao hơn.

Lưu ý: Nếu targetSdkVersion được đặt thành "18" hoặc thấp hơn, thì WebView sẽ vận hành ở chế độ "quirks mode" để tránh một số thay đổi hành vi được mô tả bên dưới, càng sát càng tốt trong khi vẫn cung cấp cho ứng dụng của ta các nâng cấp hiệu suất và tiêu chuẩn web. Hãy lưu ý là bố trí cột đơn và hẹp và mức zoom mặc định không được hỗ trợ trên Android 4.4, và có thể có sự khác biệt về hành vi khác chưa được xác định, vì vậy hãy kiểm tra chắc chắn ứng dụng của ta trên Android 4.4 trở lên ngay cả khi ta thiết lập targetSdkVersion thành "18" hoặc thấp hơn.

Để khắc phục mọi sự cố ta có thể gặp phải khi chuyển ứng dụng sang WebView Android 4.4, ta có thể bật gỡ lỗi từ xa thông qua Chrome trên máy tính để bàn bằng cách gọi setWebContentsDebuggingEnabled(). Tính năng mới này cho phép WebView kiểm tra và phân tích nội dung web, tập lệnh và hoạt động mạng của ta trong khi chạy trong một WebView. Để biết thêm thông tin, hãy xem bài viết Gỡ lỗi từ xa trên Android.

Thay đổi tác nhân người dùng

Nếu ta phân phát nội dung WebView dựa trên tác nhân người dùng, ta cần biết rằng chuỗi tác nhân người dùng đã thay đổi một chút và hiện đã được tích hợp trong phiên bản Chrome:

Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16H) AppleWebKit/537.36
(KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36

Nếu ta cần truy xuất tác nhân người dùng nhưng không cần lưu trữ ứng dụng đó cho ứng dụng hoặc không muốn khởi tạo WebView, ta nên sử dụng phương thức tĩnh là getDefaultUserAgent(). Tuy nhiên, nếu ta có ý định ghi đè chuỗi tác nhân người dùng trong WebView, thì ta có thể muốn sử dụng getUserAgentString().

Đa luồng và chặn luồng

Nếu ta gọi các phương thức trên WebView từ bất kỳ luồng nào khác ngoài luồng UI của ứng dụng, nó có thể gây ra kết quả không mong muốn. Ví dụ: nếu ứng dụng của ta sử dụng nhiều luồng, ta có thể sử dụng phương thức runOnUiThread() để đảm bảo mã lệnh thực thi trên luồng UI:

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        // Code for WebView goes here
    }
});

Ta cũng cần đảm bảo rằng không bao giờ chặn luồng UI. Một số ứng dụng từng mắc lỗi như thế này khi chờ đợi một callback JavaScript. Ví dụ: không được sử dụng mã dạng như sau:

// Đoạn code này là tồi và sẽ ngăn cản luồng UI
webView.loadUrl("javascript:fn()");
while(result == null) {
  Thread.sleep(100);
}

Thay vào đó, ta có thể sử dụng một phương thức mới là evaluateJavascript() để chạy JavaScript không đồng bộ.

Xử lý URL tùy chỉnh

 

WebView mới áp dụng các hạn chế bổ sung khi yêu cầu tài nguyên và giải quyết các liên kết sử dụng lược đồ URL tùy chỉnh. Ví dụ: nếu ta triển khai các call back như shouldOverrideUrlLoading() hoặc shouldInterceptRequest() thì chỉ WebView gọi chúng cho các URL hợp lệ.

Nếu ta đang sử dụng lược đồ URL tùy chỉnh hoặc URL cơ sở và nhận thấy rằng ứng dụng của ta sẽ nhận được ít cuộc gọi hơn đến các cuộc gọi lại này hoặc không tải tài nguyên trên Android 4.4, thì cần đảm bảo rằng các yêu cầu chỉ định URL hợp lệ phù hợp với RFC 3986.

Ví dụ: WebView mới có thể không gọi phương thức shouldOverrideUrlLoading() cho các liên kết như thế này:

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

Kết quả của việc người dùng nhấp vào một liên kết như vậy có thể khác nhau:

  • Nếu ta đã tải trang bằng cách gọi loadData() hoặc loadDataWithBaseURL() với URL cơ sở không hợp lệ hoặc không hợp lệ, thì ta sẽ không nhận được callback shouldOverrideUrlLoading() cho loại liên kết này trên trang.

    Lưu ý: Khi ta sử dụng loadDataWithBaseURL()và URL cơ sở không hợp lệ hoặc đặt null, tất cả các liên kết trong nội dung ta đang tải phải tuyệt đối.

  • Nếu đã tải trang bằng cách gọi loadUrl() hoặc cung cấp URL cơ sở hợp lệ với loadDataWithBaseURL(), thì ta sẽ nhận được callback shouldOverrideUrlLoading() cho loại liên kết này trên trang, nhưng URL ta nhận được sẽ là tuyệt đối, liên quan đến trang hiện tại. Ví dụ: URL ta nhận được sẽ là "http://www.example.com/showProfile" thay vì chỉ là "showProfile".

Thay vì sử dụng một chuỗi đơn giản trong một liên kết như được hiển thị ở trên, ta có thể sử dụng một lược đồ tùy chỉnh như sau:

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

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

// Lược đồ URL nên là dạng không phân cấp (không có gạch chéo /)
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;
}

Nếu ta không thể thay thế HTML thì ta có thể sử dụng loadDataWithBaseURL() và đặt URL cơ sở bao gồm lược đồ tùy chỉnh và máy chủ hợp lệ, chẳng hạn như "example-app://<valid_host_name>/". Ví dụ:

webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA, null, "UTF-8", null);

Tên máy chủ hợp lệ phải tuân theo RFC 3986 và điều quan trọng là bao gồm dấu gạch chéo ở cuối, nếu không, mọi yêu cầu từ trang được tải có thể bị hủy.

Thay đổi khung nhìn

Chế độ xem target-densitydpi không còn được hỗ trợ

Trước đây, WebView có hỗ trợ một thuộc tính khung nhìn được gọi target-densitydpi để giúp các trang web chỉ định mật độ màn hình dự định của chúng. Thuộc tính này không còn được hỗ trợ và ta nên chuyển sang sử dụng các giải pháp tiêu chuẩn với hình ảnh và CSS trong bài viết Pixel-Perfect UI trong WebView.

Khung nhìn phóng to khi nhỏ

Trước đây, nếu ta đặt chiều rộng của chế độ xem thành giá trị nhỏ hơn hoặc bằng "320", nó sẽ được đặt thành "chiều rộng thiết bị" và nếu ta đặt chiều cao của chế độ xem thành giá trị nhỏ hơn hoặc bằng chiều cao WebView, nó sẽ được đặt thành "chiều cao thiết bị". Tuy nhiên, khi chạy trong WebView mới thì giá trị chiều rộng hoặc chiều cao được tuân thủ và WebView phóng to để lấp đầy chiều rộng màn hình.

Nhiều thẻ viewport không được hỗ trợ

Trước đây, nếu ta đưa nhiều thẻ viewport vào trong một trang web, WebView sẽ hợp nhất các thuộc tính từ tất cả các thẻ. Trong WebView mới thì chỉ có viewport cuối cùng được sử dụng và tất cả các viewport khác được bỏ qua.

Thu phóng mặc định không được dùng nữa

Các phương thức getDefaultZoom() và setDefaultZoom() để nhận và đặt mức thu phóng ban đầu trên một trang không còn được hỗ trợ và thay vào đó ta nên xác định chế độ xem phù hợp trong trang web.

Thận trọng: Các API này hoàn toàn không được hỗ trợ trên Android 4.4 trở lên. Ngay cả khi targetSdkVersion được đặt thành "18" hoặc thấp hơn, các API này cũng không có hiệu lực.

Để biết thông tin về cách xác định các thuộc tính viewport trong HTML, hãy đọc bài viết Pixel-Perfect UI trong WebView .

Nếu không thể đặt độ rộng của chế độ xem trong HTML, thì ta nên gọi setUseWideViewPort() để đảm bảo trang được cung cấp chế độ xem lớn hơn. Ví dụ:

WebSettings settings = webView.getSettings();
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);

Thay đổi style

Thuộc tính CSS background sẽ ghi đè background-size

Chrome và các trình duyệt khác đã từng hoạt động theo cách thức này, nhưng bây giờ WebView cũng sẽ ghi đè thuộc tính CSS background-size nếu ta cũng dùng background style. Ví dụ: kích thước ở đây sẽ được đặt lại về giá trị mặc định:

.some-class {
  background-size: contain;
  background: url('images/image.png') no-repeat;
}

Cách khắc phục chỉ đơn giản ở là chuyển đổi vị trí hai thuộc tính cho nhau.

.some-class {
  background: url('images/image.png') no-repeat;
  background-size: contain;
}

Kích thước được tính bằng pixel CSS thay vì pixel màn hình

Trước đây, các tham số kích thước như  window.outerWidth và window.outerHeight trả về một giá trị trong pixel màn hình thực tế, nhưng trong WebView mới thì chúng trả về một giá trị dựa trên pixel CSS.

Nói chung, không nên thử và tính kích thước vật lý bằng pixel để định cỡ các phần tử hoặc các phép tính khác. Tuy nhiên, nếu ta đã tắt thu phóng và tỷ lệ ban đầu được đặt thành 1.0 thì ta có thể sử dụng window.devicePixelRatio để lấy tỷ lệ, sau đó nhân giá trị pixel CSS tương ứng. Ta cũng có thể tạo liên kết JavaScript để truy vấn kích thước pixel từ WebView.

Để biết thêm thông tin, xem bài viết tại quirksmode.org.

NARlaw_COLUMNS và SINGLE_COLUMN không còn được hỗ trợ

Các giá trị NARROW_COLUMNS cho WebSettings.LayoutAlgorithm không được hỗ trợ trong WebView mới.

Thận trọng: Các API này hoàn toàn không được hỗ trợ trên Android 4.4 trở lên. Ngay cả khi targetSdkVersion được đặt thành "18" hoặc thấp hơn, các API này không có hiệu lực.

Ta có thể xử lý thay đổi này theo các cách sau:

  • Thay đổi kiểu ứng dụng:

    Nếu ta có quyền kiểm soát HTML và CSS trên trang, ta có thể thấy rằng việc thay đổi thiết kế nội dung có thể là cách tiếp cận đáng tin cậy nhất. Ví dụ: đối với đoạn trích dẫn (cite) trong HTML thì ta có thể muốn bao văn bản bên trong trong <pre> như sau:

          <pre style="word-wrap: break-word; white-space: pre-wrap;">

          Điều này có thể đặc biệt hữu ích nếu ta chưa định nghĩa các thuộc tính viewport cho trang của mình.

  • Sử dụng thuật toán bố cục TEXT_AUTOSIZING mới :

    Nếu ta đang sử dụng các cột nhỏ như một cách để làm cho phổ rộng các trang web trên máy tính để bàn dễ đọc hơn trên thiết bị di động và ta không thể thay đổi nội dung HTML, thì thuật toán TEXT_AUTOSIZING mới có thể là một giải pháp thay thế phù hợp cho NARROW_COLUMNS.

Ngoài ra, giá trị SINGLE_COLUMN mà trước đó đã không còn được khuyến nghị dùng thì cũng không được WebView hỗ trợ.

Xử lý các sự kiện cảm ứng trong JavaScript

Nếu trang web của ta đang trực tiếp xử lý các sự kiện chạm trong một WebView, hãy chắc chắn rằng ta cũng đang xử lý sự kiện touchcancel. Có một vài tình huống touchcancel sẽ được gọi và có thể gây ra sự cố nếu không nhận được:

  • Một phần tử được chạm (cho nên touchstart và touchmove được gọi) và trang được cuộn, khiến cho một phần tử touchcancel bị ném ra (throw).
  • Một phần tử được chạm ( touchstart được gọi) nhưng event.preventDefault() không được gọi, kết quả là touchcancel bị ném đủ sớm (ở đây WebView đã giả sử ta không muốn sử dụng các sự kiện chạm).

Nguồn: https://developer.android.com/guide/webapps/migrating

 

» Tiếp: Hỗ trợ các màn hình khác nhau trong các ứng dụng web
« Trước: Quyền riêng tư của người dùng trong báo cáo WebView
Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Copied !!!