В этом туториале я рассмотрю пошагово, как отправлять со своего сервера уведомления на свой (или не свой) смартфон, какие средства для этого понадобятся. Эти способы универсальны и подойдут для любого языка программирования, т.к. напрямую используют API гугла, без использования библиотек. Отправить можно на смартфоны с Android, iOS и в браузеры с поддержкой Push API (на сегодня это Chrome, Firefox и их производные).

В общем всем тем, кто давно хотел отправлять уведомления со своего домашнего сервера на свой смартфон, но не знал с чего начать, посвящается.

Немного истории. В начале (с версии андроида 2.2) у гугла для доставки использовалась система C2DM (Android Cloud to Device Messaging), начиная с июня 2012 для этого стали предлагать использовать GCM (Google cloud messaging).

В настоящее время используется универсальная платформа Firebase, которая помимо доставки уведомлений имеет ещё много всяких других возможностей. Firebase тоже успела эволюционировать и протокол первого поколения уже считается устаревшим и для доставки сообщений рекомендуется использовать протокол второго поколения.

Технически, уведомления отправляются с сервера не напрямую в смартфон, а на некий промежуточный сервер, на котором при необходимости хранятся до 4-х недель (настраиваемо), и по возможности отправляются получателю. Т.е. если смартфон находится оффлайн, сервер ждёт. Как только появляется возможность — отправляет.

1. Регистрируемся в Firebase


Для регистрации в Firebase понадобится учётка гугла.



Жмём «Перейти к консоли».



Затем «Добавить проект».



Вводим название проекта. Рекомендую в диапазоне 8-16 символов.
Выбираем страну. Жмём «Создать проект».

2. Настраиваем Firebase




Прокручиваем до блока «Notifications», жмём «Начать».

Вам предложат выбрать приложение, для которого ваши уведомления будут отправляться.



Шаги для Andriod-приложения:



Шаг 1 — Вводим название проекта на Andriod.
Жмём «Зарегистрировать приложение».



Шаг 2 — Жмём «Скачать google-services.com».
Добавляем скачанный файл конфигурации в проект, рядом с файлом build.gradle (тем, который персональный для приложения).
Жмём «Продолжить».



Шаг 3 — Добавляем в проект зависимости.
в файл /build.gradle строчку
classpath 'com.google.gms:google-services:3.1.0'
в файл /<app-module>/build.gradle строчку
apply plugin: 'com.google.gms.google-services'
Тут всё, жмём «Готово».

После настройки приложения, можно сразу протестировать работает ли связь отправив тестовое сообщение (нет нельзя, у нас ещё нет ID клиента, куда слать).

3. Настройка приложения Android на приём уведомлений.


Важное примечание: некоторые оболочки, например MIUI, могут блокировать уведомления, если приложение не запущено или не висит в фоне. Делается это якобы для экономии заряда батареи.

Грубо говоря, отправлять можно два вида уведомлений:
— уведомление по запросу,
— уведомление с полезной нагрузкой.
У них разные способы взаимодействия с приложением.

Уведомление по запросу выведет уведомление в области уведомлений, но только в случае если приложение свёрнуто. При тапе пользователя оно откроет заранее выбранную (при отправке) активити приложения, и передаст бандлом экстра-параметры.

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

Ниже приведён пример службы, которая отвечает за генерацию ID клиента.
package ru.pyur.loga;

import android.util.Log;

import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;



public class TestFirebaseInstanceIdService extends FirebaseInstanceIdService {
  public static final String TAG = "TestFbseInstIdSvc";

  @Override
  public void onTokenRefresh() {
    String refreshedToken = FirebaseInstanceId.getInstance().getToken();
    Log.d(TAG, "Refreshed token: " + refreshedToken);

    //~sendRegistrationToServer(refreshedToken);
  }

}


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

package ru.pyur.loga;

import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

import static ru.pyur.loga.AcMain.context;



public class TestFirebaseMessagingService extends FirebaseMessagingService {
  public static final String TAG = "TestFbseMsgngSvc";

  @Override
  public void onMessageReceived(RemoteMessage remoteMessage) {
    Log.d(TAG, "From: " + remoteMessage.getFrom());

    if (remoteMessage.getData().size() > 0) {
      Log.d(TAG, "Message data payload: " + remoteMessage.getData());

      String val1 = remoteMessage.getData().get("val1");
      String val2 = remoteMessage.getData().get("val2");
      String val3 = remoteMessage.getData().get("val3");
      int color = (1<<16)|(1<<8)|(0);
      ShowNotification(val1, val2, color);
    }

    if (remoteMessage.getNotification() != null) {
      Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
    }
  }


  @Override
  public void onDeletedMessages() {
    // In some situations, FCM may not deliver a message. This occurs when there are too many messages (>100) pending for your app on a particular device
    // at the time it connects or if the device hasn't connected to FCM in more than one month. In these cases, you may receive a callback
    // to FirebaseMessagingService.onDeletedMessages() When the app instance receives this callback, it should perform a full sync with your app server.
    // If you haven't sent a message to the app on that device within the last 4 weeks, FCM won't call onDeletedMessages().
  }


  void ShowNotification(String title, String text, int color) {
    NotificationCompat.Builder mNotify = new NotificationCompat.Builder(context, "");
    mNotify.setLights(color, 100, 200);
    mNotify.setSmallIcon(R.drawable.service_icon);
    mNotify.setContentTitle(title);
    mNotify.setContentText(text);
    mNotify.setDefaults(Notification.DEFAULT_SOUND);

    NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

    int mId = 1001;
    try { mNotificationManager.notify(mId, mNotify.build()); }
    catch (Exception e) { e.printStackTrace(); }
  }


}


