פיתוח אפליקציות אינטרנט ב-WebView

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

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

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

במסמך הזה מוסבר איך מתחילים להשתמש ב-WebView, איך מקשרים JavaScript מדף האינטרנט לקוד בצד הלקוח באפליקציה ל-Android, איך מטפלים בניווט בדפים ואיך מנהלים חלונות כשמשתמשים ב-WebView.

עבודה עם WebView בגרסאות קודמות של Android

כדי להשתמש בבטחה ביכולות WebView עדכניות יותר במכשיר שבו האפליקציה פועלת, מוסיפים את הספרייה AndroidX Webkit. זוהי ספרייה סטטית שאפשר להוסיף לאפליקציה כדי להשתמש בממשקי API של android.webkit שלא זמינים לגרסאות קודמות של הפלטפורמה.

מוסיפים אותו לקובץ build.gradle באופן הבא:

Kotlin

dependencies {
    implementation("androidx.webkit:webkit:1.8.0")
}

Groovy

dependencies {
    implementation ("androidx.webkit:webkit:1.8.0")
}

פרטים נוספים זמינים בדוגמה WebView ב-GitHub.

הוספת WebView לאפליקציה

כדי להוסיף WebView לאפליקציה, אפשר לכלול את האלמנט <WebView> בפריסה של הפעילות או להגדיר את כל חלון Activity כ-WebView ב-onCreate().

הוספת רכיב WebView בפריסה של הפעילות

כדי להוסיף WebView לאפליקציה בתצוגה, מוסיפים את הקוד הבא לקובץ ה-XML של הפריסה של הפעילות:

<WebView
    android:id="@+id/webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

כדי לטעון דף אינטרנט ב-WebView, משתמשים ב-loadUrl(), כפי שמוצג בדוגמה הבאה:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.loadUrl("http://www.example.com")

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.loadUrl("http://www.example.com");

הוספת WebView ב-onCreate()‎

כדי להוסיף WebView לאפליקציה במקום זאת בשיטה onCreate() של פעילות, צריך להשתמש בלוגיקה דומה לזו:

Kotlin

val myWebView = WebView(activityContext)
setContentView(myWebView)

Java

WebView myWebView = new WebView(activityContext);
setContentView(myWebView);

לאחר מכן טוענים את הדף:

Kotlin

myWebView.loadUrl("http://www.example.com")

Java

myWebView.loadUrl("https://www.example.com");

לחלופין, אפשר לטעון את כתובת ה-URL ממחרוץ HTML:

Kotlin

// Create an unencoded HTML string, then convert the unencoded HTML string into
// bytes. Encode it with base64 and load the data.
val unencodedHtml =
     "<html><body>'%23' is the percent code for ‘#‘ </body></html>";
val encodedHtml = Base64.encodeToString(unencodedHtml.toByteArray(), Base64.NO_PADDING)
myWebView.loadData(encodedHtml, "text/html", "base64")

Java

// Create an unencoded HTML string, then convert the unencoded HTML string into
// bytes. Encode it with base64 and load the data.
String unencodedHtml =
     "<html><body>'%23' is the percent code for ‘#‘ </body></html>";
String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(),
        Base64.NO_PADDING);
myWebView.loadData(encodedHtml, "text/html", "base64");

לאפליקציה צריכה להיות גישה לאינטרנט. כדי לקבל גישה לאינטרנט, מבקשים את ההרשאה INTERNET בקובץ המניפסט, כמו בדוגמה הבאה:

<manifest ... >
    <uses-permission android:name="android.permission.INTERNET" />
    ...
</manifest>

אפשר להתאים אישית את WebView באמצעות אחת מהפעולות הבאות:

  • הפעלת התמיכה במסך מלא באמצעות WebChromeClient. הכיתה הזו נקראת גם כש-WebView צריך הרשאה לשנות את ממשק המשתמש של אפליקציית המארח, למשל ליצור או לסגור חלונות או לשלוח למשתמש תיבת דו-שיח של JavaScript. מידע נוסף על ניפוי באגים בהקשר הזה זמין במאמר ניפוי באגים באפליקציות אינטרנט.
  • טיפול באירועים שמשפיעים על עיבוד התוכן, כמו שגיאות בשליחת טפסים או ניווט באמצעות WebViewClient. אפשר גם להשתמש בתת-הסוג הזה כדי ליירט את טעינת כתובות ה-URL.
  • הפעלת JavaScript על ידי שינוי של WebSettings.
  • שימוש ב-JavaScript כדי לגשת לאובייקטים של Android framework שהחדרתם ל-WebView.

