פיתוח אפליקציות אינטרנט ב-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")
}

מגניב

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>

המערכת של WebView מטפלת באופן לא עקבי בכתובות URL לא תקינות, כמו זו שמוצגת בדוגמה הקודמת, לכן מומלץ להשתמש במקום זאת בכתובת 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 מטפל בהן. עם זאת, אתם לא מוגבלים להשקת כוונות. אפשר להחליף את ההפעלה של Intentים בכל התנהגות מותאמת אישית בדוגמאות הקוד הקודמות.

כשה-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(), אבל לא לבטל את ה-method onCreateWindow(), שהפעולה setSupportMultipleWindows() תלויה בה. הלוגיקה הזו מונעת טעינה של כל דף שהקישורים שלו מכילים את target="_blank".