プッシュ通知管理(Android)

サービスアカウントを設定する

AndroidはFirebase Cloud Messaging(FCM)にメッセージを送信して通知を行います。

通知を行うにはgoogle-services.jsonとサービスアカウント秘密鍵が必要です。google-services.json、サービスアカウント秘密鍵の作成はFirebaseで行います。

Firebaseのコンソール画面open in new windowに移動し、「プロジェクトを追加」よりプロジェクトを作成してください。

プロジェクト作成後、左上の歯車マーク→「プロジェクトの設定」→「Cloud Messaging」タブを開きます。

表示された画面で、Firebase Cloud Messaging API(V1)が有効になっていることを確認してください。(有効になっていない場合は、Google Cloud コンソールでFirebase Cloud Messaging APIを有効にします)

google-services.jsonを作成するには、アプリの作成が必要です。

「プロジェクトの設定」→「全般」タブを開き、「マイアプリ」→「アプリを追加」をクリックしてください。

Androidを選択します。

Androidパッケージ名を入力して、「アプリを登録」をクリックします。google-services.jsonがダウンロードできるようになるので、保存してください。

続いて、「サービス アカウント」タブを開き、「Firebase Admin SDK」→「新しい秘密鍵の生成」をクリックして、秘密鍵をダウンロードします。

最後に、プッシュ通知をBaaS@rakuzaから送信するために、サービスアカウントの設定を行います。

管理画面の「プッシュ通知管理」→「プッシュ通知環境設定」を開きます。

「Androidプッシュ通知設定」→「API」は「FCM HTTP v1 API」を選択します。

続いて、「Androidプッシュ通知設定」→「サービスアカウント秘密鍵」にて、先ほど作成したFirebaseプロジェクトのサービスアカウント秘密鍵を選択して、「更新」ボタンをクリックします。

注意

「Cloud Messaging API(レガシー)」を使用する場合、「API」は「FCM Legacy API」を選択して、APIキーを入力してください。

Firebaseを追加する

Firebase SDKをプロジェクトに追加します。

まず、サービスアカウントを設定するで作成したgoogle-services.jsonをappディレクトリの下にコピーします。

続いて、プロジェクト直下のbuild.gradleに以下を追記してください。

buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        // ...
        classpath 'com.google.gms:google-services:4.3.10'
    }
}







 


appディレクトリのbuild.gradleに以下を追記してください。

plugins {
    // ...
    id 'com.google.gms.google-services'
}
// ...
dependencies {
    // ...
    implementation 'com.google.firebase:firebase-messaging:23.0.0'
}


 




 

デバイストークンを登録する

デバイストークンを取得してBaaS@rakuzaに登録します。デバイストークンをBaaS@rakuzaに登録することで、管理画面からデバイストークンに紐づく端末にプッシュ通知を送信することができます。

デバイストークンの取得はFirebaseMessaging.getInstance().getToken()で行います。

取得したデバイストークンはregistPushDeviceTokenメソッドでBaaS@rakuzaに登録してください。

// FCMからデバイストークンを取得
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
    if (!task.isSuccessful) {
        Log.w(TAG, "トークンの取得に失敗しました。", task.exception)
        return@OnCompleteListener
    }

    val userAccessToken = "xxx"
    val token = task.result
    
    // 取得したデバイストークンをBaaS@rakuzaに登録
    RKZClient.getInstance().registPushDeviceToken(userAccessToken, token) { statusCode, rkzResponseStatus ->
        if (rkzResponseStatus.isSuccess) {
            // 成功時
            Log.d(TAG, "登録完了")
        } else {
            // 失敗
            Log.e(TAG, "statusCode: ${rkzResponseStatus.statusCode}")
            Log.e(TAG, "message: ${rkzResponseStatus.message}")
        }
    }
})
// FCMからデバイストークンを取得
FirebaseMessaging.getInstance().getToken().addOnCompleteListener(new OnCompleteListener<String>() {
    @Override
    public void onComplete(@NonNull Task<String> task) {
        if (!task.isSuccessful()) {
            Log.w(TAG, "トークンの取得に失敗しました。", task.getException());
            return;
        }

        String userAccessToken = "xxx";
        String token = task.getResult();

        // 取得したデバイストークンをBaaS@rakuzaに登録
        RKZClient.getInstance().registPushDeviceToken(userAccessToken, token, new OnRegistPushDeviceTokenListener() {
            @Override
            public void onRegistPushDeviceToken(String statusCode, RKZResponseStatus rkzResponseStatus) {
                if (rkzResponseStatus.isSuccess()) {
                    // 成功時
                    Log.d(TAG, "登録完了");
                } else {
                    // 失敗
                    Log.e(TAG, "statusCode: " + rkzResponseStatus.getStatusCode());
                    Log.e(TAG, "message: " + rkzResponseStatus.getMessage());
                }
            }
        });
    }
});

