קובצי הרחבת APK

ב-Google Play, ה-APK הדחוס שמשתמשים מורידים לא יעלה על 100MB. עבור רוב האפליקציות, זה מספיק מקום לכל הקוד והנכסים של האפליקציה. עם זאת, באפליקציות מסוימות נדרש יותר מקום לשמירת גרפיקה באיכות גבוהה, קובצי מדיה או נכסים גדולים אחרים. בעבר, אם הגודל של קובץ ההורדה הדחוס של האפליקציה חרג מ-100MB, הייתם צריכים לארח ולהוריד את הקובץ משאבים נוספים בעצמכם כשהמשתמש פותח את האפליקציה. אירוח והצגה של הקבצים הנוספים יכולים להיות יקרות, וחוויית המשתמש לרוב לא אידיאלית. כדי להקל עליכם את התהליך ונעים יותר למשתמשים, Google Play מאפשר לצרף שני קובצי הרחבה גדולים להשלים את ה-APK.

מערכת Google Play מארחת את קובצי ההרחבה של האפליקציה שלכם ומציגה אותם במכשיר בכתובת ללא עלות. קובצי ההרחבה נשמרים במיקום האחסון המשותף של המכשיר ( כרטיס SD או מחיצה שניתנת לטעינת USB; נקרא גם "חיצוני" אחסון) שדרכם האפליקציה יכולה לגשת אותם. ברוב המכשירים, Google Play מוריד את קובצי ההרחבה באותו זמן יוריד את ה-APK, כך שלאפליקציה יש את כל מה שצריך כשהמשתמש פותח אותה עבור בפעם הראשונה. עם זאת, במקרים מסוימים, האפליקציה חייבת להוריד את הקבצים מ-Google Play כשהאפליקציה מופעלת.

אם רוצים להימנע משימוש בקובצי הרחבה וההורדה הדחוסה של האפליקציה גדולה יותר מעל 100MB, עליך להעלות את האפליקציה שלך באמצעות אפליקציה ל-Android חבילות שמאפשרות הורדה דחוסה של עד 200MB. בנוסף, מכיוון שהשתמשנו ב- קובצי App Bundle מונעים יצירת APK וחתימה ל-Google Play, משתמשים מורידים חבילות APK שעברו אופטימיזציה באמצעות רק את הקוד והמשאבים הדרושים להם כדי להפעיל את האפליקציה. לא צריך לבנות, לחתום מנהלים כמה חבילות APK או קובצי הרחבה, והמשתמשים מקבלים הורדות קטנות ומשופרות.

סקירה כללית

בכל פעם שמעלים APK באמצעות Google Play Console, אפשר: מוסיפים קובץ הרחבה אחד או שניים ל-APK. כל קובץ יכול להיות בגודל של עד 2GB ויכול להיות בכל פורמט אבל מומלץ להשתמש בקובץ דחוס כדי לחסוך ברוחב פס במהלך ההורדה. בעיקרון, לכל קובץ הרחבה יש תפקיד שונה:

  • קובץ ההרחבות הראשי הוא קובץ הרחבה ראשי למשאבים נוספים שנדרשים על ידי האפליקציה.
  • קובץ ההרחבה patch הוא אופציונלי ומיועד לעדכונים קטנים של את קובץ ההרחבות הראשי.

אפשר להשתמש בשני קובצי ההרחבה בכל שלב, אבל מומלץ מספק את הנכסים הראשיים. הוא אמור להתבצע רק לעיתים רחוקות, אם הוא יעודכן אי פעם. הרחבת התיקון צריך להיות קטן יותר ולשמש כ"ספק תיקונים", שמתעדכן עם כל לשחרר או לפי הצורך.

עם זאת, גם אם עדכון האפליקציה דורש רק קובץ הרחבת תיקון חדש, עדיין להעלות APK חדש עם קובץ versionCode מעודכן במניפסט. (ה ב-Play Console אין אפשרות להעלות קובץ הרחבה ל-APK קיים).

הערה: קובץ הרחבת התיקון זהה מבחינה סמנטית את קובץ ההרחבה הראשי — אפשר להשתמש בכל קובץ בכל דרך שרוצים.

הפורמט של שם הקובץ

