הגדרת הצגה על פי דרישה

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

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

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

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

בדף הזה מוסבר איך להוסיף מודול תכונה לפרויקט האפליקציה ולהגדיר אותו להעברה לפי דרישה. לפני שמתחילים, מוודאים שמשתמשים ב-Android Studio 3.5 ומעלה וב-Android Gradle Plugin 3.5.0 ומעלה.

הגדרת מודול חדש למשלוח על פי דרישה

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

כדי להוסיף מודול תכונות לפרויקט האפליקציה באמצעות Android Studio, פועלים לפי השלבים הבאים:

  1. אם עדיין לא עשיתם זאת, פותחים את פרויקט האפליקציה בסביבת הפיתוח המשולבת.
  2. בסרגל התפריטים, לוחצים על קובץ > חדש > מודול חדש.
  3. בתיבת הדו-שיח Create New Module (יצירת מודול חדש), בוחרים באפשרות Dynamic Feature Module (מודול תכונות דינמי) ולוחצים על Next (הבא).
  4. בקטע Configure your new module (הגדרת המודול החדש), משלימים את הפעולות הבאות:
    1. בתפריט הנפתח, בוחרים באפשרות Base application module (מודול אפליקציית הבסיס) של פרויקט האפליקציה.
    2. מציינים שם מודול. סביבת הפיתוח המשולבת (IDE) משתמשת בשם הזה כדי לזהות את המודול כפרויקט משנה של Gradle בקובץ ההגדרות של Gradle. כשיוצרים קובץ App Bundle, ‏ Gradle משתמש ברכיב האחרון של שם פרויקט המשנה כדי להוסיף את מאפיין <manifest split> אל קובץ המניפסט של מודול התכונה.
    3. מציינים את שם החבילה של המודול. כברירת מחדל, Android Studio מציע שם חבילה שמשלב את שם חבילת הבסיס של מודול הבסיס ואת שם המודול שציינתם בשלב הקודם.
    4. בוחרים את רמת ה-API המינימלית שהמודול צריך לתמוך בה. הערך הזה צריך להיות זהה לערך של מודול הבסיס.
  5. לוחצים על הבא.
  6. בקטע Module Download Options (אפשרויות להורדת מודולים), מבצעים את הפעולות הבאות:

    1. מציינים את שם המודול באורך של עד 50 תווים. הפלטפורמה משתמשת בשם הזה כדי לזהות את המודול למשתמשים, למשל, כשמאשרים אם המשתמש רוצה להוריד את המודול. לכן, מודול הבסיס של האפליקציה צריך לכלול את שם המודול כמשאב מחרוזת שניתן לתרגם. כשיוצרים את המודול באמצעות Android Studio, סביבת הפיתוח המשולבת (IDE) מוסיפה את משאב המחרוזת למודול הבסיס ומזריקה את הרשומה הבאה למניפסט של מודול התכונה:

      <dist:module
          ...
          dist:title="@string/feature_title">
      </dist:module>
      
    2. בתפריט הנפתח בקטע הכללה בזמן ההתקנה, בוחרים באפשרות אל תכלול את המודול בזמן ההתקנה. ‫Android Studio מוסיף את הקוד הבא למניפסט של המודול כדי לשקף את הבחירה שלכם:

      <dist:module ... >
        <dist:delivery>
            <dist:on-demand/>
        </dist:delivery>
      </dist:module>
      
    3. מסמנים את התיבה שליד Fusing אם רוצים שהמודול הזה יהיה זמין למכשירים עם Android מגרסה 4.4 (רמת API‏ 20) ומטה, וייכלל בקובצי APK מרובים. המשמעות היא שאפשר להפעיל התנהגות לפי דרישה עבור המודול הזה ולהשבית את המיזוג כדי להשמיט אותו ממכשירים שלא תומכים בהורדה ובהתקנה של חבילות APK מפוצלות. ‫Android Studio מוסיף את הקוד הבא למניפסט של המודול כדי לשקף את הבחירה שלכם:

      <dist:module ...>
          <dist:fusing dist:include="true | false" />
      </dist:module>
      
  7. לוחצים על סיום.

