プログラムの実装

設定後、モバイルアプリに初期化処理とプッシュメッセージの受信ハンドラーを追加します。

全体の構成

チュートリアルの冒頭でも示したように、このチュートリアルでは、Google 社から提供されている FCM のサンプルコードを元に、Kii Cloud 用に改変したものを使って実装方法を説明します。オリジナルのサンプルコードは以下のとおりです。

参考ソース:https://github.com/firebase/quickstart-android/tree/master/messaging

FCM のサンプルコード、および、本チュートリアルでは、以下のような機能を実装しています。

  • FCM のデバイストークンの取得:FCM のデバイストークンを取得します。デバイストークンは FCM のライブラリー内で実装されたサービスによって取得されます。
  • メッセージ受信時の処理:受信処理がサービス化されているため、ネットワークアクセスなどの時間がかかる処理を記述しても安全に実行できます。
  • デバイストークン変更の検出:FCM 側でデバイストークンが変更された場合にも、プッシュ通知の対象デバイスを自動的に再登録できます。

クラス構成

最終的な実装は、以下の図に示すクラス構成となります。

薄い緑色と破線の箇所は、FCM のオリジナルのサンプルアプリでは実装されていない部分ですが、GCM のサンプルコード と同様の方法により、本チュートリアルでは実装されています。また、Google Play services のインストール確認の処理も GCM のサンプルコードに基づいて実装します。

ユーザーインターフェイスなどのクラスは実際のモバイルアプリに合わせて自由に構成できますが、基本的にこれと同じ構成になるはずです。また、クラス名やパッケージはモバイルアプリの構成に合わせて自由に変更することができますが、変更する場合は AndroidManifest.xml 等の変更も必要です。

クラスは大きく分けて次の 3 つで構成されます。

  • MainActivity

    画面処理などの適当なタイミングで、プッシュ通知の初期化処理を行います。今回は、簡易的に onCreate() メソッドで処理を行います。

    また、内部で BroadcastReceiver を継承したクラスを作成し、MyFirebaseInstanceIdService クラスからの完了通知を受け取って画面に反映します。

  • MyFirebaseMessagingService

    サーバーからのプッシュメッセージを受け取ったときの処理を行うサービスです。onMessageReceived() メソッドに受信処理を記述できます。

    今回はメッセージを受け取るタイミングまでを実装します。実際のモバイルアプリではステータスバーに表示したり、ブロードキャストによって目的のクラスに通知したりすることができます。

  • MyFirebaseInstanceIdService

    Firebase 側でデバイストークンが変更されたときの再登録処理を行うためのクラスです。FirebaseInstanceIdService のサブクラスとしてサービスを実行しておくと、onTokenRefersh() メソッドによってデバイストークンの変更を検知できます。

    ここでは、MainActivity と同様に、デバイストークンの取得処理と Kii Cloud への登録処理を行います。

    このクラスはサービスのため、そのままでは再登録処理の結果を画面に表示できません。今回は、ブロードキャストを使うことで、MainActivity が実装する BroadcastReceiver に結果を伝えて画面表示を行います。

なお、Android では、プッシュメッセージを受け取ってもユーザーインターフェイスへの表示は行われません。表示したい場合は、受信処理として、ステータスバーへの表示処理を記述する必要があります。その際 PendingIntent によってメインとなる Activity を起動するようにしておけば、ステータスバーのクリックによって Activity を起動することができます。

初期化開始処理の実装

デバイストークンをインストールする前に、Kii Cloud のユーザーをログイン状態にしておく必要があります。プッシュ通知はログイン中のユーザーとデバイスを紐付けることで実現するためです。

以下はユーザーのログイン処理です。これを MainActivity.onCreate() メソッドに追加します。

public class MainActivity extends AppCompatActivity {
  private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ......
    if (!checkPlayServices()) {
      Toast.makeText(MainActivity.this, "This application needs Google Play services", Toast.LENGTH_LONG).show();
      return;
    }

