web-dev-qa-db-fra.com

Comment travailler avec l'API de mise à jour in-app Android?

J'ai récemment rencontré un nouveau type de flux de mise à jour d'application qui pourrait avoir été fourni par Google Play. J'ai aimé le flux transparent pour mettre à jour une application Android. J'ai observé les étapes mentionnées ci-dessous dans l'application Hotstar.

  1. Une carte surgie du bas indiquant que la mise à jour est disponible
  2. Lorsque j'ai cliqué sur le bouton "Mettre à jour Hotstar", une boîte de dialogue est apparue (semble être fournie par Google Play)

enter image description here

  1. Le téléchargement a commencé en arrière-plan pendant que l'application était en cours d'exécution
  2. Une fois le téléchargement terminé, un SnackBar est apparu montrant l'application prête à installer
  3. L'application a redémarré après l'installation

enter image description here

Comment puis-je atteindre cet objectif? Il doit exister un moyen de communiquer avec Google Play. J'ai parcouru de nombreux blogs. Mais, je n'ai trouvé aucune solution. Cela pourrait être une fonctionnalité géniale pour un développeur si la mise à jour automatique de l'application est désactivée par l'utilisateur.

17
Pratik ED

Documentation officielle: https://developer.Android.com/guide/app-bundle/in-app-updates

Contrainte: La mise à jour dans l'application ne fonctionne qu'avec les appareils exécutant Android 5.0 (API niveau 21) ou supérieur

Étape 1: ajouter une dépendance:

dependencies {

    implementation 'com.google.Android.play:core:1.6.3'
    ...
}

Étape 2: Vérifiez la disponibilité de la mise à jour et commencez si elle est disponible

mAppUpdateManager = AppUpdateManagerFactory.create(this);

mAppUpdateManager.registerListener(installStateUpdatedListener);

mAppUpdateManager.getAppUpdateInfo().addOnSuccessListener(appUpdateInfo -> {

        if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
                && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){

            try {    
                    mAppUpdateManager.startUpdateFlowForResult(
                            appUpdateInfo, AppUpdateType.FLEXIBLE, MainActivity.this, RC_APP_UPDATE);
                }

            } catch (IntentSender.SendIntentException e) {
                e.printStackTrace();
            }

        } else if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED){
            popupSnackbarForCompleteUpdate();
        } else {
            Log.e(TAG, "checkForAppUpdateAvailability: something else");
        }
    });

Étape 3: écouter l'état de mise à jour

InstallStateUpdatedListener installStateUpdatedListener = new 
  InstallStateUpdatedListener() {
    @Override
    public void onStateUpdate(InstallState state) {
        if (state.installStatus() == InstallStatus.DOWNLOADED){
            popupSnackbarForCompleteUpdate();
        } else if (state.installStatus() == InstallStatus.INSTALLED){
            if (mAppUpdateManager != null){
          mAppUpdateManager.unregisterListener(installStateUpdatedListener);
            }

        } else {
            Log.i(TAG, "InstallStateUpdatedListener: state: " + state.installStatus());
        }
    }
};

Étape 4: Obtenez un rappel pour l'état de la mise à jour

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == RC_APP_UPDATE) {
        if (resultCode != RESULT_OK) {
            Log.e(TAG, "onActivityResult: app download failed");
        }
    }
}

Étape 5: mise à jour flexible

private void popupSnackbarForCompleteUpdate() {

    Snackbar snackbar =
            Snackbar.make(
                    findViewById(R.id.coordinatorLayout_main),
                    "New app is ready!",
                    Snackbar.LENGTH_INDEFINITE);

    snackbar.setAction("Install", view -> {
        if (mAppUpdateManager != null){
            mAppUpdateManager.completeUpdate();
        }
    });


snackbar.setActionTextColor(getResources().getColor(R.color.install_color));
    snackbar.show();
}

Pour les tests, vous pouvez utiliser FakeAppUpdateManager

https://developer.Android.com/reference/com/google/Android/play/core/appupdate/testing/FakeAppUpdateManager.html

