
Обновление приложений и поддержка актуальной версии у пользователей это очень важный момент в жизненном цикле каждого приложения. От части это зависит от того, что Android постоянно изменяется: какие-то методы и классы добавляются, какие-то становятся устаревшими, и примерно раз в год с выходом новой версии следует обновлять targetSdkVersion и переписывать код, если это требуется. Кроме того, постоянно обновляются сторонние библиотеки, которые нужно также обновить у себя в приложении, добавляются новые функции и особенности в само приложение. И про багфиксы не стоит забывать, нужно постоянно отслеживать баги в приложениях и оперативно исправлять, потому что недовольный пользователь скорее всего испортит статистику приложению негативным отзывом.
Всё это вынуждает постоянно поддерживать приложение. Отсюда вытекает следующий момент: как установить актуальную версию приложения максимальному числу пользователей.
Этот вопрос от части решает сам Google Play. Он присылает уведомления пользователю о том, что доступна новая версия приложения, предлагает включить автоматическое обновление приложений, что обеспечивает самую свежую версию приложения на устройстве. Однако не все пользователи использую эту возможность, многие обновляют от случая к случаю, игнорируют уведомления.
В этом случае мы можем взять дело в свои руки и встроить механизм обновления в само приложения.
В прошлом году Google выпустили механизм In-App Updates с целью ещё больше ускорить обновление приложений у пользователей. In-App Updates позволяет показывать пользователю при входе в приложение диалог, где ему будет предложено обновить приложение до последней версии. Этот функционал работает, начиная с версии Android 5.0 (API 21). Кроме того, приложение должно быть опубликовано в Google Play.
In-App Updates поддерживает два способа обновления приложения:
- Flexible. Если пользователь согласится на обновление приложения, загрузка начнётся в фоновом режиме, при этом пользователь сможет продолжить пользоваться приложением. Как только загрузка завершится, в приложение (если оно активно) придёт результат загрузки, после чего мы можем либо предложить перезапустить приложение, либо дождаться, когда пользователь из него выйдет. Если же на момент окончания загрузки приложение находилось в фоновом режиме, то установка новой версии начнётся сразу же. Такой подход удобен в большинстве случаев, поскольку позволяет обновить приложение, не мешая пользователю.
- Immediate. Пользователь не сможет работать в приложении до тех пор, пока не обновится до новой версии. Когда он согласится на обновление, на этом же экране начнётся процесс загрузки и установки, после чего приложение перезапустится. Такой подход нужен в случае, если вышло критическое обновление приложения, либо если старая версия приложения более не может работать корректно. В остальных случаях лучше всего использовать первый вариант.
Рассмотрим первый вариант обновления приложения на примере одного из наших приложений «Страны мира«.
Перед началом работы нужно добавить библиотеку Play Core в приложение. Для этого в файле build.gradle модуля приложения добавим следующую зависимость:
implementation 'com.google.android.play:core:1.7.2'
Теперь создадим класс UpdateManager, в котором будем проводить работу, связанную с обновлением.
public final class UpdateManager {
private static UpdateManager instance;
private UpdateManager() {
}
public static UpdateManager getInstance() {
if (instance == null) {
instance = new UpdateManager();
}
return instance;
}
}
Перед тем, как показывать диалог, следует проверить наличие новой версии. Для этого воспользуемся классом AppUpdateManager для запроса к Google Play.
private static UpdateManager instance;
private AppUpdateManager appUpdateManager;
...
public void checkForUpdate(Activity activity) {
appUpdateManager = AppUpdateManagerFactory.create(activity);
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
}
});
}
К экземпляру класса AppUpdateManager привязываем слушатель OnSuccessListener, который будет получать результат запроса. Если новая версия приложения доступна, отправляем интент, отображающий диалог для скачивания новой версии.
public final static int UPDATE_REQUEST_CODE = 8;
...
public void checkForUpdate(Activity activity) {
appUpdateManager = AppUpdateManagerFactory.create(activity);
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
if (!activity.isFinishing()) {
try {
startUpdate(appUpdateInfo, activity);
} catch (IntentSender.SendIntentException e) {
if (BuildConfig.DEBUG) {
Log.e(ERROR_TAG, "SendError: " + e);
}
}
}
}
});
}
private void startUpdate(AppUpdateInfo appUpdateInfo, Activity activity)
throws IntentSender.SendIntentException {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
activity,
UPDATE_REQUEST_CODE);
}
Если пользователь согласится на обновление, начнётся процесс загрузки в фоновом режиме. Обратите внимание, что с помощью метода startUpdateFlowForResult() мы можем узнать, какое действие выбрал пользователь. Для этого мы передаём в него константу UPDATE_REQUEST_CODE, результат с этим кодом придёт в метод onActivityResult() активности.
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == UpdateManager.UPDATE_REQUEST_CODE) {
if (resultCode != RESULT_OK) {
Log.d(LOG_TAG, "error");
}
}
super.onActivityResult(requestCode, resultCode, data);
}
Здесь мы можем получить один из следующих результатов:
- RESULT_OK — пользователь согласился на обновление. Если вместо Flexible используется Immediate, то результат может не прийти, поскольку в этот момент Google Play возьмёт работу на себя.
- RESULT_CANCELED — пользователь отклонил или отменил обновление.
- ActivityResult.RESULT_IN_APP_UPDATE_FAILED — произошла какая-то другая ошибка, из-за которой пользователь не смог дать разрешение или отклонить обновление.
Поскольку мы используем Flexible метод обновления, нам нужно узнать, когда завершится загрузка новой версии. Для этого нам нужно добавить слушатель InstallStateUpdatedListener и привязать его к экземпляру AppUpdateManager после того, как начнётся загрузка. О том, что пользователь начал загрузку, мы узнаём, получим результат в методе onActivityResult() активности.
public void registerListener() {
if (appUpdateManager == null) return;
appUpdateManager.registerListener(listener);
}
private final InstallStateUpdatedListener listener = state -> {
if (state.installStatus() == InstallStatus.DOWNLOADED) {
}
};
С помощью этого слушателя мы можем узнать, загружается обновление или уже загружено, и если загружается — получить прогресс выполнения. Таким образом, при желании можно показывать прогресс загрузки прямо в приложении, но в данном случае мы этого делать не будем.
Когда загрузка завершена, отвязываем слушатель и проверяем, активно приложение или нет. Если приложение активно, то показываем сообщение о том, что обновление готово для установки, в противном случае запускаем установку обновления. Делается это с помощью метода completeUpdate() экземпляра AppUpdateManager. Также для этих целей мы добавим слушатель UpdateListener к нашему классу UpdateManager. Если этот слушатель привязан к активности — значит приложение активно, если отвязан — значит находится в фоне.
public final class UpdateManager {
public interface UpdateListener {
void onShowSnackbar();
}
private UpdateListener updateListener;
...
private final InstallStateUpdatedListener listener = state -> {
if (state.installStatus() == InstallStatus.DOWNLOADED) {
onUpdateDownloaded();
}
};
private void onUpdateDownloaded() {
if (appUpdateManager == null) return;
appUpdateManager.unregisterListener(listener);
if (updateListener != null) {
updateListener.onShowSnackbar();
} else {
appUpdateManager.completeUpdate();
}
}
}
Когда обновление будет загружено, пользователь увидит внизу экрана Snackbar с сообщением об этом и кнопкой для перезагрузки приложения. При желании можно Snackbar заменить на что-нибудь ещё.
Сам же Snackbar выглядит очень просто, при нажатии на Restart мы вызываем completeUpdate(), как делали бы это, будучи в фоновом режиме.
Snackbar snackbar =
Snackbar.make(mainView.getRootView(),
"An update has just been downloaded.",
10000);
snackbar.setAction("RESTART", view -> UpdateManager.getInstance().completeUpdate());
snackbar.setActionTextColor(
ContextCompat.getColor(mainView.onGetContext(), R.color.colorAccent));
snackbar.show();
Если пользователь проигнорирует это сообщение, обновление останется неустановленным и будет храниться в памяти. Поэтому при каждом запуске приложения мы будем проверять, имеются ли загруженные обновления, и если да — выводить сообщение. В противном случае, как и раньше, проверяем наличие обновлений в Google Play. Для этого вернёмся к слушателю OnSuccessListener из начала и добавим в него следующее условие.
public void checkForUpdate(Activity activity) {
appUpdateManager = AppUpdateManagerFactory.create(activity);
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
if (updateListener != null) {
updateListener.onShowSnackbar();
}
} else if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
if (!activity.isFinishing()) {
try {
startUpdate(appUpdateInfo, activity);
} catch (IntentSender.SendIntentException e) {
if (BuildConfig.DEBUG) {
Log.e(ERROR_TAG, "SendError: " + e);
}
}
}
}
});
}
Таким образом сообщение будет выводиться до тех пор, пока пользователь не обновит приложение до последней версии.
Итоговый листинг класса UpdateManager выглядит следующий образом:
public final class UpdateManager {
public interface UpdateListener {
void onShowSnackbar();
}
private UpdateListener updateListener;
private static UpdateManager instance;
private AppUpdateManager appUpdateManager;
public final static int UPDATE_REQUEST_CODE = 8;
private final String ERROR_TAG = "UPDATE_ERROR";
private UpdateManager() {
}
public static UpdateManager getInstance() {
if (instance == null) {
instance = new UpdateManager();
}
return instance;
}
public void attachUpdateListener(UpdateListener listener) {
updateListener = listener;
}
public void detachUpdateListener() {
updateListener = null;
}
public void checkForUpdate(Activity activity) {
appUpdateManager = AppUpdateManagerFactory.create(activity);
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
if (updateListener != null) {
updateListener.onShowSnackbar();
}
} else if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
if (!activity.isFinishing()) {
try {
startUpdate(appUpdateInfo, activity);
} catch (IntentSender.SendIntentException e) {
if (BuildConfig.DEBUG) {
Log.e(ERROR_TAG, "SendError: " + e);
}
}
}
}
});
}
private void startUpdate(AppUpdateInfo appUpdateInfo, Activity activity)
throws IntentSender.SendIntentException {
if (appUpdateManager == null) return;
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
activity,
UPDATE_REQUEST_CODE);
}
public void registerListener() {
if (appUpdateManager == null) return;
appUpdateManager.registerListener(listener);
}
public void completeUpdate() {
if (appUpdateManager == null) return;
appUpdateManager.completeUpdate();
}
private final InstallStateUpdatedListener listener = state -> {
if (state.installStatus() == InstallStatus.DOWNLOADED) {
onUpdateDownloaded();
}
};
private void onUpdateDownloaded() {
if (appUpdateManager == null) return;
appUpdateManager.unregisterListener(listener);
if (updateListener != null) {
updateListener.onShowSnackbar();
} else {
appUpdateManager.completeUpdate();
}
}
}
Итак, мы встроили In-App Updates в приложение, осталось только проверить его работу. Для этого отлично подойдёт такая особенность Google Play, как внутренний совместный доступ (Internal app-sharing). Он позволяет загружать APK или Android App Bundle на специальную страницу загрузки, к которым будут иметь доступ только тестировщики. Это позволяет быстро проверить приложение, при этом его не обязательно подписывать релизным ключом, можно делиться также и debug-версиями. Тестировщикам отправляется специальная ссылка на скачивание, которая перенаправляет их в Google Play, где им будет предложено установить данную версию приложения.
Для того, чтобы настроить внутренний совместный доступ, нам нужно задать список тестировщиков для этого приложения. Зайдём в консоль разработчика Google Play и откроем нужное нам приложение. В меню «Инструменты разработки» выберем «Внутренний доступ к приложению«, после чего откроется страница для настройки. Здесь нас интересует только «Управление пользователями с правами загрузки«.