    String username = "user1";
    String password = "123ABC";
    KiiUser.logIn(new KiiUserCallBack() {
      @Override
      public void onLoginCompleted(int token, KiiUser user, Exception exception) {
        if (exception != null) {
          Toast.makeText(MainActivity.this, "Error login user:" + exception.getMessage(), Toast.LENGTH_LONG).show();
          return;
        }
        Toast.makeText(MainActivity.this, "Succeeded to login", Toast.LENGTH_LONG).show();

        String fcmToken = FirebaseInstanceId.getInstance().getToken();
        if (fcmToken == null) {
          Toast.makeText(MainActivity.this, "Error FCM is not ready", Toast.LENGTH_LONG).show();
          return;
        }

        boolean development = true;
        KiiUser.pushInstallation(development).install(fcmToken, new KiiPushCallBack() {
          @Override
          public void onInstallCompleted(int taskId, Exception exception) {
            if (exception != null) {
              Toast.makeText(MainActivity.this, "Error push registration:" + exception.getLocalizedMessage(), Toast.LENGTH_LONG).show();
            } else {
              Toast.makeText(MainActivity.this, "Succeeded push registration", Toast.LENGTH_LONG).show();
            }
          }
        });
      }
    }, username, password);
  }

  private boolean checkPlayServices() {
    GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
    int resultCode = apiAvailability.isGooglePlayServicesAvailable(this);
    if (resultCode != ConnectionResult.SUCCESS) {
      if (apiAvailability.isUserResolvableError(resultCode)) {
        apiAvailability.getErrorDialog(this, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST)
                .show();
      } else {
        Log.i("PushTest", "This device is not supported.");
        finish();
      }
      return false;
    }
    return true;
  }
}

ここで実行している処理は以下の 4 つです。

  1. 初めに、Google Play services が利用できることを checkPlayServices() メソッドで確認します。ここでは簡略化のため、利用できない場合はエラーメッセージを表示して初期化をスキップするだけの実装としています。なお、FCM のドキュメント では、onCreate() メソッドと onResume() メソッドの 2 か所でチェックすることが推奨されています。
  2. Kii Cloud のユーザーのログイン処理を行います。アプリ作成時 にテストで作成したユーザーは、ユーザー名が user1、パスワードが 123ABC です。ログインできない場合はエラーを表示して初期化をスキップします。
  3. FirebaseInstanceId.getInstance().getToken() メソッドによって FCM のデバイストークンを取得します。取得処理はすぐに終わるため、非同期で実行する必要はありません。ただし、戻り値は、下記 に示すように null チェックが必要です。Firebase のライブラリー内では、使用中のデバイスが Google の FCM サーバーに自動的に登録されます。登録結果はデバイストークンとして保持されており、このメソッドによって取得できます。
  4. 取得したデバイストークンは、pushInstallation() メソッドの後に install() メソッドを呼び出し、現在ログイン中のユーザーと紐付けて Kii Cloud に登録します(このタイミングでログイン中であることが要求されます)。このコードでは、開発用のプッシュ通知環境で初期化しています。配布用の場合は、development を false にします。

checkPlayServices() メソッドは、GCM のサンプル にあった Google Play services のチェック処理をそのまま引用しています。finish() メソッドによってアプリを終了させる処理が実装されているため、ご注意ください。

このコードは Kii Cloud SDK を使う場合の実装です。Thing-IF SDK を使用する場合、このままのコードで 動作を確認 した後で、Thing-IF SDK を使用する Android アプリ向けのプッシュ通知の導入手順の Thing-IF 向けの変更 に進んでください。

実際のアプリでは、次のように実装するはずです。

  • アプリの起動時と復帰時に Google Play services のチェックを実行

  • ユーザーのログイン成功、仮ユーザー(Pseudo User)の登録成功、トークンでのログインの完了などのタイミング(またはそれ以降)で、デバイストークンの取得と Kii Cloud への登録を実行

FCM デバイストークンの null チェック

FCM のデバイストークンは、FCM のライブラリーで実装されたサービスによって、Firebase のサーバーから取得されます。この処理は、モバイルアプリ起動時に画面処理と平行して実行され、すぐに完了しますが、モバイルアプリの起動直後に getToken() メソッドを呼び出すと、Firebase のサーバーからの取得が間に合わずに null を返す可能性があります。

FirebaseInstanceId.getInstance().getToken() メソッドが null を返す場合に備え、必要に応じて、タイマーなどでリトライをかけるような処理を検討してください。また、今回のチュートリアルで null が返されるためにテストできない場合は、ボタンクリックのハンドラーに変更するなどして実行のタイミングを調整してみてください。