v3.3.0から、registUserメソッド、editUserメソッドでもデバイストークンを登録できるようになりました。

val user = User()
user.userName = "People Taro"
user.attributes = mapOf(
    "push_device_token" to "発行したデバイストークン", // プッシュ通知を送りたい場合に指定
    "smartphonesb_cd" to "0001"  // プッシュ通知を送りたい場合に指定
)

RKZClient.getInstance().registUser(user) { newUser, rkzResponseStatus ->
    if (rkzResponseStatus.isSuccess) {
        // 成功時
        Log.d(TAG, "userNo: ${newUser.userNo}") // ユーザーを一意に識別する顧客番号
        Log.d(TAG, "userAccessToken: ${newUser.userAccessToken}") // ユーザーアクセストークン
        Log.d(TAG, "push_device_token: ${newUser.attributes["push_device_token"]}")
    } else {
        // 失敗
        Log.e(TAG, "statusCode: ${rkzResponseStatus.statusCode}")
        Log.e(TAG, "message: ${rkzResponseStatus.message}")
    }
}
User user = new User();
user.setUserName("People Taro");
user.setAttributesValue("push_device_token", "発行したデバイストークン"); // プッシュ通知を送りたい場合に指定
user.setAttributesValue("smartphonesb_cd", "0001"); // プッシュ通知を送りたい場合に指定

RKZClient.getInstance().registUser(user, new OnGetUserListener() {
    @Override
    public void onGetUser(User user, RKZResponseStatus rkzResponseStatus) {
        if (rkzResponseStatus.isSuccess()) {
            // 成功時
            Log.d(TAG, "userNo: " + user.getUserNo()); // ユーザーを一意に識別する顧客番号
            Log.d(TAG, "userAccessToken: " + user.getUserAccessToken()); // ユーザーアクセストークン
            Log.d(TAG, "userName: " + user.getUserName());
            Log.d(TAG, "push_device_token: " + user.getAttributesValueString("push_device_token"));
        } else {
            // 失敗
            Log.e(TAG, "statusCode: " + rkzResponseStatus.getStatusCode());
            Log.e(TAG, "message: " + rkzResponseStatus.getMessage());
        }
    }
});

プッシュ通知を送信する

プッシュ通知を送信するを参照してください。

受信したプッシュ通知を表示する

FCMには通知メッセージopen in new windowデータメッセージopen in new windowの2種類があります。

通知メッセージはFirebase SDKによって自動で通知が表示されます。

対して、データメッセージは自動で通知が表示されないため、通知を受け取った際に通知を表示する処理を実装する必要があります。

どちらの種類でプッシュ通知を送信するかは、「プッシュ通知管理」→「プッシュ通知環境設定」を開いて、「Androidプッシュ通知設定」→「メッセージタイプ」で選択することができます。

データメッセージを選択していて通知を表示したい場合は、通知を受け取るサービスを作成します。サービスはFirebaseMessagingServiceを継承して作成します。

class MyFirebaseMessagingService : FirebaseMessagingService() {

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        if (remoteMessage.data.isNotEmpty()) {
            // データペイロードがある場合、通知を表示する
            sendNotification(remoteMessage.data)
        }
    }

    private fun sendNotification(data: Map<String, String>) {
        val message = data["message"] // 通知メッセージ

        val intent = Intent(this, MainActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)

        val channelId = getString(R.string.default_notification_channel_id)
        val notificationBuilder = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.drawable.ic_stat_ic_notification) // 通知アイコン
            .setContentText(message)
            .setAutoCancel(true)
            .setContentIntent(pendingIntent)

        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // Android 8以降は通知チャンネルを作成する必要がある
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, "一般的な通知", NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }

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

    companion object {
        private const val TAG = "MyFirebaseMessagingService"
    }
}
public class MyFirebaseMessagingService extends FirebaseMessagingService {

