Pull to refresh

Comments 30

Hertbeat 8 минут не устанавливался по просьбе операторов сотовой связи, иначе:
1. Впустую расходуются радио ресурсы на открытие и дальнейшее закрытие по бездействию канала.
2. Это жрет батарею телефона так, что не возрадуешься.
Не надо прикручивать ненужный краткосрочный пинг, он действительно не нужен.
Спасибо за информацию про операторов сотовой связи — не знал. Согласен с обоими пунктами. Краткосрочный пинг нужен приложениям, в которых задержка длиною в heartbeat недопустима (мессенджер или клиент охранной сигнализации к примеру), а так, да, нужно конечно свое соединение держать отдельное от gmc. Но, думаю, когда каждое приложение начнет держать свое соединение, то батарейка будет тратиться врядли меньше, чем если бы величина hearbeat была, к примеру, настраиваемой.
Вот тут есть пару слов о пингах и потреблении батареи, а тут о неэффективности периодических сеансов связи в мобильных сетях. Я не программист, больше по сетям связи, но уверен что есть правила и рекомендации по работе с передачей данных при разработке любых мобильных приложений.
А как быть в ситуации, когда GCM меняет устройству registration_id, а push сервер работает с notification_key? В таком случае push сервер вообще не знает (не обязан знать) registration_id устройств, и canonical_ids ничего полезного ему не скажут.
Интересный вопрос. Я не работал с notification_key на практике. И что-то с ходу в документации про это найти ничего не могу, кроме faled_registration_ids поля в ответе:
{
  "from": "aUniqueKey",
  "message_type": "ack",
  "success":1,
  "failure":2,
  "failed_registration_ids":[
     "regId1",
     "regId2"
  ]
}
. Вы уверены, что пуш сервер не обязан знать registration_id каждого девайса в группе, разве не пуш-сервер должен добавлять/удалять registration_id в/из группы? Буду признателен, если расскажите как отслеживать изменение registration_id при использовании notification_key.
Задумка с notification_key была интересная: сервер приложения добавляет или удаляет идентификаторы устройств (registration_id) в группу, после чего можно отправлять сообщение сразу всей группе устройств. Тем самым мы экономим наши ресурсы за счёт ресурсов GCM. На практике возникли проблемы — по HTTP вообще не получалось отправить сообщение, хотя это было заявлено в документации. В каких-то случаях GCM сервер отвечал 500 при регистрации устройств в группе. Всё выглядело сыро, но, несмотря на это, у нас получилось реализовать работу по протоколу XMPP.
Сегодня посмотрел в документацию и увидел, что гугл пометил эту технологию Deprecated.
Вы уверены, что пуш сервер не обязан знать registration_id каждого девайса в группе, разве не пуш-сервер должен добавлять/удалять registration_id в/из группы?

Сервер приложения (не обязательно пуш сервер) регистрирует идентификаторы устройств в группах, после чего непосредственно пуш сервер оперирует только идентификаторами групп для отправки сообщений. Чисто теоретически, даже само устройство может себя зарегистрировать в группе, но это небезопасно ввиду того, что приложению окажется доступен приватный ключ GCM.
Буду признателен, если расскажите как отслеживать изменение registration_id при использовании notification_key.

