Appearance
Handle and Test Webview Process Getting Terminated
Introduction
This article explains how to handle unexpected termination of the webview process, which is a separate process from the main app process. This separation means that even if your app is stable, the webview process may still crash independently due to issues within the webview.
Such termination can occur for a variety of reasons, including large media resources that do not fit in memory, issues in the scripted content of the publication, or due to system actions like reclaiming resources under memory pressure. It's important to design your app to detect and recover from this situation gracefully.
We will also cover how you can deliberately trigger a webview termination event to test your app's handling logic.
When this event occurs, the ColibrioReadingSystemView will no longer function and must be replaced with a new instance.
Be aware that simply recreating the view and reloading the same publication may not be sufficient if the publication itself is the source of the problem. The webview process may crash again, potentially causing an endless crash-reload cycle. To help mitigate this, we provide examples in our sample apps demonstrating how to detect and break out of such loops.
Android
To get notified about the webview process being terminated, use the method ColibrioReadingSystemView.setOnRendererProcessCrash().
When the callback is called, the ColibrioReadingSystemView and its associated objects — ReaderView, ReaderPublication, SyncMediaPlayer, etc. — will no longer work as expected: some methods return an error, while others report success and emit events for changes the engine never applies. Do not trust state read from these objects, and do not rely on their destroyed flags to detect this state — SyncMediaPlayer.destroyed, for example, is not set by the crash.
To recover:
- Call
destroy()on theColibrioReadingSystemViewto clean up its resources. - Remove the view from the view tree.
- Drop all references to the view and its associated objects.
- Create a new
ColibrioReadingSystemViewand reload the publication.
kotlin
colibrioReadingSystemView.setOnRendererProcessCrash {
// Destroy the ColibrioReadingSystemView to clean up resources
colibrioReadingSystemView.destroy()
// Remove the view from the view tree and drop all references to it and its
// associated objects (ReaderView, ReaderPublication, SyncMediaPlayer, etc.).
// Add your code to replace the ColibrioReadingSystemView and reload the publication.
}Recovering a connected ColibrioAudioPlayer
If a ColibrioAudioPlayer was connected to a SyncMediaPlayer through ReadingSystemEngine.createSyncMediaPlayer(), native audio playback keeps running after the crash but can no longer be controlled: play(), pause() and seeks on the audio player, its volume, muted and playbackRate setters, and the media notification's play, pause, skip and seek buttons all have no effect. Playback continues until your app does one of the following.
- Continue playback. Call
ReadingSystemEngine.destroySyncMediaPlayer()with the connected player. It does not need the crashed renderer process, so it still takes effect: theColibrioAudioPlayerleaves synced mode and responds to its own controls and the media notification again. Apause()orplay()call made just before the crash that never reached the player is applied as part of the disconnect. Keep the audio publication'sResourceProvideropen for as long as playback continues, and reconnect the player to the new view withReadingSystemEngine.createSyncMediaPlayer(). - Stop playback. Call
AudioReadingSystemEngine.destroyAudioPlayer(). This stops audio playback, removes the media notification and stops the playback service. Do not calldestroySyncMediaPlayer()first: disconnecting applies anyplay()orpause()that was pending when the crash happened, which can make playback audible again before it stops.
Testing your handling logic
As a way to test your logic, use the following function which will terminate the webview process:
kotlin
fun killWebView() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val webViewRenderProcess = colibrioReadingSystemView.webView?.webViewRenderProcess
webViewRenderProcess?.terminate()
}
}iOS
To get notified about the webview process being terminated, use the method ColibrioReadingSystemView.setOnWebViewContentProcessDidTerminateListener(_:). The framework keeps only a weak reference to the listener, so a handler that nothing else retains is deallocated immediately and the callback never fires. Store your handler somewhere that outlives the view, or conform your view controller to OnWebViewContentProcessDidTerminateListener as in the example below.
When the listener is called, the ColibrioReadingSystemView and its associated objects — ReaderView, ReaderPublication, SyncMediaPlayer, etc. — will no longer work as expected: some operations fail with an error or never complete, while others report success and emit events for changes the engine never applies. Do not trust state read from these objects, and do not rely on their destroyed flags to detect this state — the crash does not set them.
To recover:
- Remove the
ColibrioReadingSystemViewfrom its superview to clean up its resources. - Drop all references to the view and its associated objects.
- Create a new
ColibrioReadingSystemViewand reload the publication.
swift
class ReaderViewController: UIViewController, OnWebViewContentProcessDidTerminateListener {
func onWebViewContentProcessDidTerminate(_ webView: WKWebView) {
// Remove the ColibrioReadingSystemView from its superview to clean up its
// resources, and drop all references to it and its associated objects
// (ReaderView, ReaderPublication, SyncMediaPlayer, etc.).
// Add your code to replace the ColibrioReadingSystemView and reload the publication.
}
}Recovering a connected ColibrioAudioPlayer
If a ColibrioAudioPlayer was connected to a SyncMediaPlayer through ReadingSystemEngine.createSyncMediaPlayer(colibrioAudioPlayer:completion:), native audio playback keeps running after the crash but can no longer be controlled: play(), pause() and seeks on the audio player, its volume, muted and playbackRate setters, and the Now Playing controls on the Lock Screen and in Control Center all have no effect. Playback continues until your app does one of the following.
- Continue playback. Call
ReadingSystemEngine.destroySyncMediaPlayer(_:)with the connected player. It does not need the crashed content process, so it still takes effect: theColibrioAudioPlayerleaves synced mode and responds to its own controls and the Now Playing controls again. Apause()orplay()call made just before the crash that never reached the player is applied as part of the disconnect. Keep the audio publication'sResourceProvideropen for as long as playback continues, and reconnect the player to the new view withReadingSystemEngine.createSyncMediaPlayer(colibrioAudioPlayer:completion:). - Stop playback. Call
AudioReadingSystemEngine.shared.destroyAudioPlayer(). This stops audio playback, removes the Now Playing controls and deactivates the audio session. Do not calldestroySyncMediaPlayer(_:)first: disconnecting applies anyplay()orpause()that was pending when the crash happened, which can make playback audible again before it stops.
Testing your handling logic
As a way to test your logic, follow these instructions to terminate the webview process:
- Launch your app in the simulator
- Launch the Activity Monitor app on your Mac
- Stop the process
com.apple.WebKit.WebContent