เพิ่มประสิทธิภาพการเริ่มต้นใช้งาน WebView

เมื่อแอปใช้ WebView เป็นครั้งแรก ระบบจะทำงานเริ่มต้นที่เฉพาะเจาะจง กระบวนการเริ่มต้นนี้มีขนาดใหญ่ โดยค่าเริ่มต้น การดำเนินการนี้จะเกิดขึ้นโดยนัยในเทรด UI เมื่อแอปพลิเคชันเรียกใช้ API หลายรายการภายในแพ็กเกจ android.webkit หรือ androidx.webkit เป็นครั้งแรก หรือขยายเลย์เอาต์ที่มีแท็ก WebView

ความสำคัญ

เนื่องจากการเริ่มต้นโดยนัยนี้เกิดขึ้นในเธรดหลักทั้งหมด จึงบล็อกไม่ให้แอปประมวลผลข้อมูลจากผู้ใช้ และเพิ่มความเสี่ยงของข้อผิดพลาด "แอปพลิเคชันไม่ตอบสนอง" (ANR) อย่างมาก ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีที่ Android จัดการโมเดลการดำเนินการแบบเธรดเดียวได้ที่ภาพรวมของกระบวนการและเธรด

ทริกเกอร์สำหรับการเริ่มต้นโดยนัย

การเริ่มต้นโดยนัยจะทริกเกอร์ได้ด้วยวิธีต่อไปนี้

  • โดยใช้โปรแกรม: การเรียก API เช่น WebSettings.getUserAgentString()
  • การใช้เลย์เอาต์: เรียกใช้ setContentView() หรือ layoutInflater.inflate() ในทรัพยากร XML ที่มี <WebView>

การเริ่มต้นโดยนัยยังอาจส่งผลเสียต่อเมตริกทางธุรกิจ เช่น เวลาเริ่มต้นของแอปและเวลาจนกว่าจะแสดงผลครั้งแรก หากการเริ่มต้นโดยนัยไม่เหมาะกับแอปของคุณ ให้ใช้ startUpWebView แทน

หน้านี้จะอธิบายวิธีเพิ่มประสิทธิภาพการเริ่มต้นใช้งาน WebView โดยใช้ startUpWebView API

ควบคุมการเริ่มต้นใช้งาน WebView

หากต้องการปรับปรุงประสิทธิภาพและลด ANR ให้ใช้ startUpWebView API ที่มี ในไลบรารี Jetpack Webkit API นี้ช่วยให้คุณควบคุมได้อย่างชัดเจนว่าเมื่อใดที่ WebView จะเริ่มทำงาน ซึ่งจะย้ายภาระงานของ Startup จำนวนมากไปยัง เทรดเบื้องหลัง และช่วยให้งานใดๆ ที่ต้องเกิดขึ้นในเทรด UI สามารถทำได้เป็นกลุ่มๆ แทนที่จะเป็นบล็อกขนาดใหญ่บล็อกเดียว ซึ่งจะช่วยให้เทรด UI จัดการงานอื่นๆ ที่สำคัญของแอปไปพร้อมกันได้ ทำให้โอกาสที่จะบล็อกประสบการณ์ของผู้ใช้น้อยลง

API ใช้การเรียกกลับ androidx.webkit.WebViewOutcomeReceiver ซึ่งช่วยให้คุณ ติดตามการเริ่มต้นที่สำเร็จได้

หากต้องการใช้ API นี้ ให้เพิ่มไลบรารี Jetpack Webkit ลงในไฟล์ build.gradle ตรวจสอบว่าคุณใช้เวอร์ชัน 1.16.0 ขึ้นไป

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

ใช้ startUpWebView API

วิธีเพิ่มประสิทธิภาพโฟลว์การเริ่มต้นจะขึ้นอยู่กับว่าแอปของคุณต้องแสดง WebView เมื่อใด

เมื่อ WebView ไม่ได้อยู่ในเส้นทางที่สำคัญ

หากแอปไม่จำเป็นต้องโหลด WebView ทันที คุณก็ซ่อน ค่าใช้จ่ายในการเริ่มต้นระบบได้ทั้งหมด เรียกใช้ startUpWebView ตั้งแต่เนิ่นๆ ในวงจร ของแอปและรอให้การเรียกกลับที่สำเร็จทำงาน

คุณควรรอการเรียกกลับก่อนเรียกใช้ WebView API อื่นๆ หากคุณทริกเกอร์ startUpWebView แต่ไม่ได้รอให้เสร็จสิ้นก่อนที่จะแตะ คอมโพเนนต์ WebView อื่นๆ ระบบจะบล็อกเทรด UI ขณะรอให้การเริ่มต้นเสร็จสมบูรณ์ แอปของคุณอาจได้รับประโยชน์ด้านประสิทธิภาพบางอย่างจาก งานที่ทำในเบื้องหลังที่เสร็จสมบูรณ์แล้ว แต่จะไม่ได้รับประโยชน์สูงสุด

