ב-Google Play נדרש שהקובץ ה-APK הדחוס שהמשתמשים מורידים יהיה בגודל של עד 100MB. ברוב האפליקציות, זה מספיק מקום לכל הקוד והנכסים של האפליקציה. עם זאת, אפליקציות מסוימות זקוקות ליותר מקום כדי לאחסן גרפיקה באיכות גבוהה, קובצי מדיה או נכסים גדולים אחרים. בעבר, אם גודל ההורדה הדחוס של האפליקציה היה גדול מ-100MB, הייתם צריכים לארח ולהתקין בעצמכם את המשאבים הנוספים כשהמשתמש פותח את האפליקציה. אירוח והצגה של הקבצים הנוספים יכולים להיות יקרים, ולרוב חוויית המשתמש לא הייתה אידיאלית. כדי להקל עליכם לבצע את התהליך ולשפר את חוויית השימוש של המשתמשים, ב-Google Play אפשר לצרף שני קובצי הרחבה גדולים שמהווים תוספת לקובץ ה-APK.
Google Play מארח את קובצי ההרחבה של האפליקציה ומעביר אותם למכשיר ללא עלות. קובצי ההרחבה נשמרים במיקום האחסון המשותף של המכשיר (כרטיס ה-SD או מחיצה שניתן לחבר ל-USB, שנקראת גם 'אחסון חיצוני'), שאליו לאפליקציה יש גישה. ברוב המכשירים, Google Play מורידים את קובצי ההרחבה באותו זמן שהם מורידים את קובץ ה-APK, כך שלאפליקציה יש את כל מה שהיא צריכה כשהמשתמש פותח אותה בפעם הראשונה. עם זאת, במקרים מסוימים האפליקציה צריכה להוריד את הקבצים מ-Google Play כשהיא מופעלת.
אם אתם רוצים להימנע משימוש בקובצי הרחבה וגודל האפליקציה שלכם להורדה דחוסה גדול מ-100MB, עליכם להעלות את האפליקציה באמצעות חבילות אפליקציות ל-Android, שמאפשרות גודל הורדה דחוסה של עד 200MB. בנוסף, מכיוון שהשימוש בחבילות אפליקציות מעביר את היצירה והחתימה של חבילות ה-APK ל-Google Play, המשתמשים מורידים חבילות APK שעברו אופטימיזציה עם רק הקוד והמשאבים הנחוצים להפעלת האפליקציה. אתם לא צריכים ליצור, לחתום ולנהל כמה חבילות APK או קובצי הרחבה, והמשתמשים מקבלים הורדות קטנות יותר שעברו אופטימיזציה.
סקירה כללית
בכל פעם שאתם מעלים קובץ APK באמצעות Google Play Console, יש לכם אפשרות להוסיף קובץ הרחבה אחד או שניים לקובץ ה-APK. כל קובץ יכול להיות בגודל של עד 2GB ובכל פורמט שתרצו, אבל מומלץ להשתמש בקובץ דחוס כדי לחסוך ברוחב הפס במהלך ההורדה. מבחינה מושגית, לכל קובץ הרחבה יש תפקיד שונה:
- קובץ ההרחבה main הוא קובץ ההרחבה הראשי של המשאבים הנוספים הנדרשים לאפליקציה.
- קובץ ההרחבה patch הוא אופציונלי ומיועד לעדכונים קטנים בקובץ ההרחבה הראשי.
אתם יכולים להשתמש בשני קובצי ההרחבה בכל דרך שתרצו, אבל מומלץ להשתמש בקובץ ההרחבה הראשי כדי להעביר את הנכסים הראשיים, וצריך לעדכן אותו לעיתים רחוקות, אם בכלל. קובץ ההרחבה של התיקון צריך להיות קטן יותר ולשמש כ'נושא התיקון', וצריך לעדכן אותו בכל גרסה ראשית או לפי הצורך.
עם זאת, גם אם עדכון האפליקציה מחייב רק קובץ הרחבה חדש של תיקון, עדיין צריך להעלות קובץ APK חדש עם versionCode
מעודכן במניפסט. (ב-Play Console אי אפשר להעלות קובץ הרחבה ל-APK קיים).
הערה: קובץ הרחבות התיקון זהה מבחינה סמנטית לקובץ ההרחבה הראשי – אפשר להשתמש בכל קובץ בכל דרך שרוצים.
הפורמט של שם הקובץ
כל קובץ הרחבה שאתם מעלים יכול להיות בכל פורמט שתרצו (ZIP, PDF, MP4 וכו'). אפשר גם להשתמש בכלי JOBB כדי להכיל ולהצפין קבוצה של קובצי משאבים ותיקונים הבאים לקבוצה הזו. ללא קשר לסוג הקובץ, Google Play מתייחסת אליהם כאובייקטי blob בינאריים אטומים ומשנה את השם של הקבצים לפי הסכימה הבאה:
[main|patch].<expansion-version>.<package-name>.obb
הסכימה הזו מורכבת משלושה רכיבים:
main
אוpatch
- הערך הזה מציין אם הקובץ הוא קובץ הרחבות ראשי או קובץ הרחבות תיקון. לכל קובץ APK יכולים להיות רק קובץ ראשי אחד וקובץ תיקון אחד.
<expansion-version>
- זהו מספר שלם שתואם לקוד הגרסה של חבילת ה-APK שאליה הרחבה משויכת בפעם הראשונה (הוא תואם לערך של
android:versionCode
באפליקציה).הדגש על 'הראשון' נובע מכך שמערכת Play Console מאפשרת לעשות שימוש חוזר בקובץ הרחבה שהעליתם עם קובץ APK חדש, אבל שם קובץ ההרחבה לא משתנה – הוא שומר על הגרסה שהוחלו עליו כשהעליתם אותו בפעם הראשונה.
<package-name>
- שם החבילה של האפליקציה בסגנון Java.
לדוגמה, נניח שגרסת ה-APK היא 314159 ושם החבילה הוא com.example.app. אם מעלים קובץ הרחבה ראשי, שם הקובץ ישתנה ל-:
main.314159.com.example.app.obb
מיקום אחסון
כשקבצי ההרחבה מורידים מ-Google Play למכשיר, הם נשמרים במיקום האחסון המשותף של המערכת. כדי לוודא שההתנהגות תקינה, אסור למחוק, להעביר או לשנות את השם של קובצי ההרחבה. אם האפליקציה צריכה לבצע את ההורדה מ-Google Play עצמו, צריך לשמור את הקבצים באותו מיקום בדיוק.
השיטה getObbDir()
מחזירה את המיקום הספציפי של קובצי ההרחבה בפורמט הבא:
<shared-storage>/Android/obb/<package-name>/
<shared-storage>
הוא הנתיב למרחב האחסון המשותף, שזמין דרךgetExternalStorageDirectory()
.<package-name>
הוא שם החבילה של האפליקציה בסגנון Java, שזמין מ-getPackageName()
.
לכל אפליקציה יש לעולם לא יותר משני קובצי הרחבה בספרייה הזו.
אחד הוא קובץ ההרחבה הראשי והשני הוא קובץ הרחבת התיקון (אם יש צורך). הגרסאות הקודמות נמחקות כשמעדכנים את האפליקציה באמצעות קובצי הרחבה חדשים. מאז Android 4.4 (רמה 19 של API), אפליקציות יכולות לקרוא קובצי הרחבה מסוג OBB
בלי הרשאת אחסון חיצוני. עם זאת, בהטמעות מסוימות של Android 6.0 (רמת API 23) ואילך עדיין נדרשת הרשאה, לכן תצטרכו להצהיר על ההרשאה READ_EXTERNAL_STORAGE
במניפסט של האפליקציה ולבקש הרשאה בסביבת זמן הריצה באופן הבא:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
ב-Android מגרסה 6 ואילך, צריך לבקש הרשאת אחסון חיצוני בזמן הריצה. עם זאת, בהטמעות מסוימות של Android לא נדרשת הרשאה לקריאת קבצי OBB. בקטע הקוד הבא מוסבר איך לבדוק אם יש גישה לקריאה לפני שמבקשים הרשאה לאחסון חיצוני:
Kotlin
val obb = File(obb_filename) var open_failed = false try { BufferedReader(FileReader(obb)).also { br -> ReadObbFile(br) } } catch (e: IOException) { open_failed = true } if (open_failed) { // request READ_EXTERNAL_STORAGE permission before reading OBB file ReadObbFileWithPermission() }
Java
File obb = new File(obb_filename); boolean open_failed = false; try { BufferedReader br = new BufferedReader(new FileReader(obb)); open_failed = false; ReadObbFile(br); } catch (IOException e) { open_failed = true; } if (open_failed) { // request READ_EXTERNAL_STORAGE permission before reading OBB file ReadObbFileWithPermission(); }
אם אתם חייבים לפתוח את התוכן של קובצי ההרחבה, אל תמחקו את קובצי ההרחבה OBB
לאחר מכן, ואל תשמרו את הנתונים שנפרקו באותה ספרייה. צריך לשמור את הקבצים הלא ארוזים בתיקייה שצוינה ב-getExternalFilesDir()
. עם זאת, אם אפשר, מומלץ להשתמש בפורמט קובץ עם תוספים שמאפשר לקרוא ישירות מהקובץ, במקום לפתוח את הנתונים. לדוגמה, סיפקנו פרויקט ספרייה שנקרא APK Expansion Zip Library, שקורא את הנתונים ישירות מקובץ ה-ZIP.
חשוב לדעת: בניגוד לקובצי APK, כל הקבצים שנשמרים באחסון המשותף ניתנים לקריאה על ידי המשתמש ועל ידי אפליקציות אחרות.
טיפ: אם אתם אורזים קובצי מדיה בקובץ ZIP, אתם יכולים להשתמש בקריאות להפעלת מדיה בקבצים עם אמצעי בקרה על היסט ואורך (כמו MediaPlayer.setDataSource()
ו-SoundPool.load()
) בלי צורך לפתוח את קובץ ה-ZIP. כדי שהפעולה הזו תפעל, אסור לבצע דחיסה נוספת של קובצי המדיה בזמן יצירת חבילות ה-ZIP. לדוגמה, כשמשתמשים בכלי zip
, צריך להשתמש באפשרות -n
כדי לציין את הסיומת של הקבצים שלא צריך לדחוס:
zip -n .mp4;.ogg main_expansion media_files
תהליך ההורדה
ברוב המקרים, Google Play מורידים ושומרים את קובצי ההרחבה באותו זמן שהם מורידים את קובץ ה-APK למכשיר. עם זאת, במקרים מסוימים Google Play לא יכול להוריד את קובצי ההרחבה, או שהמשתמש מחק קובצי הרחבה שהורדתם בעבר. כדי לטפל במצבים האלה, האפליקציה צריכה להיות מסוגלת להוריד את הקבצים בעצמה כשהפעילות הראשית מתחילה, באמצעות כתובת URL ש-Google Play מספקת.
תהליך ההורדה באופן כללי נראה כך:
- המשתמש בוחר להתקין את האפליקציה מ-Google Play.
- אם Google Play יכול להוריד את קובצי ההרחבה (וזה המצב ברוב המכשירים), הוא מוריד אותם יחד עם קובץ ה-APK.
אם Google Play לא מצליח להוריד את קובצי ההרחבה, הוא מוריד רק את קובץ ה-APK.
- כשהמשתמש מפעיל את האפליקציה, היא צריכה לבדוק אם קובצי ההרחבה כבר שמורים במכשיר.
- אם כן, האפליקציה מוכנה.
- אם לא, האפליקציה צריכה להוריד את קובצי ההרחבה מ-Google Play באמצעות HTTP. האפליקציה צריכה לשלוח בקשה ללקוח Google Play באמצעות השירות רישוי אפליקציות של Google Play, שמגיב עם השם, גודל הקובץ וכתובת ה-URL של כל קובץ הרחבה. לאחר מכן, תוכלו להוריד את הקבצים ולשמור אותם במיקום האחסון המתאים.
זהירות: חשוב לכלול את הקוד הנדרש להורדת קובצי ההרחבה מ-Google Play, למקרה שהקבצים עדיין לא נמצאים במכשיר כשהאפליקציה מופעלת. כפי שמתואר בקטע הבא בנושא הורדת קובצי ההרחבה, אנחנו מציעים ספרייה שמפשטת מאוד את התהליך הזה ומבצעת את ההורדה משירות עם כמות מינימלית של קוד.
רשימת משימות לפיתוח
לפניכם סיכום של המשימות שצריך לבצע כדי להשתמש בקובצי הרחבה באפליקציה:
- קודם צריך לקבוע אם גודל ההורדה הדחוסה של האפליקציה צריך להיות יותר מ-100MB. נפח האחסון חשוב מאוד, ולכן כדאי להקטין ככל האפשר את גודל ההורדה הכולל. אם האפליקציה שלכם תופסת יותר מ-100MB כדי לספק כמה גרסאות של הנכסים הגרפיים לכמה צפיפויות מסך, כדאי לשקול לפרסם במקום זאת כמה קובצי APK, שבהם כל קובץ APK מכיל רק את הנכסים הנדרשים למסכים שהוא מטרגט. כדי לקבל את התוצאות הטובות ביותר כשמפרסמים ב-Google Play, מומלץ להעלות Android App Bundle שכולל את כל המשאבים והקוד המהדר של האפליקציה, אבל מעביר את היצירה והחתימה של קובץ ה-APK ל-Google Play.
- קובעים אילו משאבי אפליקציה צריך להפריד מקובץ ה-APK ומארזים אותם בקובץ שישמש כקובץ ההרחבה הראשי.
בדרך כלל, צריך להשתמש בקובץ ההרחבה השני של התיקון רק כשמבצעים עדכונים בקובץ ההרחבה הראשי. עם זאת, אם המשאבים חורגים מהמגבלה של 2GB בקובץ ההרחבה הראשי, אפשר להשתמש בקובץ התיקון לשאר הנכסים.
- כדאי לפתח את האפליקציה כך שתשתמש במשאבים מקובצי ההרחבה במיקום האחסון המשותף של המכשיר.
חשוב לזכור שאסור למחוק, להעביר או לשנות את השם של קובצי ההרחבה.
אם האפליקציה לא דורשת פורמט ספציפי, מומלץ ליצור קובצי ZIP לקובצי ההרחבה ולאחר מכן לקרוא אותם באמצעות ספריית ה-ZIP של הרחבות ה-APK.
- מוסיפים לפעילות הראשית של האפליקציה לוגיקה שבודקת אם קובצי ההרחבה נמצאים במכשיר בזמן ההפעלה. אם הקבצים לא נמצאים במכשיר, משתמשים בשירות רישוי האפליקציות של Google Play כדי לבקש כתובות URL של קובצי ההרחבה, ואז מורידים אותם ושומרים אותם.
כדי לצמצם באופן משמעותי את כמות הקוד שצריך לכתוב ולהבטיח חוויית משתמש טובה במהלך ההורדה, מומלץ להשתמש ב-Downloader Library כדי להטמיע את התנהגות ההורדה.
אם אתם מפתחים שירות הורדה משלכם במקום להשתמש בספרייה, חשוב לזכור שאסור לשנות את השם של קובצי ההרחבה וחייבים לשמור אותם במיקום האחסון המתאים.
אחרי שתסיימו לפתח את האפליקציה, תוכלו לפעול לפי המדריך לבדיקת קובצי ההרחבה.
כללים והגבלות
הוספת קובצי הרחבה של APK היא תכונה שזמינה כשאתם מעלים את האפליקציה באמצעות Play Console. כשאתם מעלים את האפליקציה בפעם הראשונה או מעדכנים אפליקציה שמשתמשת בקובצי הרחבה, חשוב לדעת על הכללים והמגבלות הבאים:
- כל קובץ הרחבה יכול להיות בגודל של עד 2GB.
- כדי להוריד את קובצי ההרחבה מ-Google Play, המשתמש צריך לרכוש את האפליקציה מ-Google Play. אם האפליקציה הותקנה באמצעים אחרים, Google Play לא תספק את כתובות ה-URL של קובצי ההרחבה.
- כשמבצעים את ההורדה מתוך האפליקציה, כתובת ה-URL ש-Google Play מספקת לכל קובץ היא ייחודית לכל הורדה, ותוקף כל אחת מהן פג זמן קצר אחרי שהיא ניתנת לאפליקציה.
- אם מעדכנים את האפליקציה באמצעות קובץ APK חדש או מעלים כמה קובצי APK לאותה אפליקציה, אפשר לבחור קובצי הרחבה שהעליתם לקובץ APK קודם. שם קובץ ההרחבה לא משתנה – הוא שומר על הגרסה שהתקבלה בקובץ ה-APK שאליו הקובץ היה משויך במקור.
- אם אתם משתמשים בקובצי הרחבה בשילוב עם מספר חבילות APK כדי לספק קובצי הרחבה שונים למכשירים שונים, עדיין תצטרכו להעלות חבילות APK נפרדות לכל מכשיר כדי לספק ערך
versionCode
ייחודי ולהצהיר על מסננים שונים לכל חבילת APK. - לא ניתן לפרסם עדכון לאפליקציה על ידי שינוי קובצי ההרחבה בלבד – צריך להעלות קובץ APK חדש כדי לעדכן את האפליקציה. אם השינויים נוגעים רק לנכסים בקובצי ההרחבה, אפשר לעדכן את קובץ ה-APK פשוט על ידי שינוי הקובץ
versionCode
(ואולי גם את הקובץversionName
). - אין לשמור נתונים אחרים בספרייה
obb/
. אם צריך לפתוח את האריזה של נתונים מסוימים, שומרים אותם במיקום שצוין ב-getExternalFilesDir()
. - אין למחוק או לשנות את השם של קובץ ההרחבה
.obb
(אלא אם מבצעים עדכון). הפעולה הזו תגרום ל-Google Play (או לאפליקציה עצמה) להוריד שוב ושוב את קובץ ההרחבה. - כשמעדכנים קובץ הרחבה באופן ידני, צריך למחוק את קובץ ההרחבה הקודם.
הורדת קובצי ההרחבה
ברוב המקרים, Google Play מורידים את קובצי ההרחבה ושומרים אותם במכשיר בזמן התקנת קובץ ה-APK או עדכון שלו. כך קובצי ההרחבה יהיו זמינים כשהאפליקציה תושק בפעם הראשונה. עם זאת, במקרים מסוימים האפליקציה צריכה להוריד את קובצי ההרחבה בעצמה, על ידי שליחת בקשה לכתובת URL שסופקה בתגובה מהשירות לניהול רישיונות לאפליקציות של Google Play.
הלוגיקה הבסיסית שצריך להשתמש בה כדי להוריד את קובצי ההרחבה היא:
- כשהאפליקציה תתחיל לפעול, חפשו את קובצי ההרחבה במיקום האחסון המשותף (בספרייה
Android/obb/<package-name>/
).- אם קובצי ההרחבה נמצאים שם, סיימתם את ההגדרה והאפליקציה יכולה להמשיך.
- אם קובצי ההרחבה לא מופיעים:
- שולחים בקשה באמצעות רישיון האפליקציה של Google Play כדי לקבל את השמות, הגודלים וכתובות ה-URL של קובצי ההרחבה של האפליקציה.
- משתמשים בכתובות ה-URL שסופקו על ידי Google Play כדי להוריד את קובצי ההרחבה ולשמור אותם. חובה לשמור את הקבצים במיקום האחסון המשותף (
Android/obb/<package-name>/
) ולהשתמש בשם הקובץ המדויק שצוין בתגובה מ-Google Play.הערה: כתובת ה-URL ש-Google Play מספקת לקובצי האקסטנشن היא ייחודית לכל הורדה, ותוקף כל אחת מהן פג זמן קצר אחרי שהיא ניתנת לאפליקציה.
אם האפליקציה שלכם היא בחינם (לא אפליקציה בתשלום), סביר להניח שלא השתמשתם בשירות רישוי אפליקציות. המטרה העיקרית של התכונה הזו היא לאפשר לכם לאכוף את מדיניות הרישוי של האפליקציה שלכם ולוודא שלמשתמשים יש את הזכות להשתמש באפליקציה (הם שילמו עליה ב-Google Play כדי לעשות זאת). כדי להקל על הפונקציונליות של קובצי ההרחבה, שירות הרישוי שופר כדי לספק תגובה לאפליקציה שכוללת את כתובת ה-URL של קובצי ההרחבה של האפליקציה שמתארחים ב-Google Play. לכן, גם אם האפליקציה שלכם זמינה בחינם למשתמשים, עליכם לכלול את ספריית אימות הרישיון (LVL) כדי להשתמש בקובצי הרחבה של APK. כמובן, אם האפליקציה שלכם בחינם, אין צורך לאכוף אימות רישיון – פשוט צריך לבקש מהספרייה להחזיר את כתובת ה-URL של קובצי ההרחבה.
הערה: בין אם האפליקציה שלכם בחינם ובין אם לא, Google Play מחזירה את כתובות ה-URL של קובצי ההרחבה רק אם המשתמש רכש את האפליקציה מ-Google Play.
בנוסף ל-LVL, צריך קבוצת קוד שתוריד את קובצי ההרחבה דרך חיבור HTTP ותשמור אותם במיקום המתאים באחסון המשותף של המכשיר. כשאתם משלבים את התהליך הזה באפליקציה, יש כמה בעיות שצריך להביא בחשבון:
- יכול להיות שאין במכשיר מספיק מקום לקובצי ההרחבה, לכן כדאי לבדוק זאת לפני שמתחילים את ההורדה ולהזהיר את המשתמש אם אין מספיק מקום.
- הורדות קבצים צריכות להתבצע בשירות ברקע כדי למנוע חסימה של האינטראקציה של המשתמש ולאפשר למשתמש לצאת מהאפליקציה בזמן שההורדה מסתיימת.
- יכולות להתרחש מגוון שגיאות במהלך הבקשה וההורדה, וצריך לטפל בהן בצורה יעילה.
- יכול להיות שהחיבור לרשת ישתנה במהלך ההורדה, לכן צריך לטפל בשינויים כאלה, ואם ההורדה מופסקת, צריך לחדש אותה כשהדבר אפשרי.
- בזמן שההורדה מתבצעת ברקע, כדאי להציג התראה שמציינת את התקדמות ההורדה, מעדכנת את המשתמש כשהיא מסתיימת ומחזירה אותו לאפליקציה כשהוא בוחר באפשרות הזו.
כדי לפשט את העבודה הזו, פיתחנו את Downloader Library, שמבקשת את כתובות ה-URL של קובצי ההרחבה דרך שירות הרישוי, מורידת את קובצי ההרחבה, מבצעת את כל המשימות שמפורטות למעלה ואפילו מאפשרת להשהות את ההורדה ולהמשיך אותה תוך כדי פעילות. אם תוסיפו לאפליקציה את ספריית Downloader Library וכמה קוד הוק, כמעט כל העבודה להורדת קובצי ההרחבה כבר תהיה מקודדת. לכן, כדי לספק את חוויית המשתמש הטובה ביותר במאמץ מינימלי מצידכם, מומלץ להשתמש בספריית ההורדה כדי להוריד את קובצי ההרחבה. בקטעים הבאים מוסבר איך לשלב את הספרייה באפליקציה.
אם אתם מעדיפים לפתח פתרון משלכם להורדת קובצי ההרחבה באמצעות כתובות ה-URL של Google Play, עליכם לפעול לפי המסמכים בנושא רישוי אפליקציות כדי לבצע בקשת רישיון, ולאחר מכן לאחזר את השמות, הגדלים וכתובות ה-URL של קובצי ההרחבה מהנתונים הנוספים בתגובה. צריך להשתמש בכיתה APKExpansionPolicy
(שכלולה בספריית האימות של הרישיון) כמדיניות הרישוי, שמתעדת את השמות, הגדלים וכתובות ה-URL של קובצי ההרחבה משירות הרישוי.
מידע על ספריית Downloader
כדי להשתמש בקובצי הרחבה של APK באפליקציה שלכם ולספק את חוויית המשתמש הטובה ביותר במאמץ מינימלי מצידכם, מומלץ להשתמש בספריית Downloader שכלולה בחבילה של ספריית הרחבת ה-APK של Google Play. הספרייה הזו מאפשרת להוריד את קובצי ההרחבה בשירות ברקע, להציג התראה למשתמש עם סטטוס ההורדה, לטפל באיבוד החיבור לרשת, להמשיך את ההורדה כשהדבר אפשרי ועוד.
כדי להטמיע הורדות של קובצי הרחבה באמצעות ספריית ההורדה, כל מה שצריך לעשות הוא:
- אפשר להרחיב את המחלקה המשנית המיוחדת
Service
ואת המחלקה המשנית המיוחדתBroadcastReceiver
, שכל אחת מהן מחייבת רק כמה שורות קוד. - מוסיפים לפעילות הראשית לוגיקה שבודקת אם קובצי ההרחבה כבר הורדתם. אם לא, היא מפעילה את תהליך ההורדה ומציגה ממשק משתמש עם סטטוס ההתקדמות.
- מטמיעים ממשק קריאה חוזרת עם כמה שיטות בפעילות הראשית, שמקבל עדכונים על התקדמות ההורדה.
בקטעים הבאים מוסבר איך להגדיר את האפליקציה באמצעות ספריית Downloader.
הכנה לשימוש בספריית Downloader
כדי להשתמש בספריית Downloader, צריך להוריד שתי חבילות מ-SDK Manager ולהוסיף את הספריות המתאימות לאפליקציה.
קודם כל, פותחים את Android SDK Manager (Tools > SDK Manager). בקטע Appearance & Behavior > System Settings > Android SDK, בוחרים בכרטיסייה SDK Tools כדי לבחור ולהוריד את הפריטים הבאים:
- חבילת Google Play Licensing Library
- חבילת ספריית ההרחבה של APK ב-Google Play
יוצרים מודול ספרייה חדש לספריית אימות הרישיונות ולספריית ההורדה. לכל ספרייה:
- בוחרים באפשרות File > New > New Module.
- בחלון Create New Module בוחרים באפשרות Android Library ואז באפשרות Next.
- מציינים שם לאפליקציה או לספרייה, כמו 'ספריית הרישיונות של Google Play' ו'ספריית ההורדה של Google Play', בוחרים את רמת ה-SDK המינימלית ואז לוחצים על סיום.
- בוחרים באפשרות File > Project Structure.
- בוחרים בכרטיסייה Properties ובשדה Library Repository, מזינים את הספרייה מהספרייה
<sdk>/extras/google/
(play_licensing/
לספריית אימות הרישיון אוplay_apk_expansion/downloader_library/
לספריית ההורדה). - לוחצים על OK כדי ליצור את המודול החדש.
הערה: ספריית Downloader תלויה בספריית האימות של הרישיון. חשוב להוסיף את ספריית אימות הרישיון למאפייני הפרויקט של ספריית ההורדה.
לחלופין, אפשר לעדכן את הפרויקט כך שיכלול את הספריות באמצעות שורת הפקודה:
- עוברים לספרייה
<sdk>/tools/
. - מריצים את הפקודה
android update project
עם האפשרות--library
כדי להוסיף את LVL ואת ספריית Downloader לפרויקט. לדוגמה:android update project --path ~/Android/MyApp \ --library ~/android_sdk/extras/google/market_licensing \ --library ~/android_sdk/extras/google/market_apk_expansion/downloader_library
הוספת ספריית אימות הרישיונות וספריית ההורדה לאפליקציה מאפשרת לשלב במהירות את היכולת להוריד קבצים להרחבה מ-Google Play. הפורמט שבוחרים לקובצי ההרחבה והאופן שבו קוראים אותם מהאחסון המשותף הם הטמעה נפרדת שצריך להביא בחשבון בהתאם לצרכים של האפליקציה.
טיפ: חבילת Apk Expansion כוללת אפליקציה לדוגמה שמראה איך להשתמש בספריית Downloader באפליקציה. הדוגמה משתמשת בספרייה שלישית שזמינה בחבילת Apk Expansion שנקראת APK Expansion Zip Library. אם אתם מתכננים להשתמש בקובצי ZIP לקובצי ההרחבה, מומלץ להוסיף לאפליקציה גם את ספריית ה-ZIP של קובצי ההרחבה ל-APK. מידע נוסף זמין בקטע שימוש בספריית ה-ZIP של קובצי ההרחבה ל-APK שבהמשך.
הצהרת הרשאות משתמשים
כדי להוריד את קובצי ההרחבה, ספריית Downloader Library זקוקה לכמה הרשאות שצריך להצהיר עליהן בקובץ המניפסט של האפליקציה. הם:
<manifest ...> <!-- Required to access Google Play Licensing --> <uses-permission android:name="com.android.vending.CHECK_LICENSE" /> <!-- Required to download files from Google Play --> <uses-permission android:name="android.permission.INTERNET" /> <!-- Required to keep CPU alive while downloading files (NOT to keep screen awake) --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- Required to poll the state of the network connection and respond to changes --> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- Required to check whether Wi-Fi is enabled --> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <!-- Required to read and write the expansion files on shared storage --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
הערה: כברירת מחדל, ספריית Downloader Library דורשת רמת API 4, אבל ספריית APK Expansion Zip Library דורשת רמת API 5.
הטמעת שירות ההורדה
כדי לבצע הורדות ברקע, ספריית Downloader מספקת תת-סוג משלה של Service
שנקרא DownloaderService
, שצריך להרחיב. בנוסף להורדת קובצי ההרחבה בשבילכם, ה-DownloaderService
גם:
- הרשמה של
BroadcastReceiver
שמקשיב לשינויים בחיבור הרשת של המכשיר (שידור ה-CONNECTIVITY_ACTION
) כדי להשהות את ההורדה במקרה הצורך (למשל, בגלל אובדן החיבור) ולהמשיך את ההורדה כשהדבר אפשרי (כשהחיבור מתחדש). - תזמון התראה
RTC_WAKEUP
כדי לנסות שוב את ההורדה במקרים שבהם השירות מושבת. - יצירת
Notification
בהתאמה אישית שמוצגת בו התקדמות ההורדה וגם שגיאות או שינויים בסטטוס. - מאפשר לאפליקציה להשהות את ההורדה באופן ידני ולהמשיך אותה.
- לפני הורדת קובצי ההרחבה, המערכת מוודאת שהאחסון המשותף מחובר וזמין, שהקבצים לא קיימים כבר ושיש מספיק מקום. לאחר מכן, המערכת תודיע למשתמש אם אחת מההנחות האלה לא נכונה.
כל מה שצריך לעשות הוא ליצור בכיתה באפליקציה שמורידה את הכיתה DownloaderService
ולשנות את ברירת המחדל של שלוש שיטות כדי לספק פרטים ספציפיים של האפליקציה:
getPublicKey()
- הפונקציה הזו צריכה להחזיר מחרוזת שמכילה את המפתח הציבורי של RSA בקידוד Base64 לחשבון בעל התוכן הדיגיטלי. המפתח הזה זמין בדף הפרופיל ב-Play Console (מידע נוסף זמין במאמר הגדרה לצורכי רישוי).
getSALT()
- היא צריכה להחזיר מערך של בייטים אקראיים שמערכת הרישוי
Policy
משתמשת בהם כדי ליצורObfuscator
. המלח מבטיח שהקובץSharedPreferences
המעורפל שבו שמורים נתוני הרישוי יהיה ייחודי ולא ניתן לגילוי. getAlarmReceiverClassName()
- הפונקציה הזו צריכה להחזיר את שם הכיתה של ה-
BroadcastReceiver
באפליקציה שאמור לקבל את ההתראה על כך שצריך להפעיל מחדש את ההורדה (מצב שעלול לקרות אם שירות ההורדה נעצר באופן בלתי צפוי).
לדוגמה, הנה הטמעה מלאה של DownloaderService
:
Kotlin
// You must use the public key belonging to your publisher account const val BASE64_PUBLIC_KEY = "YourLVLKey" // You should also modify this salt val SALT = byteArrayOf( 1, 42, -12, -1, 54, 98, -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84 ) class SampleDownloaderService : DownloaderService() { override fun getPublicKey(): String = BASE64_PUBLIC_KEY override fun getSALT(): ByteArray = SALT override fun getAlarmReceiverClassName(): String = SampleAlarmReceiver::class.java.name }
Java
public class SampleDownloaderService extends DownloaderService { // You must use the public key belonging to your publisher account public static final String BASE64_PUBLIC_KEY = "YourLVLKey"; // You should also modify this salt public static final byte[] SALT = new byte[] { 1, 42, -12, -1, 54, 98, -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84 }; @Override public String getPublicKey() { return BASE64_PUBLIC_KEY; } @Override public byte[] getSALT() { return SALT; } @Override public String getAlarmReceiverClassName() { return SampleAlarmReceiver.class.getName(); } }
הערה: צריך לעדכן את הערך של BASE64_PUBLIC_KEY
למפתח הציבורי שמשויך לחשבון בעל התוכן הדיגיטלי. המפתח מופיע במסוף הפיתוח בקטע 'פרטי הפרופיל'. צריך לעשות זאת גם כשבודקים את ההורדות.
חשוב להצהיר על השירות בקובץ המניפסט:
<app ...> <service android:name=".SampleDownloaderService" /> ... </app>
הטמעת מקלט ההתראה
כדי לעקוב אחרי ההתקדמות של הורדות הקבצים ולהפעיל מחדש את ההורדה במקרה הצורך, ה-DownloaderService
מתזמן התראה RTC_WAKEUP
שמעבירה Intent
ל-BroadcastReceiver
באפליקציה. צריך להגדיר את ה-BroadcastReceiver
כך שיפעיל API מספריית Downloader שבודק את סטטוס ההורדה ומפעיל אותה מחדש במקרה הצורך.
פשוט צריך לשנות את השיטה onReceive()
כדי לקרוא ל-DownloaderClientMarshaller.startDownloadServiceIfRequired()
.
לדוגמה:
Kotlin
class SampleAlarmReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { try { DownloaderClientMarshaller.startDownloadServiceIfRequired( context, intent, SampleDownloaderService::class.java ) } catch (e: PackageManager.NameNotFoundException) { e.printStackTrace() } } }
Java
public class SampleAlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { try { DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, SampleDownloaderService.class); } catch (NameNotFoundException e) { e.printStackTrace(); } } }
שימו לב שזו המחלקה שאליה צריך להחזיר את השם בשיטה getAlarmReceiverClassName()
של השירות (ראו הקטע הקודם).
חשוב להצהיר על המקבל בקובץ המניפסט:
<app ...> <receiver android:name=".SampleAlarmReceiver" /> ... </app>
התחלת ההורדה
הפעילות הראשית באפליקציה (זו שמתחילה באמצעות סמל מרכז האפליקציות) אחראית לוודא שקובצי ההרחבה כבר נמצאים במכשיר, ולהתחיל את ההורדה אם הם לא נמצאים בו.
כדי להתחיל את ההורדה באמצעות ספריית Downloader, צריך לבצע את הפעולות הבאות:
- בודקים אם הקבצים הורדתם.
ספריית Downloader כוללת כמה ממשקי API בכיתה
Helper
שיעזרו בתהליך הזה:getExpansionAPKFileName(Context, c, boolean mainFile, int versionCode)
doesFileExist(Context c, String fileName, long fileSize)
לדוגמה, האפליקציה לדוגמה שסופקה בחבילת ה-Apk Expansion קוראת לשיטה הבאה ב-method
onCreate()
של הפעילות כדי לבדוק אם קובצי ההרחבה כבר קיימים במכשיר:Kotlin
fun expansionFilesDelivered(): Boolean { xAPKS.forEach { xf -> Helpers.getExpansionAPKFileName(this, xf.isBase, xf.fileVersion).also { fileName -> if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false)) return false } } return true }
Java
boolean expansionFilesDelivered() { for (XAPKFile xf : xAPKS) { String fileName = Helpers.getExpansionAPKFileName(this, xf.isBase, xf.fileVersion); if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false)) return false; } return true; }
במקרה כזה, כל אובייקט
XAPKFile
מכיל את מספר הגרסה ואת גודל הקובץ של קובץ הרחבה ידוע, וכן ערך בוליאני שמציין אם זהו קובץ ההרחבה הראשי. (פרטים נוספים זמינים בכיתהSampleDownloaderActivity
של האפליקציה לדוגמה).אם השיטה הזו מחזירה את הערך false, האפליקציה צריכה להתחיל את ההורדה.
- כדי להתחיל את ההורדה, קוראים לשיטה הסטטית
DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, Class<?> serviceClass)
.השיטה מקבלת את הפרמטרים הבאים:
context
:Context
של האפליקציה.notificationClient
:PendingIntent
להפעלת הפעילות הראשית. הוא משמש ב-Notification
שנוצר על ידיDownloaderService
כדי להציג את התקדמות ההורדה. כשהמשתמש בוחר בהתראה, המערכת מפעילה אתPendingIntent
שציינתם כאן וצריך להיפתח הפעילות שבה מוצגת התקדמות ההורדה (בדרך כלל אותה פעילות שבה התחיל ההורדה).serviceClass
: אובייקטClass
להטמעה שלDownloaderService
, שנדרש כדי להפעיל את השירות ולהתחיל את ההורדה במקרה הצורך.
השיטה מחזירה מספר שלם שמציין אם ההורדה נדרשת או לא. הערכים האפשריים הם:
NO_DOWNLOAD_REQUIRED
: הערך מוחזר אם הקבצים כבר קיימים או שההורדה כבר מתבצעת.LVL_CHECK_REQUIRED
: הערך מוחזר אם נדרש אימות רישיון כדי לקבל את כתובות ה-URL של קובצי ההרחבה.DOWNLOAD_REQUIRED
: הערך הזה מוחזר אם כתובות ה-URL של קובצי ההרחבה כבר ידועות, אבל לא הורדתם אותן.
ההתנהגות של
LVL_CHECK_REQUIRED
ושלDOWNLOAD_REQUIRED
זהה במהותה, ובדרך כלל אין צורך לדאוג לגביהן. בפעילות הראשית שמפעילה אתstartDownloadServiceIfRequired()
, אפשר פשוט לבדוק אם התגובה היאNO_DOWNLOAD_REQUIRED
או לא. אם התגובה היא אחרת מ-NO_DOWNLOAD_REQUIRED
, ספריית ההורדה תתחיל את ההורדה ותצטרכו לעדכן את ממשק המשתמש של הפעילות כדי להציג את התקדמות ההורדה (ראו את השלב הבא). אם התגובה היאNO_DOWNLOAD_REQUIRED
, הקבצים זמינים והאפליקציה יכולה להתחיל לפעול.לדוגמה:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Check if expansion files are available before going any further if (!expansionFilesDelivered()) { val pendingIntent = // Build an Intent to start this activity from the Notification Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP }.let { notifierIntent -> PendingIntent.getActivity( this, 0, notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT ) } // Start the download service (if required) val startResult: Int = DownloaderClientMarshaller.startDownloadServiceIfRequired( this, pendingIntent, SampleDownloaderService::class.java ) // If download has started, initialize this activity to show // download progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // This is where you do set up to display the download // progress (next step) ... return } // If the download wasn't necessary, fall through to start the app } startApp() // Expansion files are available, start the app }
Java
@Override public void onCreate(Bundle savedInstanceState) { // Check if expansion files are available before going any further if (!expansionFilesDelivered()) { // Build an Intent to start this activity from the Notification Intent notifierIntent = new Intent(this, MainActivity.getClass()); notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); ... PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); // Start the download service (if required) int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, SampleDownloaderService.class); // If download has started, initialize this activity to show // download progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // This is where you do set up to display the download // progress (next step) ... return; } // If the download wasn't necessary, fall through to start the app } startApp(); // Expansion files are available, start the app }
- כשה-method
startDownloadServiceIfRequired()
מחזיר משהו אחר מ-NO_DOWNLOAD_REQUIRED
, יוצרים מופע שלIStub
על ידי קריאה ל-DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService)
. ה-IStub
מספק קישור בין הפעילות לבין שירות ההורדה, כך שהפעילות מקבלת קריאות חזרה (callbacks) לגבי התקדמות ההורדה.כדי ליצור מופע של
IStub
באמצעות קריאה ל-CreateStub()
, צריך להעביר לו הטמעה של הממשקIDownloaderClient
והטמעה שלDownloaderService
. בקטע הבא, קבלת התקדמות ההורדה, מוסבר על הממשקIDownloaderClient
. בדרך כלל צריך להטמיע את הממשק הזה במחלקהActivity
כדי שתוכלו לעדכן את ממשק המשתמש של הפעילות כשמצב ההורדה משתנה.מומלץ להפעיל את
CreateStub()
כדי ליצור אתIStub
במהלך השיטהonCreate()
של הפעילות, אחרי ש-startDownloadServiceIfRequired()
מתחיל את ההורדה.לדוגמה, בדוגמת הקוד הקודמת של
onCreate()
, אפשר להגיב לתוצאהstartDownloadServiceIfRequired()
כך:Kotlin
// Start the download service (if required) val startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired( this@MainActivity, pendingIntent, SampleDownloaderService::class.java ) // If download has started, initialize activity to show progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // Instantiate a member instance of IStub downloaderClientStub = DownloaderClientMarshaller.CreateStub(this, SampleDownloaderService::class.java) // Inflate layout that shows download progress setContentView(R.layout.downloader_ui) return }
Java
// Start the download service (if required) int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, SampleDownloaderService.class); // If download has started, initialize activity to show progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // Instantiate a member instance of IStub downloaderClientStub = DownloaderClientMarshaller.CreateStub(this, SampleDownloaderService.class); // Inflate layout that shows download progress setContentView(R.layout.downloader_ui); return; }
אחרי שהשיטה
onCreate()
חוזרת, הפעילות מקבלת קריאה ל-onResume()
, שבה צריך לקרוא ל-connect()
ב-IStub
, ולהעביר לו את ה-Context
של האפליקציה. לעומת זאת, צריך להפעיל אתdisconnect()
בקריאה החוזרת (callback)onStop()
של הפעילות.Kotlin
override fun onResume() { downloaderClientStub?.connect(this) super.onResume() } override fun onStop() { downloaderClientStub?.disconnect(this) super.onStop() }
Java
@Override protected void onResume() { if (null != downloaderClientStub) { downloaderClientStub.connect(this); } super.onResume(); } @Override protected void onStop() { if (null != downloaderClientStub) { downloaderClientStub.disconnect(this); } super.onStop(); }
קריאה ל-
connect()
ב-IStub
מקשרת את הפעילות ל-DownloaderService
, כך שהפעילות מקבלת קריאות חזרה לגבי שינויים במצב ההורדה דרך ממשקIDownloaderClient
.
קבלת התקדמות ההורדה
כדי לקבל עדכונים לגבי התקדמות ההורדה ולנהל אינטראקציה עם DownloaderService
, צריך להטמיע את הממשק IDownloaderClient
של ספריית ההורדה.
בדרך כלל, הפעילות שבה אתם משתמשים כדי להתחיל את ההורדה צריכה ליישם את הממשק הזה כדי להציג את התקדמות ההורדה ולשלוח בקשות לשירות.
שיטות הממשק הנדרשות ל-IDownloaderClient
הן:
onServiceConnected(Messenger m)
- אחרי שיוצרים את המופע של
IStub
בפעילות, מתקבלת קריאה לשיטה הזו, שמעבירה אובייקטMessenger
שמקושר למופע שלDownloaderService
. כדי לשלוח בקשות לשירות, כמו השהיה והמשך של הורדות, צריך לבצע קריאה ל-DownloaderServiceMarshaller.CreateProxy()
כדי לקבל את הממשקIDownloaderService
שמחובר לשירות.הטמעה מומלצת נראית כך:
Kotlin
private var remoteService: IDownloaderService? = null ... override fun onServiceConnected(m: Messenger) { remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply { downloaderClientStub?.messenger?.also { messenger -> onClientUpdated(messenger) } } }
Java
private IDownloaderService remoteService; ... @Override public void onServiceConnected(Messenger m) { remoteService = DownloaderServiceMarshaller.CreateProxy(m); remoteService.onClientUpdated(downloaderClientStub.getMessenger()); }
אחרי שמפעילים את האובייקט
IDownloaderService
, אפשר לשלוח פקודות לשירות ההורדה, למשל להשהות ולהמשיך את ההורדה (requestPauseDownload()
ו-requestContinueDownload()
). onDownloadStateChanged(int newState)
- שירות ההורדה קורא לזה כשיש שינוי במצב ההורדה, למשל כשההורדה מתחילה או מסתיימת.
הערך של
newState
יהיה אחד מכמה ערכים אפשריים שצוינו על ידי אחת מהקבועותSTATE_*
של הכיתהIDownloaderClient
.כדי לספק למשתמשים הודעה שימושית, אפשר לבקש מחרוזת תואמת לכל מצב על ידי קריאה ל-
Helpers.getDownloaderStringResourceIDFromState()
. הפונקציה מחזירה את מזהה המשאב של אחת מהמחרוזות שכלולות בספריית ההורדה. לדוגמה, המחרוזת 'ההורדה הושהתה כי אתם נמצאים בנדידה' תואמת ל-STATE_PAUSED_ROAMING
. onDownloadProgress(DownloadProgressInfo progress)
- שירות ההורדה קורא ל-method הזה כדי לספק אובייקט
DownloadProgressInfo
שמתאר מידע שונה על התקדמות ההורדה, כולל זמן משוער שנותר, המהירות הנוכחית, התקדמות הכוללת והסה"כ, כדי שתוכלו לעדכן את ממשק המשתמש של התקדמות ההורדה.
טיפ: דוגמאות להודעות החזרה (callbacks) האלה שמעדכנות את ממשק המשתמש של התקדמות ההורדה מפורטות ב-SampleDownloaderActivity
באפליקציית הדוגמה שסופקה בחבילת ה-Apk Expansion.
כמה שיטות ציבוריות לממשק IDownloaderService
שעשויות להועיל לכם:
requestPauseDownload()
- השהיית ההורדה.
requestContinueDownload()
- המשך הורדה שהושהתה.
setDownloadFlags(int flags)
- הגדרת העדפות המשתמש לגבי סוגי הרשתות שבהם מותר להוריד את הקבצים. ההטמעה הנוכחית תומכת בדגל אחד,
FLAGS_DOWNLOAD_OVER_CELLULAR
, אבל אפשר להוסיף עוד דגלים. כברירת מחדל, הדגל הזה לא מופעל, ולכן המשתמש צריך להיות מחובר לרשת Wi-Fi כדי להוריד קבצים מורחבים. מומלץ לספק העדפה של משתמש כדי לאפשר הורדות דרך הרשת הסלולרית. במקרה כזה, אפשר להתקשר למספרים הבאים:Kotlin
remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply { ... setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR) }
Java
remoteService .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
שימוש ב-APKExpansionPolicy
אם תחליטו ליצור שירות הורדה משלכם במקום להשתמש בספריית ההורדות של Google Play, עדיין כדאי להשתמש ב-APKExpansionPolicy
שסופק בספריית אימות הרישיונות. המחלקה APKExpansionPolicy
כמעט זהה למחלקה ServerManagedPolicy
(שזמינה בספריית האימות של הרישיונות של Google Play), אבל היא כוללת טיפול נוסף בתגובות הנוספות של קובץ ההרחבה של ה-APK.
הערה: אם משתמשים בספריית ההורדה כפי שמתואר בקטע הקודם, הספרייה מבצעת את כל האינטראקציה עם APKExpansionPolicy
, כך שאין צורך להשתמש בכיתה הזו ישירות.
הכיתה כוללת שיטות שיעזרו לכם לקבל את המידע הדרוש לגבי קובצי ההרחבה הזמינים:
getExpansionURLCount()
getExpansionURL(int index)
getExpansionFileName(int index)
getExpansionFileSize(int index)
למידע נוסף על השימוש ב-APKExpansionPolicy
כשלא משתמשים בספריית ה-Downloader, אפשר לעיין במסמך הוספת רישוי לאפליקציה, שבו מוסבר איך להטמיע מדיניות רישוי כמו זו.
קריאת קובץ ההרחבות
אחרי שקובצי ההרחבה של ה-APK נשמרים במכשיר, האופן שבו קוראים את הקבצים תלוי בסוג הקובץ שבו השתמשתם. כפי שצוין בסקירה הכללית, קובצי ההרחבה יכולים להיות בכל סוג של קובץ שתרצו, אבל השם שלהם משתנה לפי פורמט ספציפי של שם קובץ והם נשמרים ב-<shared-storage>/Android/obb/<package-name>/
.
לא משנה איך קוראים את הקבצים, תמיד צריך לבדוק קודם אם האחסון החיצוני זמין לקריאה. יכול להיות שהמשתמש חיבר את ההתקן למחשב באמצעות USB או שהסיר את כרטיס ה-SD.
הערה: כשהאפליקציה מופעלת, תמיד צריך לבדוק אם נפח האחסון החיצוני זמין ואפשר לקרוא אותו, על ידי קריאה לפונקציה getExternalStorageState()
. הפונקציה מחזירה אחת מכמה מחרוזות אפשריות שמייצגות את המצב של האחסון החיצוני. כדי שהאפליקציה תוכל לקרוא אותו, הערך המוחזר צריך להיות MEDIA_MOUNTED
.
אחזור שמות הקבצים
כפי שמתואר בסקירה הכללית, קובצי ההרחבה של ה-APK נשמרים בפורמט ספציפי של שם הקובץ:
[main|patch].<expansion-version>.<package-name>.obb
כדי לקבל את המיקום והשמות של קובצי ההרחבה, צריך להשתמש בשיטות getExternalStorageDirectory()
ו-getPackageName()
כדי ליצור את הנתיב לקבצים.
זוהי שיטה שאפשר להשתמש בה באפליקציה כדי לקבל מערך שמכיל את הנתיב המלא לשני קובצי ההרחבה:
Kotlin
fun getAPKExpansionFiles(ctx: Context, mainVersion: Int, patchVersion: Int): Array<String> { val packageName = ctx.packageName val ret = mutableListOf<String>() if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) { // Build the full path to the app's expansion files val root = Environment.getExternalStorageDirectory() val expPath = File(root.toString() + EXP_PATH + packageName) // Check that expansion file path exists if (expPath.exists()) { if (mainVersion > 0) { val strMainPath = "$expPath${File.separator}main.$mainVersion.$packageName.obb" val main = File(strMainPath) if (main.isFile) { ret += strMainPath } } if (patchVersion > 0) { val strPatchPath = "$expPath${File.separator}patch.$mainVersion.$packageName.obb" val main = File(strPatchPath) if (main.isFile) { ret += strPatchPath } } } } return ret.toTypedArray() }
Java
// The shared path to all app expansion files private final static String EXP_PATH = "/Android/obb/"; static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) { String packageName = ctx.getPackageName(); Vector<String> ret = new Vector<String>(); if (Environment.getExternalStorageState() .equals(Environment.MEDIA_MOUNTED)) { // Build the full path to the app's expansion files File root = Environment.getExternalStorageDirectory(); File expPath = new File(root.toString() + EXP_PATH + packageName); // Check that expansion file path exists if (expPath.exists()) { if ( mainVersion > 0 ) { String strMainPath = expPath + File.separator + "main." + mainVersion + "." + packageName + ".obb"; File main = new File(strMainPath); if ( main.isFile() ) { ret.add(strMainPath); } } if ( patchVersion > 0 ) { String strPatchPath = expPath + File.separator + "patch." + mainVersion + "." + packageName + ".obb"; File main = new File(strPatchPath); if ( main.isFile() ) { ret.add(strPatchPath); } } } } String[] retArray = new String[ret.size()]; ret.toArray(retArray); return retArray; }
כדי להפעיל את השיטה הזו, מעבירים אליה את האפליקציה Context
וגרסה של קובץ ההרחבה הרצוי.
יש הרבה דרכים לקבוע את מספר הגרסה של קובץ ההרחבה. אחת הדרכים הפשוטות היא לשמור את הגרסה בקובץ SharedPreferences
כשההורדה מתחילה, על ידי שליחת שאילתה לגבי שם קובץ ההרחבה באמצעות השיטה getExpansionFileName(int index)
של הכיתה APKExpansionPolicy
. לאחר מכן, כשרוצים לגשת לקובץ ההרחבה, אפשר לקרוא את קובץ SharedPreferences
כדי לקבל את קוד הגרסה.
מידע נוסף על קריאה מהאחסון המשותף זמין במסמכי העזרה של Data Storage.
שימוש בספריית ה-Zip של הרחבות ל-APK
חבילת ה-Apk Expansion של Google Market כוללת ספרייה שנקראת APK Expansion Zip Library (שנמצאת ב-<sdk>/extras/google/google_market_apk_expansion/zip_file/
). זוהי ספרייה אופציונלית שעוזרת לקרוא את קובצי ההרחבה כשהם נשמרים כקובצי ZIP. השימוש בספרייה הזו מאפשר לקרוא בקלות משאבים מקובצי ZIP מורחבים כמערכת קבצים וירטואלית.
ספריית ה-Zip של הרחבות ל-APK כוללת את הכיתות וממשקי ה-API הבאים:
APKExpansionSupport
- מספק כמה שיטות לגישה לשמות של קובצי הרחבה ולקובצי ZIP:
getAPKExpansionFiles()
- השיטה שמוצגת למעלה, שמחזירה את הנתיב המלא של הקובץ לשני קובצי ההרחבה.
getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion)
- מחזירה
ZipResourceFile
שמייצג את הסכום של הקובץ הראשי ושל קובץ התיקון. כלומר, אם מציינים גם אתmainVersion
וגם אתpatchVersion
, הפונקציה מחזירהZipResourceFile
שמספק גישה לקריאה לכל הנתונים, כאשר נתוני קובץ התיקון מוזגו מעל לקובץ הראשי.
ZipResourceFile
- מייצג קובץ ZIP באחסון המשותף ומבצע את כל העבודה כדי לספק מערכת קבצים וירטואלית שמבוססת על קובצי ה-ZIP. אפשר לקבל מכונה באמצעות
APKExpansionSupport.getAPKExpansionZipFile()
או באמצעותZipResourceFile
, על ידי העברת הנתיב לקובץ ההרחבה. הכיתה הזו כוללת מגוון שיטות מועילות, אבל בדרך כלל אין צורך לגשת לרובן. כמה שיטות חשובות הן:getInputStream(String assetPath)
- מספק
InputStream
לקריאת קובץ בקובץ ה-ZIP. השדהassetPath
חייב להיות הנתיב לקובץ הרצוי, ביחס לשורש של תוכן קובץ ה-ZIP. getAssetFileDescriptor(String assetPath)
- מספק
AssetFileDescriptor
לקובץ בקובץ ה-ZIP. השדהassetPath
חייב להיות הנתיב לקובץ הרצוי, ביחס לשורש של תוכן קובץ ה-ZIP. האפשרות הזו שימושית לממשקי API מסוימים של Android שדורשיםAssetFileDescriptor
, כמו חלק מממשקי ה-API שלMediaPlayer
.
APEZProvider
- רוב האפליקציות לא צריכות להשתמש בכיתה הזו. בכיתה הזו מוגדר
ContentProvider
שמארגן את הנתונים מקובצי ה-ZIP דרך ספק התוכןUri
כדי לספק גישה לקבצים לממשקי API מסוימים של Android שמצפים ל-Uri
גישה לקובצי מדיה. לדוגמה, אפשר להשתמש באפשרות הזו כדי להפעיל סרטון באמצעותVideoView.setVideoURI()
.
דילוג על דחיסת ZIP של קובצי מדיה
אם אתם משתמשים בקובצי ההרחבה כדי לאחסן קובצי מדיה, קובץ ZIP עדיין מאפשר לכם להשתמש בקריאות להפעלת מדיה ב-Android שמספקות אמצעי בקרה על הזחה ועל אורך (כמו MediaPlayer.setDataSource()
ו-SoundPool.load()
). כדי שזה יפעל, אסור לבצע דחיסה נוספת של קובצי המדיה כשיוצרים את חבילות ה-ZIP. לדוגמה, כשמשתמשים בכלי zip
, צריך להשתמש באפשרות -n
כדי לציין את הסיומת של הקבצים שלא צריך לדחוס:
zip -n .mp4;.ogg main_expansion media_files
קריאה מקובץ ZIP
כשמשתמשים בספריית ה-Zip של הרחבות ה-APK, בדרך כלל צריך לבצע את הפעולות הבאות כדי לקרוא קובץ מקובץ ה-ZIP:
Kotlin
// Get a ZipResourceFile representing a merger of both the main and patch files val expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext, mainVersion, patchVersion) // Get an input stream for a known file inside the expansion file ZIPs expansionFile.getInputStream(pathToFileInsideZip).use { ... }
Java
// Get a ZipResourceFile representing a merger of both the main and patch files ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext, mainVersion, patchVersion); // Get an input stream for a known file inside the expansion file ZIPs InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);
הקוד שלמעלה מספק גישה לכל קובץ שקיים בקובץ ההרחבה הראשי או בקובץ ההרחבה של התיקון, על ידי קריאה ממפה מיזוגית של כל הקבצים משני הקבצים. כל מה שצריך לספק לשיטה getAPKExpansionFile()
הוא android.content.Context
של האפליקציה ומספר הגרסה של קובץ ההרחבה הראשי ושל קובץ ההרחבה של התיקון.
אם אתם מעדיפים לקרוא מקובץ הרחבה ספציפי, תוכלו להשתמש ב-constructor של ZipResourceFile
עם הנתיב לקובץ ההרחבה הרצוי:
Kotlin
// Get a ZipResourceFile representing a specific expansion file val expansionFile = ZipResourceFile(filePathToMyZip) // Get an input stream for a known file inside the expansion file ZIPs expansionFile.getInputStream(pathToFileInsideZip).use { ... }
Java
// Get a ZipResourceFile representing a specific expansion file ZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip); // Get an input stream for a known file inside the expansion file ZIPs InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);
מידע נוסף על השימוש בספרייה הזו לקבצים מורחבים זמין בכיתה SampleDownloaderActivity
של האפליקציה לדוגמה, שכוללת קוד נוסף לאימות הקבצים שהורדתם באמצעות CRC. חשוב לזכור: אם משתמשים בדוגמה הזו כבסיס להטמעה משלכם, צריך להצהיר על גודל הבייט של קובצי ההרחבה במערך xAPKS
.
בדיקת קובצי ההרחבה
לפני שמפרסמים את האפליקציה, צריך לבדוק שני דברים: קריאת קובצי ההרחבה והורדת הקבצים.
בדיקת קריאת קבצים
לפני שאתם מעלים את האפליקציה ל-Google Play, כדאי לבדוק את היכולת של האפליקציה לקרוא את הקבצים מהאחסון המשותף. כל מה שצריך לעשות הוא להוסיף את הקבצים למיקום המתאים באחסון המשותף של המכשיר ולהפעיל את האפליקציה:
- במכשיר, יוצרים את הספרייה המתאימה באחסון המשותף שבה Google Play תשמור את הקבצים.
לדוגמה, אם שם החבילה הוא
com.example.android
, צריך ליצור את הספרייהAndroid/obb/com.example.android/
במרחב האחסון המשותף. (מחברים את מכשיר הבדיקה למחשב כדי לחבר את האחסון המשותף וליצור את הספרייה הזו באופן ידני). - מוסיפים ידנית את קובצי ההרחבה לספרייה הזו. חשוב לשנות את השמות של הקבצים כך שיתאים לפורמט של שם הקובץ שבו Google Play ישתמש.
לדוגמה, ללא קשר לסוג הקובץ, קובץ ההרחבה הראשי של אפליקציית
com.example.android
צריך להיותmain.0300110.com.example.android.obb
. קוד הגרסה יכול להיות כל ערך שתרצו. חשוב לזכור:- קובץ ההרחבה הראשי תמיד מתחיל ב-
main
וקובץ התיקון מתחיל ב-patch
. - שם החבילה תמיד תואם לשם של קובץ ה-APK שאליו הקובץ מצורף ב-Google Play.
- קובץ ההרחבה הראשי תמיד מתחיל ב-
- עכשיו, כשקובצי ההרחבה נמצאים במכשיר, אפשר להתקין ולהפעיל את האפליקציה כדי לבדוק את קובצי ההרחבה.
ריכזנו כאן כמה תזכורות לגבי הטיפול בקובצי ההרחבה:
- אין למחוק או לשנות את השם של קובצי ההרחבה
.obb
(גם אם פותחים את האריזה של הנתונים במיקום אחר). הפעולה הזו תגרום ל-Google Play (או לאפליקציה עצמה) להוריד שוב ושוב את קובץ ההרחבה. - אין לשמור נתונים אחרים בספרייה
obb/
. אם צריך לפתוח את האריזה של נתונים מסוימים, שומרים אותם במיקום שצוין ב-getExternalFilesDir()
.
בדיקת הורדות קבצים
לפעמים האפליקציה צריכה להוריד את קובצי ההרחבה באופן ידני בפעם הראשונה שהיא נפתחת, לכן חשוב לבדוק את התהליך הזה כדי לוודא שהאפליקציה יכולה לשלוח שאילתות לגבי כתובות ה-URL, להוריד את הקבצים ולשמור אותם במכשיר.
כדי לבדוק את ההטמעה של תהליך ההורדה הידנית באפליקציה, תוכלו לפרסם אותה במסלול הבדיקה הפנימי, כך שהיא תהיה זמינה רק לבודקים מורשים. אם הכול פועל כצפוי, האפליקציה אמורה להתחיל להוריד את קובצי ההרחבה ברגע שהפעילות הראשית תתחיל.
הערה: בעבר אפשר היה לבדוק אפליקציה על ידי העלאת גרסת 'טיוטה' שלא פורסמה. הפונקציונליות הזו כבר לא נתמכת. במקום זאת, צריך לפרסם אותה במסלול הפצה פנימי, סגור או פתוח לבדיקה. מידע נוסף זמין במאמר התמיכה באפליקציות טיוטה הופסקה.
עדכון האפליקציה
אחד מהיתרונות הגדולים של שימוש בקובצי הרחבה ב-Google Play הוא היכולת לעדכן את האפליקציה בלי להוריד מחדש את כל הנכסים המקוריים. מכיוון שמערכת Google Play מאפשרת לכם לספק שני קובצי הרחבה לכל קובץ APK, תוכלו להשתמש בקובץ השני כ'תיקון' שמכיל עדכונים ונכסים חדשים. כך לא תצטרכו להוריד מחדש את קובץ ההרחבה הראשי, שעשוי להיות גדול ויקר למשתמשים.
קובץ הרחבות התיקון זהה מבחינה טכנית לקובץ ההרחבה הראשי, וגם מערכת Android וגם Google Play לא מבצעות תיקון בפועל בין קובץ ההרחבה הראשי לקובץ ההרחבה של התיקון. קוד האפליקציה צריך לבצע את התיקונים הנדרשים בעצמו.
אם אתם משתמשים בקובצי ZIP כקובצי הרחבה, ספריית ה-Zip של הרחבת ה-APK שכלולה בחבילת הרחבת ה-APK כוללת את היכולת למזג את קובץ התיקון עם קובץ ההרחבה הראשי.
הערה: גם אם צריך לבצע שינויים רק בקובץ ההרחבה של התיקון, עדיין צריך לעדכן את קובץ ה-APK כדי ש-Google Play יבצע עדכון.
אם אין צורך בשינויים בקוד של האפליקציה, פשוט מעדכנים את versionCode
במניפסט.
כל עוד לא משנים את קובץ ההרחבה הראשי שמשויך לקובץ ה-APK ב-Play Console, משתמשים שכבר התקינו את האפליקציה לא יורידו את קובץ ההרחבה הראשי. משתמשים קיימים מקבלים רק את קובץ ה-APK המעודכן ואת קובץ ההרחבה החדש של התיקון (קובץ ההרחבה הראשי הקודם נשאר).
ריכזנו כאן כמה בעיות שכדאי לזכור לגבי עדכונים של קובצי הרחבה:
- אפשר להשתמש רק בשני קובצי הרחבה לאפליקציה בכל פעם. קובץ הרחבות ראשי אחד וקובץ הרחבות תיקונים אחד. במהלך עדכון של קובץ, Google Play מוחקת את הגרסה הקודמת (וכך גם האפליקציה שלכם כשאתם מבצעים עדכונים ידניים).
- כשמוסיפים קובץ הרחבות תיקונים, מערכת Android לא מתקנת בפועל את היישום או את קובץ ההרחבות הראשי. עליכם לתכנן את האפליקציה כך שתתמוך בנתוני התיקון. עם זאת, חבילת Apk Expansion כוללת ספרייה לשימוש בקובצי ZIP כקובצי הרחבה, שממזגת את הנתונים מקובץ התיקון לקובץ ההרחבה הראשי כדי שתוכלו לקרוא בקלות את כל הנתונים בקובץ ההרחבה.