פיתוח אפליקציות אינטרנט ב-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 באופן הבא:

KotlinGroovy
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(), כפי שמוצג בדוגמה הבאה:

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

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

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

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

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

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

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

KotlinJava
// 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")
// 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().

דוגמה:

KotlinJava
val myWebView: WebView = findViewById(R.id.webview)
myWebView.settings.javaScriptEnabled = true
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 יכול לקרוא ל-method ב- את הקוד של Android כדי להציג Dialog, במקום להשתמש בפונקציה alert() של JavaScript.

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

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

KotlinJava
/** 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()
    }
}
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(), כמו בדוגמה הבאה:

KotlinJava
val webView: WebView = findViewById(R.id.webview)
webView.addJavascriptInterface(WebAppInterface(this), "Android")
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.

KotlinJava
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
    }
}
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:

KotlinJava
val myWebView: WebView = findViewById(R.id.webview)
myWebView.webViewClient = MyWebViewClient()
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(), למשל הזה:

KotlinJava
// 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
    }
}
// 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 מטפל בהן. עם זאת, אתם לא מוגבלים להפעלת כוונות. אפשר החלפת ההפעלה של Intents בכל התנהגות מותאמת אישית שמופיעה בקוד הקודם דוגמאות.

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

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

KotlinJava
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)
}
@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 ואילך, תוכלו לפשט עוד יותר את קטע הקוד הקודם:

KotlinJava
onBackPressedDispatcher.addCallback {
    // Check whether there's history.
    if (myWebView.canGoBack()) {
        myWebView.goBack()
    }
}
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" בקישורים שלו.