Пока не могу ответить на этот вопрос. Есть предположения, что заменой registration_id в группе должен заниматься GCM сервер, но, смотря опять же на сырость данной технологии, нельзя быть в этом уверенным. Ну и после объявления Deprecated было бы правильным вообще отказаться от неё.
По поводу Deprecated: не обратил внимание, что устаревшим помечен notification_key только для HTTP, но не для XMPP
Спасибо за подробное объяснение. Правильно я понимаю, что notification_key следует использовать, если надо ограничить круг девайсов (клиентов), которые могут получать уведомления и кол-во которых не может превышать 20 штук? И никто кроме них пуш-уведомления получать не должен? Иначе, не очень понимаю зачем он нужен.
Самое простое, понятное и наглядное применение — объединение в группу всех устройств одного пользователя. Когда нам нужно отправить сообщение, нам надо его отправить прежде всего пользователю. И дабы не вспоминать идентификаторы всех устройств пользователя (registration_id), мы используем только идентификатор пользователя (notification_key), к которому уже привязаны на стороне GCM сервера идентификаторы всех его устройств.
Для своего соединения нужен foreground service по хорошему, иначе процесс будет убиваться где-то раз в час-два.
Будет супер, если раскопаете более низкоуровневую информацию о соединении. Скажем, если есть девайс на котором heartbeat 8 минут поддерживает возможность мгновенного прихода соединения, а для heartbeat 10 минут — уже нет, то что именно изменяется на этой восьмой-девятой минуте? Приемник же до сих пор активен и может принять входящий звонок. Как это зависит от wi-fi/2g/3g/4g?
По хорошему скорее всего да, foreground service хороший кандидат, но висящая иконка в статус-баре многих раздражает (меня нет :)). Есть еще вариант со START_REDELIVER_INTENT и обычный сервис (либо IntentService). По поводу heartbeat хочу еще отметить, что проблему с ним, например, на nexus 4, nexus 5, nexus 7, 9 с чистым андроидом я не замечал, она отсутствует. А на samsung (s4, note) — да, стабильно.

Скажем, если есть девайс на котором heartbeat 8 минут поддерживает возможность мгновенного прихода соединения, а для heartbeat 10 минут — уже нет, то что именно изменяется на этой восьмой-девятой минуте?


Не совсем понял про «возможность мгновенного прихода соединения», поясните пожалуйста.
Ну это же про _Instant_ Messaging? Instant — это когда телефон сразу пиликает как только вам кто-то напишет, а не через пять минут или пару часов, даже если телефон в спячке. Как я понимаю, открытое tcp соединение может инициировать cpu wake-lock на входящих данных, и heartbeat нужен исключительно для того, чтоб сервак соединение не разрывал. Как следствие интересно, как heartbeat влияет на мнгновенность получения входящих соединений. Может быть большинство девайсов вообще не могут будить cpu на входящие сетевые воздействия? Про это и вопрос. Мне самому немного лень разбираться, т.к. сейчас никаким IM не занимаюсь, но интересно почитать.
А, я в начале опечатался немного — не соединения, а сообщения/пакета.
А существуют ли мессенджеры, лишенные проблемы с Heartbeat?
На Android или Windows Phone или iPhone.
Я на Nexus 4 менял таймаут Heartbeat. Ну… стало лучше, но не фонтан.
А в Blackberry эта проблема решена в BES?
Могу сказать, что «проблем с HeartBeat» лишены те мессенджеры, которые использует помимо GCM свое собственное соединение. Думаю скоро это будут делать абсолютно все популярные мессенджеры, сейчас пока частично. Других способов решить эту проблему вроде как нет. Это я про Android. Про другие ОС ничего не могу сказать.
Думаю скоро это будут делать абсолютно все популярные мессенджеры, сейчас пока частично.

а кто щас уже? ну крупные есть брать whatsup, viper, telegramm и тд.
Мне однажды необходимо было реализовать постоянное соединение с бекэндом для получения свежих данных в приложении.

В качестве решения был выбран довольно грубый, но действенный способ. При старте приложения создавал AlarmManager без повторения, который срабатывал через 2 минуты. В BroadcastReciver выполнял асинхронный запрос серверу, отменял AlarmManager и ставил заново. И так до бесконечности, пока не сработает метод остановки.
Ещё один BroadcastReciver ловил запуск ОС и проверял, нужно ли переустановить AlarmManager.

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

Судя по вашему описанию, ваш процесс убиваемый (ссылка на документацию ). Рекомендуют запускать Service из метода BroadcastReceiver.onReceive, это может отсрочить убийство системой вашего процесса. Либо использовать foreground service или service c флажком START_REDELIVER_INTENT .

