จัดการการสิ้นสุด WebView

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

เหตุใดการจัดการการสิ้นสุดกระบวนการจึงสำคัญ

ใน Android เวอร์ชันที่ต่ำกว่า ผู้ใช้มีแนวโน้มที่จะสังเกตเห็นปัญหาได้น้อยลงเมื่อแอปไม่สามารถจัดการกับการสิ้นสุดกระบวนการแสดงผล อย่างไรก็ตาม Android เวอร์ชันใหม่ๆ จะใช้กลไกที่ซับซ้อนมากขึ้นในการจัดการการใช้ทรัพยากรของกระบวนการเบื้องหลัง ดังนั้น หากแอปของคุณไม่ได้ใช้ตัวแฮนเดิลสำหรับ onRenderProcessGone ก็มีแนวโน้มมากขึ้นที่ผู้ใช้จะเห็นการสิ้นสุดการทำงานในเบื้องหน้าที่มองเห็นได้เมื่อกลับมาที่แอป คุณต้องใช้ Termination Handling API เพื่อให้แอปกลับมาทำงานได้อย่างราบรื่นและรักษาประสบการณ์ของผู้ใช้ให้เสถียร

ใช้ Termination Handling API

คุณจัดการสถานการณ์เหล่านี้ได้โดยลบล้าง onRenderProcessGone ใน WebViewClient

ข้อกำหนดสำคัญสำหรับการกู้คืน

เพื่อให้แอปทำงานต่อไปได้หลังจากกระบวนการแสดงผลสิ้นสุดลง WebView แต่ละรายการในแอปต้องเป็นไปตามกฎต่อไปนี้

  • อย่าใช้อินสแตนซ์ WebView ซ้ำ เมื่อโปรแกรมแสดงผลหายไป ออบเจ็กต์ WebView ที่เฉพาะเจาะจงนั้นจะไม่มีประโยชน์ คุณต้องนำออบเจ็กต์ WebView ออกจากลำดับชั้นการแสดงผล ทำลายออบเจ็กต์ และล้างการอ้างอิงถึงออบเจ็กต์ในกิจกรรมหรือ Fragment เพื่อหลีกเลี่ยงการเรียกใช้เมธอดในอินสแตนซ์ WebView ที่ถูกทำลายต่อไปโดยไม่ตั้งใจ
  • สร้างอินสแตนซ์ใหม่ หากต้องการแสดงเนื้อหาเว็บต่อไป คุณต้องมีอินสแตนซ์ WebView ใหม่
  • แสดงผลเป็น "จริง" การติดตั้งใช้งานของคุณต้องแสดงผล true เพื่อแจ้งโค้ด WebView ว่าคุณได้จัดการสถานการณ์แล้ว หาก WebView ที่ใช้กระบวนการแสดงผลนั้นไม่แสดงผล true WebView จะปิดหรือทำให้แอปขัดข้องตามเหตุผลการออกจากกระบวนการแสดงผล

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

ตัวอย่างต่อไปนี้แสดงวิธีจัดการการสิ้นสุดเหล่านี้ภายในกิจกรรมที่สมบูรณ์ โดยจะใช้ detail.didCrash() เพื่อบันทึกว่ากระบวนการขัดข้องหรือระบบหยุดการแสดงผลเพื่อเรียกคืนหน่วยความจำหรือไม่ ไม่ว่าจะเกิดจากสาเหตุใด โค้ดจะล้างข้อมูลโดยนำ WebView ที่ไม่ทำงานออกจากเลย์เอาต์ ทำลาย WebView และล้างการอ้างอิงของกิจกรรมเพื่อป้องกัน NullPointerException จากนั้นจะเตรียมอินสแตนซ์ใหม่และแสดงผล true เพื่อไม่ให้แอปปิด

หมายเหตุ: ตัวอย่างต่อไปนี้จะแสดงผล true เสมอเพื่อให้แอปทำงานต่อไป หากต้องการให้แอปสิ้นสุดการทำงานเมื่อโปรแกรมแสดงผล WebView สิ้นสุดการทำงานโดยเฉพาะ คุณสามารถแสดงผล false จาก Callback นี้ได้ อย่างไรก็ตาม เราขอแนะนำให้จัดการการสร้างใหม่และแสดงผล true เพื่อประสบการณ์ของผู้ใช้ที่เสถียร

