Как стать автором
Обновить

Комментарии 43

Спасибо. Недавно на собеседовании поднялся вопрос: зачем нужно переносить работу в сервис, если он не foreground и не bound? Ведь гарантий продолжения работы после выхода из активити нет? Ответ — система убьет процесс с запущенным сервисом с меньшей вероятностью. (где это использовать — другой вопрос:) )

Также к этому можно добавить про сочетание использования сервисов с WakeLock`ом.

Не просто с меньшей вероятностью.


Во-первых, это логично и правильно: это независимое действие, а не часть какой-то activity. (Аналогично: зачем разделять программу на функции и классы?)


Во-вторых, некоторые гарантии выполнения после выхода из activity всё-таки есть:


When an app goes into the background, it has a window of several minutes in which it is still allowed to create and use services. At the end of that window, the app is considered to be idle. At this time, the system stops the app's background services, just as if the app had called the services' Service.stopSelf() methods. Under certain circumstances, a background app is placed on a temporary whitelist for several minutes. While an app is on the whitelist, it can launch services without limitation, and its background services are permitted to run.

Я именно про Oreo и говорю, и выше процитировал именно ту страницу, на которую вы дали ссылку.

Такая схема позволяет, с одной стороны, мгновенно доставлять push-уведомления всем приложениям (не дожидаясь следующего периода синхронизации), с другой стороны, не держать множество приложений одновременно запущенными.

Простите за офф-топик, но такая схема еще позволяет доминировать на рынке прошивок. На самом деле, телефон без установленных на нем Google Play Services лишается практически всех нужны для нормальной работы приложения механизмов: пушей, карт, локации, activity recognition и т.п.

Да, это пример lock-in'а. С другой стороны, ничто не мешает кому-нибудь разработать свободный (или просто свой) аналог Play Services и использовать его.

Спасибо и за эту часть статьи. Было сложно, но я справился, осилил и осознал. Жду следующей части, так как там для меня из анонса как раз будет ВЕСЬМА интересно. Хотелось бы услышать подробнее о родной файловой системе для Андроида, а так же о поддержке других файловых систем. А так же о возможности (лёгкости / сложности) получения рута, и почему его получали через эксплойты.
Невероятно интересный цикл статей. Спасибо вам!
С удовольствием прочел весь цикл, ждем продолжения, спасибо!
Большое спасибо за информацию, тема познавательная
Офигенски. Именно такого рода инфморации мне всегда недоставало.
Пытался читать наверное с десяток книжек по архитектуре андроида. Но там всё так затянуто, и разбавлено водой что я не успел узнать ничего прежде чем мне надоело читать.

Статьи отличные. Сжато, кратко, доступно.
Я правильно понимаю, что всякие вацапы с вайберами работают, как службы в фоне удерживая постоянное соединение с сервером ожидая входящих сообщений, а GUI активности запускаются только при необходимости. Причём при удалении активити из памяти службы продолжают работать? Или GUI для них — это отдельное приложение, которое взаимодействует с сервисами?

Да, именно в этом и есть смысл разделения на activity (GUI), которые могут запускаться, когда нужно, завершаться и перезагружаться; и сервисы, которые остаются запущенными даже без activity. Нет, всё это происходит в рамках одного приложения, конечно.


Но, как я рассказал в статье, Android 8 не позволяет таким приложениям поддерживать соединение в фоне (и вообще серьёзно ограничивает фоновые сервисы); вместо этого нужно использовать push-сервисы вроде Firebase Cloud Messaging. Вероятно, многие из этих приложений так и делают.


С другой стороны, пока что Viber и WhatsApp работают на Oreo в режиме совместимости (у обоих target SDK < 26), то есть на них эти ограничения не распространяются.

Нет, всё это происходит в рамках одного приложения, конечно.

А есть вообще возможность разделения на разные приложения? Межпроцессорное взаимодействие по примеру тех-же PIPE`ов в десктопных системах или WCF из мира .Net?

Конечно есть! Обычные pipe-ы никуда не делись (Android is Linux), правда совсем не очевидно, как их прокидывать из одного приложения в другое. Основной механизм IPC на Android — Binder, про который я рассказывал в первой статье.


Я опять приведу в качестве примера Google Play Services — приложение, которое не имеет собственного интерфейса, но реализует разнообразную функциональность, которой пользуются (подключаясь к нему через Binder) другие приложения.

Спасибо за статью. Нет ли в ваших планах раскрыть тему callback hell в Андроид?

Честно говоря, именно с callback hell я в Android не встречался, но есть и много других, мягко говоря, сложностей, с которыми приходится сталкиваться разработчику — в основном это вопросы архитектуры приложения и правильной работы с lifecycle (в том числе фрагментов). Решается это использованием реактивных библиотек вроде RxJava или новых Android Architecture Components. Подробнее об этом я собираюсь рассказать в одной из следующих статей.

