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

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

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

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

Groovy

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

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

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

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

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

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

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

הגדרת עדכון באמצעות 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());

קבלת שיחה חוזרת לעדכון הסטטוס

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

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

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

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

השלבים הבאים

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