Parse.comからNIFTY Cloud mobile backendへプッシュ通知を移行する（Android編）

[こちらの記事][link-1]の移行期間中のアプリの実装方法Android編です。
Parse.comのSDKを組み込み済みのアプリに対して、追加でNIFTY Cloud mobile backendのSDKを組み込む方法を紹介します。

iOS編は[こちら][link-2]。

# 共通の作業（実施済みの場合はスキップ）

## (1) @niftyIDを取得する

　@niftyIDを持っていない方は[ここ](http://mb.cloud.nifty.com/signup.htm)から登録してください。
　　※既にお持ちの方は改めて登録する必要はありません。

## (2)mobile backendの利用登録をする

　[ここ](https://console.mb.cloud.nifty.com/)から先ほど取得した@niftyIDでmobile backendにサインアップしてください。
　　※既に利用登録済みの方は作業不要です。

## (3)mobile backendのアプリを作成する

　(2)でサインアップしたかたは利用規約の次の画面でアプリ作成画面が表示されます。任意のアプリ名でアプリを作成してください。
　既にサインアップ済みの方はダッシュボード右上の「+新しいアプリ」から新規のアプリを作ることができます。

## (4)アプリケーションキーとクライアントキーをコピーしておく

　２つのキーはアプリ実装時に使用しますのでどこかにコピーしておきましょう。後から確認する場合はダッシュボード左側のメニューで「アプリ設定」を選ぶと表示されます。

# Androidアプリ固有の手順

## (1) GCMを利用する準備

　Parse.comを利用していた多くの方はParse.comが用意している共通のGCM設定を使っていたかと思います。NIFTY Cloud mobile backendでは共通の設定はありませんので、各自でGCMを利用する準備が必要です。以下の手順に従って、Sender IDとAPI Keyを取得してください。

1. https://console.developers.google.com/ にアクセスして、API Keyを発行したいGoogleアカウントでログインしてください。
2. 「プロジェクトを作成」から新しいプロジェクトを作ります。プロジェクト名を適当に決めるだけでOKです。
3. プロジェクトを作成するとダッシュボードが表示されます。画像中の赤く塗りつぶした部分に数字の羅列が表示されているかと思いますが、これが「**Sender ID**」です。
![SenderID.png](https://qiita-image-store.s3.amazonaws.com/0/35817/340669fd-fbdc-102d-011f-b2dcddcd9a8b.png)

4. ダッシュボードの「Google APIを利用する」という青いパネルを選択してください。
5. APIの一覧が表示されるので「Google Cloud Messaging」を選択し、「Enable」ボタンを押下してGCMを有効化します。
![googleCloudMessaging.png](https://qiita-image-store.s3.amazonaws.com/0/35817/f02e262e-d13a-3dc6-13bf-05a1b0bd56bd.png)

6. API Keyを作成するため、そのままの画面（API Manager）で左側メニューの「認証情報」を選択します
7. 「Create credentials」＞「APIキー」＞「サーバーキー」という順に選択していきます。
8. キーの名前を適当に決めて、IPアドレス欄は空欄のまま作成してください。
9. 認証情報の画面に作成された **APIキー** が表示されているかと思います。
![APIKey.png](https://qiita-image-store.s3.amazonaws.com/0/35817/54e4fc65-a48f-d396-08fe-f454549bd90b.png)


　ここまでで、Sender IDとAPIキーの取得ができました。

## (2) mobile backendのSDKを導入する

　SDKはGitHubで公開されているので入手してください。（[SDKダウンロードページ][link-3]）
　NCMB.x.x.x.zipをダウンロードして解凍するとNCMB.jarファイルが手に入ります。
　これを以下の手順をプロジェクトに追加します。

　1. Android Studioで`File > New > Module`を選択
　2. `Import .JAR/.AAR Package`を選択
　3. ダウンロード済みのNCMB.jarを選択
　4. `File > Project Structure`を開く
　5. 左側のModulesから開発中のプロジェクトを選択
　6. Dependenciesタブを選んで`+ボタン > Module Dependencies`を選択
　7. NCMBモジュールが表示されるので選択

　次に、SDKが依存しているライブラリをgradleファイルに追記します。
　build.gradleを開くとparseとNCMBのSDKが記述されているdependenciesセクションがあるので、NCMBの前に「play-services-gcm」と「gson」のライブラリを追記します。

```
dependencies {
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.parse.bolts:bolts-tasks:1.3.0'
    compile 'com.parse:parse-android:1.13.0'
    compile 'com.google.android.gms:play-services-gcm:8.4.0'
    compile 'com.google.code.gson:gson:2.3.1'
    compile project(':NCMB')
}
```

## (3) mobile backendのSDKを読み込む

　MainActivity.java[^1] の冒頭で ヘッダファイルをインポートしているあたりに追記します。

```java:MainActivity.java
import com.nifty.cloud.mb.core.NCMB;
import com.nifty.cloud.mb.core.NCMBInstallation;
import com.nifty.cloud.mb.core.NCMBQuery;
import com.nifty.cloud.mb.core.NCMBException;
import com.nifty.cloud.mb.core.DoneCallback;
import com.nifty.cloud.mb.core.FindCallback;
```

## (4) mobile backendのSDKの初期化を行う

　MainActivity.java[^1] の `onCreate()` の中に追記します。

```java:MainActivity.java
NCMB.initialize(this, "NCMB_APPLICATION_KEY", "NCMB_CLIENT_KEY");
```

　NCMB_APPLICATION_KEYとNCMB_CLIENT_KEYはmobile backendのダッシュボードから取得したアプリケーションキーとクライアントキーをそれぞれ設定してください。

## (5) mobile backendへinstallationを登録するコードを追加する

　MainActivity.java[^1] の `onCreate()` の中で、SDKの初期化のすぐ後ろに追記します。
　`getRegistrationIdInBackground()` の第一引数に渡している **SENDER_ID** は、「(1)GCMを利用する準備」で取得したSender IDを設定してください。

```java:MainActivity.java
final NCMBInstallation installation = NCMBInstallation.getCurrentInstallation();
installation.getRegistrationIdInBackground("SENDER_ID", new DoneCallback() {
  @Override
  public void done(NCMBException e) {
    if (e == null) {
      installation.saveInBackground(new DoneCallback() {
        @Override
        public void done(NCMBException e) {
          if (e == null) {
            // 保存成功
          } else if (NCMBException.DUPLICATE_VALUE.equals(e.getCode())) {
            // 登録済みinstallationのdeviceTokenと重複した新規登録の場合のエラー
            updateDuplicatedInstallation(installation);
          } else if (NCMBException.DATA_NOT_FOUND.equals(e.getCode())) {
            // 更新リクエストを送ったがサーバ側のinstallationがコンパネ等から削除済みだった時のエラー
            reRegistInstallation(installation);
          } else {
            // その他のエラー
          }
        }
      });
    } else {
      // ID取得失敗
    }
  }
});
```

　さらに、installationトークンの保存処理でエラーが発生した時のリカバリー用メソッドを追加します。MainActivityクラスの内部に実装すれば良いので、`onCreate()`メソッドの後ろあたりに追加してください。

```java:MainActivity.java
// 登録済みinstallationのdeviceTokenと重複した新規登録の場合のリカバリー用メソッド
public void updateDuplicatedInstallation(final NCMBInstallation installation) {
  NCMBQuery<NCMBInstallation> installationQuery = NCMBInstallation.getQuery();
  installationQuery.whereEqualTo("deviceToken", installation.getDeviceToken());
  installationQuery.findInBackground(new FindCallback<NCMBInstallation>() {
    @Override
    public void done(List<NCMBInstallation> results, NCMBException e) {
      if (e == null) {
        installation.setObjectId(results.get(0).getObjectId());
        installation.saveInBackground(new DoneCallback() {
          @Override
          public void done(NCMBException e) {
            if (e == null) {
              // 更新成功
            } else {
              // 更新失敗
            }
          }
        });
      } else {
        // 重複したデータの検索失敗
      }
    }
  });
}

// 更新リクエストを送ったがサーバ側のinstallationがコンパネ等から削除済みだった場合のリカバリー用メソッド
public void reRegistInstallation(final NCMBInstallation installation) {
  installation.setObjectId(null);
  installation.saveInBackground(new DoneCallback() {
    @Override
    public void done(NCMBException e) {
      if (e == null) {
        // 新規登録成功
      } else {
        // 新規登録失敗
      }
    }
  });
}
```

## (6) Parse.com用の処理とmobile backend用の処理を適切に分配するためのBroadcastReceiverを作成する

　registration_idの取得やプッシュ通知の受信の処理はBroadcastIntentを使用しており、通常はParseやmobile backendのSDKがよしなに対応してくれているのですが、今回は２つのSDKが同時に存在しているため、mobile backend用のインテントなのにParseのSDKが処理してしまうという事象が発生します。これに対応するため、インテントを分配するための仕組みを追加します。

1. Android Studio のプロジェクトファイルツリーで、アプリのプロジェクトのjavaディレクトリのアプリのパッケージ名を右クリックする
2. `New > Java Class`を選択
3. 表示されたポップアップで、Nameは `GcmBroadcastDistributor`、Kindは `Class` としてOKを選択します
4. GcmBroadcastDistributor.javaが作成されるので、ファイルの内容を以下のように書き換えます

```java:GcmBroadcastDistributor.java
package your.packagename;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

import com.google.android.gms.iid.InstanceID;

public class GcmBroadcastDistributor extends com.nifty.cloud.mb.core.NCMBGcmReceiver {
  private static final String REGISTRATION_ACTION = "com.google.android.c2dm.intent.REGISTRATION";
  private static final String BUNDLE_KEY_OF_REGISTRATION_ID = "registration_id";
  private static final String BUNDLE_KEY_OF_NIFTY_PUSH_ID = "com.nifty.PushId";

  @Override
  public final void onReceive(Context context, Intent intent) {
    Bundle bundle = intent.getExtras();
    if (REGISTRATION_ACTION.equals(intent.getAction())) {
      String value = bundle.getString(BUNDLE_KEY_OF_REGISTRATION_ID);
      if (value != null) {
        InstanceID instanceId = InstanceID.getInstance(context);
        if (value.contains(instanceId.getId())) {
          // NCMB Installation
          super.onReceive(context, intent);
          abortBroadcast();
        } else {
          // Parse Installation
        }
      }
    } else {
      if (bundle.get(BUNDLE_KEY_OF_NIFTY_PUSH_ID) != null) {
        // NCMB Push
        super.onReceive(context, intent);
        abortBroadcast();
      } else {
        // Parse Push
      }
    }
  }
}
```

　`GcmBroadcastDistributor`は、mobile backendのNCMBGcmReceiverを継承して構築しています。
　mobile backend側で処理すべきインテントを受信した場合は`super.onReceive(context, intent);`でNCMBGcmReceiverへ処理を委譲してから該当インテントのブロードキャストを中断します。中断されるためParse側のBroadcastReceiverは反応しません。それ以外のインテントはスルーするので、Parse側のBroadcastReceiverがインテントを受け取って処理をします。

　判定方法は、それぞれ以下の通りです。

* REGISTRATION (端末登録)
    * mobile backendのSDKはInstanceID APIを使用して登録処理を行っているため、GCMより通知されるregistration_idのデータに登録リクエスト時に送ったInstance IDが含まれます。これが含まれている場合はmobile backend側で処理すべきものと判定しています。
    * ParseのSDKは旧来のGCM APIを使用して登録処理を行っているため、レスポンスにInstance IDが含まれません。Instance IDが含まれないものをParse側で処理すべきものと判定しています。
* RECEIVE (プッシュ通知受信)
    * mobile backendのプッシュ通知は通知中に`com.nifty.PushId`というデータを含みます。これが含まれている場合はmobile backend側で処理すべきものと判定しています。
    * 上記以外はParse側で処理すべきものとしています。

## (7) AndroidManifest.xmlに追記する

　(5)(6)で追加した処理が動作するようにAndroidManifest.xmlに定義を追記します。AndroidManifest.xmlの `</application>` タグの前に以下を追記してください。

```xml:AndroidManifest.xml
<!--ここから追記-->

  <!-- プッシュ通知からアプリを起動した際に表示されるアクティビティ -->
  <meta-data android:name="openPushStartActivity" android:value=".MainActivity" />

  <!-- プッシュ通知受信時の処理を担うServcie -->
  <service android:name="com.nifty.cloud.mb.core.NCMBGcmListenerService"
           android:exported="false">
    <intent-filter>
      <action android:name="com.google.android.c2dm.intent.RECEIVE" />
    </intent-filter>
  </service>

  <!-- registration_idの取得やプッシュ通知受信時のBroadcastIntentを扱うレシーバー -->
  <receiver android:name="your.packagename.GcmBroadcastDistributor"
            android:permission="com.google.android.c2dm.permission.SEND">
    <!-- priorityを100に設定することでParseのレシーバーより先に受け取るようにする -->
    <intent-filter android:priority="100">
      <action android:name="com.google.android.c2dm.intent.RECEIVE" />
      <action android:name="com.google.android.c2dm.intent.REGISTRATION" />

      <category android:name="your.packagename" />
    </intent-filter>
  </receiver>
<!-- ここまで追記 -->
</application>
```

## (8) 動作確認

　ビルドして動作確認をしてください。以下のポイントを全てクリアしていれば正しく実装出来ています。

* mobile backendのダッシュボード上でデータストアのinstallationクラスに端末情報が登録されている
* Parseのダッシュボード上でinstallationクラスを確認して、deviceTokenの値が異常値になっていない
* Parseのダッシュボードからプッシュ通知を送ると正しく受信できる
* mobile backendのダッシュボード上でAPIキーを設定し、プッシュ通知を送ると正しく受信できる


# 最後に

　Androidの場合、プッシュ通知関連の処理がBroadcastIntentで実現されているため、Parse.comとmobile backendのSDKを同居させるのに少しトリッキーな実装が必要となってしまいました。この方法は現行SDKの動作に基づいているので、SDKの仕様が変わると使えなくなるかもしれません。ご注意ください。



# 関連リンク
* [Qiita : Parse.comからNIFTY Cloud mobile backendへプッシュ通知を移行する際の注意点][link-1]
* [Qiita : Parse.comからNIFTY Cloud mobile backendへプッシュ通知を移行する（iOS編）][link-2]
* [GitHub : NIFTY Cloud mobile backend Android SDK][link-3]

[link-1]:http://qiita.com/ndyuya/items/513e1713e4024f7a924a
[link-2]:http://qiita.com/ndyuya/items/23fab5992cf23812daf3
[link-3]:https://github.com/NIFTYCloud-mbaas/ncmb_android/releases

[^1]: MainActivity.javaはアプリの最初に起動されるアクティビティの想定です。手元のプロジェクトに合わせて適宜読み替えてください。