เมื่อ WebView อยู่ในเส้นทางที่สำคัญ

หากเส้นทางของผู้ใช้หลักของแอปต้องใช้ WebView ทันที คุณอาจ รอให้การเริ่มต้น WebView เสร็จสมบูรณ์ไม่ได้ ในกรณีนี้ คุณยังคงควรเรียกใช้ startUpWebView โดยเร็วที่สุดในวงจรของแอป (เช่น ใน Application.onCreate) แต่อย่ารอให้มีการเรียกกลับเพื่อทริกเกอร์ แต่ให้ใช้ WebView API โดยตรงเมื่อจำเป็น

หากต้องการรับประโยชน์สูงสุดจากการเริ่มต้นแบบไม่พร้อมกัน ให้เลื่อนการสร้างอินสแตนซ์ WebView หรือการเรียกใช้ WebView API จนกว่าจะไม่มีการดำเนินการในเทรด UI ของเส้นทางวิกฤตอื่นๆ เหลือให้เรียกใช้ (เช่น การขยายลำดับชั้นเลย์เอาต์ การเริ่มต้น SDK อื่นๆ หรือการวาดเฟรมเริ่มต้น)

หากคุณเรียกใช้ startUpWebView และเรียกใช้ WebView API ทันทีหลังจากนั้นใน เทรดหลัก เธรด UI จะบล็อกเพื่อรอการเริ่มต้นให้ทัน ในกรณีนี้ จะไม่มีประโยชน์ด้านประสิทธิภาพ

หากการใช้งาน WebView อาจกลายเป็นเส้นทางวิกฤต แต่คุณไม่ต้องการเริ่มต้น WebView ทั้งหมด คุณสามารถเลือกเรียกใช้ทาการเริ่มต้น WebView แบบเฉพาะเจาะจงที่สามารถเรียกใช้ในเธรดเบื้องหลังได้ ซึ่งจะช่วยให้เธรด UI มีพื้นที่ว่างสำหรับ งานวิกฤตอื่นๆ ของแอป คุณใช้ shouldRunUiThreadStartUpTasks(false) เพื่อวัตถุประสงค์นี้ได้

ในวงจรช่วงต่อมาของแอป คุณสามารถเรียก startUpWebView อีกครั้งด้วย shouldRunUiThreadStartUpTasks(true) เพื่อทำงานเริ่มต้นที่เหลือให้เสร็จใน เธรด UI การรอการเรียกกลับ ณ จุดนั้นขึ้นอยู่กับว่าการใช้งาน WebView อยู่ในเส้นทางวิกฤตหรือไม่

ตัวอย่างการใช้งาน

API ใช้androidx.webkit.WebViewOutcomeReceiverการเรียกกลับ ซึ่งช่วยให้คุณ ติดตามการเริ่มต้นที่สําเร็จหรือจัดการความล้มเหลวในการวินิจฉัยได้

คุณเรียกใช้ได้startUpWebViewหลายครั้งจากส่วนต่างๆ ของแอป ได้อย่างปลอดภัย เราขอแนะนำให้หลีกเลี่ยงการใช้ลูปการลองใหม่แบบง่ายๆ

ตัวอย่างโค้ดต่อไปนี้แสดงวิธีใช้ WebViewCompat.startUpWebView API สำหรับการเริ่มต้นแบบอะซิงโครนัส

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);
                }
            }
    );
}

แก้ไขข้อบกพร่องของปัญหาการเริ่มต้นแบบไม่พร้อมกัน

หาก startUpWebView ไม่ให้ประโยชน์ด้านประสิทธิภาพตามที่คาดไว้ มักเป็นเพราะ WebView ได้รับการเริ่มต้นโดยอ้อมที่อื่นในแอปก่อนที่การเรียกใช้จะดำเนินการ ซึ่งอาจเกิดจากสาเหตุต่อไปนี้

  • ไลบรารีของบุคคลที่สามหรือ SDK ที่เริ่มต้นตั้งแต่เนิ่นๆ ในวงจรของแอป

  • ContentProviders ที่แทรกลงใน APK ซึ่งทริกเกอร์ WebView API ระหว่าง การเริ่มต้นแอป

  • การขยายเลย์เอาต์หรือการเรียกแบบเป็นโปรแกรม (เช่น การดึงสตริง User Agent) ที่เกิดขึ้นเร็วเกินคาด

