Управление объектами WebView

В Android есть несколько API, которые помогают управлять объектами WebView, показывающими веб-контент в приложении.

На этой странице описано, как использовать эти API для более эффективной работы с объектами WebView, повышая стабильность и безопасность приложения.

Version API

В Android 7.0 (уровень API 24) и более поздних версиях пользователи могут выбирать разные пакеты для показа веб-контента в объекте WebView. Библиотека Jetpack Webkit содержит метод getCurrentWebViewPackage() для получения информации о пакете, который показывает веб-контент в вашем приложении. Этот метод полезен при анализе ошибок, возникающих только тогда, когда приложение пытается показать веб-контент, используя реализацию WebView из определенного пакета.

Чтобы использовать этот метод, добавьте логику, показанную в следующем фрагменте кода:

Kotlin

val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(appContext)
Log.d("MY_APP_TAG", "WebView version: ${webViewPackageInfo.versionName}")

Java

PackageInfo webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(appContext);
Log.d("MY_APP_TAG", "WebView version: " + webViewPackageInfo.versionName);

Сервис "Google Безопасный просмотр"

Чтобы обеспечить пользователям более безопасный просмотр, объекты WebView проверяют URL с помощью Google Безопасного просмотра. Благодаря этому ваше приложение может показывать пользователям предупреждения при попытке перейти на потенциально опасный сайт.

Хотя значение по умолчанию для EnableSafeBrowsing – true, в некоторых случаях может потребоваться включить Безопасный просмотр только при определенных условиях или отключить его. В Android 8.0 (уровень API 26) и более поздних версиях можно использовать setSafeBrowsingEnabled(), чтобы включать и отключать Безопасный просмотр для отдельных объектов WebView.

Если вы хотите, чтобы все объекты WebView были исключены из проверок Безопасного просмотра, добавьте следующий элемент <meta-data> в файл манифеста приложения:

<manifest>
    <application>
        <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
                   android:value="false" />
        ...
    </application>
</manifest>

Как задать действия для алгоритмических продаж

Когда экземпляр WebView пытается загрузить страницу, которая классифицируется Google как известная угроза, WebView по умолчанию показывает межстраничное объявление, предупреждающее пользователей об известной угрозе. На этом экране пользователи могут выбрать, загружать ли URL или вернуться на безопасную страницу.

Если ваше приложение предназначено для Android 8.1 (уровень API 27) или более поздней версии, вы можете программно определить, как оно будет реагировать на известную угрозу, следующими способами:

  • Вы можете указать, должно ли приложение сообщать о известных угрозах в Безопасный просмотр.
  • Вы можете настроить автоматизацию в приложении так, чтобы оно автоматически выполняло определенное действие, например возвращалось на безопасную страницу, каждый раз, когда сталкивается с URL, классифицированным как известная угроза.

В приведенных ниже фрагментах кода показано, как настроить экземпляры WebView в приложении, чтобы они всегда возвращались в безопасное состояние после обнаружения известной угрозы.

MyWebActivity.java

Kotlin

private lateinit var superSafeWebView: WebView
private var safeBrowsingIsInitialized: Boolean = false

// ...

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    superSafeWebView = WebView(this)
    superSafeWebView.webViewClient = MyWebViewClient()
    safeBrowsingIsInitialized = false

    if (WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) {
        WebViewCompat.startSafeBrowsing(this, ValueCallback<Boolean> { success ->
            safeBrowsingIsInitialized = true
            if (!success) {
                Log.e("MY_APP_TAG", "Unable to initialize Safe Browsing!")
            }
        })
    }
}

Java

private WebView superSafeWebView;
private boolean safeBrowsingIsInitialized;

// ...

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    superSafeWebView = new WebView(this);
    superSafeWebView.setWebViewClient(new MyWebViewClient());
    safeBrowsingIsInitialized = false;

    if (WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) {
        WebViewCompat.startSafeBrowsing(this, new ValueCallback<Boolean>() {
            @Override
            public void onReceiveValue(Boolean success) {
                safeBrowsingIsInitialized = true;
                if (!success) {
                    Log.e("MY_APP_TAG", "Unable to initialize Safe Browsing!");
                }
            }
        });
    }
}

