如何使用 Android 的应用内更新 API?

How to work with Android's in-app update API?

我最近发现了一种由 Google Play API 提供的新型应用程序更新流程。我喜欢这种用于更新 Android 应用程序的无缝流程。我在 Hotstar 应用程序中观察了以下提到的步骤。

  1. 从底部弹出一张卡片,显示有可用更新
  2. 当我点击"Update Hotstar"按钮时,弹出了一个对话框(好像是Google Play提供的)

  1. 下载是在应用程序运行时在后台开始的 运行
  2. 下载完成后,弹出一个 SnackBar 显示应用已准备好安装
  3. 应用程序安装后重启

我怎样才能做到这一点?必须有一种与 Google Play 通信的方法。我浏览了很多博客。但是,没有找到任何解决方案。如果用户禁用自动应用更新,这对开发人员来说可能是一个很棒的功能。

我猜它是由应用程序本身控制的,而不是 Google 播放。我开发的应用程序在启动时发出 API 调用以读取 'latest' 版本号以及该版本是否为 'mandatory' 更新,并将其与 运行 应用程序版本。如果有新版本可用,则会向用户显示一个类似于您显示的对话框(尽管他们的要好得多),提醒用户有更新可用。如果更新为 'mandatory',则消息会告诉他们必须先更新应用程序才能继续。如果他们点击“更新”,他们将被带到 App Store 页面,在那里他们像往常一样开始下载更新,然后应用程序退出。如果他们单击“关闭”,应用程序就会退出。如果更新不是强制性的,系统会询问他们是要立即更新还是继续。如果他们点击“更新”,他们将被带到 App Store 页面,在那里他们像往常一样开始下载更新,然后应用程序退出。如果他们单击“继续”,那么他们只会被带到应用程序的现有版本中。

我不确定他们是如何管理后台下载然后在退出应用程序之前启动应用程序更新的。那会非常好,但我们上面的方法也非常简单,并为开发人员提供了很多功能。

Google 正在测试应用内更新 API 的早期版本,如 this blog post 所述。

它目前仅适用于部分早期测试合作伙伴,但最终应该适用于所有开发人员。请密切关注 Android 开发者博客和 Play 控制台中的公告。

Android officially announced the in-app updates to everyone today.

https://developer.android.com/guide/playcore/in-app-updates

更新: 在单个 activity 中处理即时和灵活更新; 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
    }
}

来源要点:https://gist.github.com/saikiran91/6788ad4d00edca30dad3f51aa47a4c5c

第一步:添加依赖(build.gradle (app)):

dependencies {

    implementation 'com.google.android.play:core:1.7.3'
    ...
}

第 2 步:检查更新可用性并在可用时启动

private AppUpdateManager mAppUpdateManager;
private static final int RC_APP_UPDATE = 11;

在 onStart() 方法中:

mAppUpdateManager = AppUpdateManagerFactory.create(this);

mAppUpdateManager.registerListener(installStateUpdatedListener);

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

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

            try {    
                    mAppUpdateManager.startUpdateFlowForResult(
                            appUpdateInfo, AppUpdateType.FLEXIBLE /*AppUpdateType.IMMEDIATE*/, MainActivity.this, RC_APP_UPDATE);

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

        } else if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED){
            //CHECK THIS if AppUpdateType.FLEXIBLE, otherwise you can skip
            popupSnackbarForCompleteUpdate();
        } else {
            Log.e(TAG, "checkForAppUpdateAvailability: something else");
        }
    });

第三步:监听更新状态

InstallStateUpdatedListener installStateUpdatedListener = new 
  InstallStateUpdatedListener() {
    @Override
    public void onStateUpdate(InstallState state) {
        if (state.installStatus() == InstallStatus.DOWNLOADED){
            //CHECK THIS if AppUpdateType.FLEXIBLE, otherwise you can skip
            popupSnackbarForCompleteUpdate();
        } else if (state.installStatus() == InstallStatus.INSTALLED){
            if (mAppUpdateManager != null){
          mAppUpdateManager.unregisterListener(installStateUpdatedListener);
            }

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

第 4 步:获取更新状态的回调

@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");
        }
    }
}

第 5 步:灵活更新

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

第 6 步:不要忘记注销侦听器(在 onStop 方法中)

if (mAppUpdateManager != null) {
     mAppUpdateManager.unregisterListener(installStateUpdatedListener);
}

注意:在您的应用中的任何一个 activity 中添加此侦听器,最好在 MainActivity(主页)

测试用 FakeAppUpdateManager

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

约束: In-app 更新仅适用于设备 运行 Android 5.0(API 21 级)或更高

Official Documentation: https://developer.android.com/guide/playcore/in-app-updates

为了实现这一点,已接受答案中引用的官方 Google 文档在句法上是不正确的。经过一些研究,但我终于找到了正确的语法:

而不是:

// 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);
}

这样做:

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

然后,在 onResume() 重写中编写一段类似的代码,以防安装过程中挂起:

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

请试一次。 Official Document for reference

第 1 步:在 build.gradle 文件中添加以下库(请检查并更新最新的播放代码插件版本)

implementation 'com.google.android.play:core:1.6.4'

第 2 步:在 class 中声明以下变量(例如 MainActivity.java)

    private AppUpdateManager mAppUpdateManager;
    private int RC_APP_UPDATE = 999;
    private int inAppUpdateType;
    private com.google.android.play.core.tasks.Task<AppUpdateInfo> appUpdateInfoTask;
    private InstallStateUpdatedListener installStateUpdatedListener;