не забудьте прописать службы в манифесте.

<service
  android:name=".TestFirebaseMessagingService">
  <intent-filter>
    <action android:name="com.google.firebase.MESSAGING_EVENT"/>
  </intent-filter>
</service>


<service
  android:name=".TestFirebaseInstanceIdService">
  <intent-filter>
    <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
  </intent-filter>
</service>


ID клиента генерируется на устройстве, но вы сами выбираете способ доставки этого ID к себе на сервер.

Вот теперь можно протестировать, отправив тестовое сообщение из консоли.





4. Отправляем уведомление со своего сервера


Существует несколько способов обмена данными с сервером Firebase. Мы рассмотрим два способа обмена по протоколу HTTP.

Протокол первого поколения — Legacy HTTP




Понадобится ключ. Жмём на гайку, выбираем «Настройки проекта».



Вкладка «Cloud Messaging».
Копируем «Устаревший ключ сервера».

<?php
  // ------------------------ test fcm send. legacy ------------------------ //

$socket = @fsockopen('ssl://fcm.googleapis.com', 443, $errno, $errstr, 10);

if (!$socket)  die('error: remote host is unreachable.');


  // ---- уведомление для трея ---- //
$payload = '{
  "to" : "cGAFgPJGf-s:APA91bF**...**aEVM17c9peqZ",
  "notification" : {
    "title" : "Моё первое сообщение",
    "body" : "(Legacy API) Привет!",
    "sound": "default"
  }
}';
// или
  // ---- уведомление для службы ---- //
$payload = '{
  "to" : "cGAFgPJGf-s:APA91bF**...**aEVM17c9peqZ",
  "data":{
    "val1" : "Моё первое сообщение",
    "val2" : "(Legacy API) Привет!",
    "val3" : "какие-то дополнительные данные"
  }
}';


$send  = '';
$send .= 'POST /fcm/send HTTP/1.1'."\r\n";
$send .= 'Host: fcm.googleapis.com'."\r\n";
$send .= 'Connection: close'."\r\n";
$send .= 'Content-Type: application/json'."\r\n";
$send .= 'Authorization: key=AIzaSy***************************IPSnjk'."\r\n";
$send .= 'Content-Length: '.strlen($payload)."\r\n";
$send .= "\r\n";

$send .=$payload;

$result = fwrite($socket, $send);


$receive = '';
while (!feof($socket))  $receive .= fread($socket, 8192);

fclose($socket);

echo '<pre>'.$receive.'</pre>';

?>

Здесь в поле «to» надо подставить ID клиента. В http заголовок «Authorization: key=» подставить «Устаревший ключ сервера».

Протокол второго поколения — (Modern) HTTP v1.


(источник: developers.google.com/identity/protocols/OAuth2ServiceAccount)
Не спрашивайте, почему вторая версия протокола называется V1, видимо первая считалась бетой и носила нулевой номер.
Я не углублялся в подробности, но так понимаю этот протокол более универсальный и имеет более широкие возможности, чем просто отправка уведомлений.

<?php
  // ------------------------ test fcm send. modern ------------------------ //

  // -- шаг 1. вычисляем JWT -- //
$JWT_header = base64_encode('{"alg":"RS256","typ":"JWT"}');

$issue_time = time();

$JWT_claim_set = base64_encode(
'{"iss":"firebase-adminsdk-mvxyi@<your-project>.iam.gserviceaccount.com",'.
 '"scope":"https://www.googleapis.com/auth/firebase.messaging",'.
 '"aud":"https://www.googleapis.com/oauth2/v4/token",'.
 '"exp":'.($issue_time + 3600).','.
 '"iat":'.$issue_time.'}');
  // см. примечание

$private_key = '
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwR1biSUCv4J4W
****************************************************************
****************************************************************
...
****************************************************************
teTJImCT6sg7go7toh2ODfaPmeI0nA/LwSjzWs0b8gdIYPT5fAsvfQiND0vu/M3V
7C/z/SmIKeIcfOYrcbWQwTs=
-----END PRIVATE KEY-----
';

$data = $JWT_header.'.'.$JWT_claim_set;
$binary_signature = '';

openssl_sign($data, $binary_signature, $private_key, 'SHA256');

$JWT_signature = base64_encode($binary_signature);


$JWT = $JWT_header.'.'.$JWT_claim_set.'.'.$JWT_signature;



  // -- шаг 2. авторизируемся и получаем токен -- //

$socket = @fsockopen('ssl://www.googleapis.com', 443, $errno, $errstr, 10);

if (!$socket)  die('error: remote host is unreachable.');


$payload = 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion='.rawurlencode($JWT);

$send  = '';
$send .= 'POST /oauth2/v4/token HTTP/1.1'."\r\n";
$send .= 'Host: www.googleapis.com'."\r\n";
$send .= 'Connection: close'."\r\n";
$send .= 'Content-Type: application/x-www-form-urlencoded'."\r\n";
$send .= 'Content-Length: '.strlen($payload)."\r\n";
$send .= "\r\n";

$send .= $payload;


$result = fwrite($socket, $send);

$receive = '';
while (!feof($socket))  $receive .= fread($socket, 8192);

fclose($socket);

echo '<pre>'.$receive.'</pre>';



  // -- parse answer JSON (lame) -- //

