diadia

興味があることをやってみる。自分のメモを残しておきます。

Android リモート通知機能について

通知を送信する方法

通知はfirebase コンソールを操作して送信する方法とwebアプリから送信する方法、そしてHTTPリクエストを直接投げて送信する方法がある。
通知のデバイストークンの有効性を確認をしたい等の場合にはfirebaseコンソールから送信してandroid端末の挙動を確認すれば良い。

fcmの基礎知識をまとめておく

メッセージには2種類存在する。

注意点

Android8以上だと通知チャネルを設定していない場合には通知が表示されない。したがって通知チャネルを一緒に送信する必要がある。

firebaseの管理コンソールからチャネルを設定して送信できる。また、Manifestファイルに記述するとデフォルトで通知チャネルを定めることもできるようだ。

Set up a Firebase Cloud Messaging client app on Android

まだ分かっていないこと

  • 通知ドロワーに表示される画像は、どのような画像が選択されているのか?
  • push通知の使用回数制限はあるのか?

分かったこと

  • まずアプリを起動していない状態でも通知を受け取ることができた。
  • firebaseの管理画面で送信したメッセージを削除したが、アンドロイド端末の通知は消えることがなかった。
  • FirebaseMessagingServiceを継承したクラスは自動的に起動されるので自分でサービスを起動する必要はない。

webアプリから通知を送る方法

参考

Firebase Admin SDKを使ったPush通知 - Qiita

全体像

webサーバーが以下のコードを実行すると、通知がスマホ端末にいく。 やることはpython(django)側とandroid側に分かれる。

python側で行うこと:

  1. android端末に設定されるデバイス(登録)トークンをwebアプリ側のデータベースに登録する
  2. ユーザーの挙動を検知しシグナルを発行する。シグナルを通じて通知を作成、送信する

android側で行うこと:

  1. android端末に設定されているデバイス(登録)トークンをサーバーに送信する
  2. 通知を受け取ったら通知をさばく
  3. android端末のデバイス(登録)トークンが変更された時当該トークンをサーバーに送信する

ここで重要なのはこのコードを実行するために、registration_tokenが必要であることである。また前提としてSDKの初期化が終わっている事がある。 その他に各ユーザーに通知を送るために各端末に対応するtokenをアプリケーションサーバーに登録しておく必要もある。

以下のコードはFCMサーバーと対話するために使われる。

# This registration token comes from the client FCM SDKs.
registration_token = 'YOUR_REGISTRATION_TOKEN'

# See documentation on defining a message payload.
message = messaging.Message(
    data={
        'score': '850',
        'time': '2:45',
    },
    token=registration_token,
)

# Send a message to the device corresponding to the provided
# registration token.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)

python側で行うこと

python側で行うことは

  1. android端末に設定されるデバイス(登録)トークンをwebアプリ側のデータベースに登録する
  2. ユーザーの挙動を検知しシグナルを発行する。シグナルを通じて通知を作成、送信する

である。

1. android端末に設定されるデバイス(登録)トークンをwebアプリ側のデータベースに登録する

class DeviceTokenDealAPIVeiw(APIView):

    def post(self, request, *args, **kwargs):
        """
        Android端末からトークンを受け取り、DeviceTokenオブジェクトとして保存する。
        ユーザーオブジェクトを作成した後(ユーザー登録した後)にDeviceTokenを送信したい。
        
        endpoint: /api/fcm/user/device_token/
        name: -
        """

        token   = getTokenFromHeader(self)
        userObj = getUserByToken(token)

        if userObj == None:
            return Response({"result":"fail"})

        deviceToken = request.data["deviceToken"]
        DeviceToken.objects.create(user=userObj, device_token=deviceToken)
        return Response({"result":"success"})


    def patch(self, request, *args, **kwargs):
        """
        Android端末のデバイストークンが更新されたら新たなデバイストークンを既存のDeviceTokenオブジェクトに反映させる。
        Android側でトークンが変更される場合は以下の状況であり、その際にはAndroid側でonNewTokenコールバックを実行し、このViewに連結させる。
        https://firebase.google.com/docs/cloud-messaging/android/client?hl=ja#sample-register
        
        endpoint: /api/fcm/user/device_token/
        name: -
        """

        token   = getTokenFromHeader(self)
        userObj = getUserByToken(token)

        if userObj == None:
            return Response({"result":"fail"})

        deviceToken = request.data["deviceToken"]
        deviceTokenObj = DeviceToken.objects.get(user=userObj)
        deviceTokenObj.device_token = deviceToken
        deviceTokenObj.save()
        return Response({"result":"success"})

2. ユーザーの挙動を検知しシグナルを発行する。シグナルを通じて通知を作成、送信する

通知を作成、送信する方法には2つの方法がある。 自分で通知を作成、送信する方法と通知の作成送信を担うadmin SDKを使った送信する方法である。
後者の場合は当然admin SDKをサーバーにインストールする必要がある。 私はadmin SDKを使って実装した。

