Абсолютное большинство мобильных приложений имеет интересный нюанс – «хвост» старых версий, которыми все еще продолжают пользоваться. В этой статье мы посмотрим, какие проблемы это приносит и как с этим бороться. Материал будет полезен и мобильным разработчикам, и тем, кто каким-либо образом связан с разработкой мобильных приложений, к примеру, разрабатывает backend-сервисы, которые используются в приложениях.

Что такое хвост старых версий и какие от него проблемы?

Допустим, пару лет назад вы выпустили версию 1.0.0, с тех пор произошло еще 30 релизов и текущая актуальная версия приложения в App Store или Google Play у вас, к примеру, 2.5.1. Если вы проанализируете логи обращений к серверу или посмотрите в систему аналитики, то увидите, что предыдущими версиями приложения всё ещё пользуются. Хвост старых версий – это те версии приложения, которые не просто установлены на телефонах пользователей, этими версиями продолжают пользоваться.

Если вы хотите увидеть динамику распределения версий по времени, можете построить графики на основе логов. Если у вас в приложении используется Firebase, подобный график можно посмотреть в виджете «Users by App version over time» из раздела Dashboard.

Я вижу 3 основных группы проблем, которые создает подобный хвост:

1. Баги в текущих версиях приложения

В работе версий приложения, которые все еще встречаются у пользователей, есть баг (никогда такого не было и вот опять). Это может быть креш или какой-то плавающий баг в одной из фич, который приводит к некорректной работе приложения. 
Даже если вы уже выложили новую версию с правкой, это не значит, что её уже успели скачать. «Довольные» пользователи нагружают вашу службу поддержки, пишут отзывы в App Store и Google Play, делятся негативом в социальных сетях.

2. Проблемы обеспечения обратной совместимости

Старые версии приложений могут использовать старые версии серверного API, значит нужно поддерживать обратную совместимость, а это далеко не всегда просто и бесплатно. В какой-то момент команда, отвечающая за backend, может сказать, что длительная поддержка разных версий API доставляет им сложности при разработке и тестировании. При этом неважно, каким из путей пойти: версионировать API или пытаться организовать ветвление if-else в коде имеющихся методов.

3. Недостаточно быстрое внедрение фич

В новых версиях приложения добавлен функционал, могут быть важны данные аналитики или доход, который этот функционал приносит, вот только пользователи не торопятся обновляться, приходится ждать. При таком ожидании мы можем терять время и деньги.

Форсируем обновления 

Посмотрим на варианты форсирования обновлений мобильных приложений. Учтите, что время не стоит на месте – на момент прочтения этой статьи могут быть и другие инструменты, поэтому перед тем, как возьметесь за реализацию, проведите небольшое исследование.

1. Готовые решения

Для форсирования обновлений существуют готовые библиотеки. К примеру, на iOS такой библиотекой является Siren (ранее Harpy). При запуске приложения номер актуальной версии приложения будет получен из App Store. На основе выбранных вами правил пользователю будет показаны варианты: обновиться сейчас, пропустить новую версию или отказаться от обновления.
Библиотека существует с 2015 года, имеет тысячи звезд на Github, поддерживает локализацию на десятки языков, устанавливается через SPM и CocoaPods, а еще проверяет на возможность обновиться на новую версию с поправкой на текущую версию iOS на устройстве (к примеру, если новая версия приложения требует iOS 13, а на устройстве iOS 12, возможно, пользователю бесполезно сейчас предлагать скачивать обновление). 

Решение довольно простое и надежное, рекомендую рассмотреть его, если у вас нет необходимости или желания управлять номерами версий, на которые нужно обновиться, и вы просто хотите, чтобы пользователям всегда предлагалась самая свежая версия. Библиотеки, аналогичные по функционалу Siren, есть и на Android, к примеру AppUpdater

Для Android-приложений можно воспользоваться инструментом обновления от Google in-app updates. Он тесно интегрирован с Google Play, относительно прост в использовании, требует для своей работы Play Core library. In-app updates поддерживает 2 сценария обновления.
Сценарий Flexible updates позволяет запустить скачивание обновления в фоне, не прерывая взаимодействия пользователя с приложением, с точки зрения UX это очень хорошо. Второй сценарий – Immediate updates потребует от пользователей скачать и установить обновление, прежде чем продолжить использование приложения. Про его работу на хабре уже есть обзорная статья.

Flexible update - позволяет обновить приложение в фоне, не отвлекая пользователя
Flexible update - позволяет обновить приложение в фоне, не отвлекая пользователя

2. Самописные решения

Допустим, мы выпускаем новые версии приложений очень часто и не хотим тревожить пользователя предложениями обновиться, когда в этом нет крайней необходимости, или мы хотим управлять процессом «раскатки» релизов и не хотим, чтобы приложения сразу при запуске старались обновиться на последнюю версию. В этом случае для форсированного обновления нам бы пригодилась возможность гибко управлять номерами поддерживаемых версий.