כל קובץ הרחבה שתעלו יכול להיות בכל פורמט שתבחרו (ZIP, PDF, MP4 וכו'). אפשר גם משתמשים בכלי JOBB כדי לבצע הצפנה ולהצפין קבוצה של קובצי משאבים ותיקונים נוספים לקבוצה הזו. ללא קשר לסוג הקובץ, Google Play רואה אותם blobs בינאריים אטומים ומשנה את שמות הקבצים באמצעות הסכמה הבאה:

[main|patch].<expansion-version>.<package-name>.obb

הסכמה הזו מורכבת משלושה רכיבים:

main או patch
מציין אם הקובץ הוא קובץ ההרחבה הראשי או קובץ התיקון. יכולות להיות רק קובץ ראשי אחד וקובץ תיקון אחד לכל APK.
<expansion-version>
זהו מספר שלם שתואם לקוד הגרסה של ה-APK שבו ההרחבה הראשון שמשויך לאפליקציה (הוא תואם ל-android:versionCode של האפליקציה ).

"First" על אף ש-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 (רמת API 19), אפליקציות יכולות לקרוא קובצי הרחבה של 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(). אבל, לפעמים אם ניתן, מומלץ להשתמש בפורמט של קובץ הרחבה שמאפשר לקרוא ישירות את הקובץ במקום לדרוש מכם לפרק את הנתונים. לדוגמה, סיפקנו ספרייה פרויקט שנקרא ספריית ZIP להרחבה של APK שקורא את הנתונים ישירות מקובץ ה-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.

תהליך ההורדה ברמה גבוהה ייראה כך:

  1. המשתמש בוחר להתקין את האפליקציה מ-Google Play.
  2. אם ל-Google Play יש אפשרות להוריד את קובצי ההרחבה (במקרה של רוב מכשירים), הוא מוריד אותם יחד עם ה-APK.

    אם לא תהיה ל-Google Play אפשרות להוריד את קובצי ההרחבה, תתבצע הורדה של APK בלבד.

  3. כשהמשתמש מפעיל את האפליקציה, צריך לבדוק אם קובצי ההרחבה פועלים כבר שמורות במכשיר.
    1. אם כן, האפליקציה מוכנה להפעלה.
    2. אם לא, האפליקציה חייבת להוריד את קובצי ההרחבה ב-HTTP מ-Google Play. האפליקציה שלך חייבים לשלוח בקשה ללקוח Google Play באמצעות שירות רישוי האפליקציות של Google Play, ש התגובה כוללת את השם, גודל הקובץ וכתובת ה-URL של כל קובץ הרחבה. המידע הזה מאפשר לכם להוריד את הקבצים ולשמור אותם במיקום האחסון הנכון.

זהירות: חשוב מאוד לכלול את הקוד הדרוש כדי להוריד את קובצי ההרחבה מ-Google Play אם הקבצים עדיין לא נמצאים במכשיר כשהאפליקציה מופעלת. כמו שנאמר בקטע הבא לגבי הורדת קובצי ההרחבה, הכנו לכם ספרייה זמינה מפשט מאוד את התהליך ומבצע את ההורדה משירות עם כמות מינימלית של לקבל ממך קוד.

רשימת משימות לפיתוח

הנה סיכום של המשימות שצריך לבצע כדי להשתמש בקובצי הרחבה יחד עם app:

  1. קודם כול צריך לבדוק אם גודל ההורדה הדחוס של האפליקציה צריך להיות גדול מ-100MB. השטח יקר, ועליך לשמור על גודל הורדה כולל קטן ככל האפשר. אם האפליקציה משתמשת ביותר מ-100MB כדי לספק כמה גרסאות של הנכסים הגרפיים לכמה מסכים צפיפות, במקום זאת כדאי לפרסם מספר חבילות APK שבהן כל APK מכילה רק את הנכסים הנדרשים למסכים שאליהם הוא מטרגט. לקבלת התוצאות הטובות ביותר, לפרסם ב-Google Play, להעלות קובץ Android App Bundle, כולל את כל הקוד והמשאבים שנאספו באפליקציה, אבל הוא מונע יצירת APK וכניסה ל-Google הפעלה.
  2. קובעים אילו משאבי אפליקציה צריך להפריד מה-APK ואריזתם אותם בקובץ שישמש כקובץ ההרחבה הראשי.

    בדרך כלל צריך להשתמש בקובץ הרחבת התיקון השני רק כשמבצעים עדכונים כדי בקובץ ההרחבות הראשי. עם זאת, אם המשאבים שלך חורגים ממגבלת 2GB עבור תוכלו להשתמש בקובץ התיקון לשאר הנכסים.

  3. פיתחו את האפליקציה כך שתשתמש במשאבים מקובצי ההרחבה שלכם מיקום האחסון המשותף של המכשיר.

    חשוב לזכור שאסור למחוק, להעביר או לשנות את השם של קובצי ההרחבה.

    אם באפליקציה שלך אין דרישה לפורמט מסוים, מומלץ ליצור קובצי ZIP את קובצי ההרחבה שלך, ולאחר מכן לקרוא אותם באמצעות קובץ ZIP של הרחבת APK ספרייה.

  4. צריך להוסיף לוגיקה לפעילות הראשית של האפליקציה, שבודקת אם קובצי ההרחבה נמצאים במכשיר בזמן ההפעלה. אם הקבצים לא נמצאים במכשיר, יש להשתמש בשירות רישוי אפליקציות של Google Play כדי לבקש כתובות URL של קובצי ההרחבות, ואז להוריד ולשמור אותם.

    כדי לצמצם משמעותית את כמות הקוד שצריך לכתוב ולהבטיח חוויית משתמש טובה במהלך ההורדה, מומלץ להשתמש בכלי ההורדה ספרייה כדי להטמיע את התנהגות ההורדה.

    אם אתם יוצרים שירות הורדות משלכם במקום להשתמש בספרייה, חשוב לדעת אסור לשנות את השמות של קובצי ההרחבה, והם צריכים לשמור אותם מיקום האחסון.

לאחר שמסיימים לפתח את האפליקציה, פועלים לפי המדריך בדיקה קובצי ההרחבות

כללים ומגבלות

הוספת קובצי הרחבה של APK היא תכונה שזמינה כשמעלים את האפליקציה באמצעות ה Play Console. כשמעלים את האפליקציה בפעם הראשונה או כשמעדכנים אפליקציה אפליקציה שמשתמשת בקובצי הרחבה, עליכם להיות מודעים לכללים ולמגבלות הבאים:

  1. כל קובץ הרחבה יכול להיות בנפח של עד 2GB.
  2. כדי להוריד את קובצי ההרחבה שלך מ-Google Play, המשתמש חייב צירפתם את האפליקציה מ-Google Play. Google Play לא לספק את כתובות ה-URL של קובצי ההרחבה שלך, אם האפליקציה הותקנה באמצעים אחרים.
  3. בעת ביצוע ההורדה מתוך האפליקציה, כתובת ה-URL ש-Google Play מספקת לכל קובץ באופן ייחודי, וכל הורדה פגה זמן קצר לאחר שהיא ניתנה לאפליקציה.
  4. אם מעדכנים את האפליקציה עם APK חדש או מעלים מספר חבילות APK לאותו APK של האפליקציה, תוכלו לבחור קובצי הרחבה שהעליתם לחבילת APK קודמת. שם קובץ ההרחבה לא משתנה – הוא נשאר בגרסה שהתקבלה ב-APK למשך שאליו הקובץ שויך במקור.
  5. אם אתם משתמשים בקובצי הרחבה בשילוב עם מספר חבילות APK כדי מספקים קובצי הרחבה שונים למכשירים שונים, אבל עדיין צריך להעלות חבילות APK נפרדות לכל מכשיר כדי לספק versionCode ייחודי ולהצהיר על מסננים שונים כל APK.
  6. לא ניתן לבצע עדכון לאפליקציה באמצעות שינוי קובצי ההרחבה בלבד — תצטרכו להעלות APK חדש כדי לעדכן את האפליקציה. אם רק השינויים שביצעתם בנוגע לנכסים בקובצי ההרחבה, ניתן לעדכן את ה-APK פשוט על ידי שינוי versionCode (ו אולי גם versionName).

  7. אין לשמור נתונים אחרים ב-obb/ שלנו. אם צריך לפרוק חלק מהנתונים, צריך לשמור אותם במיקום שצוין על ידי getExternalFilesDir().
  8. אין למחוק את קובץ ההרחבה .obb או לשנות את שמו (אלא אם ביצוע עדכון). הפעולה הזו תגרום ל-Google Play (או לאפליקציה עצמה) לבצע שוב ושוב מורידים את קובץ ההרחבה.
  9. כשמעדכנים קובץ הרחבה באופן ידני, צריך למחוק את קובץ ההרחבה הקודם.

מתבצעת הורדה של קובצי ההרחבה

ברוב המקרים, מערכת Google Play מורידה את קובצי ההרחבה ושומרת אותם במכשיר באותו זמן בכל פעם שתתבצע התקנה או עדכון של ה-APK. באופן כזה, קובצי ההרחבה זמינים מופעלת בפעם הראשונה. עם זאת, במקרים מסוימים האפליקציה חייבת להוריד את את קובצי ההרחבה עצמה על-ידי בקשתם מכתובת ה-URL שסופקה לך בתגובה משירות רישוי האפליקציות של Google Play.

הלוגיקה הבסיסית שנדרשת להורדת קובצי ההרחבה היא:

  1. כשהאפליקציה מופעלת, צריך לחפש את קובצי ההרחבה במיקום האחסון המשותף (בשדה Android/obb/<package-name>/).
    1. אם קובצי ההרחבה מופיעים, הכול מוכן והאפליקציה יכולה להמשיך.
    2. אם קובצי ההרחבה לא מופיעים שם:
      1. עליך לבצע בקשה באמצעות רישוי האפליקציות של Google Play כדי לקבל את את השמות, הגדלים וכתובות ה-URL של קובצי ההרחבה של האפליקציה.
      2. משתמשים בכתובות ה-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 ושומר אותן במיקום הנכון באחסון המשותף של המכשיר. במהלך פיתוח התהליך הזה באפליקציה, יש כמה בעיות שצריך לטפל בהן התעניינות ברכישה:

  • יכול להיות שאין במכשיר מספיק מקום לקובצי ההרחבה, לכן כדאי לבדוק לפני התחלת ההורדה, ולהזהיר את המשתמש אם אין מספיק מקום.
  • הורדות קבצים צריכות להתבצע בשירות ברקע כדי למנוע את חסימת המשתמש אינטראקציה ומאפשרים למשתמש לצאת מהאפליקציה במהלך ההורדה.
  • עשויות להתרחש שגיאות שונות במהלך הבקשה וההורדה שתצטרכו לטפל באלגנטיות.
  • קישוריות הרשת עשויה להשתנות במהלך ההורדה, לכן עליך לטפל בשינויים כאלה אם ההורדה הופסקה, ממשיכים את ההורדה כשאפשר.
  • בזמן שההורדה מתבצעת ברקע, עליך לספק התראה מציין את התקדמות ההורדה, מודיע למשתמש כשהסתיימה ומחזיר את המשתמש באפליקציה שנבחרה.

כדי לפשט את התהליך הזה, יצרנו את ספריית ההורדות, שמבקש את כתובות ה-URL של קובץ ההרחבה דרך שירות הרישוי, מוריד את קובצי ההרחבה, מבצע את כל המשימות שצוינו למעלה, ואפילו מאפשר לפעילות שלכם להשהות ולהמשיך להוריד. על ידי הוספה של ספריית ההורדות וכמה קטעי קוד לאפליקציה, כמעט כל להוריד את קובצי ההרחבה כבר מקודדים בשבילכם. לכן, כדי לספק חוויית משתמש מינימלית עבורך, מומלץ להשתמש בספריית ההורדות כדי מורידים את קובצי ההרחבות. בקטעים הבאים מוסבר איך לשלב את בספרייה בתוך האפליקציה.

אם אתם מעדיפים לפתח פתרון משלכם כדי להוריד את קובצי ההרחבה, באמצעות כתובות URL של Play, חייבות לפעול בהתאם לאפליקציה מסמכי רישוי לביצוע בקשת רישיון, ולאחר מכן אחזור השמות של קובצי ההרחבה, גדלים וכתובות URL מהתוספות של התשובות. עליך להשתמש במחלקה APKExpansionPolicy (כלולה בספריית אימות הרישיונות) בתור הרישוי המדיניות הזו, שמתעדת את השמות, הגדלים וכתובות ה-URL של קובצי ההרחבה משירות הרישוי.

מידע על ספריית ההורדות

כדי להשתמש בקובצי הרחבה של APK באפליקציה שלך ולספק את חוויית המשתמש הטובה ביותר מאמץ מינימלי בשמך, מומלץ להשתמש בספריית ההורדות שכלולה חבילת ספריית ההרחבה של APK ב-Google Play. הספרייה הזו מורידה את קובצי ההרחבה במסגרת שירות ברקע, מציג התראה למשתמש עם סטטוס ההורדה, מטפל ברשת אובדן קישוריות, המשך ההורדה כשהדבר יתאפשר ועוד.

כדי להטמיע הורדות של קובצי הרחבות באמצעות ספריית ההורדות, צריך רק:

  • להרחיב מחלקה מיוחדת של Service ומחלקה משנית BroadcastReceiver, שכל אחת מהן דורשת רק כמה דוגמאות שורות קוד ממך.
  • מוסיפים לוגיקה לפעילות העיקרית, שבודקת אם יש לקובצי ההרחבה שכבר הורד, ואם לא, מפעיל את תהליך ההורדה ומציג ממשק המשתמש של ההתקדמות.
  • להטמיע ממשק קריאה חוזרת (callback) שכולל מספר שיטות בפעילות העיקרית, מקבל עדכונים לגבי התקדמות ההורדה.

בקטעים הבאים מוסבר איך להגדיר את האפליקציה באמצעות ספריית ההורדות.

בהכנה לשימוש בספריית ההורדות

כדי להשתמש בספריית ההורדות, צריך להוריד שתי חבילות מ'מנהל ה-SDK' ולהוסיף את הספריות המתאימות אפליקציה.

קודם כול, פותחים את מנהל ה-SDK של Android (כלים > SDK Manager), ובקטע מראה ו התנהגות > הגדרות מערכת > Android SDK, יש לבחור כדי לבחור ולהוריד את הכרטיסייה SDK Tools:

  • חבילה של ספריית הרישיונות ב-Google Play
  • חבילת ספריית הרחבה של APK ל-Google Play

יצירת מודול ספרייה חדש ל'ספרייה לאימות רישיונות' ול'כלי ההורדה' ספרייה. לכל ספרייה:

  1. בוחרים באפשרות קובץ > חדש > מודול חדש.
  2. בחלון יצירת מודול חדש בוחרים באפשרות ספריית Android. ובוחרים באפשרות הבא.
  3. מציינים שם אפליקציה/ספרייה, כמו 'ספריית הרישיונות של Google Play' וב-Google Play Downloader Library, בוחרים באפשרות Minimum SDK level (רמת SDK מינימלית). סיום.
  4. בוחרים באפשרות קובץ > מבנה הפרויקט.
  5. בוחרים בכרטיסייה מאפיינים ובספרייה מאגר, יש להזין את הספרייה מהספרייה <sdk>/extras/google/ (play_licensing/ לספריית אימות הרישיון או play_apk_expansion/downloader_library/ לספריית ההורדות).
  6. לוחצים על OK כדי ליצור את המודול החדש.

הערה: ספריית ההורדות תלויה ברישיון ספריית האימות. חשוב להוסיף את הרישיון ספריית אימות לנכסי הפרויקט של ספריית ההורדות.

לחלופין, משורת הפקודה, מעדכנים את הפרויקט כך שיכלול את הספריות:

  1. שינוי ספריות לספרייה <sdk>/tools/.
  2. מפעילים את android update project עם האפשרות --library כדי להוסיף גם את LVL וספריית הורדות של הפרויקט. מוצרים לדוגמה:
    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 כוללת דוגמה יישום שמראה איך להשתמש בספריית ההורדות באפליקציה. הדוגמה משתמשת בספרייה שלישית זמינה בחבילת ה-APK תוספים בשם 'ספריית ZIP של תוספים ל-APK'. אם המיקום שאתם מתכננים באמצעות קובצי ZIP לקובצי הרחבה, מומלץ להוסיף גם את ספריית ה-ZIP להרחבת APK באפליקציה שלך. מידע נוסף זמין בקטע הבא על שימוש בספריית Zip להרחבת APK.

מצהיר על הרשאות המשתמש

כדי להוריד את קובצי ההרחבה, צריך להיכנס לספריית ההורדות נדרשות כמה הרשאות שעליך להצהיר בקובץ המניפסט של האפליקציה. הם הן:

<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>

הערה: כברירת מחדל, נדרש API לספריית ההורדות רמה 4, אבל לספריית Zip להרחבה של APK נדרשת רמת API 5.

הטמעת שירות ההורדה

כדי לבצע הורדות ברקע, ספריית ההורדות מספקת הבעלים של מחלקה משנית אחת (Service) בשם DownloaderService שאותה צריך להרחיב. לחשבון בנוסף להורדת קובצי ההרחבה, DownloaderService:

  • רושם BroadcastReceiver שמאזינים לשינויים ב- קישוריות הרשת של המכשיר (ה-CONNECTIVITY_ACTION שידור) כדי להשהות את ההורדה במקרה הצורך (למשל בגלל אובדן קישוריות) להמשיך את ההורדה כשזה אפשרי (קישוריות נרכשת).
  • תזמון התראה של RTC_WAKEUP כדי לנסות להוריד שוב עבור מקרים שבהם השירות נהרג.
  • יוצר Notification בהתאמה אישית שמציג את התקדמות ההורדה שגיאות או שינויים במצב.
  • מאפשרת לאפליקציה להשהות ולהמשיך את ההורדה באופן ידני.
  • מאשרת שנפח האחסון המשותף נטען וזמין, שהקבצים לא קיימים כבר, ושיש מספיק מקום, וכל זה לפני הורדת קובצי ההרחבה. לאחר מכן המשתמש שולח הודעה אם אחד מהתנאים האלה אינו נכון.

כל מה שצריך לעשות הוא ליצור באפליקציה מחלקה שתרחיב את הכיתה DownloaderService ולשנות שלוש שיטות כדי לספק פרטים ספציפיים לגבי האפליקציה:

getPublicKey()
הפעולה הזו חייבת להחזיר מחרוזת שהיא המפתח הציבורי של ה-RSA בקידוד Base64 של בעל התוכן הדיגיטלי חשבון, זמין בדף הפרופיל ב-Play Console (מידע נוסף זמין בקטע הגדרת רישוי).
getSALT()
הפעולה הזו חייבת להחזיר מערך של בייטים אקראיים שבהם נעשה שימוש ברישוי Policy כדי יוצרים Obfuscator. ה-salt מבטיח שה-SharedPreferences המעורפל (obfuscation) שבו שמורים נתוני הרישוי, יהיה ייחודי ולא ניתן יהיה לגלות.
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 מספריית ההורדות, שבודקת את סטטוס ההורדה ומפעילים מחדש במקרה הצורך.

צריך רק לעקוף את השיטה 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>

התחלת ההורדה

הפעילות העיקרית באפליקציה שלך (זו שהתחילה על ידי סמל מרכז האפליקציות) היא אחראי לבדוק אם קובצי ההרחבה כבר נמצאים במכשיר ולהתחיל ליזום ההורדה, אם לא.

כדי להתחיל את ההורדה באמצעות ספריית ההורדות, נדרשים הדברים הבאים הליכים:

  1. בודקים אם הקבצים הורדו.

    ספריית ההורדות כוללת מספר ממשקי API במחלקה Helper, עזרה בתהליך הזה:

    • getExpansionAPKFileName(Context, c, boolean mainFile, int versionCode)
    • doesFileExist(Context c, String fileName, long fileSize)

    לדוגמה, האפליקציה לדוגמה שסופקה בחבילת הרחבת ה-APK מפעילה את הבאה בשיטה 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, האפליקציה חייבת להתחיל את ההורדה.

  2. כדי להתחיל את ההורדה, קוראים לשיטה הסטטית 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
    }
    
  3. כאשר השיטה startDownloadServiceIfRequired() מחזירה ערך אחר מאשר NO_DOWNLOAD_REQUIRED, יוצרים מופע של IStub באמצעות מתקשרת אל DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService). IStub מספק קישור בין הפעילות שלך לבין כלי ההורדה כך שהפעילות שלך תקבל התקשרות חזרה לגבי התקדמות ההורדה.

    כדי ליצור מופע של IStub באמצעות קריאה ל-CreateStub(), צריך להעביר אותו הטמעה של הממשק IDownloaderClient ו-DownloaderService יישום בפועל. הקטע הבא שעוסק בקבלת התקדמות ההורדה הממשק IDownloaderClient, שאותו בדרך כלל צריך להטמיע במחלקה Activity, כדי לעדכן את ממשק המשתמש של הפעילות כשמצב ההורדה משתנה.

    מומלץ להתקשר אל CreateStub() כדי ליצור יצירה של IStub במהלך ה-method 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)
