前言
近日, Google I/O 2019 上谷歌宣布推出一款新API,允许应用程序在用户使用时安装更新升级应用。该API名为“应用内更新API(In-app Updates API)”,能够使应用程序可以更好地通过Google Play控制检查和安装更新的方式。
应用内更新支持即时更新和弹性更新两种方式。
应用内更新仅适用于运行安卓 Android 5.0(API级别21)或更高版本的设备,并且要求您使用Play Core 1.5.0或更高版本。
谷歌官方文档[英文版]:developer.android.com/guide/app-b…
注意:由于是通过GP更新,对于国内产品可能不适用。因此本文应该是对开发国际App的童鞋有所帮助。
关键词
IMMEDIATE | -- | 即时更新 |
FLEXIBLE | -- | 弹性更新(灵活更新) |
GP | -- | Google Play (Play Store) |
AS | -- | Andriod Studio |
即时更新
请注意即时更新并非强制更新,只是全屏用户体验。虽然谷歌官方说要求用户更新并重新启动应用程序才能继续使用该应用程序,但是实际上用户也是可以取消更新的。当然谷歌提供了用户取消事件通知,你可以在用户取消时重新弹出要求更新界面,那么也就可以实现强制更新效果(下载过程中的取消不适用)。
下图是谷歌提供的即时更新UI界面:
即时更新,弹出更新提醒框后,用户依然可以取消更新,下载过程中也可以取消下载,系统将通过onActivityResult返回用户已取消状态码。但是一旦取消下载后,无法再次发起即时更新(系统总是返回用户已取消)。卸载重新安装后,可以清除该状态。
用手机系统返回键取消的,不计入用户取消状态。
下载完毕后,自动开始安装,安装可能会失败,失败后不会有onActivityResult通知,可以重新进入强制更新逻辑。
安装过程中反复前后台切换的话,可能会通过onActivityResult收到安装成功提醒,但是结果并不一定是成功的。
一旦进入安装过程,也就无法取消了。
弹性更新(灵活更新)
弹性更新,将弹出一个浮动窗口,让用户确认开始更新。用户同意更新后,进入后台下载,用户可以继续使用App。但下载完毕后,安装过程只能在前台进行。为不打扰用户,下载完毕后,不弹框提醒,谷歌推荐在onResume时,检测到如果下载完成,则启动安装过程。安装时机是可控的,但不需要用户确认。
开始弹性更新前,用户可以取消更新。取消之后,不会记录用户取消状态,可再次发起弹性更新。后台下载的时候,技术上还可以将下载进度界面弹出来,变为即时更新,当然这个并不必要(甚至可以算作是一个BUG),所以这种变更后的下载完毕后是否自动安装,笔者并没有研究。实在有兴趣的童鞋可以研究一下。
可以监听下载进度,然后自行处理UI,当然并没有这个必要。
无论哪种更新方式,谷歌都提供更新确认UI,下载进度UI,以及安装过程UI,无需App再自行编写UI。参考官方文档。 安装完毕后会通过onActivityResult返回更新成功或失败。
实战
官网文档里面的代码是无法通过编译的,编译解决后也可能直接崩溃,另外笔者在研究过程中也踩坑不少,花了较多的时间才能将流程跑通,因此有必要将研究过程记录下来。
因为是走线上GP更新,要想编码调试应用内更新,首先你得有一个GP账号,然后上架一个产品,推荐新建一个Demo产品来测试。
大致步骤如下:
- 科学上网,首先你的环境需要能够访问GP。
- 使用Android Studio 新建一个Demo工程。
- 在build.gradle里面,dependencies中添加GP库:
implementation 'com.google.android.play:core:1.5.0'
复制代码
版本号必须为1.5.0或以上。
复制代码
- 然后在MainActivity.onCreate方法中添加如下代码:
final int REQUEST_CODE_UPDATE = 9001;
AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(this);
m_appUpdateManager = appUpdateManager;
Task appUpdateInfo = appUpdateManager.getAppUpdateInfo();
appUpdateInfo.addOnCompleteListener(new OnCompleteListener() {
@Override
public void onComplete(Task task) {
if (task.isSuccessful()) {
// 监听成功,不一定检测到更新
AppUpdateInfo it = (AppUpdateInfo)task.getResult();
if (it.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& it.isUpdateTypeAllowed(IMMEDIATE)) { // 检测到更新可用且支持即时更新
try {
// 启动即时更新
appUpdateManager.startUpdateFlowForResult(it, IMMEDIATE, MainActivity. this, REQUEST_CODE_UPDATE);
} catch (Exception e) {
e.printStackTrace();
}
}
} else {
// 监听失败
}
}
});
复制代码
如果startUpdateFlowForResult返回true,那么你将看见即时更新确认界面,点击Update即可开始下载更新。不过不着急,准备工作还没做好呢,接着往下看:
- 在build.gradle中,将版本号修改一下
versionCode 1000
versionName "2.0.1000"
复制代码
随便改成多少都行,总之要稍微大一点的数字,主要是versionCode,GP升级主要是检查versionCode来的。
- 使用Build / Generate Signed Bundle / APK... 创建一个签名文件,再打包一个Release APK安装包。
- 将安装包提交到GP发布。
- 修改build.gradle中的版本号,将versionCode改为100,总之就是改小就行。
- 直接编译到设备运行,此时肯定是检测不到更新的。
- 打开GP,直到刷到Demo App 最新版本信息,可以使用包名搜索。包名在build.gradle中applicationId即包名,可以自行修改,但必须要跟GP版本一致。
- 直到刷到Demo App 最新版本信息后,还需要GP上显示“Update”按钮才表示可以更新。
- 直到GP 显示可以更新后,回到AS 重新调试Demo,应该能够检测到更新信息了
- 如果你用Debug环境来调试,那么必须将release版的签名信息也应用到Debug,打开工程设置,创建一个签名信息: 然后将config应用到Debug:
如图所示,不出意外的话,已经检测到线上versionCode为1000,updateAvailability为2(UpdateAvailability.UPDATE_AVAILABLE)表示更新可用,另外两个检测为true表示当前可以进行即时更新和弹性更新。startUpdateFlowForResult就可以弹出更新确认界面了,接下来应该在MainActivity.onActivityResult中检测返回值:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_UPDATE) {
if (resultCode == Activity.RESULT_OK) {
Log.w("GPUpdate", "应用内更新成功");
} else if (resultCode == Activity.RESULT_CANCELED) {
Log.w("GPUpdate", "应用内更新, 用户取消");
} else {
Log.w("GPUpdate", "应用内更新,遇到错误");
}
}
}
复制代码
如果要使用弹性更新,下载任务放在后台进行,那么startUpdateFlowForResult应该指定AppUpdateType.FLEXIBLE参数,依然需要用户确定更新,然后更新UI隐藏,用户可以继续操作App。
可以在MainActivity.onResume中添加下载后相关处理代码,
@Override
protected void onResume() {
super.onResume();
Task appUpdateInfo = m_appUpdateManager.getAppUpdateInfo();
appUpdateInfo.addOnSuccessListener(new OnSuccessListener() {
@Override
public void onSuccess(Object o) {
AppUpdateInfo it = (AppUpdateInfo)o;
traceAppUpdateInfo(it);
try {
if (it.installStatus() == InstallStatus.DOWNLOADED) {
// 只有后台下载才会有这个通知
Log.w("GPUpdate", "下载完毕,开始执行安装");
m_appUpdateManager.startUpdateFlowForResult(it, IMMEDIATE, MainActivity. this, REQUEST_CODE_UPDATE);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
复制代码
这样可以在下载完成后,用户切换应用重新进入App时可以启动安装过程,注意这里只能使用AppUpdateType.IMMEDIATE参数,因为安装过程不能在后台进行,也不提供用户选择的入口,调用成功即开始安装,安装过程是不能取消的。
技术上也可以监听后台下载进度,下载完毕后立即启动安装,但是这样会打断用户操作,体验非常不友好,因此并不推荐。
重要:按上述步骤,可以调试下载更新过程,但最终升级会是失败的,原因可能是签名证书问题。若要升级真正能成功,那么还需要以下步骤。
- 修改versionCode = 1004, versionName = "2.0.1004"
- 打Release安装包
- 提交GP发布,发布时将更新信息填入版本号2.0.1004
- GP刷到最新版本信息
- 使用GP下载2.0.1008版进行安装
- 再修改versionCode = 1008, versionName = "2.0.1008"
- 打Release安装包
- 提交GP发布,发布时将更新信息填入版本号2.0.1008
- GP刷到最新版本信息,而且需要显示“更新”按钮,千万不要更新
- 找到刚才安装好的2.0.1004,重新启动
- 此时应该可以弹出更新确认界面了,点击“更新”。
- 等待安装完毕,GP会重新启动Demo App,查看版本号,应该变为2.0.1008了,更新成功,恭喜!大功告成!
PS:为了快速查看版本更新是否成功,建议在Demo界面上放一个TextView,显示版本号。
注意事项
- 你的手机上,你的GP账号应该要至少下载过一次App。
- 如果总是刷不到最新版本信息,那么Kill 掉GP,重新启动试试,确保GP缓存失效。
- 调试用的版本号一定要低于GP线上版本号,主要是VersionCode。
- 本地包可以调试下载更新过程,但是最终会更新失败,如果从GP下载签名再打入本地包,也许能解决这个问题,有兴趣的童鞋可继续深入研究。
- GP刷出新版本,通常需要几个小时的时间,30分钟算是很快的了,而且就算刷到最新版本信息,还不一定能进行更新(这可能是GP的BUG)。为了能快速刷出新版本信息,可以在GP后台发布内测版本,添加测试用GP账号到白名单。内测版本发布后,白名单内测用户在几秒钟可以刷到最新信息。