Для каждого приложения на стороне сервера мы можем хранить два номера версий – минимальную необходимую и минимальную рекомендуемую. Идентификатором приложения может выступать Package name для Android и Bundle ID для iOS.

При запуске приложения мы будем сравнивать номер его версии с номерами, хранящимися на бэкенде:

  • Если версия приложения ниже минимальной необходимой, нужно обновление (показываем кнопку перехода в нужный магазин приложений).

  • Если версия приложения выше минимальной необходимой, но ниже минимальной рекомендуемой, можем предложить пользователю обновление, от которого он может отказаться до следующего запуска или до выхода следующей рекомендуемой версии.

Пример простой реализации предложения обновиться
Пример простой реализации предложения обновиться

Я предлагаю хранить версии в формате семантического версионирования, а не номеров сборки. Согласен, что сравнение двух целых чисел, которыми обычно характеризуют номера сборки, проще, чем парсинг строк в формате «1.2.17» и «1.5.13» и поочередное сравнение номеров мажора, минора и патча (на все это хорошо бы иметь тесты + на iOS можно использовать стандартную функцию compare), но у семантических версий вижу важные преимущества:

  • Сотрудники службы поддержки, пользователи, аналитики, проджект и продакт менеджеры редко понимают про номера сборок, им проще посмотреть актуальную версию в Google Play и историю версий в App Store, а также историю релизов в системах аналитики.

  • Номера сборки не всегда просто переносить между разными сборочными сервисами и машинами, вам придется помнить об этом при настройке CI/CD.

Храним номера версий на своем сервере

Можно хранить номера версий на сервере и отдавать их через API. Для хранения можно использовать базу данных или статичный файл (не забудьте в этом случае поставить короткое время жизни кэша или отключить его). Преимущества файла в том, что он будет работать быстро и не создавать при этом нагрузку на базу, что может быть актуально для приложений с большой аудиторией. 
Пример реализации такого подхода можно посмотреть в материале Force-Updating Your Apps Should Be Industry Standard.

Firebase Remote Config

Firebase Remote Config – простой, бесплатный и довольно удобный инструмент для работы с различными фиче-флагами и параметрами. К вашим услугам панель администратора, где можно менять значения параметров, а также клиентский SDK, готовый к внедрению в мобильные приложения.
Номера версий вы можете хранить в Remote Config, пример реализации можно посмотреть тут.

Если в приложении уже есть Firebase, добавить Remote Config очень просто, но важно помнить про следующие нюансы:

  • Remote Config не предназначен для частого чтения и имеет кэш со своим временем жизни (по умолчанию 12 часов), а значит изменения могут долго добираться до клиентов. Вы сможете управлять временем жизни кэша и использовать более агрессивный (частый) опрос, но в этом случае можете упереться в квоты Firebase. К тому же, если в Remote Config будет много параметров, частое скачивание их полного набора создаст ненужную нагрузку.

  • В консоли Remote Config есть история изменений – вы сможете увидеть, кто и когда поменял значения, но у вас не получится ограничить доступ определенным пользователям на изменение какой-то части параметров. 

  • Remote Config не всегда стабильно вел себя при тестировании.

Мысли, идеи и советы

  1. Пользователи не всегда хотят или могут скачивать обновления. Им может быть неудобно в данный момент: садится телефон, дорогой или медленный интернет, не хватает места для установки новой версии. Если вы будете затруднять процесс использования ваших приложений частыми запросами на обновления, рискуете вызвать недовольство. 

  2. В iOS 13 было снято ограничение на размер приложений для загрузки через сотовую связь, но необходимость обновить приложение более 200 Мб может отпугнуть пользователя.

  3. Если какая-то часть кода приложения очень часто меняется и требует частых обновлений, рассмотрите варианты ее переноса на бэкенд, чтобы ее можно было менять без перевыпуска приложения.

  4. Представьте, что вам пришлось выложить новую версию приложения с другим Package name на Android или Bundle ID на iOS. Фактически это означает выкладку новых приложений в сторы. Механизм обновления перестанет работать – нужно как-то мигрировать пользователей в новые приложения. Для самописных вариантов вы сможете помимо номеров версий заложить отдельное поле с url нового приложения для миграции.

  5. Если у вас есть несколько приложений, которые собираются из общего набора модулей, каждый из которых имеет свою версию, рассмотрите вариант сравнивать версии поддерживаемых модулей, а не приложений.