אחרי ש-Android Studio מסיים ליצור את המודול, בודקים את התוכן שלו בעצמכם בחלונית Project (בוחרים באפשרות View > Tool Windows > Project מסרגל התפריטים). קוד ברירת המחדל, המשאבים והארגון צריכים להיות דומים לאלה של מודול האפליקציה הרגיל.

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

הוספה של ספריית הפצת הפיצ'רים ב-Play לפרויקט

לפני שמתחילים, צריך קודם להוסיף את ספריית Play Feature Delivery לפרויקט.

שליחת בקשה למודול On demand

כשהאפליקציה צריכה להשתמש במודול תכונות, היא יכולה לבקש אותו כשהיא ברקע באמצעות המחלקה SplitInstallManager. כששולחים בקשה, האפליקציה צריכה לציין את שם המודול כפי שהוא מוגדר ברכיב split במניפסט של מודול היעד. כשיוצרים מודול תכונות באמצעות Android Studio, מערכת ה-build משתמשת בשם המודול שסיפקתם כדי להחדיר את המאפיין הזה למניפסט של המודול בזמן ההידור. מידע נוסף זמין במאמר בנושא מניפסטים של מודולים של תכונות.

לדוגמה, נניח שיש אפליקציה עם מודול על פי דרישה לצילום ושליחה של הודעות עם תמונות באמצעות המצלמה של המכשיר, ובמודול הזה מוגדר split="pictureMessages" במניפסט שלו. בדוגמה הבאה נעשה שימוש ב-SplitInstallManager כדי לבקש את המודול pictureMessages (יחד עם מודול נוסף לכמה מסננים שיווקיים):

Kotlin

// Creates an instance of SplitInstallManager.
val splitInstallManager = SplitInstallManagerFactory.create(context)

// Creates a request to install a module.
val request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build()

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener { sessionId -> ... }
    .addOnFailureListener { exception ->  ... }

Java

// Creates an instance of SplitInstallManager.
SplitInstallManager splitInstallManager =
    SplitInstallManagerFactory.create(context);

// Creates a request to install a module.
SplitInstallRequest request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build();

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener(sessionId -> { ... })
    .addOnFailureListener(exception -> { ... });

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

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

כדי לקבל גישה לקוד ולמשאבים של המודול, צריך להפעיל את SplitCompat באפליקציה. שימו לב ש-SplitCompat לא נדרש לאפליקציות מיידיות ל-Android.

דחיית ההתקנה של מודולים לפי דרישה

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

אפשר לציין מודול להורדה מאוחרת באמצעות השיטה deferredInstall(), כמו שמוצג בהמשך. בנוסף, בניגוד ל-SplitInstallManager.startInstall(), האפליקציה לא צריכה להיות בחזית כדי ליזום בקשה להתקנה מושהית.

Kotlin

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(listOf("promotionalFilters"))

Java

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(Arrays.asList("promotionalFilters"));

בקשות להתקנות מושהות הן על בסיס best-effort, ואי אפשר לעקוב אחרי ההתקדמות שלהן. לכן, לפני שמנסים לגשת למודול שצוין להתקנה מושהית, צריך לוודא שהמודול הותקן. אם אתם צריכים שהמודול יהיה זמין באופן מיידי, אתם יכולים להשתמש בפקודה SplitInstallManager.startInstall() כדי לבקש אותו, כמו שמוסבר בקטע הקודם.

מעקב אחרי סטטוס הבקשה

כדי לעדכן את סרגל ההתקדמות, להפעיל intent אחרי ההתקנה או לטפל בצורה תקינה בשגיאת בקשה, צריך להאזין לעדכוני סטטוס מהמשימה האסינכרונית SplitInstallManager.startInstall(). כדי להתחיל לקבל עדכונים לגבי בקשת ההתקנה, צריך לרשום listener ולקבל את מזהה הסשן של הבקשה, כמו שמוצג בהמשך.

Kotlin

// Initializes a variable to later track the session ID for a given request.
var mySessionId = 0

// Creates a listener for request status updates.
val listener = SplitInstallStateUpdatedListener { state ->
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
}

