Translate

пятница, 24 февраля 2017 г.

Настройка пуш уведомлении через сервис Firebase для ANDROID и IOS [часть 2]

Доброго времени суток!

В предыдущей части, я рассказывал как сделать настройку пуш уведомления в консоли Firebase, в этой я покажу код на Delphi и серверную часть на PHP

Настройка FMX проекта

ANDROID

1) Настроим проект на прием пушей в Android (Debug/Release сборки)

IDE - Project - Options - Entitlement List - Recieve push notification = true


2) Теперь нам нужен ключ сервера из консоли Firebase (номер 1)


3) Копируем его и вставляем в IDE - Project - Options - Version Info - apiKey


4) В папке с проектом находим AndroidManifest.template.xml, открываем редактором и вставляем строчку 
<service android:name="com.embarcadero.gcm.notifications.GCMIntentService" />
после 
<%receivers%>

5) Проверьте название пакета вашего приложения (7 пункт в предыдущей статье, настройка Android)

6) Готово!

iOS

1) Открываем IDE - Project - Options - Version Info
CFBundleIdentifier вашего проекта должно совпадать с настройкой проекта в Firebase



2) Готово!

Delphi код

1) uses секция
System.PushNotification
{$IFDEF ANDROID}, FMX.PushNotification.Android{$ENDIF}
{$IFDEF IOS}, FMX.PushNotification.IOS{$ENDIF}
2) Объявим константу - это Идентификатор отправителя (см. 2 пункт настройка Android номер 2)
const
  FAndroidServerKey = '63538920422';
3) В private секции формы пишем следующее
  private
    { Private declarations }
    FDeviceID: string;
    FDeviceToken: string;

    FPushService: TPushService;
    FPushServiceConnection: TPushServiceConnection;

    procedure OnReceiveNotificationEvent(Sender: TObject; 
      const ANotification: TPushServiceNotification);
    procedure OnServiceConnectionChange(Sender: TObject; 
      AChange: TPushService.TChanges);

    procedure PushServiceRegister;
3) Реализация методов
procedure TFormMain.OnReceiveNotificationEvent(Sender: TObject; 
  const ANotification: TPushServiceNotification);
begin
  ShowMessage(ANotification.Json.ToString);
  // это событие срабатывает при открытом приложении
end;

procedure TFormMain.OnServiceConnectionChange(Sender: TObject; 
  AChange: TPushService.TChanges);
begin
  if (TPushService.TChange.DeviceToken in AChange) and 
    Assigned(FPushServiceConnection) then
  begin
    FDeviceID := FPushService.DeviceIDValue[TPushService.TDeviceIDNames.DeviceID];
    FDeviceToken := FPushService.DeviceTokenValue
      [TPushService.TDeviceTokenNames.DeviceToken];
    // тут отправляем в хранилище токенов (на сервер с БД например)
  end;
end;

procedure TFormMain.PushServiceRegister;
begin
  FPushService := nil;
  FPushServiceConnection := nil;

{$IF defined(ANDROID)}
  FPushService := TPushServiceManager.Instance.GetServiceByName<
    (TPushService.TServiceNames.GCM);
  FPushService.AppProps[TPushService.TAppPropNames.GCMAppID] := FAndroidServerKey;
{$ENDIF}
{$IF defined(IOS) AND defined(CPUARM)}
  FPushService := TPushServiceManager.Instance.GetServiceByName
   (TPushService.TServiceNames.APS);
{$ENDIF}
  if Assigned(FPushService) then
  begin
    FPushServiceConnection := TPushServiceConnection.Create(FPushService);
    FPushServiceConnection.OnChange := OnServiceConnectionChange;
    FPushServiceConnection.OnReceiveNotification := OnReceiveNotificationEvent;
    FPushServiceConnection.Active := true;

    FDeviceID := FPushService.DeviceIDValue[TPushService.TDeviceIDNames.DeviceID];
    FDeviceToken := FPushService.DeviceTokenValue
      [TPushService.TDeviceTokenNames.DeviceToken];
    // тут отправляем в хранилище токенов (на сервер с БД например)
  end;
end;

4) Не забудьте вызвать метод PushServiceRegister в одном из событий формы (OnCreate/OnShow)

5) Готово!

PHP код

Тут сначала нужно описание сделать: 
Firebase объединил APNs + GCM, поэтому токены которые выдаются из FPushService для iOS не валидны для FCM, но у Google есть специальный инструмент для этого, у Android все отлично, токен валиден

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

Эта функция как раз и делает преобразование токена APNs в FCM
$appName - этот параметр должен содержать название пакета iOS приложения
$tokens - токены APNs
$server_key - ключ сервера (настройка Android 2 пункт)