Большое спасибо! Очень интересно было бы услышать о Firebase Cloud Messaging, как его использовать на практике? Подозреваю в MIUI это поможет решить проблему с фоновой синхронизацией, так как сторонние приложения узнают о том, что пора синхронизировать что-то от Google Play Services(получат команду и инициализируют полноценное соединение)?

Туториал по практическому использованию FCM в приложениях под Android есть на официальном сайте, но там нет ничего особенно интересного. FCM/GCM — не какое-то новое решение, большинство приложений их уже использует, так что проблемы с синхронизацией, вероятно, вызваны чем-то другим.

Просьба не кидаться камнями, я начинающий разработчик и хочу внедрить более современный(правильный) и надежный механизм синхронизации в свое приложение(синхронизатор буфера обмена между девайсами), который будет использовать гуглосервисы и тем самым экономить заряд, трафик и вообще быть более правильным с точки зрения dev. Спасибо за цикл статей, очень интересно!
Но основной механизм освобождения памяти в Android — это завершение наименее используемых компонентов приложений (в основном activity)

Это неверно, если только не писать многопроцессное приложение. Завершается процесс целиком.
stackoverflow.com/questions/34834490/android-memory-management-granularity-activity-or-process

К сожалению, этот вопрос плохо описан в документации. Насколько я понимаю, здесь есть такие особенности:


  • при нехватке памяти система действительно завершает процессы целиком, при этом она может вызвать, а может и не вызвать onDestroy() у запущенных компонентов;
  • когда activity завершается не прямо перед завершением всего процесса (а, например, при повороте экрана), onDestroy() вызывается обязательно, но освобождение памяти самой activity происходит через обычный механизм сборки мусора (в том числе, если сохранить лишнюю ссылку, будет activity leak).

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


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

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

Согласен, официальная документация как раз является главной причиной подобного заблуждения. Но не стоит его поддерживать и распространять, называя «основным механизмом освобождения памяти в Android», т.к. достаточно достоверно известно (ответы Dianne Hackborn и Ian Lake на stackoverflow, а также из собственных тестов), что подобного механизма в Android вообще пока нет.

Наверно, я не очень удачно сформулировал. Конечно же, основной механизм освобождения памяти в Android — завершение всего неиспользуемого. Вопрос только в том, может ли приложение перейти из состояния «activity1 работает, acivity2 работает» напрямую в состояние «activity1 работает, acivity2 завершена», или только через состояние «activity1 заврешена, acivity2 завершена, процесс убит».


Переформулировал этот кусок в статье.

Теперь другое дело. Однако, есть ещё замечание:
даёт их компонентам шанс дополнительно освободить ресурсы, вызывая такие методы, как onDestroy, и завершает их, полностью освобождая используемую ими память и ресурсы

Насколько мне известно, это тоже неверно и onDestroy в таком случае не вызывается или как минимум не стоит на это рассчитывать. Например из статьи той же Dianne Hackborn:
Once Android determines that it needs to remove a process, it does this brutally, simply force-killing it. The kernel can then immediately reclaim all resources needed by the process, without relying on that application being well written and responsive to a polite request to exit.

Да и в документации повсюду написано, что onDestroy никому и никогда не гарантирован.

Рассчитывать не стоит и не гарантирован (когда по памяти завершают, иначе гарантирован), но в документации явно написано, что система может его вызвать в такой ситуации:


onDestroy()

Called before the activity is destroyed. This is the final call that the activity receives. The system either invokes this callback because the activity is finishing due to someone's calling finish(), or because the system is temporarily destroying the process containing the activity to save space. You can distinguish between these two scenarios with the isFinishing() method. The system may also call this method when an orientation change occurs, and then immediately call onCreate() to recreate the process (and the components that it contains) in the new orientation.
Да, но принципиально, что может не вызвать, соответственно рассчитывать на этот метод как освобождающий ресурсы или извещающий о том, что сейчас приложение окончательно убьют не имеет смысла. А абзац как раз про механизмы освобождения памяти.
Спасибо, интересно
Долгожданное продолжение отличного цикла статей. Спасибо!
Спасибо за статьи! Очень интересно!
Вопрос есть, я столкнулся с проблемой передачи данных по сети.
ru.stackoverflow.com/questions/725580/android-http-connection-refused
Мой сервис, каждые 10 секунд отправляет данные на сервер. Сервис Sticky.
На Андроид 4.1.2 все работает хорошо, а на Андроид 5.1.1 нет. Как только система уничтожит сервис при нехватке памяти, то после перезапуска сервиса отправка данных на сервер перестает работать. Если вручную удалить приложение из списка задач и запустить снова, то отправка начинает работать. Также отправка возобновляется, если я снова жму иконку программы в списке задач. Насколько я понял из статьи с 5.0 версии Андроида используется новая JVM. Не с этим ли связана эта проблема? Как мне ее решить? Запускать activity и service в разных процессах? У меня возникло ощущение, что система прибивая процесс и запуская его снова, оставляет заблокированными сетевые порты.

Нет, переход на ART вряд ли повлиял на работу сетевых соединений.