שירות ההורדות מפעיל אותו כדי לספק אובייקט DownloadProgressInfo, שמתאר מידע שונה על התקדמות ההורדה, כולל זמן משוער שנותר, המהירות הנוכחית, ההתקדמות הכוללת וסך הכול, כדי שניתן יהיה לעדכן את ממשק המשתמש של התקדמות ההורדה.

טיפ: דוגמאות של הקריאות החוזרות (callback) האלה לעדכון ההורדה ממשק המשתמש של ההתקדמות, תוכלו לראות את SampleDownloaderActivity באפליקציה לדוגמה שסופקה עם חבילת הרחבת APK.

הנה כמה שיטות ציבוריות שאולי יעזרו לך להשתמש בממשק 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 במקרים שלא באמצעות ספריית ההורדות, עיינו בתיעוד של הוספת רישיונות לאפליקציה. שמסבירה איך ליישם מדיניות רישיון כמו המדיניות הזו.

קריאת קובץ ההרחבה

אחרי שקובצי הרחבת ה-APK נשמרים במכשיר, אופן קריאת הקבצים תלוי בסוג הקובץ שבו אתם משתמשים. כפי שצוין בסקירה הכללית, קובצי הרחבות יכולים להיות כל סוג של קובץ שרוצים, אבל השמות שלהם משתנים לפי פורמט מסוים של שם הקובץ, והם נשמרים <shared-storage>/Android/obb/<package-name>/

