【问题标题】:How to work with Android's in-app update API?如何使用 Android 的应用内更新 API?
【发布时间】:2019-09-20 05:34:18
【问题描述】:

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

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

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

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

【问题讨论】:

  • 它被称为应用内更新API并由选定的合作伙伴进行测试。我也在等这个。不幸的是,Google 喜欢发布需要很长时间才能发布的功能和产品。
  • 你也可以参考这个答案stackoverflow.com/questions/15213211/…

标签: android auto-update in-app-update google-play-core


【解决方案1】:

第一步:添加依赖(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");
        }
    });

第 3 步:监听更新状态

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

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

为了测试,你可以使用 FakeAppUpdateManager

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

限制:应用内更新仅适用于运行 Android 5.0(API 级别 21)或更高版本的设备

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

【讨论】:

  • 我正在运行一些测试,但我似乎无法让它工作。我总是收到 UpdateAvailability.UPDATE_NOT_AVAILABLE 作为响应。有什么我错过的吗?
  • @kevintresuelo 我认为您的应用应该在 Play 商店上发布。测试时,将测试应用的版本代码降低到发布版本代码并尝试。
  • @PratikED 兄弟当我只是在启动画面中粘贴并运行您的代码时,我无法使用您的代码它总是给我错误 SomeThing Else 你能帮忙吗我在我的应用程序上找出错误。
  • @kevintresuelo 你能找到解决你问题的方法吗?
  • @BumuthuDilshan 我已经更新了答案!希望这会对你有所帮助。
【解决方案2】:

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

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

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

【讨论】:

  • 如何确定可用更新是IMMEDIATE 还是FLEXIBLE 我们可以在google play 开发者控制台中设置这些标志,以便客户端设备知道它是否必须立即更新。跨度>
  • 最初的想法并创建了一个要点,但事实证明我们无法在 google play store 中设置标志;相反,我使用了 Firebase 远程配置来完成该过程。我会在一段时间内更新要点。谢谢。 @MuhammadSaqib
  • 我在评论后做了一些研究,我发现他们实际上正在计划实现这样的功能。他们很快将在开发者控制台中引入更新“优先级”功能,并且播放核心 API 将能够读取该优先级。 youtu.be/_o_q6hatcIs?t=577
  • 对于那些想要实现谷歌尚未发布的标志的人来说,似乎下一个最佳选择是 Firebase 远程配置。
【解决方案3】:

为了实现这一点,在接受的答案中引用的官方 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();
                            }
                        }
                    });
}

【讨论】:

  • REQUEST_APP_UPDATE 的值是多少?
  • @CoryRobinson 什么都行 =)
【解决方案4】:

请尝试一次。 Official Document for reference

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

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

第 2 步:在类中声明以下变量(例如 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 步:在活动的 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 控制台有一个测试应用内共享的选项,我们可以上传普通的apk(不需要签名的apk)来测试。 https://support.google.com/googleplay/android-developer/answer/9303479?hl=en

  3. 需要在您的测试设备 Play 商店应用中启用应用内共享选项。 How to Enable Internal App Sharing for Android?

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

【讨论】:

  • 由于 2.condition 中的逻辑错误,onActivityResult() 中的 3.condition (resultCode == RESULT_IN_APP_UPDATE_FAILED) 总是错误的。您的 2. 条件应如下 (resultCode == RESULT_CANCELED)
【解决方案5】:

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

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

【讨论】:

  • 感谢您的宝贵时间。我们可以在应用程序中轻松开发它。但是您必须将用户带到 Play 商店。但是,如果您能猜到,Google Play 有一些回调。这就是为什么他们能够在下载完成时显示 SnackBar。
  • 酷! Google Play 现在似乎正在与选定的合作伙伴一起测试应用内更新 API。想在它可用时检查一下...android-developers.googleblog.com/2018/11/…
【解决方案6】:

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

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

【讨论】:

    【解决方案7】:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多