デバイストークン変更処理の実装

InstanceIDListenerService を使うと、Firebase のサーバー側でデバイストークンが更新されたときの処理を記述できます。

FCM では Firebase のサーバー側でデバイストークンが更新されることがあります。その場合には、新しいデバイストークンを再登録する必要があります。

更新の検出は、以下のように実装できます。新規クラスを作成し、FirebaseInstanceIdService のサブクラスとして処理を実装します。

public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService {
  private static final String TAG = "MyFirebaseIIDService";
  public static final String INTENT_PUSH_REGISTRATION_COMPLETED = "com.example.pushtest.COMPLETED";
  public static final String PARAM_ERROR_MESSAGE = "ErrorMessage";

  @Override
    public void onTokenRefresh() {
    String fcmToken = FirebaseInstanceId.getInstance().getToken();

    String error = null;
    try {
      boolean development = true;
      KiiUser.pushInstallation(development).install(fcmToken);
    } catch (Exception e) {
      error = e.getLocalizedMessage();
    }
    Intent registrationComplete = new Intent(INTENT_PUSH_REGISTRATION_COMPLETED);
    registrationComplete.putExtra(PARAM_ERROR_MESSAGE, error);
    LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete);
  }
}

ここでは、onTokenRefresh() メソッドでデバイストークンの変更を検出した際、デバイストークンをもう一度 Kii Cloud に登録する処理を記述しています。これは、先ほど実装したログイン完了時の処理と同じです。これによって、プッシュ通知の再登録処理が実行されます。

実行が終わったら、com.example.pushtest.COMPLETED という名前でブロードキャストを行います。パラメータとして、エラーメッセージを保持します。成功はエラーメッセージの null で表現されます。この受信ハンドラーは、あとで実装します。

なお、ユーザーがログインしていない状態で pushInstallation() メソッドを呼び出すと、例外が発生して catch ブロックに入ります。プロセスの再起動などでユーザーのログイン状態が失われた場合に備え、SDK による保存情報からのログイン などの機能を使ってログイン状態を保持することもできます。

初期化完了ハンドラーの実装

MainActivity に BroadcastReceiver を実装して、MyFirebaseInstanceIDService からのデバイストークン再登録処理の結果を受け取れるようにします。

MyFirebaseInstanceIDService でのプッシュ通知の初期化処理が完了すると、com.example.pushtest.COMPLETED でのブロードキャストが行われます。ここでは、ブロードキャストの受信ハンドラーを記述します。

BroadcastReceiver はどこで実装しても問題ありませんが、今回は MainActivity でメッセージを受け取ります。先ほど MainActivity.onCreate() メソッドに追加した処理の前あたりに mRegistrationBroadcastReceiver の new を追加します。さらに、そのフィールドの宣言、onResume() メソッド、onPause() メソッドを追加します。

public class MainActivity extends AppCompatActivity {
  private BroadcastReceiver mRegistrationBroadcastReceiver;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ......
    mRegistrationBroadcastReceiver = new BroadcastReceiver() {
      @Override
      public void onReceive(Context context, Intent intent) {
        String errorMessage = intent.getStringExtra(MyFirebaseInstanceIDService.INTENT_PUSH_REGISTRATION_COMPLETED);
        if (errorMessage != null) {
          Toast.makeText(MainActivity.this, "Error push registration:" + errorMessage, Toast.LENGTH_LONG).show();
        } else {
          Toast.makeText(MainActivity.this, "Succeeded push registration", Toast.LENGTH_LONG).show();
        }
      }
    };
    ......
  }

  @Override
  protected void onResume() {
    super.onResume();
    LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,
            new IntentFilter(MyFirebaseInstanceIDService.INTENT_PUSH_REGISTRATION_COMPLETED));
  }

  @Override
  protected void onPause() {
    LocalBroadcastManager.getInstance(this).unregisterReceiver(mRegistrationBroadcastReceiver);
    super.onPause();
  }
}

