WebView เรียกใช้เนื้อหาเว็บในกระบวนการโปรแกรมแสดงผลแยกต่างหาก บางครั้งกระบวนการนี้อาจสิ้นสุดลง กรณีนี้จะเกิดขึ้นหากระบบปิดโปรแกรมแสดงผลเพื่อ เรียกคืนหน่วยความจำ หรือหากกระบวนการดังกล่าวขัดข้อง หากคุณไม่จัดการการสิ้นสุดเหล่านี้ แอปของคุณก็จะออกไปด้วย
เหตุใดการจัดการการสิ้นสุดกระบวนการจึงสำคัญ
ใน Android เวอร์ชันที่ต่ำกว่า ผู้ใช้มีแนวโน้มที่จะไม่สังเกตเห็นปัญหาเมื่อ
แอปจัดการการสิ้นสุดกระบวนการแสดงผลไม่สำเร็จ อย่างไรก็ตาม Android เวอร์ชันใหม่กว่าจะใช้กลไกที่ซับซ้อนกว่าในการจัดการการใช้ทรัพยากรของกระบวนการที่ทำงานเบื้องหลัง ดังนั้น หากแอปของคุณไม่ได้ใช้ตัวแฮนเดิลสำหรับ
onRenderProcessGone ผู้ใช้มีแนวโน้มที่จะเห็นการสิ้นสุดของ
การทำงานในเบื้องหน้าเมื่อกลับมาที่แอปมากขึ้น คุณต้องใช้ Termination
Handling API เพื่อให้แน่ใจว่าแอปจะกู้คืนได้อย่างราบรื่นและรักษา
ประสบการณ์การใช้งานที่เสถียรของผู้ใช้
ใช้ Termination Handling API
คุณจัดการสถานการณ์เหล่านี้ได้โดยการลบล้าง onRenderProcessGone ใน
WebViewClient
ข้อกำหนดสำคัญสำหรับการกู้คืน
หากต้องการให้แอปทำงานต่อไปหลังจากกระบวนการตัวแสดงผลสิ้นสุดลง WebView แต่ละรายการในแอปต้องเป็นไปตามกฎต่อไปนี้
- อย่าใช้อินสแตนซ์ WebView ซ้ำ เมื่อไม่มีโปรแกรมแสดงผลแล้ว ออบเจ็กต์ WebView ที่เฉพาะเจาะจงนั้นจะใช้ไม่ได้ คุณต้องนำออบเจ็กต์ WebView ออกจากลำดับชั้นของมุมมอง ทำลายออบเจ็กต์ และล้างการอ้างอิงทั้งหมดใน Activity หรือ Fragment เพื่อหลีกเลี่ยงการเรียกเมธอดในอินสแตนซ์ WebView ที่ถูกทำลายแล้วโดยไม่ตั้งใจ
- สร้างอินสแตนซ์ใหม่ หากต้องการแสดงเนื้อหาเว็บต่อไป คุณจะต้องมีอินสแตนซ์ WebView ใหม่
- ส่งคืนค่าเป็นจริง การติดตั้งใช้งานของคุณต้องแสดงผล
trueเพื่อบอกโค้ด WebView ว่าคุณได้จัดการสถานการณ์แล้ว หากWebViewใดก็ตามที่ใช้กระบวนการแสดงผลนั้นไม่แสดงผลtrueWebView จะปิดหรือทำให้แอปขัดข้องตามเหตุผลที่กระบวนการแสดงผลสิ้นสุดลง
ตัวอย่างการใช้งาน
ตัวอย่างต่อไปนี้แสดงวิธีจัดการการสิ้นสุดเหล่านี้ภายในกิจกรรมที่สมบูรณ์ โดยจะใช้ detail.didCrash() เพื่อบันทึกว่ากระบวนการขัดข้องหรือไม่
หรือระบบปิดการแสดงผลเพื่อเรียกคืนหน่วยความจำ ไม่ว่าสาเหตุจะเป็นอะไรก็ตาม โค้ดจะล้างข้อมูลโดยนำ WebView ที่ไม่ได้ใช้งานออกจากเลย์เอาต์ ทำลาย WebView และล้างข้อมูลอ้างอิงของกิจกรรมเพื่อป้องกันไม่ให้เกิด NullPointerException
จากนั้นจะเตรียมอินสแตนซ์ใหม่และแสดง true เพื่อไม่ให้แอปปิด
หมายเหตุ: ตัวอย่างต่อไปนี้จะแสดงผล true เสมอเพื่อให้แอปทำงานต่อไป
หากต้องการให้แอปสิ้นสุดเมื่อตัวแสดงผล WebView
สิ้นสุด คุณสามารถส่งคืน false จากการเรียกกลับนี้ได้ อย่างไรก็ตาม เราขอแนะนำให้
จัดการการสร้างใหม่และส่งคืน 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 ใหม่ ขัดข้องอีกครั้ง
- ระดับการเข้าถึง: การปิดแอปที่เกิดจากการไม่มีการเรียกกลับนี้ยังไม่แสดงในรายงานข้อขัดข้องของ Play Console แม้ว่าการอัปเดตในอนาคตอาจช่วยแก้ไขช่องโหว่นี้ได้ แต่คุณต้องแก้ไขปัญหาเหล่านี้ตอนนี้เพื่อให้มั่นใจว่าแอปจะมีความเสถียร การใช้ตัวแฮนเดิลนี้จะช่วยให้คุณทราบได้ทันทีว่าเหตุใดแอปจึงออก