function apns2fcmToken($appName, $tokens, $server_key) {
    $url = 'https://iid.googleapis.com/iid/v1:batchImport';
    $headers = array('Authorization: key=' . $server_key,
      'Content-Type: application/json');

    if (is_array($tokens))
      $token_arr = $tokens;
    else
      $token_arr = array($tokens);    
 
    $fields = array('application' => $appName, 'sandbox' => false, 
      'apns_tokens' => $token_arr);
 
    $ch = curl_init();
    curl_setopt_array($ch, array(
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYHOST => 0,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_POSTFIELDS => json_encode($fields)
    ));
    $result = curl_exec($ch);
    curl_close($ch);
    if ($result === false) return false;
    $json = json_decode($result, true);
    $rows = [];
    for ($n = 0; $n < count($json['results']); $n++){
      if ($json['results'][$n]['status'] == "OK")
        $rows[$n] = $json['results'][$n]['registration_token'];
    }
    return $rows;
}
Эта функция делает отправку пушей на выбранные токены
$title - заголовок уведомления
$text - текст уведомления
$tokens - массив токенов FCM
$server_key - ключ сервера (настройка Android 2 пункт)

function pushSend($title, $text, $tokens, $server_key) {
    $url = 'https://fcm.googleapis.com/fcm/send';
    $headers = array('Authorization: key=' . $server_key, 
     'Content-Type: application/json');
 
    if (is_array($tokens))
      $fields['registration_ids'] = $tokens;
    else
      $fields['registration_ids'] = array($tokens);
 
    $fields['priority'] = 'high';
    $fields['notification'] = array('body' => $text, 'title' => $title);
    $fields['data'] = array('message' => $text, 'title' => $title);
 
    $ch = curl_init();
    curl_setopt_array($ch, array(
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYHOST => 0,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_POSTFIELDS => json_encode($fields)
    ));
    $result = curl_exec($ch);
    curl_close($ch);
    return $result; //# проверять === false
}
Вот такой очень простой код у нас получился, стоит отметить что можно добавлять полезную нагрузку в виде полей, чтобы в приложении обрабатывать и выполнять дальнейшие команды. 

Как правильно сохранять токен в БД?

1) Когда мы с приложения отправляем на сервер токен устройства, мы должны еще отправить платформу на которой запущено приложение

2) Сохраняем полученный токен и платформу в БД

3) При отправке пуш уведомления на девайсы, нужно сделать следующее:
  • Выборка токенов из БД платформы Android в массив
  • Выборка токенов из БД платформы iOS в массив, НО! токены APNs не валидны для FCM их нужно преобразовать через функцию apns2fcmToken
  • Сделать слияние двух массивов через array_merge($tokens_android, $tokens_ios_fcm)
  • И полученный массив отправить в функцию pushSend одним из параметров
Почему при отправке уведомления из консоли Firebase мы не видим текст и заголовок в центре уведомлении на Android?
Это особенность FMX, в нативной реализации проверяется наличие полей message и title, а с консоли они не приходят (но можно заполнить расширенные настройки и указать эти поля)

Как сделать многострочный текст в уведомлениях на Android?
Решение для Seattle/Berlin

На этом всё! Удачи Вам!

5 комментариев :

  1. Вроде статья не старая, но делали для iPhone уведомления, токены с iOs работают без apns2fcmToken и уведомления приходят.
    Правда есть странность в гугле, что какой токен ему не подсунь в pushSend возвращается в success: количество , даже если заведомо несуществующий токен написать.

    ОтветитьУдалить
    Ответы
    1. не подтвержаю, приходит failure с количеством недействительных токенов. даже если токен был валиден и срок его годности прошел, все равно придёт ошибка

      Удалить
    2. Да точно, методом тыка проверил, но если а начале токена, до двоеточий, что-то написать, то токен все равно проходит. например токен "dlMLBXkg6lg:APA91bGPF3", и если его изменить на "111dlMLBXkg6lg:APA91bGPF3" то он также сработает, видимо эта часть до двоеточий для чего-то другого нужна ))

      Удалить
  2. В своем проекте столкнулся со сложностями.
    Исторически сложилось, что мое приложение для iOS и Android-платформ имеет разные идентификаторы пакетов (com.yourapp.PLATFORM).
    В моем случае не получилось на FireBase собрать их в один пакет: сообщения до устройств Android доходили без проблем; iOS ни в какую не отрабатывал (несмотря на успешные сообщения об отправке со стороны Firebase {"multicast_id":7125252680403741812,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1490294673113408%91729e8c91729e8c"}]}
    ). Проблема с отправкой была не только при использовании php-скрипта, но и при попытках отправки через консоль Firebase. Даже при посыле на все iOS-устройства (без указания каких-либо конкретных токенов).
    Проблема в конце-концов решилась заведением под iOS отдельного проекта в Firebase, получении своего Server key.

    ОтветитьУдалить