MyWebViewClient.java

Kotlin

class MyWebViewClient : WebViewClientCompat() {
    // Automatically go "back to safety" when attempting to load a website that
    // Google identifies as a known threat. An instance of WebView calls this
    // method only after Safe Browsing is initialized, so there's no conditional
    // logic needed here.
    override fun onSafeBrowsingHit(
            view: WebView,
            request: WebResourceRequest,
            threatType: Int,
            callback: SafeBrowsingResponseCompat
    ) {
        // The "true" argument indicates that your app reports incidents like
        // this one to Safe Browsing.
        if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY)) {
            callback.backToSafety(true)
            Toast.makeText(view.context, "Unsafe web page blocked.", Toast.LENGTH_LONG).show()
        }
    }
}

Java

public class MyWebViewClient extends WebViewClientCompat {
    // Automatically go "back to safety" when attempting to load a website that
    // Google identifies as a known threat. An instance of WebView calls this
    // method only after Safe Browsing is initialized, so there's no conditional
    // logic needed here.
    @Override
    public void onSafeBrowsingHit(WebView view, WebResourceRequest request,
            int threatType, SafeBrowsingResponseCompat callback) {
        // The "true" argument indicates that your app reports incidents like
        // this one to Safe Browsing.
        if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY)) {
            callback.backToSafety(true);
            Toast.makeText(view.getContext(), "Unsafe web page blocked.",
                    Toast.LENGTH_LONG).show();
        }
    }
}

HTML5 Geolocation API

Для приложений, предназначенных для Android 6.0 (уровень API 23) и более поздних версий, Geolocation API поддерживается только в защищенных источниках, например HTTPS. Любой запрос к Geolocation API из небезопасного источника автоматически отклоняется без вызова соответствующего метода onGeolocationPermissionsShowPrompt().

Как отказаться от сбора показателей

WebView может загружать анонимные диагностические данные в Google, если пользователь дал на это согласие. Данные собираются отдельно для каждого приложения, в котором создается экземпляр WebView. Чтобы отключить эту функцию, добавьте в элемент <application> манифеста следующий тег:

<manifest>
    <application>
    ...
    <meta-data android:name="android.webkit.WebView.MetricsOptOut"
               android:value="true" />
    </application>
</manifest>

Данные загружаются из приложения, только если пользователь дал согласие и в приложении не отключена эта функция. Подробнее о том, как отключить отправку диагностических данных, рассказывается в статье Конфиденциальность пользователей при отправке отчетов WebView.

Termination Handling API

API обработки завершения позволяет обрабатывать случаи, когда процесс отрисовки для объекта WebView завершается, либо потому что система останавливает его, чтобы освободить память, либо потому что процесс отрисовки завершается аварийно. Используя этот API, вы позволяете приложению продолжать работу, даже если процесс рендеринга прекращается.

Если при загрузке определенной веб-страницы происходит сбой средства визуализации, то при попытке загрузить ту же страницу снова может произойти сбой визуализации нового объекта WebView.

В приведенном ниже фрагменте кода показано, как использовать этот API в Activity.

Kotlin

    
inner class MyRendererTrackingWebViewClient : WebViewClient() {
    private var mWebView: WebView? = null

    override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean {
        if (!detail.didCrash()) {
            // Renderer is 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_TAG", ("System killed the WebView rendering process " +
                "to reclaim memory. Recreating..."))

            mWebView?.also { webView ->
                val webViewContainer: ViewGroup = findViewById(R.id.my_web_view_container)
                webViewContainer.removeView(webView)
                webView.destroy()
                mWebView = null
            }

            // By this point, the instance variable "mWebView" is guaranteed to
            // be null, so it's safe to reinitialize it.

            return true // The app continues executing.
        }

        // Renderer crashes because of an internal error, such as a memory
        // access violation.
        Log.e("MY_APP_TAG", "The WebView rendering process crashed!")

        // In this example, the app itself crashes after detecting that the
        // renderer crashed. If you handle the crash more gracefully and let
        // your app continue executing, you must destroy the current WebView
        // instance, specify logic for how the app continues executing, and
        // return "true" instead.
        return false
    }
}

