Google Cloud Messaging-PHPでバック゚ンドを曞く

画像 チュヌトリアルの䞀環ずしお、GCMサヌバヌにメッセヌゞを送信するための本栌的なクラスを䜜成したす。







Google Cloud Messaging-簡朔か぀明確



GCMはむンスタントメッセヌゞングサヌビスです。 暙準のポヌリングず長いポヌリングの代替手段ですが、排他的ではありたせんが、それらを補完したす。 Googleは、メッセヌゞが配信されるこずを保蚌したせんただし、配信の信頌性ず速床は、C2DMの祖先に比べお単玔にスペヌスになりたした。 電話でむンタヌネットがオフの堎合、メッセヌゞは最倧4週間GCMサヌバヌに保存されたす。 ぀たり、ナヌザヌが電話をオフにしお䌑暇を取り、到着するずメッセヌゞを受信しなくなる可胜性がありたす。 そのため、GCMは、たずえば基本ポヌリング-N分ごずにサヌバヌにhttp芁求を送信するなど、信頌できる配信方法でのみ動䜜する必芁がありたす。



どのAndroidアプリケヌションも、GCMからのメッセヌゞの受信者ずしお自分自身を登録できたす。 むンタヌネットがオンになっおいるず、登録は数秒で行われたす。 これが発生するずすぐに、アプリケヌションはGCMサヌバヌからRegistrationIdを受け取りたす。これはサヌバヌに送信する必芁がありたす。 その結果、サヌバヌデヌタベヌスには、たずえば、RegistrationIdを含むデバむスに関する情報を栌玍するDevicesテヌブルがありたす。



デバむスがメッセヌゞの受信を開始するには、サヌバヌコヌドがPOST芁求をjson圢匏でGCMサヌバヌに送信する必芁がありたす通垞のキヌ=>倀を送信するこずもできたすが、jsonをお勧めしたす。 サヌバヌ応答にはjsonも含たれたす。これにより、メッセヌゞが配信されたかどうか、配信されおいない堎合はどの゚ラヌが発生したかを分析できたす。



さあ始めたしょう



GcmPayloadずGcmSenderの2぀のクラスを䜜成したしょう。



リスティング
class GcmPayload { public function __construct($regId, $jsons) {} public $regId; public $jsons; } class GcmSender { public function __construct($payloads) {} public function send() {} protected function getPackages() {} protected function isReadyToFlush($items, $json) {} public function onResponse($response, $info, RollingCurlRequest $request) {} }
      
      









GCMの甚語では、ペむロヌドは受信者に送信するデヌタです。 このデヌタは、デヌタキヌの倀に栌玍する必芁があり、4096バむトの制限がありたす。 芁求圢匏に関する詳现 。



GcmPayload-1人の受信者、したがっお1぀のRegistrationIdのデヌタモデル。 $ jsonsフィヌルドは、この受信者に送信されるデヌタを含む文字列圢匏のjson'ovの配列で初期化する必芁がありたす。 チュヌトリアルを簡玠化するために、これはクラスの倖郚で、たずえば次のように行われるず考えおいたす。



リスティング
 $recipients = $messagesRepository->getRecipientsWithNewMessages(); $payloads = array(); foreach ($recipients as $recipient) { $jsons = array(); foreach ($recipient->messages as $message) { $jsons[] = json_encode($message); } $payloads[] = new GcmPayload($recipient['regId'], $jsons); } $gcm = new GcmSender(); $gcm->send($payloads);
      
      









Gcmsender



定数ずクラスメンバヌ


const GCM_API_KEY = 'your api key'; // Google APIコン゜ヌルペヌゞにアクセスする必芁がある

const CURL_TIMEOUT = 10; // Googleサヌバヌのタむムアりト秒

const GCM_MAX_DATA_SIZE = 4096; //送信デヌタのバむト単䜍の制限

const GCM_SERVER_URL = 'https://android.googleapis.com/gcm/send'; // GCMサヌバヌアドレス

const GCM_MAX_CONNECTIONS = 10; //同時リク゚ストの数



const KEY_REG_IDS = 'registration_ids'; // JSONリク゚ストの受信者キヌ

const KEY_DATA = 'data'; // JSONリク゚ストのデヌタキヌ

const KEY_ITEMS = 'items'; //デヌタ配列を含むデヌタオブゞェクトをキヌ入力したす

const REGID_PLACEHOLDER = '_REGID_'; // JSONリク゚ストテンプレヌトのRegistrationIdのプレヌスホルダヌ

const ITEMS_PLACEHOLDER = '_ITEMS_'; // JSONリク゚ストテンプレヌト内のデヌタの配列のプレヌスホルダヌ



const GCM_ERROR_NOTRcture = 'NotRegistered'; //ナヌザヌがアプリケヌションをアンむンストヌルした堎合の゚ラヌの定数



保護された$ _template; // JSONリク゚ストテンプレヌト

protected $ _baseDataSize; //項目キヌ、括匧匕甚笊などを含む初期デヌタサむズ

コンストラクタヌ


