Android для любознательных: противостояние батарейки и интернета.
Летит Push-а, нельзя скушать
После комментария тов. yalexey к предыдущему псто я чота полез разбираться как реализована технология "Push" в Android-е и какие грабли там закопаны.
Для тех, кто "не в теме". Изначально Push был придуман для того, чтобы экономить заряд аккумулятора телефона. Потому как вторым после экрана потребителем электроэнергии являются радиомодули. Вот, мол, и давайте их выключать на время, пока никакого обмена информацией нет. Только тут есть одна закавыка. Если телефон что-то хочет "сказать первым", то нет проблем: "просыпаемся", устанавливаем сеанс связи, обмениваемся данными, "засыпаем" обратно. А если наоборот, нужно сообщить что-то телефону извне, то тут уже всё сложнее.
Первая часть вопроса заключается в том, что обычно у устройства есть как минимум два линка "в мир": Wi-Fi и GPRS. Причём первый демонстрирует бо́льшую энергоэффективность в случае непрерывной передачи значительных объёмов трафика: да, прожорливый, но зато, сцуко, быстрый. Второй же куда более экономен, если надо надо принимать-отдавать чего-нибудь изредка и по чуть-чуть: в отличие от Wi-Fi он умеет уходить в "дежурный режим" (Idle) и просыпаться по требованию сети сотовой связи, а точнее, так называемого Radio Resource Controller (RRC), размещённого на стороне оператора. В случае c Wi-Fi тоже имеют место быть всякие оккультные технологии типа DTIM и WMM, призванные в том числе и экономить батарейку. Но разброд и шатание среди производителей оборудования, а также тех, кто потом настраивает все эти железки, обычно не позволяют эффективно их применять.
Вторая часть вопроса упирается в некоторые особенности функционирования приложений на мобильных платформах. Так, на "взрослых" стационарных компьютерах оперативной памяти обычно столько, что хоть попой кушай, к ней "до кучи" прилагается "подкачка" (swap), код большинства приложений скомпилирован в не особо прожорливые бинарные файлы, а интерфейс пользователя позволяет эффективно взаимодействовать с человеком и в случае исчерпания системных ресурсов явно спросить у последнего что бы он хотел закрыть, а что оставить. В случае с телефонами и планшетами дела обстоят намного хуже. Оперативной памяти не так много, быдлокод на Java жрёт её как корова веники, пользователь бо́льшую часть времени может вообще не ведать что происходит внутри его устройства, так как включает его экран лишь изредка. Так что в документации для программистов явно указано: операционная система может в любой момент без спроса взять и "убить" ваше приложение, если только оно не находится на экране прямо здесь и сейчас. И любая программа всегда должна быть к этому готова. Некоторые послабления сделаны только для аудиоплееров, VoIP-клиентов и навигаторов, да и то не везде и не всегда.
Так что, допустим, написали вы собственный мессенджер а-ля WhatsApp, Telegram и иже с ними. Установили соединение с сервером и спокойно ждёте входящих сообщений. А Вася Пупкин взял и запустил на телефоне красивую разухабистую игрушку, ей стало не хватать оперативной памяти, так что операционная система, недолго думая, взяла и выгрузила ваш мессенджер. Так что сообщения от Вити Батарейкина ему больше не дойдут. Ни во время игры, ни после её завершения.
Третий аспект заключается в особенности работы GPRS-радиомодуля. Как бе всегда понятно, когда ему нужно "проснуться". Но когда ему следует "заснуть"? Как он может определить, что мы больше не собираемся ничего передавать и стоило бы уйти в дежурный режим, дабы не потреблять драгоценные милливатты? Ответ: никак. Поэтому тупо ввели ряд тайм-аутов. Если в течение какого-то интервала времени через сеть ни прошло ни одного пакета, то передатчик переходит сначала в "полусон" и ждёт ещё сколько-то (милли)секунд. Если в течение этого интервала снова ничего интересного не произошло, тогда уже он "засыпает" глубоко и крепко. И спит пока не разбудят с той (телефон) или другой (оператор связи) стороны.
Теперь представьте себе, что на телефоне одновременно запущено несколько приложений. И они никак не согласовывают друг с другом когда им хочется что-нибудь передать "в мир". Первое "будит" радиомодуль, "общается", "отпускает" его. Тот выполняет все инструкции, выжидает тайм-аут, засыпает. Но тут же что-нибудь захотело "сказать" другое приложение. Беднягу-передатчика снова будят, снова мучают, потом он снова засыпает. Но тут его дёргает третья программа. А затем снова первая. И так по кругу. Вот и выходит, что по результатам исследований AT&T Labs Research, у среднестатистического пользователя смартфона на приём-передачу примерно всего 0,2% трафика (в байтах) тратится порядка 46% заряда аккумулятора. Такие дела.
Чтобы хоть как-то скомпенсировать вышеизложенную несправедливость бытия, как раз и был придуман костыль в виде Push-а. Но и с ним, к сожалению, всё совсем не так хорошо, как хотелось бы.
Чего же придумали эти хитропопые инженеры в попытке нейтрализовать негативные эффекты, возникающие вследствие взаимодействия мобильных приложений с внешним миром? А давайте, говорят, введём в цепь обмена данными между клиентом и сервером двух посредников: специальную системную службу на телефоне и специальный "центральный" сервер, с которым будет соединяться та самая специальная системная служба. Так появился Push.
Другими словами, пока приложение работает в foreground-е (то бишь активно), оно может без ограничений общаться с кем хочет, когда хочет и в любых количествах. Но как только оно уходит в background (в "фон") неважно по какой причине, "засыпает". А заснув, лишается возможности что-либо отправлять или принимать. Но при этом может попросить системную службу "разбудить" себя либо по таймеру (например, через N секунд), либо по событию (что-то пришло снаружи).
Но как оно получит что-то снаружи, ежели само спит? А очень просто. Отправитель сообщения сперва передаст его на сервер Google / Apple / Microsoft в зависимости от марки устройства. Тот, в свою очередь, найдёт заранее установленную TCP-сессию до push-службы телефона и передаст сообщение ей. А уже push-служба, в свою очередь, разбудит приложение, передаст ему управление и скажет что надо делать.
Вроде бы как всем хорошо. Покуда экран аппарата выключен, все спят. Только шофёр push-агент не спит. Он героически "держит" одну-единственную TCP-сессию с одним-единственным сервером, причём без большой и малой нужды никакие данные через неё не гоняет. Таким образом, и радиомодули тоже спят. А push-сервер тоже, сцуко, хитрый. Если на него придёт ряд однотипных сообщений, то он сначала выжидает минуту-другую, дабы поднакопить их. А потом "выстреливает" всё это на клиентское устройство одной большой пачкой, чтобы избежать серии последовательных включений-выключений радиопередатчика. Это называется "throttling" (не путать с троллингом). Казалось бы, идиллия и рай на Земле.
Но нет. Как всегда, здесь появляется куча разных побочных эффектов.
1. Некоторые производители в дополнение к "стандартным" технологиям энергосбережения добавляют свои собственные. Особенно этим любят баловаться Sony и Samsung. Что именно они "доделывают", доподлинно неизвестно. То ли отправляют в глубокий сон самого push-агента, то ли принудительно отключают всю связь. Но факт остаётся фактом. Меньше всего проблем с Push-ем наблюдается на "родных" Nexus-ах, больше всего — на изделиях вышеупомянутых брендов.
2. На устройстве либо где-нибудь "по дороге" могут присутствовать антивирусы / файрволлы, которые вмешиваются в работу push-механизма и частенько ломают его.
3. Хотя чисто формально для работы push от пользователя не требуется наличия зарегистрированного на сервисах Google активного аккаунта, некоторые утверждают, что тем не менее без него "почему-то" push не работает.
4. Если у аппарата присутствуют и Wi-Fi, и GPRS модули, то push будет всегда предпочитать GPRS ввиду его бо́льшей энергоэффективности для обмена небольшим количеством данных. Часто даже случается, что телефон ингорирует настройку "передача данных через сотовые сети" и всё равно начинает использовать GPRS, даже если пользователь его отключил. Иногда это становится источником заметных непредвиденных финансовых расходов в роуминге. А бывает и наоборот. Допустим, имеется стабильный приём сигнала по Wi-Fi и очень неустойчивый через GPRS. Пользователь наивно полагает, что всё хорошо и он на связи, только вот push-и скорее всего не дойдут. А вместе с ними "потеряется" и уведомление о каком-нибудь важном входящем сообщении.
5. Push-агент и push-сервер периодически пингуют друг друга с некоторым интервалом дабы убедиться, что TCP-сессия ещё жива-здорова. Это называется "heartbeat". В андроиде пресловутый интервал равен 15-ти минутам для Wi-Fi и 28 минутам для GPRS. И вот тут начинаются всякие интересности. Если, например, домашний маршрутизатор, либо NAT интернет-провайдера оборвёт неактивную TCP-сессию раньше, чем пройдёт 15 минут, то с момента разрыва до окончания этого интервала push гарантированно перестанет работать. То же самое произойдёт, и в том случае, если после "пробуждения" беспроводного интерфейса DHCP-сервер выдаст телефону другой IP-адрес.
6. А ведь операторы сотовой связи (ОПСОСы) в России тоже выдают исключительно "серые" адреса, трафик с которых потом и фильтруют, и маскарадят (NATят). Каким образом они это делают, какие ограничения по количеству одновременно открытых TCP-сессий и какие тайм-ауты неактивности выставляют на своём оборудовании — никто кроме них самих не знает. То есть, вполне может произойти, что при перемещении из одной соты в другую либо через 5...10 минут все установленные с вашего устройства сессии принудительно оборвут на стороне оператора. Что при этом случится с push-ем, наверное, сами догадаетесь.
Таким образом выясняется, что механизм push — крайне ненадёжный и ломается от малейшего дуновения. И одно дело, когда таким образом тебе сообщают о погоде на завтра или выходе нового ролика на Youtube. И другое дело, когда ты ждёшь важное сообщение в электропочту или мессенджер. Естественно, программистов такой расклад совершенно не устраивает, и они начинают лепить и вставлять костыли различной степени корявости, так или иначе приводящие к увеличению расхода трафика и ускорению разряда батареи. Что они могут предпринять.
* Принудительно запретить телефону гасить радиомодули и выгружать приложение. Тут понятно, это совсем жесть.
* Не надеясь на push, установить и поддерживать отдельное постоянное TCP-соединение со своим собственным сервером.
* Как разновидность предыдущего варианта, периодически (например, раз в минуту) "будить" собственное приложение по системному таймеру, пинговать свой сервер для проверки наличия отклика и актуальности TCP-сессии, после чего "засыпать" обратно. То есть best practise violation в чистом виде.
* Собирать статистику по задержкам поступления push-сообщений. Если они находятся в разумных пределах, то продолжать использовать push. Если нет — применить какой-то из вышеописанных способов.
Эти костыли такие костыли. Но конкретно меня больше всего огорчает даже не это. Допустим, есть некая SIP-трынделка. Если нужный нам SIP-регистратор не поддерживает TCP, то это вообще клиника. Регистрация сто пудово отвалится очень быстро. Либо придётся постоянно держать включёнными радиомодули. Но предположим, что всё не так плохо.
В таком случае в целях экономии батарейки было бы очень круто постоянно "спать", а информацию о входящем вызове получать через push. Та же самая Bria, кстати, умеет так делать. Но после осознания механизмов, лежащих в основе push-а стало понятно, что это ни разу не надёжный метод. Пропущенные звонки практически гарантированы. Остаётся только всегда поддерживать собственную TCP-сессию, периодически "подновляя" её. Другими словами, запущенный на смартфоне VoIP-клиент — это всегда "до свиданья" аккумулятору, как ни крути.
И теперь мне стало понятно, почему иногда сообщения в WhatsApp приходят с большой задержкой, иногда до получаса. Но программисты в этом не виноваты. Против нас выступает само мироздание. А также ОПСОСы.
На всякий случай повторюсь, что мои умозаключения справедливы для Android, других вендоров я не рассматривал. Но сомневаюсь, что там будут какие-то прям кардинальные отличия. Ведь основополагающие принципы работы везде одни и те же.
При написании этого псто использовались следующие статьи:
https://productforums.google.com/forum/#!topic/nexus/fslYqYrULto[1-25]
https://habrahabr.ru/company/mailru/blog/260841/
https://habrahabr.ru/post/274169/
https://hpbn.co/mobile-networks/
https://hpbn.co/optimizing-for-mobile-networks/
Пожалуйста, сходите к нему и расскажите, что он большой молодец.