שימוש ב-JavaScript ב-WebView

אם דף האינטרנט שרוצים לטעון ב-WebView משתמש ב-JavaScript, צריך להפעיל את JavaScript ב-WebView. אחרי שמפעילים את JavaScript, אפשר ליצור ממשקים בין קוד האפליקציה לקוד JavaScript.

הפוך JavaScript לפעיל

JavaScript מושבת ב-WebView כברירת מחדל. אפשר להפעיל אותו דרך ה-WebSettings שמצורף ל-WebView. מאחזרים את WebSettings באמצעות getSettings(), ואז מפעילים את JavaScript באמצעות setJavaScriptEnabled().

דוגמה:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.settings.javaScriptEnabled = true

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);

WebSettings מספק גישה למגוון הגדרות נוספות שעשויות להיות שימושיות. לדוגמה, אם אתם מפתחים אפליקציית אינטרנט שמיועדת במיוחד ל-WebView באפליקציית Android שלכם, תוכלו להגדיר מחרוזת של סוכן משתמש בהתאמה אישית עם setUserAgentString(), ואז להריץ שאילתה על סוכן המשתמש בהתאמה אישית בדף האינטרנט כדי לוודא שהלקוח שמבקש את דף האינטרנט הוא אפליקציית Android שלכם.

קישור קוד JavaScript לקוד Android

כשאתם מפתחים אפליקציית אינטרנט שמיועדת במיוחד ל-WebView באפליקציית Android שלכם, אתם יכולים ליצור ממשקים בין קוד JavaScript לבין קוד Android בצד הלקוח. לדוגמה, קוד JavaScript יכול להפעיל שיטה בקוד Android כדי להציג Dialog, במקום להשתמש בפונקציה alert() של JavaScript.

כדי לקשר ממשק חדש בין קוד ה-JavaScript לקוד Android, צריך לבצע קריאה ל-addJavascriptInterface(), ולהעביר לו מופע של הכיתה שרוצים לקשר ל-JavaScript ושם של ממשק שאפשר לקרוא ל-JavaScript כדי לגשת לכיתה.

לדוגמה, אפשר לכלול את הכיתה הבאה באפליקציה ל-Android:

Kotlin

/** Instantiate the interface and set the context.  */
class WebAppInterface(private val mContext: Context) {

    /** Show a toast from the web page.  */
    @JavascriptInterface
    fun showToast(toast: String) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show()
    }
}

Java

public class WebAppInterface {
    Context mContext;

    /** Instantiate the interface and set the context. */
    WebAppInterface(Context c) {
        mContext = c;
    }

    /** Show a toast from the web page. */
    @JavascriptInterface
    public void showToast(String toast) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
    }
}

בדוגמה הזו, הכיתה WebAppInterface מאפשרת לדף האינטרנט ליצור הודעת Toast באמצעות השיטה showToast().

אפשר לקשר את הכיתה הזו ל-JavaScript שפועל ב-WebView באמצעות addJavascriptInterface(), כפי שמוצג בדוגמה הבאה:

Kotlin

val webView: WebView = findViewById(R.id.webview)
webView.addJavascriptInterface(WebAppInterface(this), "Android")

Java

WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");

הפעולה הזו יוצרת ממשק שנקרא Android ל-JavaScript שפועל ב-WebView. בשלב הזה, לאפליקציית האינטרנט יש גישה לכיתה WebAppInterface. לדוגמה, הנה קצת HTML ו-JavaScript שיוצרים הודעת טוסט באמצעות הממשק החדש כשהמשתמש מקייש על לחצן:

<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />

<script type="text/javascript">
    function showAndroidToast(toast) {
        Android.showToast(toast);
    }
</script>

אין צורך לאתחל את הממשק Android מ-JavaScript. ה-WebView הופך אותו לזמין באופן אוטומטי לדף האינטרנט שלכם. לכן, כשמשתמש מקשיב על הלחצן, הפונקציה showAndroidToast() משתמשת בממשק Android כדי לקרוא ל-method‏ WebAppInterface.showToast().

