AndroidのPush通知(FCM)をサーバー知識無しで試してみよう

##AndroidのPush通知を試してみよう
AndroidのPush通知って難しそうなイメージがありますよね。最近はFirebaseなどのmBaaSがあるので比較的簡単になってきました。しかし本来どのようにPush通知を送るのか、Firebaseなどではなく自分のサーバーからPush通知をどのような方法で出したらいいのか分からない方がいると思います。そこで今回はPush通知の出し方を簡単に紹介したいと思います。

##この記事について

##対象者
- Push通知の仕組みを知りたい
- 普段モバイルアプリを開発している
- サーバーはあまり得意ではない
- Firebaseもいいが、いつかは自分のサーバーやmBaaS以外のサーバーからPush通知を送りたい

## Push通知の大体の仕組み
1. Firebaseから固有IDを振ってもらう
2. そのIDをサーバー管理者に伝える
3. サーバーがFirebaseに対して、このIDに通知を出してくれとお願いする
4. FirebaseがAndroid端末にPush通知を発行する

<img width="564" alt="Screen Shot 0029-05-10 at 18.27.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/114922/9ca73ff6-fd46-9716-8330-ccf600b5e813.png">

##今からやるならGCMではなくFCMを使おう
[こちら](https://developers.google.com/cloud-messaging/android/android-migrate-fcm)にも書いてありますが、これからのPush通知はGCMではなくFCMを使用することを強く推奨しています。

## 今後のGCMについて
2019年4月11日にGCMは使えなくなるみたいなので、GCMを使っている人は移行しましょう。

GCM APIを使用するプロジェクトをご利用の場合は、2019年4月11日より前にクライアントとサーバーのコードをFCMを使うように変更する必要があります。既存のGCMトークンはFCMで引き続き使用でき、既存のユーザーに送信する機能は失われません。

if you have projects that are still using the GCM APIs, you will need to update your client and server code to use FCM before April 11, 2019. Your existing GCM tokens will continue to work with FCM so you won’t lose the ability to send to your existing users.

## 実装の流れ
1. Firebaseの導入
2. Androidプロジェクトのコードを準備
3. アプリを実行してInstanceIDを取得する
4. PostmanでFCMサーバーにデータを投げる

## Firebase導入
1. 新規プロジェクトの作成
2. google-services.jsonをダウンロード

### 新規プロジェクトの作成
[Firebase](https://firebase.google.com)にログイン
コンソールに行くと「新規プロジェクトの作成」と「Googleプロジェクトをインポート」がある。

既にGoogle Maps APIやGAEなどを使用しており、同じプロジェクトにFirebaseを入れたい人はインポートを選ぶ。

新規プロジェクトを作成した後は、[AndroidアプリにFirebaseを追加]を選択

<img width="551" alt="Screen Shot 0028-11-24 at 11.16.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/114922/7305aa0a-ad6a-7afc-3322-b7c6c69887f2.png">

すると以下のような画面がでてきます。

<img width="689" alt="Screen Shot 0028-11-24 at 11.23.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/114922/de639f4f-5743-a4ab-395d-fdf67647fbec.png">

必要な情報を入力しましょう。
デバッグ用のkeystoreは以下のようなコードで取得可能です。

```text:Mac/Linux
keytool -exportcert -list -v \
-alias androiddebugkey -keystore ~/.android/debug.keystore
```

```text:Windows
keytool -exportcert -list -v \
-alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore
```

### google-services.json のインポート
アプリを作成すると、google-services.jsonが生成され自動的にダウンロードされます。
こちらのファイルをAndroidプロジェクトのappの配下に入れましょう。

## Androidプロジェクトのコードを準備

### プロジェクトレベルのbuild.gradleに以下を追加

```groovy:build.gradle
buildscript {
  dependencies {
    // ここに追加
    classpath 'com.google.gms:google-services:3.0.0'
  }
}
```

### アプリレベルのbuild.gradleに以下を追加

```build.gradle
dependencies {
  compile "com.google.firebase:firebase-messaging:10.2.4"
}

// ファイルの一番下に追加
apply plugin: 'com.google.gms.google-services'
```
※ ```apply plugin: 'com.google.gms.google-services'```はファイルの一番下に設置してください。

### Push通知に使用するIDを取得するためのMyInstanceIDListenerServiceクラスを作成
こちらのIDはPush通知に使用するのでサーバー管理者に送信する。しかし今回はサーバーを使用しないのでサーバー管理者にIDを送信する処理は省略する。

```MyInstanceIDListenerService.java

import android.util.Log;

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

public class MyInstanceIDListenerService extends FirebaseInstanceIdService {

    private static final String TAG = MyInstanceIDListenerService.class.getSimpleName();

    @Override
    public void onTokenRefresh() {
        //ここで取得したInstanceIDをサーバー管理者に伝える

        String refreshedToken = FirebaseInstanceId.getInstance().getToken();
        Log.d(TAG, "Refreshed token: " + refreshedToken);
    }
}
```

### AndroidManifestにMyInstanceIDListenerServiceを追加

```AndroidManifest.xml
<application>
   <service
      android:name=".MyInstanceIDListenerService">
      <intent-filter>
          <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
      </intent-filter>
   </service>
<application/>
```

### Push通知を受け取るレシーバークラスを作成
ここで受け取った内容を通知する処理も追加します。

```MyFcmListenerService.java
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

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

import java.util.Map;

public class MyFcmListenerService extends FirebaseMessagingService {

    private final static String TAG = MyFcmListenerService.class.getSimpleName();

    @Override
    public void onMessageReceived(RemoteMessage message){
        String from = message.getFrom();
        Map data = message.getData();

        Log.d(TAG, "from:" + from);
        Log.d(TAG, "data:" + data.toString());

        String msg = data.get("data").toString();
        sendNotification(msg);
    }

    private void sendNotification(String message) {
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0 , intent,
                PendingIntent.FLAG_ONE_SHOT);

        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("Push通知のタイトル")
                .setSubText("Push通知のサブタイトル")
                .setAutoCancel(true)
                .setSound(defaultSoundUri)
                .setStyle(new NotificationCompat.BigTextStyle().bigText(message))
                .setContentIntent(pendingIntent);

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

        notificationManager.notify(0 , notificationBuilder.build());
    }
}
```

### AndroidManifestにMyFcmListenerServiceを追加

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

### Push通知を購読or解除
最後にこちらのメソッドを実行すればPush通知の購読or解除が可能です。

```MainActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.google.firebase.messaging.FirebaseMessaging;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Push通知の購読開始
        FirebaseMessaging.getInstance().subscribeToTopic("mytopic");

        //購読解除
        FirebaseMessaging.getInstance().unsubscribeFromTopic("mytopic");
    }
}
```

## アプリを実行してInstanceIDを取得する
アプリを実行してInstanceIDを取得しそのIDをメモしておいてください。
LogcatにIDが表示されるはずです。

```text:Logcat
MyInstanceIDListenerService: Refreshed token: xxxxxxxxxxxxxxxxxxxxx
```

## Postmanに必要情報を入力する
本来はサーバーからPush通知を送信しますが、今回は手動で送信したいと思います。送信にはHTTPリクエストを簡単作れる、GoogleChromeExtensionの Postman を使いたいと思います。
[Postman](http://qiita.com/ken880guchi/items/91bfa4c292c7ec86ed71)についてはこちらを参照ください。

### サーバーキーを取得しよう
Firebaseコンソールのクラウドメッセージングという項目からサーバーキーをメモしておいてください。

<img width="600" alt="Screen Shot 0029-05-10 at 17.31.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/114922/3e21a9b2-6bae-912e-fcd3-37d86c59adcc.png">

### PostmanからPush通知！！
Postmanに必要情報を入れる

1.Postmanのリクエスト方法を「Post」にする
2.URLに ```https://fcm.googleapis.com/fcm/send ```を入力 
3.HeadersのKeyに ```Authorization```と```Content-Type```を追加
4.HeadersのValueに```key=サーバーキー```と```application/json```を入力

※サーバーキーは先ほどFirebaseのコンソールで取得したものを入れてください。

<img width="600" alt="Screen Shot 0029-05-10 at 17.36.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/114922/69fc35c5-3f60-7810-817f-e46cb7860fb9.png">

5.BodyのrawにJson形式のテキストを作ってください。
6.idに先ほどLogcatで取得したトークンを入れてください。 

<img width="600" alt="Screen Shot 0029-05-10 at 17.58.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/114922/cf22d6d2-e09c-b566-a425-0174584cbaae.png">

## Push通知を送信する
ここまで入力が終えたら終了です。Sendボタンを押してPush通知を試してみてください。以下の様にAndroidに通知が表示されれば成功です。

<img width="300" alt="Android" src="https://qiita-image-store.s3.amazonaws.com/0/114922/e2852aac-0294-2860-2162-08ff9c3b9e04.png">

※送信時点でエラーが起きるとPostmanにエラーが返ってきます。

Push通知の大体の仕組みを見ていただきました。あとはPostmanでやっている処理をサーバーに実装するだけでサーバーからPush通知を作成することができます。Push通知を活用して１ランク上の開発にトライしてみてください。