class MyWebViewActivity : AppCompatActivity() {

    private var webViewContainer: ViewGroup? = null
    private var myWebView: WebView? = null

    private var crashCount = 0
    private val MAX_CRASHES = 3

    // NEW FLAG: Tracks if the WebView crashed while the activity was hidden
    private var isWebViewPendingRecovery = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_web_view)
        webViewContainer = findViewById(R.id.my_web_view_container)

        initWebView()
    }

    // NEW LIFECYCLE HOOK: Triggers when the user brings the app back to the foreground
    override fun onResume() {
        super.onResume()
        // If a crash happened in the background, rebuild it now that the user can see it
        if (isWebViewPendingRecovery) {
            isWebViewPendingRecovery = false
            Log.i("MY_APP_TAG", "Activity resumed. Recovering pending WebView now.")
            recoverWebView()
        }
    }

    private fun initWebView() {
        val webView = WebView(this)
        myWebView = webView

        webViewContainer?.addView(webView)

        webView.webViewClient = object : WebViewClient() {
            override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean {
                if (detail.didCrash()) {
                    Log.e("MY_APP_TAG", "The WebView rendering process crashed!")
                } else {
                    Log.e("MY_APP_TAG", "System killed the WebView rendering process to reclaim memory.")
                }

                // Safely clean up the dead WebView
                webViewContainer?.removeView(webView)
                webView.destroy()
                myWebView = null

                // MODIFIED LOGIC: Check the lifecycle state of the Activity
                // lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) means the activity is visible and active
                if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
                    Log.i("MY_APP_TAG", "Activity is in foreground. Recovering immediately.")
                    recoverWebView()
                } else {
                    // The app is in the background. Mark it for later!
                    Log.w("MY_APP_TAG", "Activity is in background. Postponing recovery until user returns.")
                    isWebViewPendingRecovery = true
                }

                return true
            }
        }
        webView.loadUrl("https://www.example.com")
    }

    // HELPER FUNCTION: Extracted the recovery logic to reuse it
    private fun recoverWebView() {
        if (crashCount < MAX_CRASHES) {
            crashCount++
            Log.w("MY_APP_TAG", "Recovering WebView... Attempt $crashCount")
            initWebView()
        } else {
            Log.e("MY_APP_TAG", "WebView crashed too many times. Showing error UI.")
            showErrorUI()
        }
    }

    private fun showErrorUI() {
        // Example: Inflate an error view
    }

    override fun onDestroy() {
        super.onDestroy()
        myWebView?.let {
            webViewContainer?.removeView(it)
            it.destroy()
        }
        myWebView = null
    }
}

สิ่งสำคัญที่ควรพิจารณา

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

  • การขัดข้องซ้ำๆ: หากโปรแกรมแสดงผลขัดข้องขณะโหลดหน้าเว็บหนึ่งๆ อย่าเพิ่งโหลดหน้าเดิมซ้ำทันที ซึ่งอาจทำให้ WebView ใหม่ขัดข้องอีกครั้ง
  • ระดับการเข้าถึง: การปิดแอปที่เกิดจากการไม่มี Callback นี้ยังไม่แสดงในรายงานข้อขัดข้องของ Play Console แม้ว่าการอัปเดตในอนาคตอาจแก้ไขช่องว่างนี้ แต่คุณต้องแก้ไขปัญหาเหล่านี้ตอนนี้เพื่อให้แอปมีความเสถียร การใช้แฮนเดิลนี้จะช่วยให้คุณเห็นสาเหตุที่แอปออกทันที
  • แยกความแตกต่างระหว่างการสิ้นสุดการแสดงผลกับการสิ้นสุดการประมวลผลของแอป: onRenderProcessGone จะจัดการสถานการณ์ที่กระบวนการแสดงผลสิ้นสุดลงขณะที่กระบวนการของแอปยังคงทำงานอยู่ หากระบบปิดกระบวนการทั้งหมดของแอปในเบื้องหลัง ให้ใช้ onSaveInstanceState() กับ WebView.saveState() และ WebView.restoreState() เพื่อคงและกู้คืนประวัติการนำทาง หากต้องการจัดการสถานะที่บันทึกไว้อย่างปลอดภัยโดยไม่เกินขีดจำกัดธุรกรรม โปรดดูหัวข้อจัดการสถานะ WebView อย่างมีประสิทธิภาพ