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

מודולים של תכונות מאפשרים לכם להפריד תכונות ומשאבים מסוימים מהמודול הבסיסי של האפליקציה ולכלול אותם בחבילת האפליקציות. באמצעות 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. מציינים שם מודול. סביבת הפיתוח המשולבת משתמשת בשם הזה כדי לזהות את המודול כפרויקט משנה של Gradle בקובץ ההגדרות של Gradle. כשמפתחים את חבילת האפליקציות, Gradle משתמש ברכיב האחרון בשם הפרויקט המשני כדי להחדיר את המאפיין <manifest split> למניפסט של מודול התכונה.
    3. מציינים את שם החבילה של המודול. כברירת מחדל, Android Studio מציע שם חבילה שמשלב את שם החבילה ברמה הבסיסית של המודול הבסיסי ואת שם המודול שציינתם בשלב הקודם.
    4. בוחרים את רמת ה-API המינימלית שרוצים שהמודול יתמוך בה. הערך הזה צריך להיות זהה לערך של המודול הבסיסי.
  5. לוחצים על הבא.
  6. בקטע אפשרויות הורדה של מודול, מבצעים את הפעולות הבאות:

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

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

      <dist:module ... >
        <dist:delivery>
            <dist:on-demand/>
        </dist:delivery>
      </dist:module>
      
    3. מסמנים את התיבה שלצד מיזוג אם רוצים שהמודול יהיה זמין למכשירים עם 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 Feature Delivery לפרויקט

לפני שתתחילו, עליכם להוסיף את ספריית Play Feature Delivery לפרויקט.

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

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

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

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

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

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

אפשר לציין מודול להורדה במועד מאוחר יותר באמצעות השיטה 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"));

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

מעקב אחרי מצב הבקשה

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

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 Store לא מותקנת במכשיר. עליכם להודיע למשתמש שצריך להוריד את אפליקציית Play Store כדי להוריד את התכונה הזו.
APP_NOT_OWNED האפליקציה לא הותקנה על ידי Google Play ואי אפשר להוריד את התכונה. השגיאה הזו יכולה להתרחש רק בהתקנות מושהות. אם אתם רוצים שהמשתמשים יורידו את האפליקציה מ-Google Play, תוכלו להשתמש ב-startInstall() כדי לקבל את אישור המשתמש הנדרש.
INTERNAL_ERROR אירעה שגיאה פנימית ב-Play Store. מנסים שוב לשלוח את הבקשה.

אם משתמש מבקש להוריד מודול על פי דרישה ונוצרת שגיאה, מומלץ להציג תיבת דו-שיח עם שתי אפשרויות למשתמש: Try again (ניסיון חוזר) ו-Cancel (ביטול הבקשה). לקבלת תמיכה נוספת, כדאי גם לספק קישור לעזרה שיפנה את המשתמשים אל מרכז העזרה של 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 Instant שפועלת ב-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 {
    ...
}

SplitCompatApplication פשוט מבטל את ContextWrapper.attachBaseContext() כדי לכלול את SplitCompat.install(Context applicationContext). אם אתם לא רוצים שהקלאס 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. כדי לעשות זאת, מוסיפים ל-base רכיב 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 זמין במאמר טיפים ל-JNI ב-Android.

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

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

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

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

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

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

כשמבקשים מודול על פי דרישה לאפליקציה מיידית ל-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++‎ ממודול שכבר הורדתם למכשיר באפליקציית Instant, צריך להשתמש ב-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() ולבדוק את התוצאה, כפי שמתואר בקטע הקודם.

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

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

אם אתם רוצים לתת לאפליקציה גישה למשאבי שפה נוספים, למשל כדי להטמיע בורר שפות באפליקציה, תוכלו להשתמש בספרייה של 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);
}

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

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

בדף הזה נסביר איך לפרוס את חבילות ה-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 ולפרוס אותן במכשיר הבדיקה, ה-build כולל מטא-נתונים שמנחים את ספריית 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);