Firebaseを使ったAndroidアプリのプッシュ通知 + PHPによるプッシュ送信の実装


Androidアプリにてアプリ利用者全員に向けたプッシュ通知の実装を行います。

# 今回の目標
作成したAndroidアプリをインストールしてくれたユーザ全てに対して、PHPで作成した管理画面からプッシュ送信を行います。
メッセージを受信した端末はプッシュ通知受信音を鳴らしながら振動し、通知リストにアプリのアイコンとメッセージが表示され、そこをタップするとアプリが起動するという仕組みを作成したいと思います。

# 前提知識
FirebaseにはGCM(Google Cloud Messaging)から進化したFCM(Firebase Cloud Messaging)というプッシュ通知用のサービスがあり、ほぼノンプログラムで利用できる"Notificationメッセージ"と、GCMと同じような実装が可能となる"Dataメッセージ"の２種類のプッシュ通知の方法があります。

この２つは全くの別物というワケではなく、FCMのAPIをコールする時のJSONデータの内容によって切り分けることができます。

#### Notificationメッセージ
FCMに対してプッシュ通知を依頼するPOSTリクエストを投げる際に、notificationをキーとしたデータを含めると、"Notificationメッセージ"として動作します。
(notificationに記述できるパラメータは、下記で説明します)

この際、Androidアプリ側では受信した際の処理を書かなくても、勝手にシステムトレイにメッセージが表示されます。

NotificationメッセージのPOSTリクエスト例は以下の通りです。

```
Content-Type:application/json
Authorization:key=*******************
{
    "to" : "**************",
    "notification" : {
        "title" : "テストタイトル",
        "body" : "テスト本文"
    }
}
```

#### Dataメッセージ
FCMに対してプッシュ通知を依頼するPOSTリクエストを投げる際に、dataをキーとしたデータを含めると、"Dataメッセージ"として動作します。
(dataに記述できるパラメータの内容は自由です)

DataメッセージのPOSTリクエスト例は以下の通りです。

```
Content-Type:application/json
Authorization:key=*******************
{
    "to" : "**************",
    "data" : {
        "id" : "適当な値",
        "message" : "何でもOK",
        "metadata" : "自由に設定可能",
    }
}
```

#### Bothメッセージ
FCMに対してプッシュ通知を依頼するPOSTリクエストを投げる際に、notificationとdataの両方を含めると"Bothメッセージ"として動作します。

BothメッセージのPOSTリクエスト例は以下の通りです。

```
Content-Type:application/json
Authorization:key=*******************
{
    "to" : "**************",
    "notification" : {
        "title" : "テストタイトル",
        "body" : "テスト本文"
    },
    "data" : {
        "id" : "適当な値"",
        "message" : "何でもOK",
        "metadata" : "自由に設定可能",
    }
}
```

FCMを理解するうえで、このモードの違いを理解しておかないと苦労すると思います。POSTリクエストの内容によって自動的にモードが切り替わるということを理解しておいてください。