第 3 步:在 onCreate() 方法中添加以下代码(初始化变量)

        // Creates instance of the manager.
        mAppUpdateManager = AppUpdateManagerFactory.create(this);
        // Returns an intent object that you use to check for an update.
        appUpdateInfoTask = mAppUpdateManager.getAppUpdateInfo();
        //lambda operation used for below listener
        //For flexible update
        installStateUpdatedListener = installState -> {
            if (installState.installStatus() == InstallStatus.DOWNLOADED) {
                popupSnackbarForCompleteUpdate();
            }
        };
        mAppUpdateManager.registerListener(installStateUpdatedListener);

第 4 步:在 activity 的 onDestroy() 方法中取消注册监听器

 @Override
    protected void onDestroy() {
        mAppUpdateManager.unregisterListener(installStateUpdatedListener);
        super.onDestroy();
    }

第 5 步:在 onResume() 中,我们需要通过以下代码监听灵活更新和立即更新。

@Override
        protected void onResume() {
           try {   
  mAppUpdateManager.getAppUpdateInfo().addOnSuccessListener(appUpdateInfo -> {
        if (appUpdateInfo.updateAvailability() == 
           UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
           // If an in-app update is already running, resume the update.
                       try {
                            mAppUpdateManager.startUpdateFlowForResult(
                                    appUpdateInfo,
                                    inAppUpdateType,
                                    this,
                                    RC_APP_UPDATE);
                        } catch (IntentSender.SendIntentException e) {
                            e.printStackTrace();
                        }
                    }
                });
    
       
  mAppUpdateManager.getAppUpdateInfo().addOnSuccessListener(appUpdateInfo -> {
     //For flexible update            
       if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                        popupSnackbarForCompleteUpdate();
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            super.onResume();
    }

第6步:在onActivityResult()中我们需要处理用户的点击动作(仅用于灵活更新)

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_APP_UPDATE) {
            //when user clicks update button
            if (resultCode == RESULT_OK) {
                Toast.makeText(MainActivity.this, "App download starts...", Toast.LENGTH_LONG).show();
            } else if (resultCode != RESULT_CANCELED) {
                //if you want to request the update again just call checkUpdate()
                Toast.makeText(MainActivity.this, "App download canceled.", Toast.LENGTH_LONG).show();
            } else if (resultCode == RESULT_IN_APP_UPDATE_FAILED) {
                Toast.makeText(MainActivity.this, "App download failed.", Toast.LENGTH_LONG).show();
            }
        }
}

第 7 步:创建一个方法来检查更新是否可用并开始更新(立即更新)

private void inAppUpdate() {

        try {
            // Checks that the platform will allow the specified type of update.
            appUpdateInfoTask.addOnSuccessListener(new OnSuccessListener<AppUpdateInfo>() {
                @Override
                public void onSuccess(AppUpdateInfo appUpdateInfo) {
                    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
                            // For a flexible update, use AppUpdateType.FLEXIBLE
                            && appUpdateInfo.isUpdateTypeAllowed(inAppUpdateType)) {
                        // Request the update.

                        try {
                            mAppUpdateManager.startUpdateFlowForResult(
                                    // Pass the intent that is returned by 'getAppUpdateInfo()'.
                                    appUpdateInfo,
                                    // Or 'AppUpdateType.FLEXIBLE' for flexible updates.
                                    inAppUpdateType,
                                    // The current activity making the update request.
                                    MainActivity.this,
                                    // Include a request code to later monitor this update request.
                                    RC_APP_UPDATE);
                        } catch (IntentSender.SendIntentException ignored) {

                        }
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

第 8 步:精细地创建一个快餐对话框或任何警报,以向用户显示灵活更新已下载并准备好更新(需要使用操作来开始更新 - 仅适用于灵活更新)

private void popupSnackbarForCompleteUpdate() {
        try {
        Snackbar snackbar =
            Snackbar.make(
                    findViewById(R.id.id_of_root_loyout),
                    "An update has just been downloaded.\nRestart to update",
                    Snackbar.LENGTH_INDEFINITE);

    snackbar.setAction("INSTALL", view -> {
        if (mAppUpdateManager != null){
            mAppUpdateManager.completeUpdate();
        }
    });
snackbar.setActionTextColor(getResources().getColor(R.color.install_color));
    snackbar.show();

        } catch (Resources.NotFoundException e) {
            e.printStackTrace();
        }
    } 

第 9 步:现在在您想要开始检查更新的任何地方调用具有应用内更新类型(灵活或立即)的方法。

//For Immediate 
inAppUpdateType = AppUpdateType.IMMEDIATE; //1
inAppUpdate();

//For Flexible 
inAppUpdateType = AppUpdateType.FLEXIBLE; //0
inAppUpdate();

要记住的要点:

  1. 灵活更新将先下载,然后通知用户下载完成,然后用户必须开始更新(步骤 8 上给出的选项)。

  2. 在google play console中有一个测试应用内分享的选项,我们可以上传普通apk(不需要签名apk)来测试。 https://support.google.com/googleplay/android-developer/answer/9303479?hl=en

  3. 需要在您的测试设备 Play 商店应用中启用应用内共享选项。

  4. 仍然,Play商店中的任何问题,只需清除缓存和清除数据然后重新启动设备并尝试。

试试这些库,您可以在其中用几行代码实现。

https://github.com/SanojPunchihewa/InAppUpdater

https://github.com/dnKaratzas/android-inapp-update