Доброго времени суток!
В предыдущей части, я рассказывал как сделать настройку пуш уведомления в консоли Firebase, в этой я покажу код на Delphi и серверную часть на PHP
update 11.04.2017
Как сделать многострочный текст в уведомлениях на Android?
Решение для Seattle/Berlin
update 11.04.2017
UPDATE: для iOS 10+ рабочая связка Delphi Berlin Up2+xCode8.1
На этом всё! Удачи Вам!
В предыдущей части, я рассказывал как сделать настройку пуш уведомления в консоли 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) Реализация методов
update 12.04.2017
update 12.04.2017
procede TFormMain.OnReceiveNotificationEvent(Sender: TObject; const ANotification: TPushServiceNotification); const FCMSignature = 'gcm.notification.body'; GCMSignature = 'message'; APNsSignature = 'alert'; var aText: string; aObj: TJSONValue; begin // это событие срабатывает при открытом приложении {$IFDEF ANDROID} aObj := ANotification.DataObject.GetValue(GCMSignature); if aObj <> nil then aText := aObj.Value else aText := ANotification.DataObject.GetValue(FCMSignature).Value; {$ELSE} aObj := ANotification.DataObject.GetValue(APNsSignature); if aObj <> nil then aText := aObj.Value; {$ENDIF} ShowMessage(aText); 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) Готово!
C++ Builder код
update 11.04.2017
Не силён в билдере, мне перевели код. Спасибо Kitty
// H файл #includeВАЖНО для тех кто пишет на C++ Builder#include #include //*** private: // User declarations String FDeviceID; String FDeviceToken; TPushService * FPushService; TPushServiceConnection * FPushServiceConnection; void __fastcall OnReceiveNotificationEvent(TObject *Sender, TPushServiceNotification* const ANotification); void __fastcall OnServiceConnectionChange(TObject *Sender, TPushService::TChanges AChange); void __fastcall PushServiceRegister(); void __fastcall RegisterDevice(); //CPP файл #include #include #if defined(__ANDROID__) #include // Workaround for RSP-17714 namespace Fmx { namespace Pushnotification { namespace Android { _INIT_UNIT(Fmx_Pushnotification_Android); } } } // End-of-Workaround #endif #if defined(__APPLE__) && (defined(__arm__) || defined(__arm64__)) #include namespace Fmx { namespace Pushnotification { namespace Ios { _INIT_UNIT(Fmx_Pushnotification_Ios); } } } #endif TForm1 *Form1; const String FAndroidServerKey = L"63538920422"; // --------------------------------------------------------------------------- void ClearAllNotification() { std::unique_ptr aNotificationCenter(new TNotificationCenter(NULL)); if(aNotificationCenter->Supported()) { aNotificationCenter->ApplicationIconBadgeNumber = -1; aNotificationCenter->CancelAll(); } } // --------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { ClearAllNotification(); PushServiceRegister(); } // --------------------------------------------------------------------------- bool __fastcall CheckInet() { bool result = false; THTTPClient *aHTTP = THTTPClient::Create(); try { try { _di_IHTTPResponse aResp = aHTTP->Head("http://google.com"); result = (aResp->StatusCode < 400); } catch (const System::Sysutils::Exception &E) { result = false; } } __finally { delete aHTTP; } return result; } // --------------------------------------------------------------------------- //*** блок для TTask::Run *** #if defined(_PLAT_IOS) String sPlatform = L"IOS"; #elif defined(__ANDROID__) String sPlatform = L"ANDROID"; #else String sPlatform = L""; #endif class TCppTask : public TCppInterfacedObject { String FDeviceID, FDeviceToken; public: TCppTask(String AnID, String AToken) : FDeviceID(AnID), FDeviceToken(AToken) { } void __fastcall Invoke() { String link = String().sprintf (L"http://rzaripov.kz/pushTest/api.php?method=saveToken&deviceID=%s&deviceToken=%s&platform=%s", UTF8String(FDeviceID).c_str(), UTF8String(FDeviceToken).c_str(), UTF8String(sPlatform).c_str()); std::unique_ptr aHTTP(THTTPClient::Create()); aHTTP->Get(link); } }; // --------------------------------------------------------------------------- void __fastcall TForm1::RegisterDevice() { TTask::Run(_di_TProc(new TCppTask(FDeviceID, FDeviceToken))); } //*** конец блока для TTask::Run *** // --------------------------------------------------------------------------- void __fastcall TForm1::PushServiceRegister() { bool result = CheckInet(); if (result == true) { FPushService = nullptr; FPushServiceConnection = nullptr; #if defined(__ANDROID__) FPushService = TPushServiceManager::Instance->GetServiceByName (TPushService_TServiceNames_GCM); if(FPushService) FPushService->AppProps [TPushService_TAppPropNames_GCMAppID] = FAndroidServerKey; #endif #if defined(__APPLE__) && (defined(__arm__) || defined(__arm64__)) FPushService = TPushServiceManager::Instance->GetServiceByName (TPushService_TServiceNames_APS); #endif if(FPushService) { FPushServiceConnection = new TPushServiceConnection(FPushService); FPushServiceConnection->OnChange = &OnServiceConnectionChange; FPushServiceConnection->OnReceiveNotification = &OnReceiveNotificationEvent; FPushServiceConnection->Active = true; FDeviceID = FPushService->DeviceIDValue[TPushService_TDeviceIDNames_DeviceID]; FDeviceToken = FPushService->DeviceTokenValue [TPushService_TDeviceTokenNames_DeviceToken]; if(FDeviceID != "" && FDeviceToken != "") { RegisterDevice(); } } }//if (result == true) } // --------------------------------------------------------------------------- void __fastcall TForm1::OnServiceConnectionChange(TObject *Sender, TPushService::TChanges AChange) { if (AChange.Contains(TPushService::TChange::DeviceToken) && (FPushServiceConnection)) { FDeviceID = FPushService->DeviceIDValue[TPushService_TDeviceIDNames_DeviceID]; FDeviceToken = FPushService->DeviceTokenValue [TPushService_TDeviceTokenNames_DeviceToken]; if(FDeviceID != "" && FDeviceToken != "") { RegisterDevice(); } } } // --------------------------------------------------------------------------- void __fastcall TForm1::OnReceiveNotificationEvent(TObject *Sender, TPushServiceNotification* const ANotification) { const String FCMSignature = L"gcm.notification.body"; const String GCMSignature = L"message"; const String APNsSignature = L"alert"; String aText = ""; TJSONValue * aObj; #if defined(__ANDROID__) aObj = ANotification->DataObject->GetValue(GCMSignature); if(aObj != NULL) { aText = aObj->Value(); } else { aText = ANotification->DataObject->GetValue(FCMSignature)->Value(); } #endif #if defined(__APPLE__) && (defined(__arm__) || defined(__arm64__)) aObj = ANotification->DataObject->GetValue(APNsSignature); if (aObj != NULL) { aText = aObj->Value(); } #endif ShowMessage(aText); } // ---------------------------------------------------------------------------
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 }
Вот такой очень простой код у нас получился, стоит отметить что можно добавлять полезную нагрузку в виде полей, чтобы в приложении обрабатывать и выполнять дальнейшие команды.
update 11.04.2017
Как правильно сохранять токен в БД?
Создаём нашу табличку чтобы хранить токены
Создаём нашу табличку чтобы хранить токены
CREATE TABLE PushTokens (В таблице поле deviceID сделано уникальным, чтобы не допускать дублирование токенов
id int (11) AUTO_INCREMENT,
deviceID varchar(255) NOT NULL,
deviceToken varchar(511) NOT NULL,
platform varchar(11) NOT NULL,
PRIMARY KEY (id),
CONSTRAINT unq_deviceID UNIQUE (`deviceID`(100))
);
1) Когда мы с приложения отправляем на сервер токен устройства, мы должны еще отправить платформу на которой запущено приложение
2) Сохраняем полученный deviceToken, deviceID, platform в нашу таблицу PushTokens
update 11.04.2017
3) При отправке пуш уведомления на девайсы, нужно сделать следующее:
update 11.04.2017
SQL запрос будет следующим
$sql = "INSERT INTO PushTokens (`deviceToken`, `deviceID`, `platform`) VALUE ('$deviceToken', '$deviceID', '$platform') ON DUPLICATE KEY UPDATE `deviceToken` = '$deviceToken'";
3) При отправке пуш уведомления на девайсы, нужно сделать следующее:
- Выборка токенов из БД платформы Android в массив
- Выборка токенов из БД платформы iOS в массив, НО! токены APNs не валидны для FCM их нужно преобразовать через функцию apns2fcmToken
- Сделать слияние двух массивов через array_merge($tokens_android, $tokens_ios_fcm)
- И полученный массив отправить в функцию pushSend одним из параметров
update 11.04.2017
ВАЖНО! За один раз можно отправить только на 1000 токенов, поэтому если зарегистрированных девайсов у вас много, нужно будет разделить на несколько этапов
Почему при отправке уведомления из консоли Firebase мы не видим текст и заголовок в центре уведомлении на Android?
Это особенность FMX, в нативной реализации проверяется наличие полей message и title, а с консоли они не приходят (но можно заполнить расширенные настройки и указать эти поля)
update 11.04.2017
Как сделать многострочный текст в уведомлениях на Android?
Решение для Seattle/Berlin
update 11.04.2017
Выложил проект на GitHub
UPDATE: для iOS 10+ рабочая связка Delphi Berlin Up2+xCode8.1
На этом всё! Удачи Вам!
Google Play
Зачет! Спасибо!
ОтветитьУдалитьВроде статья не старая, но делали для iPhone уведомления, токены с iOs работают без apns2fcmToken и уведомления приходят.
ОтветитьУдалитьПравда есть странность в гугле, что какой токен ему не подсунь в pushSend возвращается в success: количество , даже если заведомо несуществующий токен написать.
не подтвержаю, приходит failure с количеством недействительных токенов. даже если токен был валиден и срок его годности прошел, все равно придёт ошибка
УдалитьДа точно, методом тыка проверил, но если а начале токена, до двоеточий, что-то написать, то токен все равно проходит. например токен "dlMLBXkg6lg:APA91bGPF3", и если его изменить на "111dlMLBXkg6lg:APA91bGPF3" то он также сработает, видимо эта часть до двоеточий для чего-то другого нужна ))
УдалитьВ своем проекте столкнулся со сложностями.
ОтветитьУдалитьИсторически сложилось, что мое приложение для 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.
Sorry, not working. ADeviceID comes up correctly, but AdeviceToken comes up blank. I use Delphi 10.2 Tokyo. I have this message to try get the Token: raised exception class EJNIException with message 'java.io.IOException: MAIN_THREAD'.
ОтветитьУдалитьThis is a bug in Tokyo, you need to write in QC
УдалитьEqual, in Delphi 10.1 Berlin not working. The Firebase's APIs has been changed?
ОтветитьУдалитьAndroid platform?
УдалитьTo get a token, you need:
internet
sender ID from firebase console
correctly configured project
Этот комментарий был удален автором.
ОтветитьУдалитьThe DeviceToken variable is empty, when the operating system is IOS.
ОтветитьУдалитьAndroid works perfectly.
Would you help me?
Большое спасибо за столь обстоятельный труд! Первый раз вижу, что решение представлено не только на паскале, но и С++, что делает картину полностью завершенной и полезной для полной RAD. У меня в планах разработка для туристической фирмы, там пользователей больше чем 1000. Не могли бы Вы модернизировать Ваш сервер, чтобы обойти ограничение на 1000 устройств? Это бы окончательно дополнило картину статьи. Спасибо.
ОтветитьУдалитьСпасибо!
УдалитьРешение на билдере мне перевели)
Пока нету времени модернизировать пуш-сервер, но решение простое:
1) узнать кол-во зарегистрированных устройств, поделить на 1000, чтобы узнать кол-во повторов для цикла
2) используя limit и offset в mysql запросе получаем "пачки" токенов
3) выполняем преобразования (для iOS) и отправляем пользователям
Здравствуйте.
ОтветитьУдалитьВы усовершенствовали свой php сервер методом pushSendOver1000.
Почему-то если заменить вызов sendPush методом pushSendOver1000 то получаю сообщение:
status "ERROR"
text "Такой метод не найден"
С чем это может быть связано?
push.php заменил новым. Спасибо.
Доброго времени суток!
УдалитьБыл Pull Request с изменениями от другого разработчика, я его просто одобрил, но не тестил.
Только что обновился еще раз, проверьте
Здравствуйте.
ОтветитьУдалитьПочему то в новой версии этот вызов показывает, что метод не не найден:
http://мой сайт/pushTest/api.php?method=pushSendOver1000&title=Заголовок&text=Текст
Вроде разобрался... Надо делать по прежнему вызов sendPush, а не pushSendOver1000...
ОтветитьУдалитьда все верно, обработка запроса не поменялось.
Удалитьmethod - это просто название переменой, по которой скрипт понимает что нужно сделать и какой функционал использовать
Спасибо за отличную статьи и полнофункциональный php сервер!!!
ОтветитьУдалитьПользуйтесь на здоровье, но хорошо бы подтянуть знания по php, чтобы в будущем его модернизировать и улучшить
Удалитькак принять пуши с сервера на delphi fmx 10.1 отправку токена сделал на сервер,сервер шлет мне пуши
УдалитьTenho problemas para receber o Token no Dispositivo iOS.
ОтветитьУдалитьFiz o certeificado .p12 , logo depois colocar o IdentifyBundler como na explicação mas não consigo o retorno.Alguma forma de auxilio.
Uso Tokio 10.2.3
как принять пуши с сервера на delphi fmx 10.1 отправку токена сделал на сервер,сервер шлет мне пуши
ОтветитьУдалитькак сделать push с картинкой или ссылкой на сайт
ОтветитьУдалитьДруг, реализация для laravel, вы где-нибудь публиковали? Я хотел бы посмотреть, как вы это делаете.
ОтветитьУдалитьДруг, реализация для laravel, вы где-нибудь публиковали? Я хотел бы посмотреть, как вы это делаете.
ОтветитьУдалить