SDK の追加方法を示す資料

$ sudo pip install firebase-admin

ドキュメント

通知を作成、送信
以下のような通知を作成送信するものを作っておき、何らかのイベントを検知したらシグナルを通じて通知を作成、送信すれば良い。

import firebase_admin
from firebase_admin import credentials
from firebase_admin import messaging
import os
from config.settings.base import BASE_DIR
from api.models import DeviceToken

FILE_NAME = "Hoge.json"  



class FireBaseMassagingDeal(object):

    '''使用例
    fcmd_obj = FireBaseMassagingDeal()
    deviceToken = fcmd_obj.getDeviceToken(userObj)
    fcmd_obj.deviceToken = deviceToken
    notification = fcmd_obj.makeNotification(strTitle="タイトルです", strBody="通知内容です")
    fcmd_obj.sendNotificaion()
    '''


    def __init__(self):
        self.deviceToken  = None
        self.notification = None

    def initFirebaseAdminSDK(self):
        """
        FirebaseAdminSDKの初期化を実施する
        """
        #FILE_PATH = os.path.join(BASE_DIR, "/config/settings/", FILE_NAME)
        FILE_PATH = BASE_DIR + os.path.join("/config/settings/", FILE_NAME)
        print(FILE_PATH)
        cred = credentials.Certificate(FILE_PATH)
        firebase_admin.initialize_app(cred)

    def getDeviceToken(self, userObj):
        """機能
        DjangoのモデルからdeviceTokenを取得する

        Args:
            userObj: django.contrib.auth.models.Userオブジェクト

        Returns:
            str: deviceTokenの文字列を返す
        """

        try:
            deviceToken = DeviceToken.objects.get(user=userObj)
        except:
            deviceToken = None
        return deviceToken

    def makeNotification(self, strTitle=None, strBody=None):
        """
        Notificationオブジェクトを生成する。
        
        Args:
            strTitle: str
            strBody : str

        """
        if strTitle == None:
            strTitle = 'test server'
        if strBody == None:
            strBody = 'test server message'

        notification=messaging.Notification(
            title = strTitle,
            body = strBody,
        )
        return notification

    def sendNotificaion(self):

        messaging.Message(
            notification = self.notification,
            token = self.deviceToken
            )

android側で行うこと

  1. firebaseのプロジェクトとAndroidアプリをリンクさせる
  2. android端末に設定されているデバイス(登録)トークンをサーバーに送信する
  3. 通知を受け取ったら通知をさばく
  4. android端末のデバイス(登録)トークンが変更された時当該トークンをサーバーに送信する

1.firebaseのプロジェクトとAndroidアプリをリンクさせる

Android Studio内でリンクさせる方法を採用する。
Android StudioのToolメニューからFirebaseを選択する。
表示される中からCloud Messagingを選択する。
表示されたステップに従う。 Connect your app to Firebaseでは、firebaseで予め作成していたプロジェクトを選択しリンクさせる。 Add FCM to your appでは、表示されるモジュールをprojectレベル、moduleレベルのgradleに追加する。 自分の場合projectレベルでは以下を追加する。

classpath 'com.google.gms:google-services:4.2.0'

moduleレベルで以下を追加する事になった。

apply plugin: 'com.google.gms.google-services'
implementation 'com.google.firebase:firebase-messaging:17.3.4'

これらを追加してSync Nowを実行したがエラーが出てしまった。kotlinの環境で起きる可能性があるようで以下も追加するとエラーを解消することができた。

androidx.fragment:fragment-ktx:1.2.2

I had the same problem in a Kotlin project here, the solution was to add implementation 'androidx.fragment:fragment-ktx:1.2.2' dependency to my module's build.gradle file.

java - ERROR: In project 'app' a resolved Google Play services library dependency depends on another at an exact version while iintegrating onesignal - Stack Overflow

2. android端末に設定されているデバイス(登録)トークンをサーバーに送信する

android端末に登録されている通知のためのトークン(デバイス登録トークン)は、アプリをはじめて起動した時に生成される。このトークンが変更される状況は存在する。 このトークンをアプリケーションサーバーに送信するタイミングを調べたら、毎度アプリケーションを立ち上げた時に送信するのが良いようだ。

FirebaseInstanceId.getInstance().instanceId
        .addOnCompleteListener(OnCompleteListener { task ->
            if (!task.isSuccessful) {
                Log.w(TAG, "getInstanceId failed", task.exception)
                return@OnCompleteListener
            }

            // Get new Instance ID token
            val token = task.result?.token

            // Log and toast
            val msg = getString(R.string.msg_token_fmt, token)
            Log.d(TAG, msg)
            Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
        })

Android で Firebase Cloud Messaging クライアント アプリを設定する