טיפול בניווט בדפים

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

כדי לפתוח קישורים שהמשתמש הקיש עליהם, צריך לספק WebViewClient ל-WebView באמצעות setWebViewClient(). כל הקישורים שהמשתמש מקייש עליהם נטענים ב-WebView. אם אתם רוצים יותר שליטה על המיקום שבו נטען הקישור שנלחץ עליו, תוכלו ליצור WebViewClient משלכם שיחליף את השיטה shouldOverrideUrlLoading(). בדוגמה הבאה MyWebViewClient הוא כיתה פנימית של Activity.

Kotlin

private class MyWebViewClient : WebViewClient() {

    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
        if (Uri.parse(url).host == "www.example.com") {
            // This is your website, so don't override. Let your WebView load
            // the page.
            return false
        }
        // Otherwise, the link isn't for a page on your site, so launch another
        // Activity that handles URLs.
        Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
            startActivity(this)
        }
        return true
    }
}

Java

private class MyWebViewClient extends WebViewClient {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        if ("www.example.com".equals(request.getUrl().getHost())) {
      // This is your website, so don't override. Let your WebView load the
      // page.
      return false;
    }
    // Otherwise, the link isn't for a page on your site, so launch another
    // Activity that handles URLs.
    Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl());
    startActivity(intent);
    return true;
  }
}

לאחר מכן יוצרים מכונה של WebViewClient החדש הזה עבור WebView:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.webViewClient = MyWebViewClient()

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new MyWebViewClient());

עכשיו, כשהמשתמש מקייש על קישור, המערכת קוראת לשיטה shouldOverrideUrlLoading(), שבה בודקים אם מארח כתובת ה-URL תואם לדומיין ספציפי, כפי שהוגדר בדוגמה הקודמת. אם יש התאמה, השיטה מחזירה את הערך false ולא מבטלת את טעינת כתובת ה-URL. הוא מאפשר ל-WebView לטעון את כתובת ה-URL כרגיל. אם מארח כתובת ה-URL לא תואם, נוצר Intent כדי להפעיל את Activity שמוגדרת כברירת מחדל לטיפול בכתובות URL, שמפנה לדפדפן האינטרנט שמוגדר כברירת מחדל של המשתמש.

טיפול בכתובות URL בהתאמה אישית

WebView מחיל הגבלות כשמבקשים משאבים ומפנים קישורים שמשתמשים בסכימת כתובת URL מותאמת אישית. לדוגמה, אם מטמיעים קריאות חזרה (callbacks) כמו shouldOverrideUrlLoading() או shouldInterceptRequest(), WebView מפעיל אותן רק עבור כתובות URL תקינות.

לדוגמה, יכול להיות ש-WebView לא יפעיל את השיטה shouldOverrideUrlLoading() עבור קישורים כמו:

<a href="showProfile">Show Profile</a>

כתובות URL לא תקינות, כמו זו שמוצגת בדוגמה הקודמת, מטופלות באופן לא עקבי ב-WebView, לכן מומלץ להשתמש במקום זאת בכתובת URL תקינה. אפשר להשתמש בסכמה מותאמת אישית או בכתובת URL מסוג HTTPS לדומיין שבשליטת הארגון.

במקום להשתמש במחרוזת פשוטה בקישור, כמו בדוגמה הקודמת, אפשר להשתמש בסכימה מותאמת אישית כמו זו:

<a href="example-app:showProfile">Show Profile</a>

לאחר מכן תוכלו לטפל בכתובת ה-URL הזו בשיטה shouldOverrideUrlLoading() באופן הבא:

Kotlin

// The URL scheme must be non-hierarchical, meaning no trailing slashes.
const val APP_SCHEME = "example-app:"

override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
    return if (url?.startsWith(APP_SCHEME) == true) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length), "UTF-8")
        respondToData(urlData)
        true
    } else {
        false
    }
}

Java

// The URL scheme must be non-hierarchical, meaning no trailing slashes.
private static final String APP_SCHEME = "example-app:";

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith(APP_SCHEME)) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length()), "UTF-8");
        respondToData(urlData);
        return true;
    }
    return false;
}

