Zarządzaj obiektami WebView

Android udostępnia kilka interfejsów API, które pomagają zarządzać obiektami WebView wyświetlającymi treści internetowe w aplikacji.

Na tej stronie dowiesz się, jak używać tych interfejsów API, aby efektywniej pracować z obiektami WebView i zwiększyć stabilność oraz bezpieczeństwo aplikacji.

Version API

Od Androida 7.0 (poziom interfejsu API 24) użytkownicy mogą wybierać spośród kilku różnych pakietów do wyświetlania treści internetowych w obiekcie WebView. Biblioteka Jetpack Webkit zawiera getCurrentWebViewPackage() metodę, która służy do pobierania informacji o pakiecie wyświetlającym treści internetowe w aplikacji. Ta metoda jest przydatna podczas analizowania błędów, które występują tylko wtedy, gdy aplikacja próbuje wyświetlić treści internetowe za pomocą implementacji WebView w określonym pakiecie.

Aby użyć tej metody, dodaj logikę pokazaną w tym fragmencie kodu:

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 Safe Browsing Service

Aby zapewnić użytkownikom bezpieczniejsze przeglądanie, WebView obiekty weryfikują adresy URL za pomocą Bezpiecznego przeglądania Google, dzięki czemu aplikacja może wyświetlać ostrzeżenie, gdy użytkownicy próbują przejść do potencjalnie niebezpiecznej witryny.

Domyślna wartość EnableSafeBrowsing to true, ale w niektórych przypadkach możesz chcieć włączyć Bezpieczne przeglądanie tylko warunkowo lub je wyłączyć. Android 8.0 (poziom interfejsu API 26) i nowsze wersje obsługują używanie setSafeBrowsingEnabled() do przełączania Bezpiecznego przeglądania w przypadku poszczególnych obiektów WebView.

Jeśli chcesz, aby wszystkie WebView obiekty nie były sprawdzane przez Bezpieczne przeglądanie , dodaj ten element <meta-data> do pliku manifestu aplikacji:

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

Określanie działań programowych

Gdy instancja WebView próbuje wczytać stronę, która jest klasyfikowana przez Google jako znane zagrożenie, WebView domyślnie wyświetla ona reklamę pełnoekranową ostrzegającą użytkowników przed tym zagrożeniem. Na tym ekranie użytkownicy mogą wczytać adres URL lub wrócić do poprzedniej bezpiecznej strony.

Jeśli kierujesz aplikację na Androida 8.1 (poziom interfejsu API 27) lub nowszego, możesz programowo określić, jak aplikacja ma reagować na znane zagrożenie:

  • Możesz określić, czy aplikacja ma zgłaszać znane zagrożenia do Bezpiecznego przeglądania.
  • Możesz sprawić, że aplikacja będzie automatycznie wykonywać określone działanie (np. wracać do bezpiecznego stanu) za każdym razem, gdy napotka adres URL sklasyfikowany jako znane zagrożenie.

Te fragmenty kodu pokazują, jak poinstruować instancje WebView w aplikacji, aby zawsze wracały do bezpiecznego stanu po napotkaniu znanego zagrożenia:

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

W przypadku aplikacji kierowanych na Androida 6.0 (poziom interfejsu API 23) i nowsze wersje Geolocation API jest obsługiwane tylko w bezpiecznych źródłach, takich jak HTTPS. Każde żądanie do Geolocation API w źródłach niebezpiecznych jest automatycznie odrzucane bez wywoływania odpowiedniej metody onGeolocationPermissionsShowPrompt().

Rezygnacja ze zbierania danych

Gdy użytkownik wyrazi zgodę, WebView może przesyłać anonimowe dane diagnostyczne do Google. Dane są zbierane w przypadku każdej aplikacji, która tworzy instancję WebView. Aby zrezygnować z tej funkcji, utwórz ten tag w elemencie manifestu <application>:

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