לא משנה איך אתה קורא את הקבצים שלך, תמיד עליך לבדוק קודם אחסון זמין לקריאה. יש סיכוי שהאחסון של המשתמש מותקן ב מחובר באמצעות USB או למעשה הסיר את כרטיס ה-SD.

הערה: כשהאפליקציה מופעלת, תמיד צריך לבדוק אם שטח האחסון החיצוני זמין וקריא על ידי שליחת קריאה אל getExternalStorageState(). הפונקציה מחזירה אחת מתוך כמה מחרוזות אפשריות שמייצגים את המצב של האחסון החיצוני. כדי שהטקסט יהיה קריא app, הערך המוחזר חייב להיות 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 כשרוצים לגשת לתוסף חדש.

למידע נוסף על קריאה מהאחסון המשותף, אפשר לעיין באחסון הנתונים התיעוד.

שימוש בספריית ZIP להרחבה של APK

חבילת ה-Apk בקשת Google Market כוללת ספרייה בשם ה-APK ספריית ZIP להרחבה (נמצאת ב-<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 וירטואלי מערכת הקבצים על סמך קובצי ה-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);

הקוד שלמעלה מספק גישה לכל קובץ שקיים בקובץ ההרחבה הראשי או תיקון קובץ הרחבות, על ידי קריאה של מפה ממוזגת של כל הקבצים משני הקבצים. נשאר רק אצלך צריך לספק את ה-method 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, צריך צריך לבדוק את היכולת של האפליקציה לקרוא את הקבצים מהאחסון המשותף. כל מה שצריך לעשות הוא להוסיף את הקבצים למיקום המתאים באחסון המשותף במכשיר ולהפעיל את app:

  1. במכשיר שלכם, יוצרים את הספרייה המתאימה באחסון המשותף, שבו Google הקבצים שלך יישמרו ב-Play.

    לדוגמה, אם שם החבילה הוא com.example.android, עליך ליצור הספרייה Android/obb/com.example.android/ בשטח האחסון המשותף. (חיבור את מכשיר הבדיקה למחשב כדי לטעון את נפח האחסון המשותף וליצור אותו באופן ידני ).

  2. להוסיף באופן ידני את קובצי ההרחבה לספרייה הזו. חשוב לוודא ששינית את שמות הקבצים ל- תואם לפורמט שם הקובץ שישמש את Google Play.

    לדוגמה, בלי קשר לסוג הקובץ, קובץ ההרחבה הראשי של אפליקציית com.example.android צריך להיות main.0300110.com.example.android.obb. קוד הגרסה יכול להיות כל ערך שרוצים. רק חשוב לזכור:

    • קובץ ההרחבה הראשי תמיד מתחיל ב-main וקובץ התיקון מתחיל ב- patch.
    • שם החבילה תמיד תואם לשם ה-APK שאליו הקובץ מצורף Google Play.
  3. עכשיו כשקובצי ההרחבה נמצאים במכשיר, אפשר להתקין ולהפעיל את האפליקציה לבדוק את קובצי ההרחבה.