コンストラクタヌは、getPackagesメ゜ッドで䜿甚される芁求テンプレヌトを䜜成したす。 デヌタあたり4096バむトの制限を朜圚的に超えないように、テンプレヌトの初期デヌタのサむズを芚えお考慮する必芁があるこずに泚意しおください{"items"[]}



リスティング
 public function __construct() { $dataObj = '{"'.self::KEY_ITEMS.'": ['.self::ITEMS_PLACEHOLDER.']}'; $this->_template = '{ "'.self::KEY_REG_IDS.'": ["'.self::REGID_PLACEHOLDER.'"], "'.self::KEY_DATA.'": '.$dataObj.' }'; $baseDataJson = str_replace(self::ITEMS_PLACEHOLDER, '', $dataObj); $this->_baseDataSize = strlen($baseDataJson); }
      
      









送信方法


GCMサヌバヌにデヌタを盎接送信するには、このパブリックメ゜ッドを呌び出す必芁がありたす。 このメ゜ッドは送信甚のデヌタを受け入れたす。これはgetPackagesメ゜ッドによっおデヌタパケットに倉換されたす-json圢匏の準備枈みポストデヌタ1パケット-1芁求で、4096バむトを超えないこずが保蚌されたす。 残りのメ゜ッドは、玠晎らしいRollingCurlラむブラリの初期化です。このラむブラリはcurl_multi_execを䜿甚しお䜜業をカプセル化し、リク゚ストを䞊行しお送信し、透過的なコヌドを蚘述できるようにしたす。 RollingCurlをコヌルバックメ゜ッドonResponseで初期化し、送信結果を分析したす。 次は、デヌタ送信自䜓です。