Java

public class MyRendererTrackingWebViewClient extends WebViewClient {
    private WebView mWebView;

    @Override
    public boolean onRenderProcessGone(WebView view,
            RenderProcessGoneDetail detail) {
        if (!detail.didCrash()) {
            // Renderer is 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_TAG", "System killed the WebView rendering process " +
                    "to reclaim memory. Recreating...");

            if (mWebView != null) {
                ViewGroup webViewContainer =
                        (ViewGroup) findViewById(R.id.my_web_view_container);
                webViewContainer.removeView(mWebView);
                mWebView.destroy();
                mWebView = null;
            }

            // By this point, the instance variable "mWebView" is guaranteed to
            // be null, so it's safe to reinitialize it.

            return true; // The app continues executing.
        }

        // Renderer crashes because of an internal error, such as a memory
        // access violation.
        Log.e("MY_APP_TAG", "The WebView rendering process crashed!");

        // In this example, the app itself crashes after detecting that the
        // renderer crashed. If you handle the crash more gracefully and let
        // your app continue executing, you must destroy the current WebView
        // instance, specify logic for how the app continues executing, and
        // return "true" instead.
        return false;
    }
}

Renderer Importance API

Когда объекты WebView работают в многопроцессном режиме, у вас есть некоторая гибкость в том, как ваше приложение обрабатывает ситуации нехватки памяти. Вы можете использовать Renderer Importance API, представленный в Android 8.0, чтобы задать приоритет для средства отрисовки, назначенного определенному объекту WebView. В частности, вы можете захотеть, чтобы основная часть приложения продолжала выполняться, когда завершается работа отрисовщика, который показывает объекты WebView. Например, это может понадобиться, если вы не планируете показывать объект WebView в течение длительного времени, чтобы система могла освободить память, которую использовал отрисовщик.

В приведенном ниже фрагменте кода показано, как назначить приоритет процессу отрисовки, связанному с объектами WebView вашего приложения:

Kotlin

val myWebView: WebView = ...
myWebView.setRendererPriorityPolicy(RENDERER_PRIORITY_BOUND, true)

Java

WebView myWebView;
myWebView.setRendererPriorityPolicy(RENDERER_PRIORITY_BOUND, true);

В этом фрагменте приоритет рендерера такой же, как и приоритет по умолчанию для приложения. Аргумент true снижает приоритет рендерера до RENDERER_PRIORITY_WAIVED, когда связанный объект WebView больше не виден. Другими словами, аргумент true указывает, что для приложения не имеет значения, будет ли система поддерживать работу процесса визуализации. Из-за этого процесс отрисовки может быть завершен при нехватке памяти.

Подробнее о том, как диагностировать и оптимизировать объем потребляемой памяти при использовании веб-контента, рассказывается в статье Как управлять памятью WebView и диагностировать проблемы с ней. Подробнее о том, как система справляется с нехваткой памяти, читайте в статье Процессы и жизненный цикл приложения.

Сохранение состояния и истории навигации

Когда система освобождает ресурсы, используемые в фоновом режиме, или когда конфигурация устройства меняется, объект activity приложения и его объекты WebView могут быть уничтожены. Чтобы сохранить контекст навигации пользователя, используйте метод saveState(Bundle) в onSaveInstanceState() для сериализации истории навигации в Bundle, а затем восстановите ее с помощью restoreState(Bundle).

Поскольку в Android действует строгое ограничение в 1 МБ на весь процесс для savedInstanceState, сериализация больших сеансов просмотра с помощью стандартного API фреймворка может привести к ошибке TransactionTooLargeException. Чтобы принудительно ограничить размер или безопасно удалить записи истории переходов вперед, используйте метод Jetpack Webkit WebViewCompat.saveState().

Подробные стратегии и рекомендации по эффективному управлению состоянием приведены в статье Эффективное управление состоянием WebView.