// Registers the listener.
splitInstallManager.registerListener(listener)

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener { sessionId -> mySessionId = sessionId }
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener { exception ->
        // Handle request errors.
    }

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener)

Java

// Initializes a variable to later track the session ID for a given request.
int mySessionId = 0;

// Creates a listener for request status updates.
SplitInstallStateUpdatedListener listener = state -> {
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
};

// Registers the listener.
splitInstallManager.registerListener(listener);

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener(sessionId -> { mySessionId = sessionId; })
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener(exception -> {
        // Handle request errors.
    });

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener);

טיפול בשגיאות בבקשות

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

מבחינת הקוד, צריך לטפל בכשלים בהורדה או בהתקנה של מודול באמצעות addOnFailureListener(), כמו שמוצג בהמשך:

Kotlin

splitInstallManager
    .startInstall(request)
    .addOnFailureListener { exception ->
        when ((exception as SplitInstallException).errorCode) {
            SplitInstallErrorCode.NETWORK_ERROR -> {
                // Display a message that requests the user to establish a
                // network connection.
            }
            SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED -> checkForActiveDownloads()
            ...
        }
    }

fun checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .sessionStates
        .addOnCompleteListener { task ->
            if (task.isSuccessful) {
                // Check for active sessions.
                for (state in task.result) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        }
}

Java

splitInstallManager
    .startInstall(request)
    .addOnFailureListener(exception -> {
        switch (((SplitInstallException) exception).getErrorCode()) {
            case SplitInstallErrorCode.NETWORK_ERROR:
                // Display a message that requests the user to establish a
                // network connection.
                break;
            case SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED:
                checkForActiveDownloads();
            ...
    });

void checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .getSessionStates()
        .addOnCompleteListener( task -> {
            if (task.isSuccessful()) {
                // Check for active sessions.
                for (SplitInstallSessionState state : task.getResult()) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        });
}

בטבלה הבאה מתוארים מצבי השגיאה שאולי האפליקציה שלכם תצטרך לטפל בהם:

קוד שגיאה תיאור הצעה לפעולה
ACTIVE_SESSIONS_LIMIT_EXCEEDED הבקשה נדחתה כי יש לפחות בקשה קיימת אחת שנמצאת כרגע בתהליך הורדה. בודקים אם יש בקשות שעדיין נמצאות בתהליך הורדה, כמו שמוצג בדוגמה שלמעלה.
MODULE_UNAVAILABLE למערכת Google Play אין אפשרות למצוא את המודול המבוקש על סמך הגרסה המותקנת הנוכחית של האפליקציה, המכשיר וחשבון Google Play של המשתמש. אם למשתמש אין גישה למודול, צריך להודיע לו על כך.
INVALID_REQUEST הבקשה התקבלה ב-Google Play, אבל היא לא תקינה. מוודאים שהמידע שכלול בבקשה מלא ומדויק.
SESSION_NOT_FOUND לא נמצאה סשן עם מזהה סשן נתון. אם אתם מנסים לעקוב אחרי סטטוס הבקשה לפי מזהה הסשן, ודאו שמזהה הסשן נכון.
API_NOT_AVAILABLE ספריית Play Feature Delivery לא נתמכת במכשיר הנוכחי. כלומר, המכשיר לא יכול להוריד ולהתקין תכונות לפי דרישה. במכשירים עם Android בגרסה 4.4 (רמת API‏ 20) ומטה, צריך לכלול מודולים של תכונות בזמן ההתקנה באמצעות מאפיין המניפסט dist:fusing. מידע נוסף זמין במאמר בנושא מניפסט של מודול תכונות.
NETWORK_ERROR הבקשה נכשלה בגלל שגיאה בחיבור לרשת. הצגת הנחיה למשתמש ליצור חיבור לרשת או לעבור לרשת אחרת.
ACCESS_DENIED לא ניתן לרשום את הבקשה באפליקציה בגלל הרשאות לא מספיקות. בדרך כלל זה קורה כשהאפליקציה פועלת ברקע. כדאי לנסות לשלוח את הבקשה כשהאפליקציה חוזרת לחזית.
INCOMPATIBLE_WITH_EXISTING_SESSION הבקשה מכילה מודול אחד או יותר שכבר נשלחה לגביו בקשה, אבל הוא עדיין לא הותקן. אפשר ליצור בקשה חדשה שלא כוללת מודולים שהאפליקציה כבר ביקשה, או לחכות שכל המודולים שהבקשה הנוכחית כוללת יסיימו את ההתקנה ואז לנסות שוב לשלוח את הבקשה.

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

SERVICE_DIED השירות שאחראי לטיפול בבקשה לא פעיל. מנסים לשלוח את הבקשה שוב.

התגובה SplitInstallStateUpdatedListener מתקבלת עם קוד השגיאה SplitInstallSessionState, הסטטוס FAILED ומזהה הסשן -1.

INSUFFICIENT_STORAGE אין במכשיר מספיק נפח אחסון פנוי להתקנת מודול התכונות. הודעה למשתמש שאין לו מספיק נפח אחסון כדי להתקין את התכונה הזו.
SPLITCOMPAT_VERIFICATION_ERROR, SPLITCOMPAT_EMULATION_ERROR, SPLITCOMPAT_COPY_ERROR לא הייתה אפשרות לטעון את מודול התכונה SplitCompat. השגיאות האלה אמורות להיפתר באופן אוטומטי אחרי ההפעלה מחדש הבאה של האפליקציה.
PLAY_STORE_NOT_FOUND אפליקציית חנות Play לא מותקנת במכשיר. ליידע את המשתמש שצריך להוריד את התכונה הזו מאפליקציית חנות Play.
APP_NOT_OWNED האפליקציה לא הותקנה דרך Google Play ואי אפשר להוריד את התכונה. השגיאה הזו יכולה להתרחש רק בהתקנות מושהות. אם רוצים שהמשתמש ירכוש את האפליקציה ב-Google Play, צריך להשתמש ב-startInstall(), שבאמצעותו אפשר לקבל את אישור המשתמש הנדרש.
INTERNAL_ERROR אירעה שגיאה פנימית ב-Play Store. מנסים לשלוח את הבקשה שוב.

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

טיפול בעדכוני סטטוס

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

Kotlin

override fun onStateUpdate(state : SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIED) {
       // Retry the request.
       return
    }
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.DOWNLOADING -> {
              val totalBytes = state.totalBytesToDownload()
              val progress = state.bytesDownloaded()
              // Update progress bar.
            }
            SplitInstallSessionStatus.INSTALLED -> {

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
            }
        }
    }
}

