Android: Chuyển sang WebView trong Android 4.4
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ậptargetSdkVersion
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ặcloadDataWithBaseURL()
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 callbackshouldOverrideUrlLoading()
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ớiloadDataWithBaseURL()
, thì ta sẽ nhận được callbackshouldOverrideUrlLoading()
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 choNARROW_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ưngevent.preventDefault()
không được gọi, kết quả làtouchcancel
bị ném đủ sớm (ở đâyWebView
đã 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
Giải phóng thời gian, khai phóng năng lực