Тут мы можем формировать различные списки тестировщиков, включать их и отключать. При нажатии на «Создать список» появится окно, где будет предложено ввести электронную почту каждого из участников тестирования, а также задать имя новому списку. Создадим список и добавим в него себя как тестировщика.

Теперь мы можем перейти на страницу загрузки для внутреннего совместного доступа. Сюда мы может публиковать различные APK для тестирования. Соберём два APK нашего приложения с разными кодами версий (например, 23 и 24) и загрузим их на эту страницу. При загрузке мы также можем задать название данной версии, чтобы было проще их различать.

После того, как APK будут загружены, они отобразятся в списке.

С этого момента мы готовы тестировать эти версии. Зайдём на устройство, с которого будем запускать приложение, и на нём перейдём по ссылке от версии 1.23. Откроется специальная страница, которая предложит перейти в Google Play для установки.

После перехода в Google Play нам будет предложено установить эту версию приложения.

Примечание! Если вместо страницы приложения вы видите сообщение «Настройки для разработчиков отключены», то нужно будет выполнить следующее:
- Зайдите на устройстве в Google Play.
- Откройте Настройки и найдите внизу «Версия Play Маркета».
- Кликайте по этой надписи несколько раз, до тех пор пока не появится уведомление «Вы стали разработчиком«. После этого у вас появятся дополнительные настройки в Google Play.
- Включите появившуюся опцию сверху «Внутренний доступ к приложениям«.
- Теперь при переходе по ссылке у вас будет загружаться страница приложения.
Как только установка завершится, открываем приложение. В нашем приложении под списком меню указывается текущий код и версия приложения, поэтому будем ориентироваться на неё, что обновление прошло успешно. Как видно, сейчас текущая версия 1.23.