הנה כמה תזכורות לגבי הטיפול בקובצי ההרחבות:

  • אין למחוק או לשנות את השם של קובצי ההרחבה .obb (גם אם פורקים את החבילה) את הנתונים למיקום אחר). הפעולה הזו תגרום ל-Google Play (או לאפליקציה עצמה) להוריד את קובץ ההרחבה שוב ושוב.
  • אין לשמור נתונים אחרים ב-obb/ שלנו. אם צריך לפרוק חלק מהנתונים, צריך לשמור אותם במיקום שצוין על ידי getExternalFilesDir().

בדיקה של הורדות קבצים

כי לפעמים האפליקציה צריכה להוריד באופן ידני את קובצי ההרחבה בפעם הראשונה. נפתחת, חשוב לבדוק את התהליך כדי לוודא שהאפליקציה יכולה לשלוח שאילתות של כתובות ה-URL, מורידים את הקבצים ושומרים אותם במכשיר.

כדי לבדוק את היישום של תהליך ההורדה הידנית באפליקציה: אפשר לפרסם אותו במסלול הבדיקה הפנימית, כך שהוא יהיה זמין רק בודקים מורשים. אם הכול פועל כצפוי, האפליקציה אמורה לפעול להתחיל להוריד את קובצי ההרחבה ברגע שהפעילות הראשית מתחילה.