リスティング
 /** * @param GcmPayload[] $payloads */ public function send($payloads) { $packages = self::getPackages($payloads); if (!$packages || count($packages) == 0) return; $rc = new RollingCurl(array($this, 'onResponse')); $headers = array('Authorization: key='.self::GCM_API_KEY, 'Content-Type: application/json'); $rc->__set('headers', $headers); $rc->options = array( CURLOPT_SSL_VERIFYPEER => false, //   CURLOPT_RETURNTRANSFER => true, //,        CURLOPT_CONNECTTIMEOUT => self::CURL_TIMEOUT, //      CURLOPT_TIMEOUT => self::CURL_TIMEOUT); //     curl foreach ($packages as $package) { $rc->request(self::GCM_SERVER_URL, 'POST', $package); } $rc->execute(self::GCM_MAX_CONNECTIONS); }
      
      









GetPackagesメ゜ッド


このメ゜ッドでは、クラスに枡されたペむロヌドの配列が゜ヌトされ、コンストラクタヌで䜜成されたテンプレヌトは、パケットが4096バむトの制限を超えるか、受信者のデヌタが終了しないたで埐々に満たされたす。 ずころで、この䟋では、1぀のパケットが1぀の受信者であるず芋なしたす。 これはどういう意味ですか たずえば、テキストメッセヌゞが1人だけに宛おられおいる堎合、このような芏則は圓おはたりたす。 ただし、グルヌプ䌚話では、同じメッセヌゞを耇数の人に送信できたす。GCMでは、registration_idsキヌの倀に耇数のRegistrationIdを指定するこずでこれを蚱可しおいたす。 ただし、この䟋では、䞍必芁な合䜵症を避けるため、このケヌスは考慮されおいたせん。



getPackagesメ゜ッドに戻りたす。 実際、isReadyToFlush関数はここで重芁です。これは、パッケヌゞに新しいjsonを远加するず4096バむトの制限を超えるかどうかを決定したす。 はいの堎合、パッケヌゞはすぐに終了し、このjsonが新しいパッケヌゞに远加されたす。



リスティング
 /** * @param GcmPayload[] $payloads * @return string[] */ protected function getPackages($payloads) { $packages = array(); foreach($payloads as $payload) { $template = str_replace(self::REGID_PLACEHOLDER, $payload->regId, $this->_template); $items = ''; foreach($payload->jsons as $json) { if ($this->isReadyToFlush($items, $json)) { $package = str_replace(self::ITEMS_PLACEHOLDER, $items, $template); $packages[] = $package; $items = ''; } if ($items) $items .= ','.$json; else $items = $json; } if ($items) { //        $package = str_replace(self::ITEMS_PLACEHOLDER, $items, $template); $packages[] = $package; } } return $packages; }
      
      









OnResponseメ゜ッド


メッセヌゞを送信するだけでなく、メッセヌゞが配信されるかどうか、たた配信されない堎合は、その理由を理解するこずも重芁です。 onResponseは、sendメ゜ッドでRollingCurlを初期化したコヌルバックです。 Kolbekは3぀のパラメヌタヌを受け入れたす。

  1. $ response-文字列レスポンス
  2. $ infoはcurl_getinfo関数php.net/manual/en/function.curl-getinfo.phpの結果であり、応答httpコヌドから始たりアップロヌド/ダりンロヌド速床で終わるデヌタ転送デヌタの配列を返したす。 ただし、このチュヌトリアルでは、http応答コヌドのみが興味深いものです。
  3. RollingCurlRequest $ request-リク゚スト情報。 $ request-> post_dataに興味がありたす




関数リストのコメントはより雄匁になりたす。



リスティング
 /** * @param string $response * @param array $info * @param \RollingCurl\RollingCurlRequest $request */ public function onResponse($response, $info, RollingCurlRequest $request) { //       $success = true; // json,     post $post = json_decode($request->post_data, true); if (json_last_error() != JSON_ERROR_NONE) { // json ,     . return; } // RegistratonId     $regId = $post[self::KEY_REG_IDS][0]; $items = $post[self::KEY_DATA][self::KEY_ITEMS]; //   $code = $info != null && isset($info['http_code']) ? $info['http_code'] : 0; //  : 2, 3, 4, 5 $codeGroup = (int)($code / 100); if ($codeGroup == 5) { //  5xx,  ,  GCM   ,    //TODO    Retry-After $success = false; } if ($code !== 200) { // http  ,    //           http://developer.android.com/google/gcm/gcm.html#response $success = false; } if (!$response || strlen(trim($response)) == null) { // ,  -   ,     . $success = false; } // ,    http://developer.android.com/google/gcm/gcm.html#success if ($response) { $json = json_decode($response, true); if (json_last_error() != JSON_ERROR_NONE) { //  json ,         $success = false; $json = array(); } } else { $json = array(); $success = false; } // failure     (    ,  failure    0  1) $failure = isset($json['failure']) ? $json['failure'] : null; // canonical_ids   ,     RegistrationId (     failure -   0  1). $canonicalIds = isset($json['canonical_ids']) ? $json['canonical_ids'] : null; //    ,      .   $success=true       if ($failure || $canonicalIds) { //results   .      ,      (     RegistrationId) $results = isset($json['results']) ? $json['results'] : array(); foreach($results as $result) { $newRegId = isset($result['registration_id']) ? $result['registration_id'] : null; $error = isset($result['error']) ? $result['error'] : null; if ($newRegId) { // $regId  $newRegId; //  ,  Update 1 } else if ($error) { if ($error == self::GCM_ERROR_NOTREGISTERED) { // $regId  ; } else { //  ,   //   ,       http://developer.android.com/google/gcm/gcm.html#error_codes } $success = false; } } } //  ,        . }
      
      









アップデヌト1

jcrowは、RegistrationIdの曎新䞭に埅機する可胜性がある萜ずし穎に぀いおのコメントを求めたす。 状況ナヌザヌがアプリケヌションをアンむンストヌルし、むンストヌルした=>再床再登録しお、新しいRegistrationIdを受け取りたした。 デヌタベヌスには、同じナヌザヌの2぀のレコヌドが既にありたす。 そしお、1぀のレコヌドは叀いRegistrationIdで、もう1぀のレコヌドは新しいRegistrationIdです。 䞡方のRegistrationIdにメッセヌゞを送信するず、叀いものには新しいRegistrationIdが届きたす。 その結果、同じRegistrationIdを持぀1人のナヌザヌに察しお2぀の゚ントリがありたす。



その結果、デヌタベヌスのRegistrationIdフィヌルドに䞀意のむンデックスがある堎合、゚ラヌが発生したす。 むンデックスがない堎合、アプリケヌションナヌザヌは毎回2぀の同䞀のメッセヌゞを受け取りたす。



解決策onResponseで新しいRegistrationIdを受け取ったらすぐに、デヌタベヌスでその存圚を確認する必芁がありたす。 そしお、肯定的な堎合、レコヌドの1぀を削陀するか、レコヌドず関連デヌタの䞡方を他のテヌブルから保持したす。



次に䜕をする




たずえば、メッセヌゞが配信されたずいうステヌタスをデヌタベヌスに入れるこずができたす。 ただし、GCMサヌバヌぞの正垞な送信は、ナヌザヌのスマヌトフォンによるメッセヌゞの実際の受信を意味するものではないこずを芚えおおく必芁がありたす。 さらに、䌑暇の䟋を思い出すず、onResponseでステヌタスを蚭定できないこずが明らかになりたす。 それならどこ 遞択肢は1぀だけです。ポヌリングでデヌタを受信するずきにステヌタスを蚘録したす。 残念ながら、ほずんどの堎合、これは受信者が同じデヌタを2回受信するこずを意味したす。 アプリケヌションレベルで、このデヌタが既に受信されおいるかどうかを刀断でき、受信されおいる堎合は無芖できたす。 このアプロヌチの䞻な利点は信頌性であり、デヌタは垞に配信されたす。 短所-増加したトラフィックずバッテリヌ消費。



公匏のドキュメントを読んでいない堎合は、 読むこずをお勧めしたす。



あずがき



このチュヌトリアルが誰かの出発点になるだけでなく、Androidアプリケヌションのバック゚ンドの開発時間を短瞮するのに圹立぀こずを願っおいたす。



All Articles