Khi ứng dụng của bạn sử dụng WebView lần đầu tiên, 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 nặng. Theo mặc định, điều này sẽ xảy ra một cách ngầm định 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 tăng kích thước một bố cục có chứa thẻ WebView.
Lý do điều này quan trọng
Vì quá trình khởi động ngầm này diễn ra hoàn toàn trên luồng chính, nên ứng dụng của bạn sẽ không xử lý được dữ liệu đầ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 đơn luồng, hãy xem bài viết Tổng quan về quy trình và luồng.
Điều kiện kích hoạt để khởi động ngầm
Bạn có thể kích hoạt quá trình khởi động ngầm theo những cách sau:
- Theo 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>.
Hoạt động khởi động ngầm 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 tạo ngầm không phù hợp với ứng dụng của bạn, hãy sử dụng startUpWebView.
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 WebView khởi động. Việc này sẽ 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 nguyên khối lớn. Đ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 khả năng 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. Gọi startUpWebView sớm trong vòng đời của ứng dụng và chờ 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, 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ừ tác vụ ở chế độ 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 chính của người dùng trong ứng dụng yêu cầu WebView ngay lập tức, thì có thể 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.
Để khai thác tối đa lợi ích từ quá trình khởi động không đồng bộ, điều quan trọng là bạn phải hoãn việc tạo thực thể WebView hoặc gọi 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 cần chạy (chẳng hạn như tăng hệ thống 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 chờ quá trình khởi tạo 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 một luồng 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 được điều 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ó chờ 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ý các 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 nên tránh triển khai một 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ơi khác trong ứng dụng của bạn trước khi lệnh gọi của bạn thực thi. Nguyên nhân có thể là do:
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 API WebView trong quá trình khởi động ứng dụng.Hoạt động mở rộng bố cục hoặc các lệnh gọi theo chương trình (chẳng hạn như tìm nạp chuỗi tác nhân người dùng) xảy ra quá sớm một cách không mong muốn.
Để giúp bạn chẩn đoán vị trí và lý do xảy ra những lần khởi tạo 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 những vị trí mà các thao tác 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ể nơi các tác vụ khởi động đang chạy chặn các luồng trong 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 chính xác lớp và phương thức đã kích hoạt quá trình khởi tạo.
Ví dụ về cách triển khai
Bạn có thể kiểm tra các vị trí này 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 vấn đề về hiệu suất:
Tìm dấu vết ngăn xếp không mong muốn: Nếu
getUiThreadBlockingStartUpLocations()không trống, hãy xem dấu vết ngăn xếp đã in. Nếu thấy các lớp thuộc về SDK của bên thứ ba hoặc các thành phần không mong muốn, thì bạn đã tìm thấy một điểm tắc nghẽn khởi chạy ngầm.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 định xảy ra trước lệnh gọi
startUpWebViewtheo cách thủ công, bạn nên di chuyển quá trình khởi chạystartUpWebViewlên sớm hơn trong ứng dụng hoặc định cấu hình SDK vi phạm để 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 đây
Trước đây, bạn có thể đã sử dụng các giải pháp rõ ràng để buộc khởi chạy WebView trên một luồng trong nền, chẳng hạn như tìm nạp chuỗi tác nhân người dùng.
Những giải pháp 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 rõ ràng, không có tài liệu nào để 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 mọi phiên bản Android và WebView mà thư viện Jetpack Webkit hỗ trợ.
Đảm bảo khả năng phục hồi và tính ổn định của ứng dụng
Việc sử dụng 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 chế độ 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 thủ công. Nhờ đó, bạn có thể á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 bị ảnh hưởng đến hiệu suất trên các thiết bị cũ.
Mặc dù việc tối ưu hoá quá trình khởi động WebView giúp giảm nguy cơ xảy ra lỗi ANR trong quá trình khởi chạy ứng dụng, nhưng bạn cũng nên bảo vệ ứng dụng của mình khỏi các sự cố về trình kết xuất thời gian chạy và việc thu hồi bộ nhớ hệ thống. Để duy trì độ ổn định toàn diện của ứng dụng sau khi WebView đang chạy, hãy xem phần Xử lý việc chấm dứt WebView.
Nếu bạn gặp vấn đề hoặc có ý kiến phản hồi về API startUpWebView, hãy báo cáo lỗi trên trình theo dõi lỗi công khai.