הערה: בעבר אפשר היה לבדוק אפליקציה לפי העלאת "טיוטה" שלא פורסמה . הפונקציונליות הזו כבר לא בתוקף נתמך. במקום זאת, צריך לפרסם אותה לבדיקה פנימית, בקבוצה מוגדרת או לבדיקה פתוחה לציבור טראק. מידע נוסף זמין במאמר הבא: אפליקציות טיוטה לא תמיכה ארוכה יותר.

האפליקציה מתעדכנת

אחד היתרונות הנהדרים של שימוש בקובצי הרחבה ב-Google Play הוא היכולת לעדכן את האפליקציה בלי להוריד מחדש את כל הנכסים המקוריים. כי Google Play מאפשר לספק שני קובצי הרחבות לכל APK, אפשר להשתמש בקובץ השני כ-patch שמספק עדכונים ונכסים חדשים. פעולה זו מונעת תצטרכו להוריד מחדש את קובץ ההרחבה הראשי, שעשוי להיות גדול ויקר למשתמשים.

מבחינה טכנית קובץ הרחבת התיקון זהה לקובץ ההרחבה הראשי, מערכת Android או Google Play מבצעים תיקונים אמיתיים בין הרחבת התיקון . קוד האפליקציה חייב לבצע את התיקונים הדרושים בעצמו.