    private final static String TAG = "MyFirebaseMessagingService";

    @Override
    public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
        if (!remoteMessage.getData().isEmpty()) {
            // データペイロードがある場合、通知を表示する
            sendNotification(remoteMessage.getData());
        }
    }

    private void sendNotification(Map<String, String> data) {
        String message = data.get("message"); // 通知メッセージ

        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);

        String channelId = getString(R.string.default_notification_channel_id);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.drawable.ic_stat_ic_notification) // 通知アイコン
            .setContentText(message)
            .setAutoCancel(true)
            .setContentIntent(pendingIntent);

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

        // Android 8以降は通知チャンネルを作成する必要がある
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId, "一般的な通知", NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        }

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

作成したサービスはAndroidManifest.xmlに登録します。

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

これでデータメッセージの場合も通知を表示できるようになります。

注意

メッセージタイプがデータメッセージの場合、タイトルとメッセージの一部文字がHTMLエンティティに変換されます。変換されるのは以下の文字です。

  • &(アンパサンド)→&amp;
  • "(ダブルクォート)→&quot;
  • '(シングルクォート)→&#039;
  • <(小なり)→&lt;
  • >(大なり)→&gt;

これらの文字を本来の表現形式で通知に表示したい場合は、HtmlCompat.fromHtmlopen in new windowで変換を行ってください。

.setContentText(if (message != null) HtmlCompat.fromHtml(message, HtmlCompat.FROM_HTML_MODE_LEGACY) else null)
.setContentText(message != null ? HtmlCompat.fromHtml(message, HtmlCompat.FROM_HTML_MODE_LEGACY) : null)

受信したプッシュ通知を処理する

受信したプッシュ通知の情報をハンドリングすることができます。

BaaS@rakuzaではデータペイロードとして以下の項目を送信しています。データペイロードはプッシュ通知を受信した時や、通知バナーをタップした時に参照することができます。

項目名概要備考
push_noプッシュ番号プッシュ通知の予約単位で発番されるキー。開封率の計測で使用します。
messageメッセージ
news_idお知らせID※お知らせをプッシュ通知した場合のみ送信
news_tenant_idお知らせを配信しているテナントID※お知らせをプッシュ通知した場合のみ送信
urlURL
custom_varsカスタム情報

プッシュ通知を受信すると、受信したプッシュ通知を表示するで作成したMyFirebaseMessagingServiceonMessageReceivedメソッドが呼び出されます。

データメッセージはremoteMessage.getData()から取得することができます。

override fun onMessageReceived(remoteMessage: RemoteMessage) {
    if (remoteMessage.data.isNotEmpty()) {
        remoteMessage.data["news_id"]?.let {
            Log.d(TAG, "newsId: " + it)
        }
    }
}
@Override
public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
    if (!remoteMessage.getData().isEmpty()) {
        String newsId = remoteMessage.getData().get("news_id");
        if (newsId != null) {
            Log.d(TAG, "newsId: " + newsId);   
        }
    }
}

注意

メッセージタイプが通知メッセージの場合、アプリの状態がバックグラウンドの時はonMessageReceivedが呼び出されません。


メッセージタイプが通知メッセージの場合、通知バナーをタップするとランチャーアクティビティが起動します。その際、インテントの追加部分からデータペイロードを取得することができます。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // インテントの追加部分からお知らせIDを取得
    val newsId = intent?.getStringExtra("news_id")
    Log.d(TAG, "newsId: " + newsId)
    
    // ...
}
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // インテントの追加部分からお知らせIDを取得
    String newsId = getIntent().getStringExtra("news_id");
    Log.d(TAG, "newsId: " + newsId);

    // ...
}

メッセージタイプがデータメッセージの場合、通知を表示する時にインテントにデータペイロードを設定します。

class MyFirebaseMessagingService : FirebaseMessagingService() {
    // ...
    private fun sendNotification(data: Map<String, String>) {
        val message = data["message"] // 通知メッセージ

        val intent = Intent(this, MainActivity::class.java)
        data.forEach { intent.putExtra(it.key, it.value) } // データペイロードをインテントに追加

        val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
        
        // ...
    }
}






 