Java

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIES) {
       // Retry the request.
       return;
    }
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.DOWNLOADING:
              int totalBytes = state.totalBytesToDownload();
              int progress = state.bytesDownloaded();
              // Update progress bar.
              break;

            case SplitInstallSessionStatus.INSTALLED:

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
        }
    }
}

בטבלה שבהמשך מפורטים הסטטוסים האפשריים של בקשת ההתקנה.

מצב הבקשה תיאור הצעה לפעולה
בהמתנה הבקשה אושרה וההורדה אמורה להתחיל בקרוב. אתחול של רכיבי ממשק משתמש, כמו סרגל התקדמות, כדי לספק למשתמש משוב על ההורדה.
REQUIRES_USER_CONFIRMATION ההורדה דורשת אישור מהמשתמש. הסטטוס הזה מופיע בדרך כלל אם האפליקציה לא הותקנה דרך Google Play. המשתמש יתבקש לאשר את הורדת התכונה דרך Google Play. מידע נוסף זמין בקטע על קבלת אישור מהמשתמשים.
הורדה ההורדה מתבצעת. אם אתם מספקים סרגל התקדמות להורדה, צריך להשתמש בשיטות SplitInstallSessionState.bytesDownloaded() ו-SplitInstallSessionState.totalBytesToDownload() כדי לעדכן את ממשק המשתמש (ראו את דוגמת הקוד שמעל הטבלה הזו).
הורדו המודול הורד למכשיר אבל ההתקנה עדיין לא התחילה. כדי לקבל גישה למודולים שהורדו ולמנוע את המצב הזה, האפליקציות צריכות להפעיל את SplitCompat. הפעולה הזו נדרשת כדי לגשת לקוד ולמשאבים של מודול התכונות.
מתקין המודול מותקן כרגע במכשיר. מעדכנים את סרגל ההתקדמות. המצב הזה בדרך כלל קצר.
מותקן המודול מותקן במכשיר. קוד גישה ומשאב במודול כדי להמשיך את תהליך המשתמש.