(Этот абзац не специфичен для Android, а верен на любой Linux-системе) Действительно, система может освобождать занятые порты не сразу после завершения державшего их процесса, а только после отправки всех буферизованных данных (это называется socket linger, и с этим часто борются с помощью SO_REUSEADDR), но это актуально для портов с заранее известным номером. Если вы просто делаете POST-запрос, система автоматически назначит вам подходящий свободный порт, и этой проблемы не возникнет.


Хабр — не Тостер, но думаю, вам стоит посмотреть с помощью сниффера, в чём разница в успешных и неуспешных запросах, как вам и порекомендовали на StackOverflow.


Ну и не могу не упомянуть, что отправка данных на сервер каждые 10 секунд — это, мягко говоря, не лучшая идея. Создавать для этого каждый раз новый сокет и забывать его закрыть — тоже.

Спасибо за ответ! Да, попробую в направлении закрытия сокетов порыть. Хотя я пробовал использовать хваленую библиотеку async-http, ничего не меняется. Сниффером смотрел, система создает каждый раз новый порт. После перезапуска сервиса, http запросы в телефоне висят в состоянии SYN-SENT, т.е. запрос на открытие порта отправляется на сервер, но обратный пакет SYN-ACK похоже блокируется системой.
Порылся в направлении сокетов. Все корректно закрывается, портов открытых нет, сниффер показывает пустоту. Лишь мелькает запрос раз в 10 секунд SYN_SENT и все (на 5.1.1). На 4.1.2 работает без сбоев.
За несколько лет разработки под Android я запомнил, что система не богами писалась, и никто ничем не обязан. Даже если стандартные методы lifecycle приложения должны вызываться, то наверняка будут, но не обязаны. Если броадкасты работают, то они могут перестать работать после очередного обновления системы. Если даже они не указаны как sticky, то при подписке могут приходить события «changed» с текущим состоянием, т.е. sticky-поведением. Всегда надёжнее проверить каждый метод на практике, чем надеяться что оно всегда работает как описано в документации. Никто не гарантирует, что support-библиотека всегда будет отрабатывать корректно на всех устройствах, а не будет приводить к крашу приложения (привет, Samsung). Нельзя заранее узнать, будет ли тот или иной кодек или контейнер поддерживаться устройством, особенно с включенным аппаратным ускорением (привет, китайские производители). Нельзя заранее узнать, сколько видео одновременно может воспроизводиться. Кстати, если на андроиде 4.4 и ниже инициализировать штатный VideoView и удалять его много раз подряд, то через какое-то время в системе кончится лимит транзакций биндера, при любом системном обращении будет выброшен TransactionTooLargeException (репа с деталями). Недавно столкнулся с низкоуровневой сетевой ошибкой «ENOBUFS (No buffer space available)» при долгой работе устройства с частым реконнектом к серверу, независимо от версии системы. И если разобраться с самой системой обычно можно благодаря открытости системы и наличию исходного кода, то всё гораздо интереснее с вещами, которые предоставляет железо и вендоры устройства.
Да, странная вещь у меня получилась. Если сервис словил ошибку соединения, то он будет периодически слать запросы на подключение серверу (SYN_SENT), а запросы подтверждения (SYN_ACK) почему-то будут блокироваться. Стоит только активность запустить, сразу же соединение устанавливается. Может это на моих Xiaomi (5.1.1, 6.0.1) какая-то особенность.
Какие продукты написал? Есть что на Google Play посмотреть? Я только год в Андроиде, свою первую прогу написал, выложил в сеть. Смотрю, а у пользователей связь рвется через некоторое время. Вот и нашел эту особенность с потерей сети в сервисах. Сам Google рекомендует использовать FCM сервис оповещений для фоновых приложений. Хотя я смотрю у WhatsApp соединение с XMPP сервером постоянное. Как они умудряются это делать в фоне непонятно.
В основном коммерческая разработка. По рвущемуся коннекту – есть аналогичное, при выключении экрана активное соединение рвётся через 10-15 минут, и больше не переподключается, несмотря на всевозможные wake-lock'и, только активация экрана помогает. Это оптимизации со стороны системы, чтобы фоновые приложения не жрали аккумулятор, трафик и прочие ресурсы. Для периодических подключений рекомендуют использовать JobScheduler, либо пуш-уведомления через GCM (нынче Firebase). В качестве костыля у меня работает программная активация экрана (через wakelock) при потере коннекта.
Спасибо за подсказку! GCM уже начал изучать, а вот про wakelock даже в голову не приходило. Пишут с 8 андроида фоновым сетевым соединениям вообще трындец настанет, так что похоже надо на GCM по любому переходить.

Спасибо за статью, очень интересно. На первый взгляд кажется что вся эта машинерия и все эти абстракции в android довольно элегантны но чувствую должна быть и критика и перечень недостатков в сравнением с каким-нибудь десктопным ubuntu, так как сейчас есть планшеты как на андроиде так и на десктопных ос (как windows так и на ubuntu, debian и др)

Зарегистрируйтесь на Хабре, чтобы оставить комментарий