Khi ứng dụng của bạn lần đầu tiên sử dụng WebView, hệ thống sẽ thực hiện các tác vụ khởi động cụ thể.
Quá trình khởi động này rất tốn kém. Theo mặc định, quá trình này sẽ diễn ra ngầm ẩn trên luồng giao diện người dùng vào lần đầu tiên ứng dụng gọi nhiều API trong các gói android.webkit hoặc androidx.webkit hoặc mở rộng một bố cục có chứa thẻ WebView.
Tại sao điều này lại quan trọng
Vì quá trình khởi động ngầm ẩn này diễn ra hoàn toàn trên luồng chính, nên quá trình này sẽ chặn ứng dụng của bạn xử lý hoạt động đầu vào của người dùng và làm tăng đáng kể nguy cơ xảy ra lỗi Ứng dụng không phản hồi (ANR). Để biết thêm thông tin về cách Android xử lý mô hình thực thi một luồng, hãy xem bài viết Tổng quan về tiến trình và luồng overview.
Trình kích hoạt cho quá trình khởi động ngầm ẩn
Bạn có thể kích hoạt quá trình khởi động ngầm ẩn theo những cách sau:
- Theo phương thức lập trình: Gọi các API như
WebSettings.getUserAgentString(). - Sử dụng bố cục: Gọi
setContentView()hoặclayoutInflater.inflate()trên một tài nguyên XML có chứa<WebView>.
Quá trình khởi động ngầm ẩn cũng có thể ảnh hưởng tiêu cực đến các chỉ số kinh doanh của bạn, chẳng hạn như thời gian khởi động
ứng dụng và thời gian hiển thị khung hình đầu tiên. Nếu quá trình khởi chạy ngầm ẩn không
tối ưu cho ứng dụng của bạn, hãy sử dụng startUpWebView thay thế.
Trang này thảo luận về cách tối ưu hoá hiệu suất khởi động WebView bằng API startUpWebView.
Kiểm soát quá trình khởi động WebView
Để cải thiện hiệu suất và giảm thiểu lỗi ANR, hãy sử dụng API startUpWebView có trong thư viện Jetpack Webkit. API này giúp bạn kiểm soát rõ ràng thời điểm khởi động WebView. API này chuyển một lượng đáng kể khối lượng công việc khởi động sang một luồng chạy nền và cho phép mọi công việc phải diễn ra trên luồng giao diện người dùng được thực hiện theo từng phần, thay vì một khối lớn nguyên khối. Điều này giúp giải phóng luồng giao diện người dùng để xử lý song song các tác vụ quan trọng khác của ứng dụng, giảm nguy cơ chặn trải nghiệm người dùng.
API này sử dụng lệnh gọi lại androidx.webkit.WebViewOutcomeReceiver, cho phép bạn theo dõi các lần khởi chạy thành công.
Để sử dụng API này, hãy thêm thư viện Jetpack Webkit vào tệp build.gradle.
Đảm bảo bạn sử dụng phiên bản 1.16.0 trở lên:
dependencies {
implementation("androidx.webkit:webkit:1.16.0")
}
Sử dụng API startUpWebView
Cách bạn tối ưu hoá quy trình khởi động phụ thuộc vào thời điểm ứng dụng thực sự cần hiển thị WebView.
Khi WebView không nằm trên đường dẫn quan trọng
Nếu ứng dụng của bạn không cần tải WebView ngay lập tức, bạn có thể ẩn hoàn toàn chi phí khởi chạy. Hãy gọi startUpWebView sớm trong vòng đời của ứng dụng và đợi lệnh gọi lại thành công kích hoạt.
Tốt nhất là bạn nên đợi lệnh gọi lại trước khi gọi các API WebView khác. Nếu bạn kích hoạt startUpWebView nhưng không đợi quá trình này hoàn tất trước khi chạm vào các thành phần WebView khác, thì hệ thống sẽ chặn luồng giao diện người dùng trong khi chờ quá trình khởi chạy hoàn tất. Ứng dụng của bạn có thể nhận được một số lợi ích về hiệu suất từ công việc chạy nền đã hoàn tất, nhưng không phải là lợi ích tối đa.
Khi WebView nằm trên đường dẫn quan trọng
Nếu hành trình cốt lõi của người dùng trong ứng dụng yêu cầu WebView ngay lập tức, thì có lẽ bạn không thể đợi quá trình khởi động WebView hoàn tất. Trong trường hợp này, bạn
vẫn nên gọi startUpWebView càng sớm càng tốt trong vòng đời của ứng dụng
(chẳng hạn như trong Application.onCreate), nhưng đừng đợi lệnh gọi lại
kích hoạt. Thay vào đó, hãy sử dụng trực tiếp các API WebView khi cần.
Để tận dụng tối đa lợi ích từ quá trình khởi động không đồng bộ, bạn cần trì hoãn việc tạo thực thể WebView hoặc gọi các API WebView cho đến khi không còn thao tác nào khác trên luồng giao diện người dùng theo đường dẫn quan trọng để chạy (chẳng hạn như mở rộng hệ phân cấp bố cục, khởi chạy các SDK khác hoặc vẽ khung hình ban đầu).
Nếu bạn gọi startUpWebView và ngay lập tức gọi các API WebView sau đó trên luồng chính, thì luồng giao diện người dùng sẽ chặn quá trình chờ khởi chạy để bắt kịp. Trong trường hợp này, không có lợi ích nào về hiệu suất.
Nếu việc sử dụng WebView có thể nằm trên đường dẫn quan trọng nhưng bạn không muốn khởi động hoàn toàn WebView, thì bạn có thể chọn chạy có chọn lọc các tác vụ khởi động WebView có khả năng chạy trên luồng chạy nền, giải phóng luồng giao diện người dùng cho các tác vụ quan trọng khác của ứng dụng. Để làm việc này, bạn có thể sử dụng shouldRunUiThreadStartUpTasks(false).
Sau này trong vòng đời của ứng dụng, bạn có thể gọi lại startUpWebView bằng shouldRunUiThreadStartUpTasks(true) để hoàn tất các tác vụ khởi động còn lại trên luồng giao diện người dùng. Việc bạn có đợi lệnh gọi lại tại thời điểm đó hay không phụ thuộc vào việc sử dụng WebView có nằm trên đường dẫn quan trọng hay không.
Ví dụ về cách triển khai
API này sử dụng lệnh gọi lại androidx.webkit.WebViewOutcomeReceiver, cho phép bạn theo dõi các lần khởi chạy thành công hoặc xử lý lỗi chẩn đoán.
Bạn có thể gọi startUpWebView nhiều lần từ các phần khác nhau của ứng dụng. Bạn không nên triển khai vòng lặp thử lại đơn giản.
Mã mẫu sau đây minh hoạ cách sử dụng API WebViewCompat.startUpWebView để khởi chạy không đồng bộ.
Kotlin
import android.content.Context
import android.util.Log
import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewOutcomeReceiver
import androidx.webkit.WebViewStartUpConfig
import androidx.webkit.WebViewStartUpResult
import androidx.webkit.WebViewStartupException
import java.util.concurrent.Executors
fun initializeWebView(context: Context) {
// 1. Create a startup configuration specifying the background thread
// that WebView will use to run its initialization tasks.
val startUpConfig = WebViewStartUpConfig.Builder(
Executors.newSingleThreadExecutor()
).build()
// 2. Trigger WebView startup asynchronously
WebViewCompat.startUpWebView(
context,
startUpConfig,
object : WebViewOutcomeReceiver<WebViewStartUpResult, WebViewStartupException> {
override fun onResult(result: WebViewStartUpResult) {
// Success: The WebView has finished its background initialization.
// This callback is guaranteed to be invoked on the UI thread.
setupWebView()
}
override fun onError(error: WebViewStartupException) {
// Failure: The initialization encountered a startup exception.
Log.e("WebViewStartup", "Failed to initialize WebView", error)
}
}
)
}
Java
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewOutcomeReceiver;
import androidx.webkit.WebViewStartUpConfig;
import androidx.webkit.WebViewStartUpResult;
import androidx.webkit.WebViewStartupException;
import java.util.concurrent.Executors;
public void initializeWebView(Context context) {
// 1. Create the startup configuration specifying the background thread pool
// to handle internal non-UI initialization processes.
WebViewStartUpConfig startUpConfig = new WebViewStartUpConfig.Builder(
Executors.newSingleThreadExecutor()
).build();
// 2. Trigger WebView startup asynchronously
WebViewCompat.startUpWebView(
context,
startUpConfig,
new WebViewOutcomeReceiver<WebViewStartUpResult, WebViewStartupException>() {
@Override
public void onResult(@NonNull WebViewStartUpResult result) {
// Success: The WebView has finished its background initialization.
// This callback is invoked directly on the UI thread.
setupWebView();
}
@Override
public void onError(@NonNull WebViewStartupException error) {
// Failure: Handled using the concrete WebViewStartupException
Log.e("WebViewStartup", "Failed to initialize WebView", error);
}
}
);
}
Gỡ lỗi các vấn đề về khởi động không đồng bộ
Nếu startUpWebView không mang lại lợi ích về hiệu suất như mong đợi, thì thường là do WebView đang được khởi chạy ngầm ẩn ở nơi khác trong ứng dụng trước khi lệnh gọi của bạn thực thi. Điều này có thể là do các lý do sau:
Thư viện hoặc SDK của bên thứ ba được khởi chạy sớm trong vòng đời của ứng dụng.
ContentProvidersđược chèn vào APK của bạn để kích hoạt các API WebView trong quá trình khởi động ứng dụng.Mở rộng bố cục hoặc các lệnh gọi theo phương thức lập trình (chẳng hạn như tìm nạp chuỗi tác nhân người dùng) xảy ra sớm hơn dự kiến.
Để giúp bạn chẩn đoán vị trí và lý do xảy ra các lần khởi chạy ngoài dự kiến này, đối tượng WebViewStartUpResult cung cấp các chức năng kiểm tra tích hợp:
getUiThreadBlockingStartUpLocations(): Trả về danh sách các đối tượngStartUpLocationđại diện cho các vị trí mà các tác vụ khởi động WebView chặn luồng giao diện người dùng chính.getNonUiThreadBlockingStartUpLocations(): Trả về các vị trí gọi cụ thể mà các tác vụ khởi động đang chạy chặn các luồng chạy nền.
Mỗi StartUpLocation chứa một dấu vết ngăn xếp mà bạn có thể ghi nhật ký hoặc kiểm tra để tìm lớp và phương thức chính xác đã kích hoạt quá trình khởi chạy.
Ví dụ về cách triển khai
Bạn có thể kiểm tra các vị trí này bên trong lệnh gọi lại onResult để kiểm tra đường dẫn khởi động:
override fun onResult(result: WebViewStartUpResult) {
// Check if WebView startup was blocked on the UI thread prior to or during initialization
val uiBlockingLocations = result.getUiThreadBlockingStartUpLocations()
if (!uiBlockingLocations.isNullOrEmpty()) {
for (location in uiBlockingLocations) {
// Log the stack trace of the call site that triggered the UI-blocking startup
Log.w("WebViewDebug", "WebView startup blocked the UI thread here:", location.getStack())
}
} else {
Log.i("WebViewDebug", "Excellent! No UI-blocking WebView startup detected.")
}
// Check where background initialization tasks were executed
val backgroundLocations = result.getNonUiThreadBlockingStartUpLocations()
backgroundLocations?.forEach { location ->
Log.d("WebViewDebug", "WebView background startup occurred at: ${location.getStack()}")
}
setupWebView()
}
Cách sử dụng dữ liệu này trong quá trình kiểm tra
Khi kiểm tra quá trình khởi động WebView của ứng dụng, hãy sử dụng các chiến lược sau để phân tích dữ liệu chẩn đoán và giải quyết các nút thắt về hiệu suất:
Tìm các dấu vết ngăn xếp ngoài dự kiến: Nếu
getUiThreadBlockingStartUpLocations()không trống, hãy xem các dấu vết ngăn xếp đã in. Nếu bạn thấy các lớp thuộc SDK của bên thứ ba hoặc các thành phần ngoài dự kiến, thì bạn đã tìm thấy nút thắt khởi chạy ngầm ẩn.Xác minh thứ tự gọi: Nếu đầu ra nhật ký cho thấy quá trình khởi chạy ngầm ẩn xảy ra trước lệnh gọi
startUpWebViewthủ công, thì bạn nên di chuyển quá trình khởi chạystartUpWebViewsớm hơn trong ứng dụng hoặc định cấu hình SDK gây lỗi để trì hoãn các tác vụ phụ thuộc vào WebView.
Di chuyển từ các giải pháp tạm thời trước đó
Trước đây, bạn có thể đã sử dụng các giải pháp tạm thời rõ ràng để buộc khởi chạy WebView trên luồng chạy nền, chẳng hạn như tìm nạp chuỗi tác nhân người dùng.
Các giải pháp tạm thời này được coi là các phương pháp không được hỗ trợ và hành vi cơ bản của chúng có thể thay đổi trong các bản phát hành sắp tới. Nếu ứng dụng của bạn dựa vào bất kỳ giải pháp tạm thời rõ ràng nào chưa được ghi lại để kích hoạt hoặc quản lý quá trình khởi động WebView, thì bạn nên sử dụng API startUpWebView. API startUpWebView hoạt động trên tất cả các phiên bản Android và WebView được thư viện Jetpack Webkit hỗ trợ.
Việc sử dụng cách triển khai Jetpack Webkit giúp đảm bảo hành vi nhất quán trên toàn bộ hệ sinh thái Android. Một ưu điểm chính của API này là khả năng phục hồi: trên các thiết bị cũ không có các phương pháp tối ưu hoá mới hơn, API này vẫn duy trì hiệu suất tương đương với các giải pháp tạm thời thủ công. Điều này cho phép bạn áp dụng các lợi ích khởi động hiện đại trên các thiết bị mới hơn mà không làm ảnh hưởng đến hiệu suất trên các thiết bị cũ.
Nếu bạn gặp vấn đề hoặc có ý kiến phản hồi về API startUpWebView, hãy báo
lỗi trên trình theo dõi lỗi công khai.