כשמנהלים את מחזור החיים של אפליקציית Android, שמירה על מצב המשתמש במהלך השבתה של משאבים ברקע היא רכיב מרכזי בחוויית משתמש חלקה. באפליקציות שמשלבות תהליכי עבודה באינטרנט, WebView.saveState(Bundle)
מאפשרת לכם לבצע סריאליזציה של היסטוריית הניווט והמצב של WebView לתוך Bundle.
אפשר לשחזר את הנתונים האלה באמצעות WebView.restoreState(Bundle).
עם זאת, הטמעות רגילות עלולות להיתקל במגבלות על גודל העסקה במהלך הפעלות גלישה אינטנסיביות. בדף הזה מתוארים המגבלות האלה ברמת הארכיטקטורה, ומוצגות אסטרטגיות למניעת חריגות שקשורות לזיכרון, תוך שמירה על היסטוריית הניווט.
מגבלת העסקאות של 1MB ומחיקת הנתונים
ב-Android יש מגבלה של 1MB על נפח הנתונים הכולל שאפשר לאחסן ב-savedInstanceState. התקציב הזה של 1MB משותף לכל התהליך של האפליקציה. אם אפליקציה משלבת כמה מופעים של WebView, מצב הניווט וההיסטוריה המשותפים שלהם צריכים להתאים להקצאה המשותפת הזו. חריגה מהגבול הזה מפעילה TransactionTooLargeException, וכתוצאה מכך האפליקציה קורסת.
אחת מאסטרטגיות ההגנה הנפוצות, אך הבעייתיות, היא מעקב אחרי הגודל של חבילת המצב של WebView ומחיקה מלאה של ההיסטוריה של WebView אם היא חוצה סף בטיחות שרירותי (למשל 300KB). הפעולה הזו מונעת קריסה, אבל היא גורמת לרגרסיות חמורות בחוויית המשתמש:
אובדן של ניווט אחורה: מערכת Android מפסיקה לעיתים קרובות תהליכים של אפליקציות ברקע כדי לפנות זיכרון למשימות אחרות. אפשר להשתמש ב-
saveState(Bundle)בתוך ה-onSaveInstanceState()קריאה חוזרת במחזור חיים כדי לשמור את היסטוריית הניווט. אם מוחקים את ההיסטוריה הזו כדי להימנע מתקרת התשלומים ל-1MB, כל ערימת הניווט נמחקת. כשהמשתמש חוזר לאפליקציה, הכפתור 'הקודם' במערכת יוצא מיד מהרכיב או מהאפליקציה כי לא נשאר הקשר היסטורי לתמיכה בניווט אחורה, בלי קשר לשאלה אם התרחש הפעלה מחדש של התהליך.ביטול התוקף של BFCache: ניקוי ההיסטוריה מונע מהאפליקציה להשתמש ב-Back-Forward Cache (BFCache), ולכן אי אפשר יותר להציג באופן מיידי דפים שביקרו בהם בעבר.
זמן אחזור ארוך יותר: המשתמשים מאבדים את המצב הנוכחי שלהם ב-WebView, ולכן נדרשת ניווט מחדש ואתחול מחדש מלאים. התהליך הזה מגדיל באופן משמעותי את התקורה של הרשת ואת זמן האחזור של העסקאות.
אסטרטגיות להפחתת הסיכון בארכיטקטורה
כדי למנוע קריסות של TransactionTooLargeException בלי לפגוע בחוויית המשתמש באמצעות מחיקה מלאה של ההיסטוריה, צריך לשמור על איזון קפדני בין שמירת מצב לבין יעילות הזיכרון. האסטרטגיות הבאות לאופטימיזציה יעזרו לכם לנהל את תקציב העסקאות של 1MB בצורה בטוחה, תוך שמירה על היסטוריית הניווט החיונית ועל שלמות הסשן.
אכיפת מגבלות גודל על סריאליזציית מצבים
במקום למחוק לחלוטין את ערימת הניווט כשהיא גדלה מדי, דפוס יעיל יותר הוא לחתוך את הנתונים ההיסטוריים:
מדיניות השמטה ממוקדת: אפשר להשתמש ב-
WebViewCompat.saveState()כדי לבצע סריאליזציה של מצב תוך אכיפה של מגבלת בייטים ספציפית (לדוגמה,WebViewCompat.saveState(webView, outState, maxSizeBytes)). ה-API הזה משמיט באופן אוטומטי רשומות ניווט ישנות יותר ברצף עד שמטען הייעודי הכולל מתאים להקצאה שהגדרתם. חשוב לציין שהפעולה הזו רק מקצרת אתBundleבלי לשנות או לנקות את ההיסטוריה הפעילה שלWebView, כך שאפשר לחזור אחורה באופן מיידי.הסרת רשומות של ניווט קדימה: אם בממשק האפליקציה יש לחצן חזרה אבל אין לחצן ייעודי לניווט קדימה, אפשר להגדיר את הפרמטר
includeForwardStateשלsaveStateAPI לערךfalseכדי לבטל את כל הרשומות של ניווט קדימה. הפעולה הזו מקטינה באופן משמעותי את גודל המטען הייעודי (payload) בלי להשפיע על נתיבי הניווט הזמינים למשתמש.
ניהול חביון המשאבים באמצעות HTTP Cache Quota API
saveState מנהל את המגבלה של 1MB Bundle עבור היסטוריית ניווט זמנית, אבל HTTP Cache Quota API מספק שליטה ידנית במשאבי אינטרנט מתמשכים (מטמון דיסק) על בסיס כל פרופיל. כך נוצר הבדל ברור בין הקשר של הניווט לטווח קצר לבין נכסים שנשמרו במטמון לטווח ארוך.
בחירת מכסת שימוש מתאימה כרוכה בפשרה בין ביצועים לבין עלות:
- מכסות גבוהות יותר משפרות את הזמינות במצב אופליין ואת זמן הטעינה של המשאבים, כי יותר נכסים נשמרים בדיסק.
- מכסות נמוכות יותר ממזערות את טביעת הרגל של האפליקציה בדיסק ומונעות פינוי מטמון בהובלת מערכת ההפעלה של נתוני האפליקציה הקריטיים האחרים.
ההגדרות האלה נשמרות גם אחרי הפעלה מחדש של האפליקציה, וצריך להגדיר אותן מה-main thread.
ההטמעה הבאה מדגימה איך להגדיר מכסת מטמון בדיסק לפרופיל ברירת המחדל:
Kotlin
if (WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE) &&
WebViewFeature.isFeatureSupported(WebViewFeature.HTTP_CACHE)) {
val defaultProfile = ProfileStore.getInstance()
.getOrCreateProfile(Profile.DEFAULT_PROFILE_NAME)
val httpCache = defaultProfile.httpCache
// Set explicit cache size to 50MB (50 * 1024 * 1024 bytes)
httpCache.setQuotaBytes(50L * 1024 * 1024)
}
Java
if (WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE) &&
WebViewFeature.isFeatureSupported(WebViewFeature.HTTP_CACHE)) {
Profile defaultProfile = ProfileStore.getInstance()
.getOrCreateProfile(Profile.DEFAULT_PROFILE_NAME);
HttpCache httpCache = defaultProfile.getHttpCache();
// Set explicit cache size to 50MB (50 * 1024 * 1024 bytes)
httpCache.setQuotaBytes(50L * 1024 * 1024);
}
מידע נוסף על אסטרטגיות לקביעת גודל המכסה, על ניהול מחזור החיים ועל גבולות הפרופיל זמין במאמר ניהול מכסת מטמון HTTP ב-WebView.
שיקולי ביצועים מרכזיים
בנקודות הבאות מודגשות המגבלות הטכניות וההתנהגויות הפנימיות של הנתונים שקובעות את התנהגות המצב של WebView:
בלובים אטומים של
PageState: כ-70% מהנתונים שמאוחסנים על ידיsaveStateמורכבים מבלובים פנימיים שלPageStateממנוע העיבוד. הנתונים האלה מתעדים מצבים מפורטים של סשנים, כולל קלט של טפסים ומיקומי גלילה של iframe. אל תנסו לנתח או להסיר ידנית פלחים בודדים מה-blobs האלה, כי פעולה כזו יוצרת סיכוני אבטחה חמורים ופוגעת בשלמות של שחזור הסשן.ניהול היסטוריה ברמת פירוט גבוהה: ממשק ה-API הרגיל של
WebBackForwardListלא תומך בהסרה שרירותית של רכיבים היסטוריים נפרדים. כדי לנהל את המצב בצורה קפדנית, צריך להטמיע אסטרטגיות חיתוך באמצעות הפרמטריםmaxSizeBytesו-includeForwardStateבתוךWebViewCompat.saveState()כדי להבטיח אבטחה ארכיטקטונית.