WebView는 별도의 렌더러 프로세스에서 웹 콘텐츠를 실행합니다. 이 프로세스는 때때로 종료될 수 있습니다. 이는 메모리를 확보하기 위해 시스템이 렌더러를 종료하거나 프로세스 자체가 다운되는 경우에 발생합니다. 이러한 종료를 처리하지 않으면 앱도 종료됩니다.
프로세스 종료 처리가 중요한 이유
이전 버전의 Android에서는 앱이 렌더러 프로세스 종료를 처리하지 못할 때 사용자가 문제를 인식할 가능성이 낮았습니다. 하지만 최신 버전의 Android에서는 더 복잡한 메커니즘을 사용하여 백그라운드 프로세스의 리소스 소비를 관리합니다. 따라서 앱이 onRenderProcessGone 핸들러를 구현하지 않으면 사용자가 앱으로 돌아올 때 눈에 보이는 포그라운드 종료가 발생할 가능성이 높아집니다. 앱이 원활하게 복구되고 안정적인 사용자 환경을 유지하려면 종료 처리 API를 구현해야 합니다.
종료 처리 API 사용
WebViewClient에서 onRenderProcessGone를 재정의하여 이러한 상황을 처리할 수 있습니다.
복구의 주요 요구사항
렌더러 프로세스가 종료된 후에도 앱이 계속 실행되려면 앱의 각 WebView가 다음 규칙을 따라야 합니다.
- WebView 인스턴스를 재사용하지 마세요. 렌더러가 사라지면 해당 WebView 객체는 쓸모가 없습니다. 삭제된 WebView 인스턴스에서 실수로 메서드를 계속 호출하지 않으려면 뷰 계층 구조에서 WebView 객체를 삭제하고, 이를 제거하고, Activity 또는 Fragment에서 WebView 객체에 대한 참조를 삭제해야 합니다.
- 새 인스턴스를 만듭니다. 웹 콘텐츠를 계속 표시하려면 새 WebView 인스턴스가 필요합니다.
- true를 반환합니다. 구현은
true를 반환하여 상황을 처리했음을 WebView 코드에 알려야 합니다. 해당 렌더러 프로세스를 사용하는WebView가true를 반환하지 않으면 WebView는 렌더러 프로세스 종료 이유에 따라 앱을 종료하거나 비정상 종료합니다.
구현 예시
다음 예에서는 완전한 활동 내에서 이러한 종료를 처리하는 방법을 보여줍니다. detail.didCrash()를 사용하여 프로세스가 다운되었는지 또는 시스템이 메모리를 확보하기 위해 렌더러를 종료했는지 기록합니다. 원인과 관계없이 코드는 레이아웃에서 사용하지 않는 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 비정상 종료 보고서에 반영되지 않습니다. 향후 업데이트에서 이 격차가 해결될 수 있지만 앱 안정성을 보장하려면 지금 이러한 문제를 해결해야 합니다. 이 핸들러를 구현하면 앱이 종료되는 이유를 즉시 파악할 수 있습니다.