אם המודול מיועד לאפליקציה מיידית ל-Android שפועלת ב-Android 8.0 (רמת API‏ 26) ומעלה, צריך להשתמש ב-splitInstallHelper כדי לעדכן את רכיבי האפליקציה במודול החדש.

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

קבלת אישור מהמשתמש

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

Kotlin

override fun onSessionStateUpdate(state: SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          // an activity result launcher registered via registerForActivityResult
          activityResultLauncher)
    }
    ...
 }

Java

@Override void onSessionStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          // an activity result launcher registered via registerForActivityResult
          activityResultLauncher);
    }
    ...
 }

אפשר לרשום כלי להפעלת תוצאות של פעילות באמצעות החוזה המובנה ActivityResultContracts.StartIntentSenderForResult. מידע נוסף על ממשקי API של תוצאות פעילות

הסטטוס של הבקשה מתעדכן בהתאם לתשובת המשתמש:

  • אם המשתמש מאשר את הבקשה, הסטטוס שלה משתנה ל-PENDING וההורדה מתבצעת.
  • אם המשתמש ידחה את האישור, סטטוס הבקשה ישתנה ל-CANCELED.
  • אם המשתמש לא יבחר אפשרות לפני שהתיבה תיסגר, סטטוס הבקשה יישאר REQUIRES_USER_CONFIRMATION. האפליקציה יכולה להציג למשתמש שוב בקשה להשלמת הפעולה.

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

Kotlin

registerForActivityResult(StartIntentSenderForResult()) { result: ActivityResult -> {
        // Handle the user's decision. For example, if the user selects "Cancel",
        // you may want to disable certain functionality that depends on the module.
    }
}

Java

registerForActivityResult(
    new ActivityResultContracts.StartIntentSenderForResult(),
    new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            // Handle the user's decision. For example, if the user selects "Cancel",
            // you may want to disable certain functionality that depends on the module.
        }
    });

ביטול בקשת התקנה

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

Kotlin

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId)

Java

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId);

גישה למודולים

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

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

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

הפעלת SplitCompat

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

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

הצהרה על SplitCompatApplication במניפסט

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

<application
    ...
    android:name="com.google.android.play.core.splitcompat.SplitCompatApplication">
</application>

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

הפעלת SplitCompat בזמן הריצה

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

אם יש לכם מחלקה מותאמת אישית של Application, צריך להגדיר שהיא תהיה הרחבה של SplitCompatApplication כדי להפעיל את SplitCompat באפליקציה, כמו שמוצג כאן:

Kotlin

class MyApplication : SplitCompatApplication() {
    ...
}

Java

public class MyApplication extends SplitCompatApplication {
    ...
}

פשוט מבטל את ContextWrapper.attachBaseContext() כדי לכלול את SplitCompat.install(Context applicationContext).SplitCompatApplication אם לא רוצים שהמחלקה Application תרחיב את SplitCompatApplication, אפשר לשנות את שיטת attachBaseContext() באופן ידני, כך:

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this);
}

אם מודול ההורדה לפי דרישה תואם לאפליקציות מיידיות ולאפליקציות מותקנות, אפשר להפעיל את SplitCompat באופן מותנה, באופן הבא:

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this)
    }
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this);
    }
}

הפעלת SplitCompat לפעילויות של מודולים

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

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this);
}

גישה לרכיבים שמוגדרים במודולים של תכונות

איך מתחילים פעילות שמוגדרת במודול תכונות

אחרי שמפעילים את SplitCompat, אפשר להפעיל פעילויות שמוגדרות במודולים של תכונות באמצעות startActivity().

Kotlin

startActivity(Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...))

Java

startActivity(new Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...));

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

אם יש לכם פעילות במודול תכונות שהורדתם לפי דרישה, אתם צריכים להפעיל את SplitCompat בפעילות.

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