Теперь закрываем приложение, снова открываем браузер и переходим уже по ссылке от версии 1.24. Нам аналогично будет предложено перейти в Google Play, только на этот раз мы можем не установить, а обновить или удалить приложение. Важно: не нужно обновлять приложение с этой страницы! Смотрим только, что она есть, и закрываем.

Снова заходим в наше приложение и, если всё сделано верно, отобразится диалог с предложением обновить версию.

Нажимаем на «Обновить«, после чего начинается скачивание обновления. За его прогрессом можно следить в шторке уведомлений.

Как только загрузка завершится, в наш слушатель придёт состояние DOWNLOADED, после чего внизу экрана отобразится сообщение с предложением перезагрузить приложение.

Нажимаем Restart, и здесь в работу включаются сервисы Google Play, которые обновят приложение до новой версии.

После того, как установка завершится, приложение запустится автоматически. Скроллим список вниз и видим, что версия приложения теперь стала 1.24, значит обновление прошло успешно.

Таким образом, благодаря In-App Updates, пользователи смогут более быстро получать новые версии, чем при обновлении непосредственно через Google Play.





А как сослаться на UpdateManager например в MainActivity?
А как сослаться на UpdateManager например в MainActivity? Что нужно писать в MainActivity что бы вызвать проверку и установку?
Здравствуйте!
UpdateManager это singleton-класс, поэтому вы можете обращаться к нему через метод UpdateManager.getInstance(), который вернёт экземпляр класса, и затем уже вызывать нужные методы.
Например, чтобы вызвать проверку установки, нужно написать следующее:
UpdateManager.getInstance().checkForUpdate(this);Делать это можно, например, в onCreate() активности.
Также в методах onResume() и onPause() следует привязывать и отвязывать соответственно слушатель.
@Override public void onResume() {
UpdateManager.getInstance().attachUpdateListener(updateListener);
}
@Override public void onPause() {
UpdateManager.getInstance().detachUpdateListener();
}
Когда обновление загружено и готово к установке, запустить его можно следующим образом:
UpdateManager.getInstance().completeUpdate()