ה-API של shouldOverrideUrlLoading() מיועד בעיקר להפעלת כוונות לכתובות URL ספציפיות. כשמיישמים אותו, חשוב להחזיר את הערך false לכתובות URL ש-WebView מטפל בהן. עם זאת, אתם לא מוגבלים להפעלת כוונות. אפשר להחליף את כוונת ההפעלה בכל התנהגות בהתאמה אישית בדוגמאות הקוד שלמעלה.

כשה-WebView מבטל את טעינת כתובת ה-URL, הוא מצטבר באופן אוטומטי היסטוריה של דפי אינטרנט שנצפו. אפשר לנווט אחורה וקדימה בהיסטוריה באמצעות goBack() ו-goForward().

לדוגמה, כך אפשר להשתמש בלחצן 'הקודם' במכשיר כדי לנווט לאחור ב-Activity:

Kotlin

override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    // Check whether the key event is the Back button and if there's history.
    if (keyCode == KeyEvent.KEYCODE_BACK && myWebView.canGoBack()) {
        myWebView.goBack()
        return true
    }
    // If it isn't the Back button or there isn't web page history, bubble up to
    // the default system behavior. Probably exit the activity.
    return super.onKeyDown(keyCode, event)
}

Java

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    // Check whether the key event is the Back button and if there's history.
    if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
        myWebView.goBack();
        return true;
    }
    // If it isn't the Back button or there's no web page history, bubble up to
    // the default system behavior. Probably exit the activity.
    return super.onKeyDown(keyCode, event);
}

אם האפליקציה שלכם משתמשת ב-AndroidX AppCompat מגרסה 1.6.0 ואילך, תוכלו לפשט עוד יותר את קטע הקוד הקודם:

Kotlin

onBackPressedDispatcher.addCallback {
    // Check whether there's history.
    if (myWebView.canGoBack()) {
        myWebView.goBack()
    }
}

Java

onBackPressedDispatcher.addCallback {
    // Check whether there's history.
    if (myWebView.canGoBack()) {
        myWebView.goBack();
    }
}

השיטה canGoBack() מחזירה את הערך true אם יש היסטוריית דפי אינטרנט שהמשתמש יכול לבקר בהם. באופן דומה, אפשר להשתמש ב-canGoForward() כדי לבדוק אם יש היסטוריה של העברה קדימה. אם לא מבצעים את הבדיקה הזו, אחרי שהמשתמש מגיע לסוף ההיסטוריה, goBack() ו-goForward() לא עושים כלום.

טיפול בשינויים בהגדרות המכשיר

במהלך זמן הריצה, שינויים במצב הפעילות מתרחשים כשהגדרות המכשיר משתנות, למשל כשמשתמשים מסובבים את המכשיר או סוגרים עורך שיטת קלט (IME). השינויים האלה גורמים להשמדת הפעילות של אובייקט WebView וליצירת פעילות חדשה, שבהמשך יוצרים אובייקט WebView חדש שמעלה את כתובת ה-URL של האובייקט שהושמד. כדי לשנות את התנהגות ברירת המחדל של הפעילות, אפשר לשנות את האופן שבו היא מטפלת בשינויים ב-orientation במניפסט. למידע נוסף על טיפול בשינויים בהגדרות במהלך זמן הריצה, קראו את המאמר טיפול בשינויים בהגדרות.

ניהול החלונות

כברירת מחדל, המערכת מתעלמת מבקשות לפתיחת חלונות חדשים. זה נכון גם אם הם נפתחים על ידי JavaScript וגם אם הם נפתחים על ידי מאפיין היעד בקישור. אתם יכולים להתאים אישית את WebChromeClient כדי לקבוע את ההתנהגות שלכם כשאתם פותחים כמה חלונות.

כדי לשפר את האבטחה של האפליקציה, מומלץ למנוע פתיחה של חלונות קופצים וחלונות חדשים. הדרך הבטוחה ביותר להטמיע את ההתנהגות הזו היא להעביר את "true" אל setSupportMultipleWindows(), אבל לא לשנות את השיטה onCreateWindow(), ש-setSupportMultipleWindows() תלויה בה. הלוגיקה הזו מונעת את הטעינה של כל דף שמשתמש ב-target="_blank" בקישורים שלו.