При старте приложения создавал AlarmManager без повторения, который срабатывал через 2 минуты.

Если система убъет ваш процесс до того как выполнился асинхронный запрос, то следующий запуск AlarmManager не случится никогда, судя по вашему описанию или нет?
Ну, тестов провели много, запрещая фоновые процессы, ограничивая их число. Не убивался. Делал так: в onRecive первым делом переустанавливал AlarmManager и делал асинктаск.
Могу завтра привести пример кода, если интересно.
Да, очень любопытно взглянуть.
Как и обещал.

Когда нужно запустить сервис по получению данных с бекэнда
        // устанавливаем AlarmManager
        AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(this, OrderChecker.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0,
                intent, PendingIntent.FLAG_CANCEL_CURRENT);
        // отменяем все установки, если они были
        am.cancel(pendingIntent);
        // устанавливаем на разовое выполнение 
        int repeat = Integer.parseInt(profile_settings.getString("upd_period", "120000"));
        am.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + repeat, pendingIntent);


А вот BroadcastReceiver

    @Override
    public void onReceive(Context context, Intent intent) {
            // сохраняю контекст для других методов класса 
            myApp = context;
            db = new DatabaseHelper(myApp);
            profile_settings = PreferenceManager.getDefaultSharedPreferences(context);
            repeat = Integer.parseInt(profile_settings.getString("upd_period", "120000"));
            am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
            pendingIntent = PendingIntent.getBroadcast(context, 0,
                    intent, PendingIntent.FLAG_CANCEL_CURRENT);
            try {
                 // взяли из БД заказы
                orders = db.getOrdersFromDB(db.getReadableDatabase(),"");
                if (orders.size() == 0) {
                     // если ничего нет, отменяем новый запуск
                    am.cancel(pendingIntent);
                } else {
                     // вот тут у меня AsyncTask 
                    getNewStatusesFromServer(orders);
                }
            } catch (Exception e) {
                 // если словил любую ошибку, устанавливаю новый запуск AlarmManager
                am.cancel(pendingIntent);
                am.set(AlarmManager.ELAPSED_REALTIME,
                        SystemClock.elapsedRealtime() + repeat, pendingIntent);
            }
    }

    private void getNewStatusesFromServer(List<String> orders) {
                 // некоторая реализация 
                 // и в конце я устанавливаю новый запуск AlarmManager
                am.cancel(pendingIntent);
                am.set(AlarmManager.ELAPSED_REALTIME,
                        SystemClock.elapsedRealtime() + repeat, pendingIntent);
        }


получается, что onReceive отработал и установил новый таймер — сработать через 2 минуты. И так пока orders.size() != 0

Если телефон перезагрузили
public class CheckerStarter extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent i) {
        if ("android.intent.action.BOOT_COMPLETED".equals(i.getAction()) || "android.intent.action.QUICKBOOT_POWERON".equals(i.getAction())) {
            AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
            Intent intent = new Intent(context, OrderChecker.class);
            PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
                    intent, PendingIntent.FLAG_CANCEL_CURRENT);
            am.cancel(pendingIntent);
            int repeat = Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(context).getString("upd_period", "120000"));
            am.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + repeat, pendingIntent);
        }
    }
}


З.Ы. Я заранее говорил, что метод очень грубый. Но это позволило не держать постоянный коннект и не держать отдельный поток с Thread.sleep(120000). За 2 минуты простоя ось не отменит AlarmManager из-за нехватки ресурсов.
1. Грузить заказы из базы данных в методе
 onReceive 
очень грубое решение) Этот метод вызывается в UI потоке.
2. Хорошей практикой считается запускать сервис из метода onReceive дабы не делать слишком многого в нем
3. В принципе, как вы сами говорите, решение грубое. Утверждать, что за 2 минут простоя ОС не убьет ваш процесс я бы не стал :)