אחרי שמפעילים את SplitCompat, אפשר להפעיל שירותים שמוגדרים במודולים של תכונות באמצעות startService().

Kotlin

startService(Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...))

Java

startService(new Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...));

ייצוא רכיב שמוגדר במודול תכונות

אסור לכלול רכיבי Android שיוצאו בתוך מודולים אופציונליים.

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

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

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

גישה לקוד ולמשאבים ממודולים מותקנים

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

קוד גישה ממודול אחר

גישה לקוד הבסיס ממודול

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

גישה לקוד של מודול ממודול אחר

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

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

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

Kotlin

// In the base module
interface MyInterface {
  fun hello(): String
}

// In the feature module
object MyInterfaceImpl : MyInterface {
  override fun hello() = "Hello"
}

// In the base module, where we want to access the feature module code
val stringFromModule = (Class.forName("com.package.module.MyInterfaceImpl")
    .kotlin.objectInstance as MyInterface).hello();

Java

// In the base module
public interface MyInterface {
  String hello();
}

// In the feature module
public class MyInterfaceImpl implements MyInterface {
  @Override
  public String hello() {
    return "Hello";
  }
}

// In the base module, where we want to access the feature module code
String stringFromModule =
   ((MyInterface) Class.forName("com.package.module.MyInterfaceImpl").getConstructor().newInstance()).hello();

גישה למשאבים ולנכסים ממודול אחר

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

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

טעינת קוד מקורי באפליקציה באמצעות העברה לפי דרישה

מומלץ להשתמש ב-ReLinker כדי לטעון את כל הספריות המקוריות כשמשתמשים באספקת מודולים של תכונות לפי דרישה. ‫ReLinker פותר בעיה בטעינה של ספריות Native אחרי ההתקנה של מודול תכונות. מידע נוסף על ReLinker זמין בטיפים בנושא Android JNI.

טעינת קוד מקורי ממודול אופציונלי

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

אם אתם משתמשים ב-System.loadLibrary() כדי לטעון את הקוד המקורי, ולספרייה המקורית יש תלות בספרייה אחרת במודול, אתם צריכים לטעון את הספרייה האחרת הזו קודם באופן ידני. אם אתם משתמשים ב-ReLinker, הפעולה המקבילה היא Relinker.recursively().loadLibrary().

אם אתם משתמשים ב-dlopen() בקוד Native כדי לטעון ספריה שמוגדרת במודול אופציונלי, הפעולה לא תעבוד עם נתיבי ספריה יחסיים. הפתרון הכי טוב הוא לאחזר את הנתיב המוחלט של הספרייה מקוד Java באמצעות ClassLoader.findLibrary() ואז להשתמש בו בקריאה ל-dlopen(). צריך לעשות את זה לפני שמזינים את הקוד המקורי, או להשתמש בקריאת JNI מהקוד המקורי ל-Java.

גישה לאפליקציות אינסטנט ל-Android שהותקנו

אחרי שמודול של אפליקציה ללא התקנה ל-Android מדווח על מצב INSTALLED, אפשר לגשת לקוד ולמשאבים שלו באמצעות Context של אפליקציה שרעננה. הקשר שהאפליקציה יוצרת לפני התקנת מודול (לדוגמה, מודול שכבר מאוחסן במשתנה) לא מכיל את התוכן של המודול החדש. אבל הקשר חדש כן עוזר – אפשר לקבל אותו, למשל, באמצעות createPackageContext.

Kotlin

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                val newContext = context.createPackageContext(context.packageName, 0)
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                val am = newContext.assets
            }
        }
    }
}

Java

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                AssetManager am = newContext.getAssets();
        }
    }
}

אפליקציות אינסטנט ל-Android ב-Android מגרסה 8.0 ואילך