Комментарии (11)


  1. ws233
    21.01.2022 12:27

    Согласен, что сравнение двух целых чисел, которыми обычно характеризуют номера сборки, проще, чем парсинг строк в формате «1.2.17» и «1.5.13» и поочередное сравнение номеров мажора, минора и патча (на все это хорошо бы иметь тесты)

    Почему-то мне казалось, что в строку iOS уже вшит этот функционал. Нет необходимости дублировать имеющийся функционал и писать на копию тесты.


    1. slutsker Автор
      21.01.2022 13:37
      +1

      да, на iOS для этой задачи можно использовать compare (по ссылке есть примеры и граничные кейсы). спасибо, добавлю в текст


  1. bogolt
    21.01.2022 15:26
    +5

    > 1. Баги в текущих версиях приложения
    Пользователь сам имеет право выбирать оставаться на старой версии или нет. Если выбрал оставаться на старой значит он добровольно выбрал баги

    > 2. Проблемы обеспечения обратной совместимости
    Да да, разработчики привыкшие писать вебсайты очень удивляются когда оказывается что бывают старые версии и нужно было проектировать взаимодействие с бэкендом с учетом этого. Чтож или они научится или пользователи проголосуют ногами. Не вижу почему это должно быть причиной для обновления.

    >3. Недостаточно быстрое внедрение фич
    Итак ваши маркетологи сказали что запили еще 100500 АБ тестов, которые добавили всего-то 300 МБ в приложение и хотят поскорее обкатать их на пользвателях… и ах да вырезали ту ненужную фичу для которой не было иконки и все равно ей пользовалось не более 15% аудитории которой можно пренебречь.


  1. belch84
    21.01.2022 16:36
    +5

    Пользователь сам имеет право выбирать оставаться на старой версии или нет. Если выбрал оставаться на старой значит он добровольно выбрал баги
    Возможно, он выбрал не столько баги, сколько право не нагружать свое устройство кучей украшений и доработок, которые кажутся полезными исключительно авторам приложения. К сожалению, при установках обновлений нельзя выбирать только исправление ошибок, а не исправление ошибок + добавление «сверполезных» возможностей + внесение новых ошибок


    1. pyrk2142
      21.01.2022 17:22
      +2

      Максимально поддержу. Я недавно обновил Телеграмм, когда старая версия перестала работать. Плюсов не заметил. Из минусов - вылеты, поехавшая верстка, более долгая загрузка.

      Проблема не в том, что пользователи не хотят обновляться, проблема в том, что разработчики делают так, что пользователю стремно обновлять приложения.


  1. mopsicus
    22.01.2022 10:08

    Мы делаем проще, в приложениях есть версия протокола, если необходимо всех перевести на последнюю версию, то при авторизации на нашем сервере проверяется версия протокола приложения и текущая актуальная. Если в приложении меньше, то оно блокируется и требует обновится, приходит команда с сообщением и урлом на стор. Таким образом можно даже сообщить обновится на другое приложение, если текущее по каким-либо причинам забанят.


    1. slutsker Автор
      22.01.2022 10:16

      То есть обновить отдельно, к примеру, iOS приложение не получится? Нужно будет сразу и Android обновлять?


      1. mopsicus
        22.01.2022 19:13

        Почему же, можно и отдельно. При авторизации на сервере клиент передает инфу о себе: язык, платформа, протокол и еще 5-8 параметров. И можно легко сегментировать кому обновляться.


        1. m0tral
          22.01.2022 22:50

          Принудительное обновление приложений это хреновый кейс, особенно в случае редизайна приложений, пример Тинькофф, снесли темный интерфейс, и потом год получали 1* в отзывах, пока не выкатили наконец обновление, или умышленное сокрытие.


          1. mopsicus
            23.01.2022 00:27
            +1

            Принудительное обновление это все-таки не рядовая ситуация :) Но бывает без него никак.


  1. DabjeilQutwyngo
    24.01.2022 06:57

    В какой-то момент команда, отвечающая за backend, может сказать, что длительная поддержка разных версий API доставляет им сложности при разработке и тестировании.

    1) Как вы и такая команда предполагаете обрабатывать ситуацию развития приложения и его перехода на новые версии ОС, когда у пользователя одно и тоже аппаратное обеспечение, которое не может обеспечить должную производительность и ресурсы новым версиям? Т.е. пользователь не обновляет ОС и не может: потолок производительности устройства достигнут, а то и новая ОСь на него просто не встанет из-за архитектурной несовместимости.

    2) Каким образом предполагается перенос всех приложений и данных пользователя на новую ОС и устройство, чтобы он мог перейти на актуальную версию вашего приложения? Это нормально вообще отменять усилия пользователя по приобретению/установке/настройке/привыканию/наработке опыта эксплуатации? Если бы эти усилия не отменялись, и обновление приложения действительно было безопасно и не затратно по усилиям, тогда бы можно было обновлять автоматически. А пока это вызывает означенный конфликт.