Перенеся am.cancel am.set в onReceive() мы будем уверенны, что новый запуск AlarmManager будет установлен, даже если в какой-то момент андроид прибил наш AsyncTask.

Вот про это не совсем понял, кто будет устанавливать новый запуск аларм менеджера?
1. Грузить заказы из базы данных в методе
onReceive
очень грубое решение) Этот метод вызывается в UI потоке.

Так это sqlite, внутренняя БД. + там только заказы не в конечном статусе, по которым понадобиться push.

2. Хорошей практикой считается запускать сервис из метода onReceive дабы не делать слишком многого в нем

Как я понял из документации, сервисы, потоки, работающие после завершения работы onRecive могут быть убиты

3. В принципе, как вы сами говорите, решение грубое. Утверждать, что за 2 минут простоя ОС не убьет ваш процесс я бы не стал :)

Так это ж AlarmManager. Он как раз и не висит потоком. Ось же не убивает установки будильника :)

Вот про это не совсем понял, кто будет устанавливать новый запуск аларм менеджера?

Я же и устанавливаю в onRecive. У меня есть интент. Из него получить PendingIntent не проблема. Ну и флаг FLAG_CANCEL_CURRENT заменяет AlarmManager:
pendingIntent = PendingIntent.getBroadcast(context, 0,
                    intent, PendingIntent.FLAG_CANCEL_CURRENT);
Как я понял из документации, после отработки onReceive(), Андроид может убить процесс, если даже мы запустили где-то свой поток (AsyncTask).
Эту проблему можно побороть так:
                if (orders.size() == 0) {
                     // если ничего нет, отменяем новый запуск
                    am.cancel(pendingIntent);
                } else {
                     // вот тут у меня AsyncTask 
                    getNewStatusesFromServer(orders);
                     // вот тут мы пересоздаем AlarmManager
                am.cancel(pendingIntent);
                am.set(AlarmManager.ELAPSED_REALTIME,
                        SystemClock.elapsedRealtime() + repeat, pendingIntent);
                }

    private void getNewStatusesFromServer(List<String> orders) {
                 // некоторая реализация 
        }



Перенеся am.cancel am.set в onReceive() мы будем уверенны, что новый запуск AlarmManager будет установлен, даже если в какой-то момент андроид прибил наш AsyncTask.

Если ошибаюсь, поправьте…
Метод вполне рабочий, если закрыть глаза на возможность ANR при чтении БД и то, что это высасывает батарейку будя CPU и радиомодуль каждые две минуты. Это особенно будет заметно на андроид M, где это по факту выключит Doze режим. Не хотел бы я такое на своем телефоне иметь, если такое постоянно крутится.
на возможность ANR при чтении БД

Почему должен быть ANR при чтении с БД?

и то, что это высасывает батарейку будя CPU и радиомодуль каждые две минуты

Второй вариант, держать коннект с бекэндом в сервисе или в потоке постоянно. Разве это не высасывает батарейку?

В любом случае это делалось в приложении для такси и таким образом отслеживалось изменение статуса заказа. А тут очень важно было быстро прислать пуш клиенту, что его машина приехала или взят заказ. GCM тут не удобен
ANR при чтении возможен на старых девайсах когда происходит активная запись, например, установка игрушки или что-то в этом стиле.

Коннект я думаю можно держать почти бесплатно если железо грамотно это поддерживает. Например, телефон же как-то знает, что идет входящий звонок и для этого wake-lock держать точно не нужно, даже если текущее соединение — lte.
Да, из опыта держать открытый коннект батарею сильно не садит. GCM работает именно так — он просто держит xmpp соединение, правда процесс видимо whitelisted благодаря чему андроид его не убивает периодически, не смотря на то, что он не foreground importance.

Я не говорю, что решение не самое оптимальное для этого применения, просто говорю что возможны неприятности такого плана.
Sign up to leave a comment.