7

Below I posted my current onRenderProcessGone.

  1. In the if (!detail.didCrash()) {} the instance variable "view" is guaranteed to be null, it's safe to reinitialize it. Should I myself reinitialize it or system will do it?
  2. Could you specify the example of logic for how the app can continue executing? How to handle the crash more gracefully?

        @TargetApi(Build.VERSION_CODES.O)
        @Override
        public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
            // WebViewClient.onRenderProcessGone was added in O.
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
                return false;
            }
            super.onRenderProcessGone(view, detail);
    
            if (!detail.didCrash()) {
                // Renderer was killed because the system ran out of memory.
                // The app can recover gracefully by creating a new WebView instance
                // in the foreground.
                Log.e("MY_APP", "System killed the WebView rendering process " +
                        "to reclaim memory. Recreating...");
    
                if (view != null) {
                    ((ViewGroup)view.getParent()).removeView(view);
                    view.destroy();
                    view = null;
                }
    
                // By this point, the instance variable "view" is guaranteed
                // to be null, so it's safe to reinitialize it.
    
                return true; // The app continues executing.
            }
    
            // Renderer crashed because of an internal error, such as a memory
            // access violation.
            Log.e("MY_APP", "The WebView rendering process crashed!");
    
            // In this example, the app itself crashes after detecting that the
            // renderer crashed. If you choose to handle the crash more gracefully
            // and allow your app to continue executing, you should 1) destroy the
            // current WebView instance, 2) specify logic for how the app can
            // continue executing, and 3) return "true" instead.
            return false;
        }
    
Alexander Savin
  • 1,952
  • 1
  • 15
  • 30

1 Answers1

8

I have implemented this method and do not make a distinction between the renderer crashing or being killed by the system. As far as the app is concerned the result is the same - the webview is unusable, and if this method returns false the app will also be killed.

I maintain a reference to the WebView and two the layout root view in the Fragment (or Activity) that is initialised during the Fragment/Activity creation.

        m_homeWebSwipe = v.findViewById(R.id.homeWebViewSwipe);
        m_homeWebView = v.findViewById(R.id.homeWebView);
        initializeWebView(m_homeWebView);

My WebView is the child of a SwipeRefreshLayout. The layout XML is not critical, but for reference it is:

<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/homeWebViewSwipe"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <WebView
        android:id="@+id/homeWebView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </WebView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

The key here is the WebView is contained in a Layout container at the root of the view.

On receiving the onRenderProcessGone event, the goal is to recreate a functional view containing a new WebView (which will have a new renderer process associated with it).

            @Override
            public boolean onRenderProcessGone(final WebView view, RenderProcessGoneDetail detail) {
                // Renderer was killed or died, recreate the webview
                Log.e("HomeWebView", "web content rendering process killed - resetting WebView: " + view.hashCode());

                // Only handle our WebView
                if (m_homeWebView.equals(view)) {
                    // Get the parent container of the inflated layout
                    ViewGroup container = (ViewGroup) m_homeWebSwipe.getParent();
                    ViewGroup.LayoutParams params = container.getLayoutParams();
                    // Remove the inflated view from the container and cleanup
                    // the dead webview specifically (if it is not GC'ed it will cause
                    // problems later, the next time the renderer dies)
                    container.removeView(m_homeWebSwipe);
                    m_homeWebSwipe = null;
                    m_homeWebView = null;
                    view.destroy();
                    // Reinflate the view layout and add it back into the container
                    View v = getLayoutInflater().inflate(R.layout.home_webview_screen, container, false);
                    m_homeWebSwipe = v.findViewById(R.id.homeWebViewSwipe);
                    m_homeWebView = v.findViewById(R.id.homeWebView);
                    // Initialise webview here, same as when it was originally created                    
                    initializeWebView(m_homeWebView);
                    assert(getActivity() != null);
                    getActivity().setContentView(v, params);
                    // reload the displayed page, or load a new page here
                    reloadPage();
                    return true; // The app continues executing.
                }
                return false; // the app is killed
            }

You can test the code in the onRenderProcessGone method by arranging for the WebView to load the URL chrome://crash

BitByteDog
  • 3,074
  • 2
  • 26
  • 39
  • I can recover it only once. After the second recovery it just freezes. Any idea? – Besnik Dec 27 '22 at 09:09
  • What is the compile and target sdk level? – BitByteDog Jan 01 '23 at 00:08
  • I have set 31 for both – Besnik Jan 02 '23 at 10:36
  • If I'm understanding you correctly, `onRenderProcessGone` is called on your `override` even if it isn't related to your `WebView` instance? – casolorz Jul 05 '23 at 15:52
  • A render process can be associated with multiple web views. When a render process dies, all affected web views are signalled using the override `onRenderProcessGone`, each WebView in your app should implement `onRenderProcessGone`, and should check the specific web view passed before attempting cleanup. More info can be found here https://developer.android.com/reference/android/webkit/WebViewClient#onRenderProcessGone(android.webkit.WebView,%20android.webkit.RenderProcessGoneDetail) – BitByteDog Jul 05 '23 at 18:48