21
Pratik ED

Android a officiellement annoncé les mises à jour intégrées à tout le monde aujourd'hui. https://developer.Android.com/guide/app-bundle/in-app-updates

Mise à jour: Gestion des mises à jour IMMÉDIATE et FLEXIBLE en une seule activité; Façon Kotlin.

import Android.app.Activity
import Android.content.Intent
import Android.content.IntentSender
import Android.os.Bundle
import Android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.Android.material.snackbar.Snackbar
import com.google.Android.play.core.appupdate.AppUpdateManager
import com.google.Android.play.core.appupdate.AppUpdateManagerFactory
import com.google.Android.play.core.install.InstallState
import com.google.Android.play.core.install.InstallStateUpdatedListener
import com.google.Android.play.core.install.model.AppUpdateType
import com.google.Android.play.core.install.model.InstallStatus
import com.google.Android.play.core.install.model.UpdateAvailability
import timber.log.Timber

class BaseUpdateCheckActivity : AppCompatActivity() {

    private val appUpdateManager: AppUpdateManager by lazy { AppUpdateManagerFactory.create(this) }
    private val appUpdatedListener: InstallStateUpdatedListener by lazy {
        object : InstallStateUpdatedListener {
            override fun onStateUpdate(installState: InstallState) {
                when {
                    installState.installStatus() == InstallStatus.DOWNLOADED -> popupSnackbarForCompleteUpdate()
                    installState.installStatus() == InstallStatus.INSTALLED -> appUpdateManager.unregisterListener(this)
                    else -> Timber.d("InstallStateUpdatedListener: state: %s", installState.installStatus())
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_ad_view)
        checkForAppUpdate()
    }

    private fun checkForAppUpdate() {
        // 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) {
                // Request the update.
                try {
                    val installType = when {
                        appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE) -> AppUpdateType.FLEXIBLE
                        appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) -> AppUpdateType.IMMEDIATE
                        else -> null
                    }
                    if (installType == AppUpdateType.FLEXIBLE) appUpdateManager.registerListener(appUpdatedListener)

                    appUpdateManager.startUpdateFlowForResult(
                            appUpdateInfo,
                            installType!!,
                            this,
                            APP_UPDATE_REQUEST_CODE)
                } catch (e: IntentSender.SendIntentException) {
                    e.printStackTrace()
                }
            }
        }
    }


    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == APP_UPDATE_REQUEST_CODE) {
            if (resultCode != Activity.RESULT_OK) {
                Toast.makeText(this,
                        "App Update failed, please try again on the next app launch.",
                        Toast.LENGTH_SHORT)
                        .show()
            }
        }
    }

    private fun popupSnackbarForCompleteUpdate() {
        val snackbar = Snackbar.make(
                findViewById(R.id.drawer_layout),
                "An update has just been downloaded.",
                Snackbar.LENGTH_INDEFINITE)
        snackbar.setAction("RESTART") { appUpdateManager.completeUpdate() }
        snackbar.setActionTextColor(ContextCompat.getColor(this, R.color.accent))
        snackbar.show()
    }


    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()
                    }

                    //Check if Immediate update is required
                    try {
                        if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
                            // If an in-app update is already running, resume the update.
                            appUpdateManager.startUpdateFlowForResult(
                                    appUpdateInfo,
                                    AppUpdateType.IMMEDIATE,
                                    this,
                                    APP_UPDATE_REQUEST_CODE)
                        }
                    } catch (e: IntentSender.SendIntentException) {
                        e.printStackTrace()
                    }
                }
    }

    companion object {
        private const val APP_UPDATE_REQUEST_CODE = 1991
    }
}

Gist source: https://Gist.github.com/saikiran91/6788ad4d00edca30dad3f51aa47a4c5c

12
Sai

En essayant de l'implémenter, la documentation officielle de Google citée dans la réponse acceptée est syntaxiquement incorrecte. Il a fallu quelques recherches, mais j'ai finalement trouvé la syntaxe correcte:

Au lieu de:

// Creates an instance of the manager.
AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

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

// Checks that the platform will allow the specified type of update.
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
  // For a flexible update, use AppUpdateType.FLEXIBLE
  && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request the update.

    appUpdateManager.startUpdateFlowForResult(
        // Pass the intent that is returned by 'getAppUpdateInfo()'.
        appUpdateInfo,
        // Or 'AppUpdateType.FLEXIBLE' for flexible updates.
        AppUpdateType.IMMEDIATE,
        // The current activity making the update request.
        this,
        // Include a request code to later monitor this update request.
        MY_REQUEST_CODE);
}

Faites ceci:

    private AppUpdateManager appUpdateManager;
    ...
    // onCreate(){ 
    // Creates instance of the manager.
    appUpdateManager = AppUpdateManagerFactory.create(mainContext);

    // Don't need to do this here anymore
    // Returns an intent object that you use to check for an update.
    //Task<AppUpdateInfo> appUpdateInfo = appUpdateManager.getAppUpdateInfo();

    appUpdateManager
            .getAppUpdateInfo()
            .addOnSuccessListener(
                    appUpdateInfo -> {

                        // Checks that the platform will allow the specified type of update.
                        if ((appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE)
                                && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE))
                        {
                            // Request the update.
                            try {
                                appUpdateManager.startUpdateFlowForResult(
                                        appUpdateInfo,
                                        AppUpdateType.IMMEDIATE,
                                        this,
                                        REQUEST_APP_UPDATE);
                            } catch (IntentSender.SendIntentException e) {
                                e.printStackTrace();
                            }
                        }
                    });

Ensuite, codez un morceau de code similaire dans la substitution onResume () au cas où une installation se bloquerait en cours de route:

//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.
                            try {
                                appUpdateManager.startUpdateFlowForResult(
                                        appUpdateInfo,
                                        AppUpdateType.IMMEDIATE,
                                        this,
                                        REQUEST_APP_UPDATE);
                            } catch (IntentSender.SendIntentException e) {
                                e.printStackTrace();
                            }
                        }
                    });
}
6
Michael Dougan

Google teste une première version d'une API de mise à jour dans les applications, comme décrit sur cet article de blog .

Il n'est disponible que pour certains des premiers partenaires de test pour le moment, mais il devrait éventuellement être disponible pour tous les développeurs. Gardez un œil sur le Android Developers Blog et pour les annonces dans la console Play.

0
Nick Fortescue

Je suppose qu'il est contrôlé par l'application elle-même, plutôt que par Google Play. J'ai développé des applications qui lancent un appel API au démarrage pour lire le numéro de version `` la plus récente '' et si cette version est une mise à jour `` obligatoire '' ou non, et la compare à la version de l'application en cours d'exécution. Si une nouvelle version est disponible, l'utilisateur est présenté avec une boîte de dialogue comme celle que vous avez affichée (bien que la leur soit beaucoup plus agréable) alertant l'utilisateur qu'une mise à jour est disponible. Si la mise à jour est "obligatoire", le message leur indique qu'ils doivent mettre à jour l'application avant de continuer. S'ils cliquent sur Mettre à jour, ils sont dirigés vers la page App Store où ils lancent le téléchargement de la mise à jour comme d'habitude et l'application se ferme. S'ils cliquent sur Fermer, l'application se ferme simplement. Si la mise à jour n'est pas obligatoire, on leur demande s'ils souhaitent mettre à jour maintenant ou continuer. S'ils cliquent sur Mettre à jour, ils sont dirigés vers la page App Store où ils lancent le téléchargement de la mise à jour comme d'habitude et l'application se ferme. S'ils cliquent sur Continuer, ils sont simplement transférés dans la version existante de l'application.

Je ne sais pas comment ils ont géré le téléchargement en arrière-plan, puis ont lancé la mise à jour de l'application avant de quitter l'application. Ce serait très bien, mais notre méthode ci-dessus était également très facile et donne beaucoup de capacités au développeur.

0
Michael Dougan