תמיכה בעדכונים מתוך האפליקציה (Kotlin או Java)

במדריך הזה מוסבר איך לתמוך בעדכונים מתוך האפליקציה באמצעות Kotlin או Java. יש מדריכים נפרדים למקרים שבהם ההטמעה משתמשת בקוד Native ‏ (C/C++‎) ולמקרים שבהם ההטמעה משתמשת ב-Unity או ב-Unreal Engine.

הגדרת סביבת הפיתוח

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

מגניב

// In your app's build.gradle file:
...
dependencies {
    // This dependency is downloaded from the Google's Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation 'com.google.android.play:app-update:2.1.0'

    // For Kotlin users also add the Kotlin extensions library for Play In-App Update:
    implementation 'com.google.android.play:app-update-ktx:2.1.0'
    ...
}

Kotlin

// In your app's build.gradle.kts file:
...
dependencies {
    // This dependency is downloaded from the Google's Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation("com.google.android.play:app-update:2.1.0")

    // For Kotlin users also import the Kotlin extensions library for Play In-App Update:
    implementation("com.google.android.play:app-update-ktx:2.1.0")
    ...
}

בודקים אם יש עדכונים שצריך להתקין

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

Kotlin

val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
        // This example applies an immediate update. To apply a flexible update
        // instead, pass in AppUpdateType.FLEXIBLE
        && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
    ) {
        // Request the update.
    }
}

Java

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          // This example applies an immediate update. To apply a flexible update
          // instead, pass in AppUpdateType.FLEXIBLE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request the update.
    }
});

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

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

בדיקת עדכניות העדכון

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

משתמשים ב-clientVersionStalenessDays() כדי לבדוק כמה ימים עברו מאז שהעדכון הפך לזמין בחנות Play:

Kotlin

val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// Checks whether the platform allows the specified type of update,
// and current version staleness.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && (appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
              // Request the update.
    }
}

Java

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks whether the platform allows the specified type of update,
// and current version staleness.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.clientVersionStalenessDays() != null
          && appUpdateInfo.clientVersionStalenessDays() >= DAYS_FOR_FLEXIBLE_UPDATE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
              // Request the update.
    }
});

בדיקת העדיפות של העדכון

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

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

כדי לקבוע את העדיפות, מערכת Google Play משתמשת בערך של מספר שלם בין 0 ל-5, כאשר 0 הוא ברירת המחדל ו-5 הוא העדיפות הגבוהה ביותר. כדי להגדיר את העדיפות של עדכון, משתמשים בשדה inAppUpdatePriority בקטע Edits.tracks.releases בממשק API של Google Play למפתחים. כל הגרסאות החדשות שנוספו לגרסה להפצה נחשבות כבעלות עדיפות זהה לזו של הגרסה להפצה. אפשר להגדיר את העדיפות רק כשמשיקים גרסה חדשה, ואי אפשר לשנות אותה אחר כך.

מגדירים את העדיפות באמצעות ממשק ה-API של Google Play למפתחים, כפי שמתואר במסמכי התיעוד של Play Developer API. צריך לציין את העדיפות של העדכון באפליקציה במשאב Edit.tracks שמועבר ב-method‏ Edit.tracks: update. בדוגמה הבאה מוצג פרסום של אפליקציה עם קוד גרסה 88 ו-inAppUpdatePriority 5:

{
  "releases": [{
      "versionCodes": ["88"],
      "inAppUpdatePriority": 5,
      "status": "completed"
  }]
}

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

  • אתם משיקים את גרסה 1 במסלול לסביבת הייצור ללא עדיפות.
  • אתם מפיצים את גרסת ההפצה 2 למסלול לבדיקה פנימית עם עדיפות 5.
  • אתם מפרסמים את גרסה 3 במסלול לסביבת הייצור ללא עדיפות.

כשמשתמשים בסביבת הייצור מעדכנים מגרסה 1 לגרסה 3, הם מקבלים את העדיפות 5, למרות שגרסה 2 פורסמה במסלול אחר.

Kotlin

val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// Checks whether the platform allows the specified type of update,
// and checks the update priority.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.updatePriority() >= 4 /* high priority */
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request an immediate update.
    }
}

Java

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks whether the platform allows the specified type of update,
// and checks the update priority.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.updatePriority() >= 4 /* high priority */
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request an immediate update.
    }
});

התחלת עדכון

אחרי שמוודאים שיש עדכון זמין, אפשר לבקש עדכון באמצעות AppUpdateManager.startUpdateFlowForResult():

Kotlin

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build())

Java

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build());

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

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

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

הגדרת עדכון באמצעות AppUpdateOptions

‫AppUpdateOptions מכיל שדה AllowAssetPackDeletion שמגדיר אם העדכון יכול למחוק חבילות נכסים במקרה של האחסון במכשיר מוגבל. כברירת מחדל, השדה הזה מוגדר לערך false, אבל אפשר להשתמש ב-method‏ setAllowAssetPackDeletion() כדי להגדיר אותו לערך true:

Kotlin

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build())

Java

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build());

קבלת קריאה חוזרת (callback) לגבי סטטוס העדכון

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

Kotlin

registerForActivityResult(StartIntentSenderForResult()) { result: ActivityResult ->
    // handle callback
    if (result.resultCode != RESULT_OK) {
        log("Update flow failed! Result code: " + result.resultCode);
        // If the update is canceled or fails,
        // you can request to start the update again.
    }
}

