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

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

<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 מטפל בהן. אבל אתם לא מוגבלים להפעלת כוונות. אתם יכולים להחליף את כוונות ההפעלה בכל התנהגות מותאמת אישית בדוגמאות הקוד שלמעלה.

כש-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" בקישורים שלו.