כשמבקשים מודול לפי דרישה באפליקציה ל-Android ללא התקנה ב-Android מגרסה 8.0 (רמת API‏ 26) ואילך, אחרי שבקשת התקנה מדווחת כ-INSTALLED, צריך לעדכן את האפליקציה בהקשר של המודול החדש באמצעות קריאה ל-SplitInstallHelper.updateAppInfo(Context context). אחרת, האפליקציה עדיין לא מודעת לקוד ולמשאבים של המודול. אחרי עדכון המטא-נתונים של האפליקציה, צריך לטעון את התוכן של המודול במהלך האירוע הבא של השרשור הראשי על ידי הפעלה של Handler חדש, כמו שמוצג בהמשך:

Kotlin

override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                // You need to perform the following only for Android Instant Apps
                // running on Android 8.0 (API level 26) and higher.
                if (BuildCompat.isAtLeastO()) {
                    // Updates the app’s context with the code and resources of the
                    // installed module.
                    SplitInstallHelper.updateAppInfo(context)
                    Handler().post {
                        // Loads contents from the module using AssetManager
                        val am = context.assets
                        ...
                    }
                }
            }
        }
    }
}

Java

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
            // You need to perform the following only for Android Instant Apps
            // running on Android 8.0 (API level 26) and higher.
            if (BuildCompat.isAtLeastO()) {
                // Updates the app’s context with the code and resources of the
                // installed module.
                SplitInstallHelper.updateAppInfo(context);
                new Handler().post(new Runnable() {
                    @Override public void run() {
                        // Loads contents from the module using AssetManager
                        AssetManager am = context.getAssets();
                        ...
                    }
                });
            }
        }
    }
}

טעינה של ספריות C/C++‎

אם רוצים לטעון ספריות C/C++‎ ממודול שהמכשיר כבר הוריד באפליקציה מיידית, משתמשים ב-SplitInstallHelper.loadLibrary(Context context, String libName), כמו בדוגמה הבאה:

Kotlin

override fun onStateUpdate(state: SplitInstallSessionState) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.INSTALLED -> {
                // Updates the app’s context as soon as a module is installed.
                val newContext = context.createPackageContext(context.packageName, 0)
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, my-cpp-lib)
                ...
            }
        }
    }
}

Java

public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.INSTALLED:
                // Updates the app’s context as soon as a module is installed.
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, my-cpp-lib);
                ...
        }
    }
}

מגבלות ידועות

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

ניהול מודולים מותקנים

כדי לבדוק אילו מודולים של תכונות מותקנים כרגע במכשיר, אפשר לקרוא ל-SplitInstallManager.getInstalledModules(), שמחזירה Set<String> של שמות המודולים המותקנים, כמו שמוצג בהמשך.

Kotlin

val installedModules: Set<String> = splitInstallManager.installedModules

Java

Set<String> installedModules = splitInstallManager.getInstalledModules();

הסרת מודולים

אפשר לבקש מהמכשיר להסיר מודולים על ידי הפעלת הפקודה SplitInstallManager.deferredUninstall(List<String> moduleNames), כפי שמוצג בהמשך.

Kotlin

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(listOf("pictureMessages", "promotionalFilters"))

Java

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(Arrays.asList("pictureMessages", "promotionalFilters"));

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

הורדת משאבים בשפות נוספות

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

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

Kotlin

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply()
...

// Creates a request to download and install additional language resources.
val request = SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build()

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request)

Java

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply();
...

// Creates a request to download and install additional language resources.
SplitInstallRequest request =
    SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build();

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request);

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

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

Kotlin

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Java

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

גישה למשאבי שפה שהורדו

כדי לקבל גישה למשאבי שפה שהורדו, האפליקציה צריכה להפעיל את השיטה SplitCompat.installActivity() בתוך השיטה attachBaseContext() של כל פעילות שנדרשת לה גישה למשאבים האלה, כמו שמוצג בהמשך.

Kotlin

override fun attachBaseContext(base: Context) {
  super.attachBaseContext(base)
  SplitCompat.installActivity(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
  super.attachBaseContext(base);
  SplitCompat.installActivity(this);
}

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

Kotlin

override fun attachBaseContext(base: Context) {
  val configuration = Configuration()
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
  val context = base.createConfigurationContext(configuration)
  super.attachBaseContext(context)
  SplitCompat.install(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
  Configuration configuration = new Configuration();
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));
  Context context = base.createConfigurationContext(configuration);
  super.attachBaseContext(context);
  SplitCompat.install(this);
}

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