$line = explode("\r\n", $receive);
if ($line[0] != 'HTTP/1.1 200 OK')  die($line[0]);

$pos = FALSE;
if (($pos = strpos($receive, "\r\n\r\n", 0)) !== FALSE ) {
  if (($pos = strpos($receive, "{", $pos+4)) !== FALSE ) {
    if (($pose = strpos($receive, "}", $pos+1)) !== FALSE ) {
      $post = substr($receive, $pos, ($pose - $pos+1) );
      $aw = json_decode($post, TRUE);
      $access_token = $aw['access_token'];
      }
    else die('} not found.');
    }
  else die('{ not found.');
  }
else die('\r\n\r\n not found.');



    // -- шаг 3. отправляем запрос на Firebase сервер -- //

$socket = @fsockopen('ssl://fcm.googleapis.com', 443, $errno, $errstr, 10);

if (!$socket)  die('error: remote host is unreachable.');


$payload = '{
  "message":{
    "token" : "cGAFgPJGf-s:APA91bF**...**aEVM17c9peqZ",
    "notification" : {
      "title" : "Заголовок сообщения",
      "body" : "(Modern API) Моё первое сообщение через Firebase!"
      }
   }
}';
// или
  $payload = '{
"message": {
  "token" : "cGAFgPJGf-s:APA91bF**...**aEVM17c9peqZ",
  "data":{
    "val1" : "Заголовок сообщения",
    "val2" : "(Modern API) Моё первое сообщение через Firebase!",
    "val3" : "дополнительные данные"
    }
  }
}';


$send  = '';
$send .= 'POST /v1/projects/pyur-test-id/messages:send HTTP/1.1'."\r\n";
$send .= 'Host: fcm.googleapis.com'."\r\n";
$send .= 'Connection: close'."\r\n";
$send .= 'Content-Type: application/json'."\r\n";
$send .= 'Authorization: Bearer '.$access_token."\r\n";
$send .= 'Content-Length: '.strlen($payload)."\r\n";
$send .= "\r\n";

$send .=$payload;


$result = fwrite($socket, $send);

$receive = '';
while (!feof($socket))  $receive .= fread($socket, 8192);

fclose($socket);


echo '<pre>'.$receive.'</pre>';

?>




по адресу console.firebase.google.com/project/poject-id/settings/serviceaccounts/adminsdk надо скопировать «Сервисный аккаунт Firebase» и подставить в переменную "$JWT_claim_set", в поле «iss».

Жмём «Создание закрытого ключа»



Создаём ключ, сохраняем, никому не показываем. В скачанном файле будет содержаться «Закрытый ключ», его подставляем в переменную "$private_key".

Хинт: токен, полученный в шагах 1 и 2 можно и нужно кешировать в локальном временном хранилище, например файле, или базе данных. И только по истечении времени (по умолчанию один час), запрашивать у сервера авторизации следующий токен.



Важно! Перед использованием Modern Http API необходимо явно разрешить его использование здесь: console.developers.google.com/apis/library/fcm.googleapis.com/?project=your-project

Бонус, дополнительные параметры для уведомлений:


sound — либо «default», либо имя ресурса в приложении. Должен располагаться в "/res/raw/". Формат MP3, AAC или ещё чего подходящее.
icon — меняет иконку уведомления. Должна храниться в «drawable» приложения. Если отсутствует, FCM будет использовать иконку приложения (указанную как «launcher icon» в манифесте приложения).
tag — Следует использовать для группировки однотипных уведомлений. Новые уведомления будут выводиться поверх уже имеющихся с таким же тегом.
color — цвет иконки, задаётся как "#rrggbb" (у меня в MIUI не заработало)
click_action — запускаемое активити, при нажатии пользователем на уведомлении.

Заключение


В будущем API вероятно будет изменяться, объявляться depricated и т.п. Поэтому сегодня думаю стоит делать сразу на протоколе HTTP v1.

Мне будет интересно почитать в комментариях оригинальные способы применения уведомлений, помимо новых сообщений из вконтактика. К примеру у меня настроен мониторинг вентиляторов ардуиной, и если они остановятся, отправляется уведомление.