# プッシュ通知受信時の動作について
<img width="512" alt="20160529161944.png" src="https://qiita-image-store.s3.amazonaws.com/0/142507/048b50f7-5ce8-96af-78f8-15476bf5110d.png">
 (https://firebase.google.com/docs/cloud-messaging/downstream から引用)
 
 各メッセージとアプリ起動中/待機中によって、プッシュ通知を受信した際の動作が変わります。
onMessageReceivedはメッセージ受信イベントによって起動されるメソッドのことで、このメソッドの処理は自由にカスタマイズすることができます。SystemTrayとはFCMが勝手に通知メッセージをシステム上に表示してくれる仕組みで、Notificationメッセージでサポートされているパラメータの範囲でカスタマイズすることが可能です。
 
# Notificationメッセージでサポートされているパラメータについて
プッシュ通知を受信→通知をタッチしてアプリを開くという流れを実装する程度であれば、Dataメッセージを使わなくても、Notificationメッセージでサポートされているパラメータをカスタマイズすることで理想の動作を実現することができます。
Notificationメッセージには、title・body・icon・sound・tag・color・click_action等のパラメータを設定することができるので、ここに適切なパラメータを設定すれば、Androidアプリ側の実装が"ほぼゼロ"になります。

Notificationメッセージに設定できるパラメータは以下のURLを参考にしてください。
https://firebase.google.com/docs/cloud-messaging/http-server-ref
(「Notification payload support」のパラメータ一覧が記載されています)
 
# 制作方針
本アプリの目的はプッシュ通知の受信と通知をタッチして時のアプリ起動のみなので、実装が容易なNotificationメッセージでも作成可能かもしれませんが、Notificationメッセージではバイブレーターが動作しなかった＆将来的に機能追加を行う事も考え、Dataメッセージを使って実装してみます。(作成の過程でNotificationメッセージの動作確認も行えるような流れになっていますので、Notificationメッセージで作成する際のサンプルにもなるかと思います)

# その前に・・・トピックによるユーザ管理
FCMにはトピックという概念があります。個人的にはグループという言葉の方がしっくりきますが、要はプッシュ通知を複数の端末に同時送信したい場合、その端末をまとめたものがトピックという概念になります。
今回はアプリをインストールしてくれたユーザ全員にプッシュ通知を実施したい為、インストールしたユーザは全員同じトピックに属してもらい、そのトピックに対してプッシュ通知を実施することで、全ユーザに対してメッセージを送る仕組みとなります。
なお、トピックを作成するのはFCMの管理コンソールからではなく、各端末からトピックに属したいという命令が実行された場合に、そのトピックが存在しない場合に自動的にトピックが生成される仕組みとなっています。
トピックの作成には経験上半日程度時間がかかる為、Android端末からトピックを登録する命令を実行しても、FCMの管理コンソール上ですぐに作成されたトピックを確認することはできませんので注意してください。


# Firebaseの登録＋プッシュ通知受信Androidアプリの作成

#### Firebase
https://firebase.google.com/

#### Firebaseアカウント登録
WEBブラウザで上記のURLにアクセスしてFirebaseのアカウントを作成してください。
Googleアカウントがあれば登録は簡単です。

#### Android新規プロジェクト作成
AndroidStudioを起動し、EmptyActivityベースで適当なプロジェクトを作成してください。
![empty.png](https://qiita-image-store.s3.amazonaws.com/0/142507/09930fd8-4106-bb7a-25f5-5877a81b882c.png)

#### Firebase新規プロジェクト作成
Firebaseに戻り、「新規プロジェクトを作成」をクリックし、適当なプロジェクト名と国を選択してください。

#### Firebaseプロジェクトにアプリ情報の追加
上記で作成したFirebaseプロジェクトに対して、Androidアプリを追加してください。
![firebase.png](https://qiita-image-store.s3.amazonaws.com/0/142507/5abba385-08d2-bf53-9254-8fadde16f1ff.png)

パッケージ名は先ほど作成したAndroid新規プロジェクトのパッケージ名を、デバッグ用の署名はヘルプボタンをクリックすると確認方法が表示されますので、それを参考に証明書のSHA-1を入力してください。
「アプリを追加」ボタンをクリックすると、google-services.jsonがダウンロードされるので、画面の指示に従って、Androidプロジェクトのapp配下にコピーしてください。

#### build.gradleの設定
AndroidStudioに戻り、Android新規プロジェクトの直下にあるbuild.gradleの"dependencies"にパスを追加します。

```build.gradle
dependencies {
    ...
    classpath 'com.google.gms:google-services:3.0.0'
}
```

#### app/build.gradleの設定
次に、プロジェクト内のapp配下にあるbuild.gradleの"dependencies"とその下に次の記述を追加します。

```app/build.gradle
dependencies {
    ...
    compile 'com.google.firebase:firebase-core:9.6.0'
    compile 'com.google.firebase:firebase-messaging:9.6.0'
}
apply plugin: 'com.google.gms.google-services'
```

#### MainActivityに"test"トピックへの参加処理を追加
AndroidStudioでMainActivity.javaを開き、onCreateメソッドのsetContentViewを呼び出した後に、以下のコードを追加します。

````java
FirebaseMessaging.getInstance().subscribeToTopic("test");
````

これで、このアプリを起動したユーザはtestトピックのプッシュ通知を受け取る準備ができました。エミュレータで構わないので一旦アプリを実行してみてください。

先ほども説明した通り、トピックの反映には半日程度掛かる為、このアプリを起動してから半日後にFirebaseのコンソール上にtestトピックが作成されるはずです。

なお、トピックの作成時にlogcatには以下のように表示されます。

````
D/FirebaseInstanceId: topic sync succeeded
````

# 半日待つ間にPHPでプッシュ送信処理を作成する
PHPの標準関数のみでプッシュ送信のAPIをコールしてみます。

上記で作成したAndroidアプリには受信処理を記述していません。(上記説明で実際にソースコードを追加した部分はMainActivityへの１行のみです)
とりあえず動作確認として受信処理をFCMが勝手にやってくれるNotificationメッセージを使ってプッシュ通知を実現したいと思います。

ソースコード中にAPI KEYを求められる箇所がありますが、Firebaseの管理コンソールからプロジェクトを選択し、設定ボタンをクリックしたら、「クラウドメッセージング」というタブがあるのでそれを選択すると「サーバーキー」という名称でAPI KEYが表示されるので、そのキーをPHPのプログラムに反映してください。

```php
$api_key  = "ここにFirebaseから割り当てられたAPI KEYを入力してください";
$base_url = "https://fcm.googleapis.com/fcm/send";

// toに指定しているのはトピック名:testに対して一括送信するという意味
// 個別に送信したい場合はここに端末に割り振られたトークンIDを指定する
$data = array(
	 "to"			=> "/topics/test"
	,"priority"		=> "high"
	,"notification"	=> array(
							 "title" => "テスト送信タイトル"
							,"body"	 => "テスト送信本文"
	)
);
$header = array(
     "Content-Type:application/json"
    ,"Authorization:key=".$api_key
);
$context = stream_context_create(array(
    "http" => array(
         'method' => 'POST'
        ,'header' => implode("\r\n",$header)
        ,'content'=> json_encode($data)
	)
));
file_get_contents($base_url,false,$context);
```

# 半日経過したら・・・Notificationメッセージの確認
Firebase管理コンソールから[Grow]->[Notification]を選択し、新しいメッセージの作成を選択。![firebase1.png](https://qiita-image-store.s3.amazonaws.com/0/142507/698b76a1-aeac-4c2d-46e2-583582445146.png)

ターゲットで「トピック」を選択、トピック一覧に"test"が表示されればOK！

この画面を使えば、PHPのプログラムを実行しなくてもメッセージを送信することが可能だが、せっかくPHPのプログラムを作成したのでPHPからメッセージを送ってみましょう。

まずは、作成したAndroidアプリを一度起動し、すぐに終了しておいてください。
アプリの終了を確認したら、PHPプログラムを実行します。

すると、画面の上部にドロイド君の影が表示されます。
![push1.png](https://qiita-image-store.s3.amazonaws.com/0/142507/5ee4f4ec-5609-e920-4b3b-1cf1618ddfdf.png)

システムトレイを引き出してみると、PHPで設定したメッセージが正常に受信できていることがわかります。
![push2.png](https://qiita-image-store.s3.amazonaws.com/0/142507/9db089df-6a01-86ce-1ed8-5f019f726345.png)

Android側のプログラムは、EmptyActivityテンプレートをベースにしたMainActivityに1行追加しただけのプログラムですが、正常にプッシュ通知を受信し、画面に表示することができました。

驚くほど簡単にプッシュ通知を実装することができました。これがNotificationメッセージの凄さです。
PHPプログラムのnitification連想配列を編集することで、様々なメッセージを送信することができるので試してみてください。

# PHPプログラムをDataメッセージに切り替える
PHPプログラムのnotificationキーの箇所をdataキーに書き換えるだけで、Dataメッセージに切り替えることができます。
Dataキーの内容は適当にid・label・textとしておきました。

```php
$data = array(
	 "to"			=> "/topics/test"
	,"priority"		=> "high"
	,"data"	        => array(
							 "id"   => "1"
							,"label"=> "Dataテストタイトル"
							,"text"	=> "Dataテスト本文"
	)
);
```

変更したPHPを実行してみても、Android側には何も反応はありません。
これはDataメッセージになったことで、FCMが勝手に通知を表示してくれなくなってしまった為です。

# Dataメッセージを受け取れるようにする
AndroidManifest.xmlのActivityタグ内に次の記述を追加してください。
これはプッシュ通知をアプリに引き渡す為の定義になります。

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

これで、このアプリにプッシュ通知が渡ってきた場合に、MyFirebaseMessagingServiceを呼び出すようになりました。
それではMyFirebaseMessagingServiceクラスを実装してみましょう。

````java
public class MyFirebaseMessagingService extends FirebaseMessagingService {
	@Override
	public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);
  
        // データの受信
        Map<String, String> data		= remoteMessage.getData();
		String id		      = "";
		String label   		= "";
		String text			= "";
		if (data.containsKey("id")){ id = data.get("id"); }
		if (data.containsKey("label") ){ label = data.get("label");  }
		if (data.containsKey("text") ){ text = data.get("text");  }

        // 通知の作成
        NotificationCompat.Builder builder	= new NotificationCompat.Builder(getApplicationContext());
		builder.setSmallIcon(R.mipmap.ic_launcher);
		builder.setContentTitle("".equals(label)?getString(R.string.app_name):label);
		builder.setContentText(text);
		builder.setWhen(System.currentTimeMillis());
		builder.setDefaults(Notification.DEFAULT_SOUND
                          | Notification.DEFAULT_VIBRATE
                          | Notification.DEFAULT_LIGHTS);
		builder.setAutoCancel(true);
        
        // タッチ時、自アプリを起動する
        Intent intent = new Intent(this, MainActivity.class);
		PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
		builder.setContentIntent(contentIntent);
        
        // 通知の表示
        NotificationManagerCompat manager = NotificationManagerCompat.from(getApplicationContext());
        manager.notify(1, builder.build());
    }
}
````

PHPプログラムからDataメッセージを送信すると
![push3.png](https://qiita-image-store.s3.amazonaws.com/0/142507/029a9f41-ec92-4402-5b39-68bd6ea3cdd7.png)

このように、Dataとして送った内容を反映した通知が表示されます。
onMessageReceivedメソッド内で色々ロジックを書けば、Notificationメッセージにはできない様々な処理を行うことができます。


# まとめ
今回はトピック機能を使うことで、利用者に対して一斉に送信する方法をテストしてみました。
個別に送信する必要がある場合は、FCMを使って端末毎のトークンIDを自サーバで管理する必要があるので手間がかかりますが、一括送信だけであれば、思ったよりも簡単に実装することができました。

個人的には、「Notificationメッセージ・Dataメッセージの違い」「トピックの作成方法」を理解するまでに時間が掛かりましたが、分かってしまえば単純な仕組みでしたので、この記事が理解の助けになれば幸いです。

次回はFirebaseを使ってiOSのプッシュ通知の方法を公開したいと思っています。