Firebase Cloud Messagingを用いて開発した際、躓いたポイントだったので備忘録として書きます。
Push通知受信後、バックグラウンドで処理を行う
Remote notificationでは、Push通知を送信する際に、送信側リクエストボディに
"content_available" : true
を付与することで、バックグラウンド状態でPush通知を受信した際にdidReceiveRemoteNotification
メソッドが呼び出されバックグラウンドで最大30秒程度処理を行うことができます。
30秒程度と記述しているのは、OS側で処理時間を短くする場合があるためです。そのため本メソッドでは、重い処理を行わずアプリがフォアグラウンドになった際に情報を更新するためのトリガー実行のフラグを立てるような簡単な処理を行うことが適しています。
iOS13.0 ~ iOS13.3.1までのiOSでは、動かない
iOS11,iOS12でのテスト時にアプリがバックグラウンドになっていても、didReceiveRemoteNotification
メソッドが正しく呼び出すことが出来ました。
しかし、テストを進める中でiOS13.0〜iOS13.3.1までのOSでバックグラウンド状態で、didReceiveRemoteNotification
メソッドが呼び出されないことがわかりました。
アプリをバックグラウンドにした直後にFCMから通知を送ると稀にdidReceiveRemoteNotification
メソッドが呼び出されることもありログを追跡した結果、下記のエラーが表示前であればdidReceiveRemoteNotification
メソッドを正しく呼び出すことができることがわかりました。
Can’t end BackgroundTask: no background task exists with identifier 1 (0x1), or it may have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug.
本エラー自体は、iOS13.4以降で解消されておりiOS13.4.1をインストールした実機に対して通知を送付して確認しましたが、アプリバックグラウンド時に通知受信後にdidReceiveRemoteNotification
メソッドが正しく呼び出されることを確認しています。
呼び出せないことで生じる問題
通知受信後、ユーザーがアプリアイコンをタップまたは、マルチタスク画面から起動中のアプリを選択し、フォアグラウンド状態に戻したときに通知を受け取ったフラグが取得できないという問題が生じます。
これは、バックグラウンド時にdidReceiveRemoteNotification
メソッドが呼び出されないため通知に含まれるUserInfoの情報から更新する情報を取得したり受信フラグをアプリ側で保持する処理が行えないため生じる問題になります。
iOS13.4以降では本問題は、解決しているもののiOS13~iOS13.3.1のそれぞれのマイナーバージョンのユーザーはまだ多く残っていると考えられるため、何らかのフォローを行う必要があります。
代替手段
バックグラウンド時に通知受信時に受信フラグを立てるのではなく、アプリがアクティブに切り替わったタイミングで受信済みの通知から受信フラグを立てるような方法で今回解決に至りました。
func applicationDidBecomeActive(_ application: UIApplication) {
UNUserNotificationCenter.current().getDeliveredNotifications(
completionHandler: { deliveredNotifications in
deliveredNotifications.forEach({ notification in
let userInfo = notification.request.content.userInfo
// 以下で、配信済みの通知のUserInfoをもとに
// 各通知に対しての処理を行う
})
})
}
アプリがアクティブになる際にAppDelegate
のapplicationDidBecomeActive
呼び出されます。
applicationDidBecomeActive
メソッド内で、通知センターに表示されている通知の一覧を取得するUNUserNotificationCenter
のgetDeliveredNotifications
メソッドを呼びます。
getDeliveredNotifications
は非同期で実行され処理完了後、handlerの引数にUNNotificationの配列が渡されるので、取得したUNNotificationの中から処理をUserInfoを抽出し、受信済みの各通知に対しての処理を行うことで受信フラグを立てることでアプリアイコン経由での起動でも取りこぼしを少なくすることが出来ています。
代替手段の問題点
本来、didReceiveRemoteNotification
はアプリがバックグラウンド時であっても通知をもとにアプリのステータスをアプリがバックグラウンド時でも変更できるものです。アプリがバックグラウンドの状態でもステータスを切り替えることで、ユーザーが次にアプリをフォアグラウンドにしたときに直ぐにユーザーに対してイベントを伝えることが可能でした。
今回の代替手段では、アプリがアクティブになったときに配信済みの通知から何らかの処理を介してアプリのステータスを変更し、ユーザーにイベントを伝えるまでを行うため、処理内容によっては起動して直ぐにユーザーにイベントを伝えることができず若干のタイムラグとなってしまうことが問題点だと感じています。
しかし、現状これ以外の打開策が検討した以外では思いつかず、ユーザーへのフォローも考えた結果、本代替手段による対応が良いと判断したため採用することしました。
一番いいのはiOSの動きが、想定通りであることなのですが...
おわりに
ちょっとトリッキーなやり方ではありますが、ユーザーに対して最大の価値を提供するために選んだ1つの手段として参考にしていただければ幸いです。
調べた中で誤った知識などもあるかもしれません。もしよければ指摘いただけますと幸いです。