Да, я в курсе, что существует Zabbix и т.п., но тема статьи — домашние сервера, и прочие умные дома. Считаю системы корпоративного класса перебором в любительских поделках.

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


  1. RPG18
    01.01.2018 20:09
    -2

    Зачем так сложно, когда есть slack, telegram?


    1. kedobear
      01.01.2018 20:20
      +6

      Зачем так сложно, если есть jabber и почта?


      1. TheShock
        02.01.2018 12:10

        Зачем так сложно, если можно нарисовать на руке крестик?


      1. RPG18
        03.01.2018 00:57

        В slack'е есть удобный вебхуки. Можно хоть из баша курлом отправить.


    1. pyur Автор
      01.01.2018 21:03
      +4

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


    1. 6095959
      02.01.2018 11:41

      Поколение телеграм)
      Зачем что-то делать самому, когда есть телеграм, инстаграм и тп?


      1. TimsTims
        02.01.2018 23:13

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


    1. trueClearThinker
      02.01.2018 11:53

      Зачем так сложно, когда есть OneSignal?


  1. KorP
    01.01.2018 20:11
    -5

    Почему не телеграм?
    Там же вообще в 3 строчки можно отправлять себе сообщений откуда угодно… Не увидел в статье описания преимуществ Firebase, а ставить отдельное приложение для уведомлений, как то не очень хочется.


    1. pyur Автор
      01.01.2018 20:58
      +3

      наверно по той же причине, по которой некоторые люди поднимают своё облачное хранилище на домашнем сервере, вместо Dropbox, да что уж там, свои почтовые серверы поднимают, да и много чего ещё.


      1. KorP
        01.01.2018 21:13

        Собственному облачному хранилищу или необходимости в почтовом сервере — я могу понять, использовать одно из миллиона API (которых вагон), для сообщений в который (и только для него) нужен отдельный клиент — не укладывается в это понимание. А судя по тому, что вы не описали всё-таки никаких плюсов его использования — их и нет.
        Минусящих — с новым годом! :)


        1. pyur Автор
          01.01.2018 22:11

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


          1. KorP
            01.01.2018 22:19

            Я поверю во многое, но судя по вашим ответам в других ветках камментов, вы изначально не верно задали вектор тексту своей статьи, отсюда и столь много вопросов про телеграм и обоснованность вашего выбора.
            Вы говорите «В этом туториале я рассмотрю пошагово, как отправлять со своего сервера уведомления на свой (или не свой) смартфон» — для решения данной задачи есть очень много более простых способов, но, если вы то оказывается делаете упор на получение этих сообщений собственным приложением под андройд, и тут вообще как бы совершенно о другом — о разработке взаимодействия вашего сервера и вашего же приложения. На сколько я понял это из других камментов и перечитывания части про клиента.


            1. pyur Автор
              02.01.2018 00:53

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


            1. m0Ray
              02.01.2018 08:40

              >андройд
              … а также гиперболойд, планетойд и биткоин.

              Граммар наци негодуэ.
              Прошу прощения за оффтопик.


              1. KorP
                02.01.2018 08:55

                Граммар наци негодуэ.

                Им же хуже :)


      1. konchok
        02.01.2018 11:42

        Аргумент весьма странный, учитывая что для вашего способа на телефоне нужно держать Гугол Плей и обвязку общим весом минимум 500Мб.


        1. pyur Автор
          02.01.2018 12:27

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


          1. konchok
            02.01.2018 12:53

            >без GAPS смартфон превратится в некий аналог тыквы

            Весьма распространённое заблуждение среди разработчиков на Андроид. Предполагаю что оно специально Гуглом и культивируется. За пределами сервисов Гугла жизнь тоже существует и все серьезные приложения типа фб, телеграм, вайбер, алиэкспресс итд используют свою собственную систему уведомлений и отлично работают без GAPPS. И этим приложениям даже (внезапно!) не нужен рут.


            1. pyur Автор
              02.01.2018 13:08

              спасибо. полезная информация. обязательно проверю её и упомяну в будущих статьях.


            1. dimka11
              02.01.2018 20:46

              Т.е. если на устройстве установлены google services, то они все равно их не используют и дополнительно разряжают аккумулятор?! Насколько я знаю, в последних версиях Android, существенно ограничили фоновые активности сторонним приложениям, в целях повышения энергосбережения.


              1. konchok
                02.01.2018 21:10

                Само только отсутствие GAPPS увеличивает работу от батареи субъективно на десятки процентов. Фоновая активность для отдельных приложений выставляется в настройках.


              1. pyur Автор
                02.01.2018 21:12

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


    1. nidalee
      02.01.2018 17:33

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


      1. pyur Автор
        02.01.2018 18:37

        написали. я точно такое уже читал.


  1. Pilat
    01.01.2018 20:20
    -3

    Почему просто не использовать Telegram Bot API ?


    1. autuna
      01.01.2018 20:29

      Больше способов хороших и разных.
      Особенно в нашей стране, в которой уже носятся с идеей забанить Telegram.
      ИМХО, разумеется.


      1. KorP
        01.01.2018 21:15

        Что то мне кажется, если я сейчас напишу ещё десяток таких же туториалов по Pushover, Prowl, Pushbullet, Pushall, etc — мне спасибо никто не скажет :)

        уже носятся с идеей забанить Telegram

        Они с ней уже пол года носятся, да вот только что то затихли в последнее время.


    1. pyur Автор
      01.01.2018 20:58

      наверно по той же причине, по которой некоторые люди поднимают своё облачное хранилище на домашнем сервере, вместо Dropbox, да что уж там, свои почтовые серверы поднимают, да и много чего ещё.


      1. Pilat
        01.01.2018 22:04

        Вот "свои почтовые серверы" и есть аналог бота на Telegram. А то что тут сделано — скорее аналог своего почтового клиента. Поднимая облачные хранилища, свой клиент к такому хранилищу не пишут.
        "Послать себе сообщение" подразумевает многое. В частности способ его прочитать, способ организовать безпроблемную отправку. Telegram бот делает всё это простыми средствами. Firtebase — требует выполнения многочисленных действий и нескольких длительных операций (например предварительно запросить токен у Firebase и обработать ошибки получения токена, которые случаются и почему-то не обрабатываются в статье).


        1. pyur Автор
          01.01.2018 22:21

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


          1. saboteur_kiev
            02.01.2018 00:31

            У вас полезная статья, но нападки например на телеграм напрасны.
            В телеграмм и bot-api и клиент-апи, то есть вы можете написать свой клиент и своего бота, без закладок.
            Опять таки — вы говроите, что нужно установить сам телеграм, но ведь ваше приложение тоже нужно установить? Или опишите чуть подробнее, чем google API лучше чем нативная поддержка sms, или mail, который есть в каждом телефоне, или даже тот же телеграм?


            1. pyur Автор
              02.01.2018 00:48

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


              1. barbanel
                02.01.2018 13:08

                по которому помимо просто уведомлений можно и данные гонять в приложение с сервера

                Скажите плиз, а какой обьем данных можно передать на клиент?
                Можно ли передать что-то вроде json в пару килобайт?


                1. pyur Автор
                  02.01.2018 13:15

                  формально в документации прописано 4KB на одно сообщение.
                  бинарные данные придётся чем-то вроде base64 заворачивать, а это ещё 33% overhead'а.
                  ну и текст самого сообщения пару сотен байт.


  1. Waki
    01.01.2018 21:23

    Стоит упомянуть что для получения уведомлений на андроид 8+ нужно еще регистрировать каналы.
    А вообще не понимаю почему статья в плюсе, это ведь как обычный урок по андроид разработке или это из-за названия статьи?


    1. pyur Автор
      01.01.2018 21:59

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


  1. SimWhite
    01.01.2018 21:45
    +1

    1. pyur Автор
      01.01.2018 22:02

      прикольно. еще, пока гуглил тему, видел ребят pushall_ru. всё это готовые решения «искаропки». статья для тех, кто любит всё делать сам. я даже акцентировал (видимо недостаточно) внимание на том, что не юзаются стандартные библиотеки, предлагаемые гуглом, а всё выполняется максимально прозрачно, через HTTP запросы.


      1. KorP
        01.01.2018 22:15

        Так и там всё выполняется через HTTP запросы, никаких библиотек, как и с телеграмом и другими сервисами, в которых не надо 100500 шагов по настройке выполнять. Об этом я вам выше и пишу, а вы не понимаете.


        1. pyur Автор
          01.01.2018 22:24

          по поводу телеграмов ответил выше, посмотрите по тайм-коду «22:21». может поэтому и не понимаю.


  1. savostin
    01.01.2018 22:45

    Отправить можно на смартфоны с Android, iOS и в браузеры с поддержкой Push API (на сегодня это Chrome, Firefox и их производные).

    Что-то я потерялся в Android и не заметил как отправлять в iOS и браузер…


    1. pyur Автор
      01.01.2018 22:54
      -1

      ну я только упомянул, что это возможно. я не обещал рассказать как это сделать. я только хотел подчеркнуть, что Firebase это очень универсальная платформа.
      в целом мне показалось, что вся сложность именно в отправке (90%), с приёмом у меня не возникло совсем никаких проблем (10%). весь приём в андроид приложении заключался в добавлении «пары строк и пары классов».
      думаю сильно не ошибусь, если предположу, что и на других платформах с приёмом не будет шибко больших проблем.


  1. shushu
    02.01.2018 03:30

    Вы сами почти все сделали, поэтому регистрироваться в Firebase совсем не обязательно.


  1. chimvl
    02.01.2018 11:44

    Осталось непонятно будут ли сообщения типа data появляться, когда приложение находиться в состоянии foreground. Ведь в методе onMessageReceived нет никаких проверок на то находиться ли приложение на экране или нет. Например в почтовом клиенте, я же не получаю еще и уведомление о новом письме, если нахожусь на активности со списком писем.Как добиться что бы уведомления типа data появлялись только когда приложение в работает фоне или не запущено? Использовать уведомления типа notification нельзя, так как они очень ограничены в оформлении и группировке.


    1. pyur Автор
      02.01.2018 11:58

      можно в методе 'onMessageReceived' проверить состояние через 'ActivityManager'.
      ActivityManager.RunningAppProcessInfo appProcessInfo = new ActivityManager.RunningAppProcessInfo();


  1. SinClaus
    02.01.2018 11:45

    Есть люди с телефонами (сотовыми, обычными, не смарт), и им тоже нужно отправлять уведомления. Так что альтернативы sms нет.


    1. pyur Автор
      02.01.2018 11:48

      есть )) я ещё помню те досотовые времена, когда люди по городу с обычными радиотелефонами ездили ) в радиусе 4-5 км от дома вполне себе был приём.
      сегодня хорошей альтернативой служат всякие IoT радиомодули, LoRa и т.п. дальнобойность тоже может измеряться километрами, но вот скорость — аховая. для задач уведомлений — подойдёт.
      (про пейджеры ничего не напишу, они уже через сотовую сеть работали)


  1. limita
    02.01.2018 11:56

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

    $payload = '{
      "to" : "cGAFgPJGf-s:APA91bF**...**aEVM17c9peqZ",


    1. pyur Автор
      02.01.2018 13:18

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


  1. Tertium
    02.01.2018 12:09

    Вот жеж блин Гугл! Был же GCM, все нужды покрывал, ну зачем без конца все менять, усложнять, накручивать? А главное, даже в статье сказано, что вся эта сложность бесполезна, если оболочка режет фон, а нового реально ничего. Просто раз в год-два приходит новый перспективный молодой и энергичный дурачок и переделывает все до основания, в результате те же яйца, вид сбоку, но 50 дополнительных переключателей и добавлена так нужная всем возможность разогревать сосиски, но не больше 1 в час. Собственно это касается и других сервисов гугла, простоты для разработчика как пользователя библиотек и сервисов там не ищут. Поэтому, если есть возможность, надо все делать самому, иначе погрязнешь в постоянных переделках под гугл, ведь сохранять интерфейсы для обратной совместимости это ж не круто.


    1. pyur Автор
      02.01.2018 12:19

      сам офигел, когда взялся делать! думал «сейчас, за пару часиков слабаю, тремя строчками кода». ага… на два дня пропал в чертогах гугловых api доков… вернулся весь седой и с бородой до колен.
      как оказалось, уведомления это лишь верхушка айсберга, там целая туча всякой аналитики через тот же канал гоняется, вплоть до того, как часто вы чихали, с какой силой и продолжительностью. заботятся о вас и вашем здоровье.
      но с какой-то стороны перманентно мутирующие интерфейсы это даже благо, например они таким образом не дают остаться без работы (а следственно и без хлеба) толпам программистов.


  1. fukkit
    02.01.2018 14:37
    +2

    Во дожили! Не успеет человек из лучших побуждений рассказать хабру и миру о какой-нибудь классической самопальной приблуде, слышен рев гироскутеров, и подьезжающая толпа фанатов майора Дурова, плюясь смузи сквозь бороду, начинает защищать телегу (кто б на неё ещё нападал?).
    Тезис: уметь делать нечто независимо и самостоятельно — правильно, полагаться на вечность и бесконечность поделок 3,4,5...15й стороны — неправильно.
    P.S.: автор — молодец.


    1. pyur Автор
      02.01.2018 15:05

      спасибо за поддержку ))
      первая мысль от таких комментариев и была — стартаперы с вейпами, смузи и спиннером. побоялся, что шапками забросают.
      рад что на хабре ещё есть и адепты старой школы ) батя может в СИ.
      p.s. ничего не имею против новой школы, когда то и я был новой школой, писал на новомодном PHP3, и вызывал недоверчивые взгляды у коллег.


  1. Vsemmira
    02.01.2018 14:58
    +1

    Спасибо автору. В любом случае есть полезная информация


  1. kafeman
    02.01.2018 15:09

    Зашел почитать, как действительно отправить уведомление со своего сервера, а в итоге нужен посредник в виде Firebase. Кто занимается разработкой под Android / iOS, расскажите, возможно ли реализовать протокол с регистрацией по типу SIP, чтобы отправлять уведомления без посредников? Или firewall не даст это сделать? Что насчет потребления батареи по сравнению со способом, описанным в статье? Насколько быстрее будет разряжаться телефон с открытым сокетом?


    1. pyur Автор
      02.01.2018 15:19

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


      1. kafeman
        02.01.2018 15:37

        система нещадно душит соединения фоновых процессов
        А зачем нужно соединение? Разве нельзя попросить систему «разбудить» нас, когда придет, например, UDP-сообщение на некий определенный порт? Из описанного в статье можно сделать вывод, что сетевой стек продолжает работать, даже когда телефон «спит». Единственная проблема, которую я тут вижу, это отправка любого сообщения с определенным интервалом на сервер, чтобы «пробить» NAT. По-моему, сейчас никто из ОпСоСов не предлагает статический адрес… :-(


        1. pyur Автор
          02.01.2018 15:45

          я подозреваю, что сетевой стёк скрыт от простых смертных приложений, на пользовательском уровне. и доступен только привелегированным, а им вероятно нужен рут. но это всё мои догадки, не проверял реально, как обстоят дела.
          если интересно, для nat-to-nat соединений прекрасно работает en.wikipedia.org/wiki/UDP_hole_punching. да, понадобится всё-таки сервер посредник, но исключительно для обмена адресами и портами.


          1. kafeman
            02.01.2018 15:55

            Не понял, зачем нам сервер-посредник? У вас и сервер за NAT'ом что-ли?


            1. pyur Автор
              02.01.2018 16:12

              я на фразе «пробить NAT» подумал, что вы имеете в виду p2p соединение, ошибся.
              а для соединения nat-to-real никакие хитрости и не нужны. достаточно поддерживать соединение отправкой пустых keepalive пакетов по UDP, чтоб всякие промежуточные прокси не посчитали соединение покинутым и не перестали его обслуживать.
              а в таких вещах как push, постоянное соединение поддерживать невыгодно с точки зрения батареи. поэтому там реализовано так:
              — клиент подключается к серверу
              — отправляет свои (скопившиеся) сообщения на сервер
              — принимает с сервера скопившиеся сообщения
              — отсоединяется от сервера
              — снимает питание с сетевухи (модема), чтоб по человечески поспать
              поэтому на спящий смартфон сообщения не приходят мгновенно, а только когда смарт просыпается, и чекает сервак. и эти интервалы разные, в зависимости от предпочтений ОС по энергосбережению.


        1. pyur Автор
          02.01.2018 15:50

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


          1. kafeman
            02.01.2018 16:00

            Да откуда же берется-то это «соединение», если у нас UDP? Да, где-то серверах ОпСоСа оно существует, но не в телефоне! Все, что требуется от ОС, принять наш bind(...) на интересующий нас порт. Я просто не знаю, как это делается в Android / iOS, описываю, как бы я это делал на десктопе.


            1. pyur Автор
              02.01.2018 16:14

              сервер не устанавливает соединение со смартфоном по своей инициативе. он только ждёт, когда смарт САМ того пожелает, установит соединение с ним, обменяется данными и закроет соединение. так работает пуш.


              1. kafeman
                02.01.2018 16:37

                Мне не удалось найти детальной информации о том, как работает FCM / GCM. Не могли бы вы привести источник, из которого вы взяли, что не сервер отправляет сообщения на смартфон, а смартфон регулярно запрашивает их с сервера?

                Из личных наблюдений, Wi-Fi на моем телефоне не отключается сразу, как только я заблокировал экран. Значит, некая микросхема, ответственная за сеть, имеет возможность послать прерывание процессору, когда получен новый пакет.

                Конечно, это можно проверить с помощью Wireshark / tcpdump, но мне как-то лень.


                1. pyur Автор
                  02.01.2018 16:53

                  источников не припомню уже.
                  и у WiFi и модема очень разные стратегии по энергопотреблению. замечал, что на планшет, после перевода в спящий режим я еще могу заходить несколько секунд, около 20. потом рубит. но это планшет на Win10, на андроиде вероятно по другому. яж говорю, все эти стратегии, когда отрубать питалово с модемов и сетевух, отданы на откуп вендорам, и они сами, на своё усмотрение их реализуют, единого стандарта нет.
                  даже могут менять в версиях прошивок. отсюда и отзывы пользователей «новая прошивка жрёт батарею!!1». это не прошивка жрёт, а сетевуха стала реже отключаться.
                  думаю, проверить сетевым анализатором это будет самый верный и быстрый способ. я только знаю, что гугл рекомендует настраивать фаерволы на порт 5228 (дополнительно 5229 и 5230), чтоб не блокировали их на исходящий. (например)


                  1. kafeman
                    02.01.2018 17:06

                    Насколько я понимаю, все эти фишки требуют наличие установленного закрытого ПО от Google (вроде Play Services и т. д.), а значит, Google имеет контроль над вендорами.

                    Исходящий трафик, безусловно, есть. Как-то же телефон должен себя регистрировать. Проверю, если будет время. Проблема только в том, что у меня заблокированы все уведомления :-) Но если телефон, как вы говорите, сам ходит на сервер, то я это увижу. Такие уведомления только уже правильно будет называть не push, а pull.


                    1. pyur Автор
                      02.01.2018 17:16

                      да, вот хотел написать… push оно только с маркетинговой точки зрения, это абстракция. на техническом уровне это pull.
                      гугл ещё на ранних этапах понял, что каждое второе приложение будет создавать коннект со своим сервером, и вся эта вакханалия будет жрать батарею и сокеты. и решили всё это дело объединить в единую точку входа, и интегрировали её в GAPS. и получается, да, именно гапс определяет с какой частотой ходить на сервера, и чекать что там новенького.
                      с GPS такая же петрушка. тоже как-то писал приложение с GPS, много интересных поведений начитал. там тоже все попытки душить в фоне GPS мотивировали жёром батарейки.


    1. anton1234
      02.01.2018 17:36

      Этот гугло сервис решает ровно две проблемы.
      1. Снимает вопрос безопасности, позволяя приложению не иметь открытых портов.
      1.1 Большинство мобильных устройств не имеют адресов Internet(белых адресов) и таким образом пуш на них вообще не возможен.
      2. Снизить энергопотребление устройств. У сипа пуш весьма условный. Это не сервер что-то шлет на клиент, а клиент с завидным постоянством спрашивает сервер «ну как есть, что для меня?».
      Никто не запрещает, бери пиши свой FCM, опрашивай свой сервер каждую секунду. Для этого в андройде есть сервисы. Там есть свои нюнасы, как и в целом во всей мобильной разработке.

      Автору тут уже n раз намекнули, что каждой задаче свой инструмент. FCM это исключительно удел разработки под android и кстати ios, тк он и на apple шлет уведомления. И если нет задачи писать свое мобильное приложение, то лучше использовать любой другой более выскоуровневый сервис.

      also, забавный нюанс. помимо упомянутых web api, сервис еще и через xmpp может принимать запросы от сервера. У гугла на удивление очень подробная документация с картинками к этому сервису.


      1. kafeman
        02.01.2018 18:21

        Большинство мобильных устройств не имеют адресов Internet(белых адресов) и таким образом пуш на них вообще не возможен.
        Какой-то адрес они все-таки имеют. И, зная, например, в случае с TCP / UDP номер порта, до них можно достучаться.

        У сипа пуш весьма условный. Это не сервер что-то шлет на клиент, а клиент с завидным постоянством спрашивает сервер «ну как есть, что для меня?».
        Для банальных INVITE или MESSAGE именно устройство пользователя выступает в качестве сервера. Телефон не спрашивает каждые N секунд, «а не звонит ли мне сейчас кто-нибудь». SIP приводился просто как пример протокола, позволяющий клиенту свободно путешествовать по сети. Исключительно для Push-уведомлений я его использовать не предлагаю (хоть это и возможно).

        И если нет задачи писать свое мобильное приложение
        Так о чем еще идет речь?

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


        1. pyur Автор
          02.01.2018 18:33

          Какой-то адрес они все-таки имеют.

          нынче это не так, сотовые операторы в большинстве случаев даже серого IP не дают. натят.


          1. kafeman
            02.01.2018 19:13

            Опустив неточности терминологии в вашем комментарии, важно понимать, что разные протоколы на разных уровнях имеют свое понятие адреса. За одним MAC-адресом могут гипотетически находиться до 4294967296 IPv4-адресов, а за одним IPv4-адресом могут находиться, например, до 65536 TCP-портов. На каждом TCP-порту могут также «прописаться» до бесконечности адресатов, например, HTTP-хостов.

            Какой-то IP-адрес ОпСоС вам все-таки выдает, и он точно не «серый», поскольку такие адреса в Интернете не маршрутизируются. Другое дело, что этот же адрес могут получить одновременно несколько абонентов. Но при этом вы можете связаться с нужным клиентом используя «адрес», который лежит на уровень выше. Например, TCP порт. Иначе как вы получаете от сервера ответ на свой запрос? :-) Можно пойти еще дальше. На shared-хостингах у вас с соседями не только один IP-адрес, но также и один TCP-порт (80). При этом запрос по-прежнему маршрутизируется на нужный сайт благодаря HTTP-заголовку Host.

            TL;DR; Нет никакой проблемы выйти на связь с нужным телефоном. Не верь, читатель, слепо всему, что пишут в комментариях на Хабрахабре.


            1. pyur Автор
              02.01.2018 19:26

              яж присылал про UDP hole punching. я разрабатываю под сетевые протоколы. и если не ошибаюсь, IP адрес в паре с портом называется сокетом, и используется натом, чтоб понять кому дальше слать пакет (когда он пришёл как входящий).
              также я писал про то, что нат может преждевременно закрыть активный сокет, если по нему слишком долго не будут ходить пакеты. поэтому, чтоб он не закрывался, гоняют пустые пакеты, keepalive.
              я посмотрел ваш профиль. верю, что разбираетесь в сетях. но и мне доводилось писать сетевые драйверы на микроконтроллеры. о протоколах MAC, IP, TCP, UDP и ICMP знаю не по наслышке. Wireshark'ом пользовался, когда его ещё не было )) была под 98-ми виндами прога Commview.


              1. kafeman
                02.01.2018 19:47

                IP адрес в паре с портом называется сокетом
                Какой порт имеет Unix- или raw-сокет? :-) Отвечать не обязательно, но это то, что меня смутило в вашей терминологии (как ОпСоСы «даже серого IP не дают», а что тогда 100.64.0.0/10 у большинства провайдеров, не просто «натят», а «маскарадят» и т. д.), поэтому я на всякий случай и решил разжевать все подробно, чтобы точно не осталось вопросов. Ну и кроме того это не личная переписка, так что у неокрепших умов, читающих наши комментарии, может сложиться неверная картина мира.

                Wireshark'ом пользовался, когда его ещё не было )) была под 98-ми виндами прога Commview.
                Вы могли пользоваться Wireshark'ом до его появления только в случае, если вы сами являетесь его разработчиком. Опять у меня трудности с понимаем вашей терминологии (я этот класс программ, как и большинство людей, называю снифферами, а не Wireshark'ами).

                Критику мою слишком серьезно, конечно, воспринимать не стоит. Мне, например, как-то никогда в жизни не приходилось писать своего сетевого драйвера (в отличии от вас).


                1. pyur Автор
                  02.01.2018 19:57

                  да, про Wireshark это такая шутка конечно ) я хотел сказать, что пользовался снифферами до появления Wireshark'а )
                  статься уже в архиве, никто в такие дебри не будет заходить и читать каменты. теперь только ищущие по теме (для кого я собственно её и писал) будут её находить и читать. хипсторы на гироскутерах слава богу уехали.


      1. pyur Автор
        02.01.2018 18:35

        Автору тут уже n раз намекнули ...

        ох, теперь я понимаю, что надо было ЯВНО написать, что такой способ актуален ТОЛЬКО если вы планируете прикрутить уведомления к своему УЖЕ существующему приложению. это сняло бы половину комментариев с недоумениями.


        1. anton1234
          02.01.2018 23:35

          Акценты важны.
          Но тема очень интересная. Я сам недавно прошел путь отправки пуш сообщений на телефон, но только задача частью написания мобильного приложения и серверного бэкэнда.
          Были те же мысли с оттенком паранойи, зачем зависеть от чьего-то сервиса, почему не написать все самому? А еще был вопрос, а из чего собственно выбирать?
          Насколько я понимаю практически все мобильные приложения используют FCM. Альтернативой является собственная реализация MQTT, но это задача весьма не тривиальная и требует постоянного бэкграунд процесса, который на андройд потенциально может быть закрыт системой, а на IOS такого понятия как постоянно работающий бэкграунд процесс и вовсе нет.
          PS. Наверное, неплохо бы вам еще парсить ответ на отправку сообщения в примере, хотя бы статус.
          А вот тут есть описания проверки токена
          firebase.google.com/docs/auth/admin/verify-id-tokens.


          1. pyur Автор
            02.01.2018 23:54

            конечно, в реальном ПО ответы парсятся, и как минимум проверяется код ответа.
            я в примерах только в одном месте поставил наспех написанный, максимально кратко, пример как проверять ответы «parse answer JSON (lame)». очень не хотелось раздувать код.
            по себе знаю, как тяжело читать чужие сорцы, когда суть разбавлена всякой мишурой эксепшенов и прочего. я же тут не выпендриваться пришёл, какие крутые у меня парсеры и обработчики исключений. я был бы благодарен всем кто пишет примеры, если бы они тоже старались акцентировать внимание в примерах максимально на сути.
            ну и да, я не старался написать прям уж исчерпывающий мануал, со всеми нюансами и аспектами. те кто уловят суть, думаю без труда и разработают вопрос, до той степени как им надо.


            1. anton1234
              03.01.2018 00:21

              Само собой. Это я так, чтобы мой комментарий был хотя бы слегка информативным докинул ссылку.
              Я не спец в php, но ваша обработка вполне достаточна чтобы понять успешно отправлено сообщение или нет.

              if ($line[0] != 'HTTP/1.1 200 OK') die($line[0]);

              400 будет в случае если, что-то не так в отправляемом сообщении, 404 если не так с токеном клиента.


  1. dmitry_pacification
    02.01.2018 17:01

    Сейчас как раз изучаю вопрос пуш уведомлений. Спасибо за статью, возьму инфу на вооружение.