Dane są przesyłane z aplikacji tylko wtedy, gdy użytkownik wyrazi na to zgodę i aplikacja nie zrezygnuje z tej funkcji. Więcej informacji o rezygnacji z raportowania danych diagnostycznych znajdziesz w artykule Prywatność użytkowników w raportach WebView reporting.

Termination Handling API

Termination Handling API obsługuje przypadki, w których proces renderowania obiektu WebView zostaje zakończony, ponieważ system go zamyka, aby odzyskać niezbędną pamięć, lub gdy proces renderowania ulega awarii. Dzięki temu interfejsowi API aplikacja może nadal działać, nawet jeśli proces renderowania zostanie zakończony.

Jeśli proces renderowania ulegnie awarii podczas wczytywania określonej strony internetowej, próba ponownego wczytania tej samej strony może spowodować, że nowy obiekt WebView będzie się zachowywać tak samo.

Ten fragment kodu pokazuje, jak używać tego interfejsu API w 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

Gdy WebView obiekty działają w trybie wieloprocesowym, masz pewną elastyczność w sposobie, w jaki aplikacja obsługuje sytuacje, w których brakuje pamięci. Możesz użyć Renderer Importance API, wprowadzonego w Androidzie 8.0, aby ustawić zasadę priorytetu dla procesu renderowania przypisanego do określonego obiektu WebView. W szczególności możesz chcieć, aby główna część aplikacji nadal działała, gdy proces renderowania wyświetlający obiekty WebView aplikacji zostanie zamknięty. Możesz to zrobić na przykład wtedy, gdy spodziewasz się, że obiekt WebView nie będzie wyświetlany przez dłuższy czas, aby system mógł odzyskać pamięć używaną przez proces renderowania.

Ten fragment kodu pokazuje, jak przypisać priorytet do procesu renderowania powiązanego z obiektami WebView aplikacji:

Kotlin

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

Java

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

W tym fragmencie priorytet procesu renderowania jest taki sam jak domyślny priorytet aplikacji lub jest z nim powiązany. Argument true zmniejsza priorytet procesu renderowania do RENDERER_PRIORITY_WAIVED, gdy powiązany obiekt WebView nie jest już widoczny. Innymi słowy, argument true oznacza, że aplikacja nie dba o to, czy system utrzymuje proces renderowania przy życiu. W rzeczywistości ten niższy poziom priorytetu sprawia, że proces renderowania jest bardziej narażony na zamknięcie w sytuacjach, w których brakuje pamięci.

Więcej informacji o diagnozowaniu i optymalizowaniu wykorzystania pamięci przez aplikację podczas korzystania z treści internetowych znajdziesz w artykule Zarządzanie pamięcią komponentu WebView i jej diagnozowanie. Więcej informacji o tym, jak system radzi sobie z sytuacjami, w których brakuje pamięci, znajdziesz w artykule Procesy i cykl życia aplikacji.

Zachowywanie stanu i historii nawigacji

Gdy system odzyskuje zasoby działające w tle lub urządzenie zmienia konfigurację, aktywność aplikacji i jej obiekty WebView mogą zostać zniszczone. Aby zachować kontekst nawigacji użytkownika, użyj metody saveState(Bundle) w onSaveInstanceState()aby serializować historię nawigacji do Bundle, a następnie przywrócić ją za pomocą restoreState(Bundle).

Ponieważ Android wymusza ścisły limit 1 MB w całym procesie dla savedInstanceState, serializowanie dużych sesji przeglądania za pomocą standardowego interfejsu API platformy może spowodować wyjątek TransactionTooLargeException. Aby wymusić limity rozmiaru lub bezpiecznie usunąć wpisy z historii do przodu, użyj metody Jetpack Webkit WebViewCompat.saveState().

Szczegółowe strategie i sprawdzone metody skutecznego zarządzania stanem znajdziesz w artykule Skuteczne zarządzanie stanem WebView.