ここでは、以下の処理を行っています。

  • クラスのフィールドとして、ブロードキャストレシーバ mRegistrationBroadcastReceiver を保持します。
  • onCreate() メソッドでは、BroadcastReceiver の実装を追加しています。ここでは、プッシュ通知の初期化完了時の処理を記述します。先ほど実装した MyFirebaseInstanceIDService の最後では、Intent のパラメータとして、ErrorMessage フィールドにエラーの有無とエラーメッセージを登録しました。これを getStringExtra() メソッドで取得して、エラーメッセージまたは完了メッセージを出力しています。
  • onResume() メソッドでは、ブロードキャストレシーバを登録します。ここでは、com.example.pushtest.COMPLETEDmRegistrationBroadcastReceiver を関連付けているため、MyFirebaseInstanceIDService の最後で実行している完了通知がこのクラスに届くようになります。
  • onPause() メソッドでは、登録したブロードキャストレシーバを破棄します。

受信ハンドラーの実装

FCM によるプッシュメッセージを受信したときは、FirebaseMessagingService のサブクラスが呼び出されます。

新規クラスを作成し、下記を追加します。

public class MyFirebaseMessagingService extends FirebaseMessagingService {
  private static final String TAG = "MyFirebaseMsgService";

  @Override
  public void onMessageReceived(RemoteMessage remoteMessage) {
    Map<String, String> payload = remoteMessage.getData();
    Bundle bundle = new Bundle();
    for (String key : payload.keySet()) {
      bundle.putString(key, payload.get(key));
    }

    ReceivedMessage message = PushMessageBundleHelper.parse(bundle);
    KiiUser sender = message.getSender();
    PushMessageBundleHelper.MessageType type = message.pushMessageType();
    switch (type) {
      case PUSH_TO_APP:
        PushToAppMessage appMsg = (PushToAppMessage)message;
        Log.d(TAG, "PUSH_TO_APP Received");
        break;
      case PUSH_TO_USER:
        PushToUserMessage userMsg = (PushToUserMessage)message;
        Log.d(TAG, "PUSH_TO_USER Received");
        break;
      case DIRECT_PUSH:
        DirectPushMessage directMsg = (DirectPushMessage)message;
        Log.d(TAG, "DIRECT_PUSH Received");
        break;
    }
  }
}

ここでは、プッシュメッセージの種類に応じてデバッグログにメッセージを出力しています。このチュートリアルでは、プッシュメッセージを受信できるところまでを、ログによって確認します。

Kii Cloud のプッシュ通知では、PUSH_TO_APP、PUSH_TO_USER、DIRECT_PUSH の 3 通りの機能を用意しています。このチュートリアルの最後では、開発者ポータルのユーザーインターフェイスからプッシュメッセージを送信するテストを行いますが、その場合には DIRECT_PUSH を受け取ることになります。

プッシュ通知を受け取った際にステータスバーにメッセージを表示したい場合は、ステータスバーへの表示 を参照してください。

画面表示のための拡張

プッシュメッセージを受信した際、その結果をモバイルアプリのユーザーインターフェイスに直接反映したい場合は、構成の拡張が必要な場合があります(ステータスバーへの反映は上記のリンクの通りです)。

本チュートリアルでは、MyFirebaseMessagingService で直接プッシュメッセージの処理を記述していますが、サービス内で直接ユーザーインターフェイスを操作することは基本的にできません。

モバイルアプリが動作中のとき、受信結果を画面に反映させたい場合は、上記の初期化完了通知と同様に、ブロードキャストを利用できます。画面反映を行いたいユーザーインターフェイス処理クラスに BroadcastReceiver を実装し、サービスの受信処理からブロードキャストを送信して処理を委譲します。

なお、受信処理はサービスとして動作しているため、Activity が存在しない場合にもプッシュメッセージを受け取る可能性がある点にご注意ください。

アプリの実行

実装完了後、アプリを実行してみます。

起動後、トーストとして、ログイン完了のメッセージ「Succeeded to login」と、プッシュ通知初期化完了時のメッセージ「Succeeded push registration」の 2 つが表示されれば成功です。

エラーが表示される場合やメッセージが表示されない場合、デバッガなどを使ってプログラムと AndroidManifest.xml を確認して問題を解決してください。

次のステップ テストメッセージの送信 で実際にテストメッセージを送信してみましょう。


<< マニフェストの設定 テストメッセージの送信 >>