Kotlin

when (state.status()) {
  SplitInstallSessionStatus.INSTALLED -> {
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate()
  }
  ...
}

Java

switch (state.status()) {
  case SplitInstallSessionStatus.INSTALLED:
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate();
  ...
}

הסרת משאבי שפה נוספים

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

Kotlin

val installedLanguages: Set<String> = splitInstallManager.installedLanguages

Java

Set<String> installedLanguages = splitInstallManager.getInstalledLanguages();

לאחר מכן תוכלו להחליט אילו שפות להסיר באמצעות השיטה deferredLanguageUninstall(), כמו שמוצג בהמשך.

Kotlin

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Java

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

בדיקה מקומית של התקנות מודולים

הספרייה Play Feature Delivery מאפשרת לכם לבדוק באופן מקומי את היכולות הבאות של האפליקציה, בלי להתחבר לחנות Play:

בדף הזה נסביר איך לפרוס את קובצי ה-APK המפוצלים של האפליקציה במכשיר הבדיקה, כדי ש-Play Feature Delivery ישתמש אוטומטית בקובצי ה-APK האלה כדי לדמות בקשה, הורדה והתקנה של מודולים מחנות Play.

לא צריך לבצע שינויים בלוגיקה של האפליקציה, אבל צריך לעמוד בדרישות הבאות:

  • מורידים ומתקינים את הגרסה העדכנית של bundletool. צריך להשתמש ב-bundletool כדי ליצור קבוצה חדשה של קובצי APK שניתנים להתקנה מחבילת האפליקציה.

יצירת קבוצה של קובצי APK

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

  1. יוצרים חבילת אפליקציות לאפליקציה באחת מהשיטות הבאות:
  2. משתמשים בפקודה bundletool כדי ליצור קבוצה של קובצי APK לכל תצורות המכשירים:

    bundletool build-apks --local-testing
      --bundle my_app.aab
      --output my_app.apks
    

הדגל --local-testing כולל מטא-נתונים במניפסטים של קובצי ה-APK, שמאפשרים לספריית Play Feature Delivery לדעת להשתמש בקובצי ה-APK המפוצלים המקומיים כדי לבדוק התקנה של מודולים של תכונות, בלי להתחבר לחנות Play.

פריסת האפליקציה במכשיר

אחרי שיוצרים קבוצה של קובצי APK באמצעות הדגל --local-testing, משתמשים בדגל bundletool כדי להתקין את גרסת הבסיס של האפליקציה ולהעביר קובצי APK נוספים לאחסון המקומי של המכשיר. אפשר לבצע את שתי הפעולות באמצעות הפקודה הבאה:

bundletool install-apks --apks my_app.apks

עכשיו, כשמפעילים את האפליקציה ומשלימים את תהליך המשתמש להורדה ולהתקנה של מודול תכונות, ספריית Play Feature Delivery משתמשת בקובצי ה-APK שמועברים לאחסון המקומי של המכשיר.bundletool

סימולציה של שגיאה בחיבור לרשת

כדי לדמות התקנות של מודולים מחנות Play, ספריית Play Feature Delivery משתמשת בחלופה ל-SplitInstallManager, שנקראת FakeSplitInstallManager, כדי לבקש את המודול. כשמשתמשים ב-bundletool עם הדגל --local-testing כדי ליצור קבוצה של קובצי APK ולפרוס אותם במכשיר הבדיקה, הוא כולל מטא-נתונים שמנחים את ספריית Play Feature Delivery לעבור אוטומטית מקריאות ה-API של האפליקציה להפעלת FakeSplitInstallManager, במקום SplitInstallManager.

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

Kotlin

// Creates an instance of FakeSplitInstallManager with the app's context.
val fakeSplitInstallManager = FakeSplitInstallManagerFactory.create(context)
// Tells Play Feature Delivery Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true)

Java

// Creates an instance of FakeSplitInstallManager with the app's context.
FakeSplitInstallManager fakeSplitInstallManager =
    FakeSplitInstallManagerFactory.create(context);
// Tells Play Feature Delivery Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true);