ניהול אובייקטים של 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

כדי לספק למשתמשים חוויית גלישה בטוחה יותר, אובייקטים מאמתים כתובות URL באמצעות הגלישה הבטוחה של Google, וכך האפליקציה יכולה להציג למשתמשים אזהרה כשהם מנסים לעבור לאתר שעלול להיות לא בטוח.WebView

למרות שערך ברירת המחדל של 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

‫Termination Handling API מטפל במקרים שבהם תהליך הרינדור של אובייקט WebView נעלם, או בגלל שהמערכת מפסיקה את הרינדור כדי לפנות זיכרון נחוץ, או בגלל שתהליך הרינדור קורס. באמצעות ה-API הזה, אתם מאפשרים לאפליקציה שלכם להמשיך לפעול, גם אם תהליך הרינדור נעלם.

אם רכיב ה-Renderer קורס בזמן טעינה של דף אינטרנט מסוים, ניסיון לטעון את אותו דף שוב עלול לגרום לאובייקט WebView חדש להציג את אותה התנהגות של קריסת ה-Rendering.

קטע הקוד הבא מדגים איך להשתמש ב-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);

בקטע הקוד הזה, העדיפות של רכיב ה-renderer זהה לעדיפות שמוגדרת כברירת מחדל לאפליקציה, או קשורה אליה. הארגומנט true מקטין את העדיפות של רכיב ה-renderer ל-RENDERER_PRIORITY_WAIVED כשאובייקט WebView המשויך כבר לא גלוי. במילים אחרות, ארגומנט true מציין שלאפליקציה לא משנה אם המערכת תשמור על תהליך ה-renderer פעיל. למעשה, רמת העדיפות הנמוכה הזו מגדילה את הסיכוי שתהליך ה-renderer יופסק במצבים של חוסר זיכרון.

מידע נוסף על אופן הטיפול של המערכת במצבים של זיכרון נמוך זמין במאמר בנושא תהליכים ומחזור החיים של האפליקציה.