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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

השלבים הבאים

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