ออบเจ็กต์ WebViewStartUpResult มีความสามารถในการตรวจสอบในตัวเพื่อช่วยคุณวินิจฉัยว่าการเริ่มต้นที่ไม่คาดคิดเหล่านี้เกิดขึ้นที่ใดและเพราะเหตุใด

  • getUiThreadBlockingStartUpLocations(): แสดงรายการออบเจ็กต์ StartUpLocation ซึ่งแสดงถึงตำแหน่งที่งานเริ่มต้นของ WebView บล็อก UI หลัก เธรด

  • getNonUiThreadBlockingStartUpLocations(): แสดงผลตำแหน่งการเรียกที่เฉพาะเจาะจงซึ่ง การเรียกใช้ฟังก์ชันเริ่มต้นบล็อกเธรดในเบื้องหลัง

แต่ละ StartUpLocation มีการติดตามสแต็กที่คุณบันทึกหรือตรวจสอบเพื่อ ค้นหาคลาสและเมธอดที่เรียกการเริ่มต้นได้

ตัวอย่างการใช้งาน

คุณตรวจสอบตำแหน่งเหล่านี้ได้ภายในแฮนเดิลการเรียกกลับของ onResult เพื่อตรวจสอบเส้นทางการเริ่มต้น ของคุณ

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()
}

วิธีใช้ข้อมูลนี้ในระหว่างการตรวจสอบ

เมื่อตรวจสอบการเริ่มต้น WebView ของแอป ให้ใช้กลยุทธ์ต่อไปนี้เพื่อ วิเคราะห์ข้อมูลการวินิจฉัยและแก้ไขปัญหาคอขวดด้านประสิทธิภาพ

  • มองหาร่องรอยของสแต็กที่ไม่คาดคิด: หาก getUiThreadBlockingStartUpLocations() ไม่ว่าง ให้ดูร่องรอยของสแต็กที่พิมพ์ออกมา หากเห็นคลาสที่อยู่ใน SDK ของบุคคลที่สามหรือคอมโพเนนต์ที่ไม่คาดคิด แสดงว่าคุณพบคอขวดการเริ่มต้นโดยนัย

  • ยืนยันลำดับการเรียก: หากเอาต์พุตของบันทึกแสดงว่ามีการเริ่มต้นโดยนัย ก่อนการเรียก startUpWebView ด้วยตนเอง คุณควร ย้ายการเริ่มต้น startUpWebView ไปไว้ก่อนหน้าในแอป หรือกำหนดค่า SDK ที่ทำให้เกิดปัญหาให้หน่วงเวลาการทำงานที่ขึ้นอยู่กับ WebView

ย้ายข้อมูลจากวิธีแก้ปัญหาชั่วคราวที่ผ่านมา

ก่อนหน้านี้คุณอาจเคยใช้การแก้ปัญหาที่ชัดเจนเพื่อบังคับให้ WebView เริ่มต้นในเธรดเบื้องหลัง เช่น การดึงสตริง User Agent

วิธีแก้ปัญหาเหล่านี้ถือเป็นแนวทางปฏิบัติที่ไม่รองรับ และลักษณะการทำงานพื้นฐานอาจเปลี่ยนแปลงได้ในรุ่นต่อๆ ไป หากแอปของคุณใช้การแก้ปัญหาที่ชัดเจน ซึ่งไม่ได้ระบุไว้เพื่อทริกเกอร์หรือจัดการการเริ่มต้น WebView เราขอแนะนำให้ใช้ startUpWebView API แทน startUpWebView API ทำงานใน Android และ WebView ทุกเวอร์ชันที่ไลบรารี Jetpack Webkit รองรับ

การใช้การติดตั้งใช้งาน Jetpack Webkit จะช่วยให้มั่นใจได้ว่าลักษณะการทำงานจะสอดคล้องกันทั่วทั้งระบบนิเวศของ Android ข้อได้เปรียบที่สำคัญของ API นี้คือความยืดหยุ่น ในอุปกรณ์รุ่นเก่าที่ไม่มีการเพิ่มประสิทธิภาพใหม่ๆ API จะยังคงรักษาประสิทธิภาพเทียบเท่ากับวิธีแก้ปัญหาด้วยตนเอง ซึ่งช่วยให้คุณใช้ประโยชน์จากการเริ่มต้นระบบที่ทันสมัยในอุปกรณ์รุ่นใหม่ได้โดยไม่ต้องเสียค่าปรับด้านประสิทธิภาพในอุปกรณ์รุ่นเก่า

หากพบปัญหาหรือมีข้อเสนอแนะเกี่ยวกับ startUpWebView API โปรดรายงานข้อบกพร่องในเครื่องมือติดตามปัญหาแบบสาธารณะ