אם משתמשים בקובצי ZIP כקובצי הרחבה, קובץ ZIP של הרחבת ה-APK ספרייה שכלולה בחבילת ההרחבה Apk כוללת את האפשרות למזג על תיקון בקובץ ההרחבה הראשי.

הערה: גם אם צריך לערוך שינויים רק בתיקון עליך לעדכן את ה-APK כדי ש-Google Play יבצע עדכון, בכל זאת. אם לא צריך לבצע שינויים בקוד באפליקציה, צריך פשוט לעדכן את versionCode .

כל עוד לא משנים את קובץ ההרחבה הראשי שמשויך ל-APK ב-Play Console, משתמשים שהתקינו את האפליקציה שלך בעבר לא מורידים את קובץ ההרחבה הראשי. משתמשים קיימים מקבלים רק את ה-APK המעודכן ואת התיקון החדש קובץ הרחבות (ששומר על קובץ ההרחבה הראשי הקודם).

ריכזנו כאן כמה בעיות שכדאי לזכור לגבי עדכונים בקובצי הרחבה:

  • ניתן להפעיל רק שני קובצי הרחבה לאפליקציה שלכם בו-זמנית. הרחבה ראשית אחת קובץ וקובץ הרחבות אחד של תיקונים. במהלך עדכון קובץ, Google Play מוחקת את הגרסה הקודמת (וכך גם האפליקציה בעת ביצוע עדכונים ידניים).
  • בעת הוספת קובץ הרחבת תיקון, מערכת Android לא באמת מתקן את האפליקציה או קובץ ההרחבה הראשי. צריך לעצב את האפליקציה כך שתתמוך בנתוני התיקונים. עם זאת, חבילת ההרחבה Apk כוללת ספרייה לשימוש בקובצי ZIP כקובצי הרחבות, שממזגים את הנתונים מקובץ התיקון אל קובץ ההרחבה הראשי, תוכלו לקרוא בקלות את כל הנתונים של קובץ ההרחבה.