Java

registerForActivityResult(
    new ActivityResultContracts.StartIntentSenderForResult(),
    new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            // handle callback
            if (result.getResultCode() != RESULT_OK) {
                log("Update flow failed! Result code: " + result.getResultCode());
                // If the update is canceled or fails,
                // you can request to start the update again.
            }
        }
    });

יש כמה ערכים שאפשר לקבל מהקריאה החוזרת (callback) של onActivityResult():

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

טיפול בעדכון גמיש

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

מעקב אחרי מצב העדכון הגמיש

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

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

Kotlin

// Create a listener to track request state updates.
val listener = InstallStateUpdatedListener { state ->
    // (Optional) Provide a download progress bar.
    if (state.installStatus() == InstallStatus.DOWNLOADING) {
      val bytesDownloaded = state.bytesDownloaded()
      val totalBytesToDownload = state.totalBytesToDownload()
      // Show update progress bar.
    }
    // Log state or install the update.
}

// Before starting an update, register a listener for updates.
appUpdateManager.registerListener(listener)

// Start an update.

// When status updates are no longer needed, unregister the listener.
appUpdateManager.unregisterListener(listener)

Java

// Create a listener to track request state updates.
InstallStateUpdatedListener listener = state -> {
  // (Optional) Provide a download progress bar.
  if (state.installStatus() == InstallStatus.DOWNLOADING) {
      long bytesDownloaded = state.bytesDownloaded();
      long totalBytesToDownload = state.totalBytesToDownload();
      // Implement progress bar.
  }
  // Log state or install the update.
};

// Before starting an update, register a listener for updates.
appUpdateManager.registerListener(listener);

// Start an update.

// When status updates are no longer needed, unregister the listener.
appUpdateManager.unregisterListener(listener);

התקנת עדכון גמיש

כשמזהים את המצב InstallStatus.DOWNLOADED, צריך להפעיל מחדש את האפליקציה כדי להתקין את העדכון.

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

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

בדוגמה הבאה מוצגת הטמעה של סרגל הודעות של Material Design שמבקש מהמשתמש לאשר את הפעלת האפליקציה מחדש:

Kotlin

val listener = { state ->
    if (state.installStatus() == InstallStatus.DOWNLOADED) {
        // After the update is downloaded, show a notification
        // and request user confirmation to restart the app.
        popupSnackbarForCompleteUpdate()
    }
    ...
}

// Displays the snackbar notification and call to action.
fun popupSnackbarForCompleteUpdate() {
    Snackbar.make(
        findViewById(R.id.activity_main_layout),
        "An update has just been downloaded.",
        Snackbar.LENGTH_INDEFINITE
    ).apply {
        setAction("RESTART") { appUpdateManager.completeUpdate() }
        setActionTextColor(resources.getColor(R.color.snackbar_action_text_color))
        show()
    }
}

Java

InstallStateUpdatedListener listener = state -> {
    if (state.installStatus() == InstallStatus.DOWNLOADED) {
        // After the update is downloaded, show a notification
        // and request user confirmation to restart the app.
        popupSnackbarForCompleteUpdate();
    }
    ...
};

// Displays the snackbar notification and call to action.
private void popupSnackbarForCompleteUpdate() {
  Snackbar snackbar =
      Snackbar.make(
          findViewById(R.id.activity_main_layout),
          "An update has just been downloaded.",
          Snackbar.LENGTH_INDEFINITE);
  snackbar.setAction("RESTART", view -> appUpdateManager.completeUpdate());
  snackbar.setActionTextColor(
      getResources().getColor(R.color.snackbar_action_text_color));
  snackbar.show();
}

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

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

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

Kotlin

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all app entry points.
override fun onResume() {
    super.onResume()

    appUpdateManager
        .appUpdateInfo
        .addOnSuccessListener { appUpdateInfo ->
            ...
            // If the update is downloaded but not installed,
            // notify the user to complete the update.
            if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                popupSnackbarForCompleteUpdate()
            }
        }
}

Java

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all app entry points.
@Override
protected void onResume() {
  super.onResume();

  appUpdateManager
      .getAppUpdateInfo()
      .addOnSuccessListener(appUpdateInfo -> {
              ...
              // If the update is downloaded but not installed,
              // notify the user to complete the update.
              if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                  popupSnackbarForCompleteUpdate();
              }
          });
}

טיפול בעדכון מיידי

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

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

Kotlin

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all entry points into the app.
override fun onResume() {
    super.onResume()

    appUpdateManager
        .appUpdateInfo
        .addOnSuccessListener { appUpdateInfo ->
            ...
            if (appUpdateInfo.updateAvailability()
                == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
            ) {
                // If an in-app update is already running, resume the update.
                appUpdateManager.startUpdateFlowForResult(
                  appUpdateInfo,
                  activityResultLauncher,
                  AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build())
            }
        }
}

Java

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all entry points into the app.
@Override
protected void onResume() {
  super.onResume();

  appUpdateManager
      .getAppUpdateInfo()
      .addOnSuccessListener(
          appUpdateInfo -> {
            ...
            if (appUpdateInfo.updateAvailability()
                == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
                // If an in-app update is already running, resume the update.
                appUpdateManager.startUpdateFlowForResult(
                  appUpdateInfo,
                  activityResultLauncher,
                  AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build());
            }
          });
}

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

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

השלבים הבאים

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