public class MyFirebaseMessagingService extends FirebaseMessagingService {
    // ...
    private void sendNotification(Map<String, String> data) {
        String message = data.get("message"); // 通知メッセージ

        Intent intent = new Intent(this, MainActivity.class);
        for (Map.Entry<String, String> entry : data.entrySet()) {
            intent.putExtra(entry.getKey(), entry.getValue()); // データペイロードをインテントに追加
        }

        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);

        // ...
    }
}






 
 
 






すると、通知バナーをタップした時にインテントの追加部分からデータペイロードを取得することができます。

// アクティビティが再利用される場合は、onNewIntentが呼び出されます
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // インテントの追加部分からお知らせIDを取得
    val newsId = intent?.getStringExtra("news_id")
    Log.d(TAG, "newsId: " + newsId)
    
    // ...
}
// アクティビティが再利用される場合は、onNewIntentが呼び出されます
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // インテントの追加部分からお知らせIDを取得
    String newsId = getIntent().getStringExtra("news_id");
    Log.d(TAG, "newsId: " + newsId);

    // ...
}

ヒント

Android固有のより詳しい情報については、Android アプリでメッセージを受信するopen in new windowを参照してください。

開封率を計測する

開封率を計測するには、プッシュ通知を受信した際、アプリ側に計測用のコードを埋め込む必要があります。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val userAccessToken = "xxx"
    val pushNo = intent?.getStringExtra("push_no")
    if (pushNo != null) {
        RKZClient.getInstance().openPush(userAccessToken, pushNo.toInt()) { statusCode, rkzResponseStatus ->
            if (rkzResponseStatus.isSuccess) {
                // 成功時
                Log.d(TAG, "計測完了")
            } else {
                // 失敗
                Log.e(TAG, "statusCode: ${rkzResponseStatus.statusCode}")
                Log.e(TAG, "message: ${rkzResponseStatus.message}")
            }
        }
    }

    // ...
}
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    String userAccessToken = "xxx";
    String pushNo = getIntent().getStringExtra("push_no");
    if (pushNo != null) {
        RKZClient.getInstance().openPush(userAccessToken, Integer.valueOf(pushNo), new OnOpenPushListener() {
            @Override
            public void onOpenPush(String statusCode, RKZResponseStatus rkzResponseStatus) {
                if (rkzResponseStatus.isSuccess()) {
                    // 成功時
                    Log.d(TAG, "計測完了");
                } else {
                    // 失敗
                    Log.e(TAG, "statusCode: " + rkzResponseStatus.getStatusCode());
                    Log.e(TAG, "message: " + rkzResponseStatus.getMessage());
                }
            }
        });
    }

    // ...
}

計測した開封率は管理画面の「プッシュ通知」→「プッシュ通知履歴」→「開封件数 / 配信済件数」から確認することができます。

デバイストークンをクリアする

ユーザーがログアウトするなど、デバイストークンが不要になった場合はクリアすることができます。

デバイストークンのクリアはclearPushDeviceTokenメソッドで行います。

val userAccessToken = "xxx"

RKZClient.getInstance().clearPushDeviceToken(userAccessToken) { statusCode, rkzResponseStatus ->
    if (rkzResponseStatus.isSuccess) {
        // 成功時
        Log.d(TAG, "クリア完了")
    } else {
        // 失敗
        Log.e(TAG, "statusCode: ${rkzResponseStatus.statusCode}")
        Log.e(TAG, "message: ${rkzResponseStatus.message}")
    }
}
String userAccessToken = "xxx";

RKZClient.getInstance().clearPushDeviceToken(userAccessToken, new OnClearPushDeviceTokenListener() {
    @Override
    public void onClearPushDeviceToken(String statusCode, RKZResponseStatus rkzResponseStatus) {
        if (rkzResponseStatus.isSuccess()) {
            // 成功時
            Log.d(TAG, "クリア完了");
        } else {
            // 失敗
            Log.e(TAG, "statusCode: " + rkzResponseStatus.getStatusCode());
            Log.e(TAG, "message: " + rkzResponseStatus.getMessage());
        }
    }
});