View
8
Download
0
Category
Preview:
Citation preview
Xamarin アプリとプッシュ通知Nobuhiro Ito 2017/06/17 学生でもわかるXamarin勉強会
自己紹介
• 伊藤 伸裕 @iseebi • フェンリル株式会社 アプリケーション共同開発部 チーフエンジニア • 兼 新規事業部 BoltzEngine プロダクトオーナー
• 主にスマートフォンアプリの設計・開発を担当。最近はマネージメントも。 • iOS (Swift, Objective-C) / Android (Java) / Xamarin (C#) / Web (PHP)
名古屋でイベントやってます
• Mobile Act NAGOYA • フェンリル名古屋支社メンバーが主催 • 隔月くらいで開催中 (直近は昨日!!) • https://mobileactnagoya.connpass.com/
サマーインターン開催予定• プロジェクト研修で使われている課題で、実際の業務の流れを体験できます • エンジニア向け • 2017/08/28 - 09/01 : 大阪本社
• 2017/09/04 - 09/08 : 東京支社・島根支社・名古屋支社 • デザイナー向け • 2017/08/28 - 09/01 : 大阪本社・東京支社
• 詳細は来週公開予定 @fenrir_recruit をフォローお願いします。
プッシュ通知とは
• ユーザーがアプリから離れていても、端末・ブラウザへメッセージなどの通知を送る仕組みのこと。新着情報・更新情報などをユーザーに提供することができる。
• タイマー・ジオフェンスでアプリ単体で完結する「ローカルプッシュ」と サーバーからサービス提供者側が送信する「リモートプッシュ」がある • 「プッシュ通知」というとリモートプッシュのことを言ってる場合が多い。この発表でも基本的にリモートプッシュのことを指します。
プッシュ通知の重要性
• ユーザーに価値のある情報を即時に届けることができる
• アプリをユーザーに使ってもらう習慣づけをすることができる
• プッシュ通知を許可したアプリは起動回数や継続率が高くなる
参考: アプリユーザーをアクティブにする!専門家が教える「プッシュ通知」5つの真実と間違い。D2CRアプリセミナー http://appmarketinglabo.net/push-livepass/
プッシュ通知のしくみ
APNs / FCMサービス提供者の サーバー
アプリ
プッシュ通知のしくみ
APNs / FCMサービス提供者の サーバー
アプリ
トークン要求
プッシュ通知のしくみ
APNs / FCMサービス提供者の サーバー
アプリ
トークン要求
トークン返却
プッシュ通知のしくみ
APNs / FCMサービス提供者の サーバー
アプリ
トークン要求
トークン返却
トークン登録
プッシュ通知のしくみ
APNs / FCMサービス提供者の サーバー
アプリ
プッシュ通知のしくみ
APNs / FCMサービス提供者の サーバー
アプリ
メッセージ送信 (認証+トークン+メッセージ)
プッシュ通知のしくみ
APNs / FCMサービス提供者の サーバー
アプリ
メッセージ送信 (認証+トークン+メッセージ)
メッセージ配信
プッシュ通知のしくみ
APNs / FCMサービス提供者の サーバー
アプリ
メッセージ送信 (認証+トークン+メッセージ)
メッセージ配信
! メッセージが届きました
iOS でのプッシュ通知実装
Apple への登録• Apple Developer Program へ App ID を登録する必要がある • App ID の登録 • APNs 証明書の発行 • プロファイルの作成
App ID の登録
App ID の登録
App ID の登録
APNs 証明書の発行• App ID 登録後の画面に表示される • ビルドによって使う証明書が異なり、接続先も異なる • Debug ビルド時、Sandbox • Release ビルド時、Production
詳しくは• BoltzEngine サポートページをご覧ください • APNs を使う iOS アプリのビルドに必要な契約・各種証明書の準備をするにはhttps://www.fenrir-inc.com/jp/boltzengine/support/ios_certificates/
実装• OS バージョンごとに異なる (文献の参考時に注意!)
• iOS 7 までは、リモートプッシュの登録のみ (受信種別の登録もする) • UIApplication.SharedApplication.RegisterForRemoteNotificationTypes
• iOS 8 以降は、通知の許可とリモートプッシュの登録が分かれた • UIApplication.SharedApplication.RegisterUserNotificationSettings • UIApplication.SharedApplication.RegisterForRemoteNotification
• iOS 10 以降、UserNotification Framework に分離された • UNUserNotificationCenter.Current.RequestAuthorization • UIApplication.SharedApplication.RegisterForRemoteNotification
ということは• OSバージョンごとに使用する方法を使い分ける必要がある • 使っている OS で実装されていない API 叩くとクラッシュ • iOS 10 以降にできたら最高 • iOS 7 はさすがにもうないでしょう。 • iOS 9 と iOS 10 の共存は現段階ではしかたがない場面多そうUIDevice.CurrentDevice.CheckSystemVersion でチェックするほかなし
private async void RequestPushNotificationAuthorization(){ if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0)) { var result = await UNUserNotificationCenter.Current.RequestAuthorizationAsync( UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound); if (result.Item1) { UIApplication.SharedApplication.RegisterForRemoteNotifications(); } } else { var settings = UIUserNotificationSettings.GetSettingsForTypes( UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound, null); UIApplication.SharedApplication.RegisterUserNotificationSettings(settings); }}public override void DidRegisterUserNotificationSettings(UIApplication application, UIUserNotificationSettings notificationSettings){ UIApplication.SharedApplication.RegisterForRemoteNotifications();}
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken){ var tokenString = BitConverter.ToString(deviceToken.ToArray()).Replace("-", ""); Console.WriteLine(tokenString); // ダメ絶対 // tokenString = deviceToken.ToString().Replace(" ", "");}
• NSData.ToString は - (NSString *) description にマップされている
• このメソッドはデバッグ用で出力は不定とされている(将来変わる可能性も微レ存)
• 一方 BitConverter.ToString は AA-BB-CC-00-12-34… という形式であるとリファレンスで明示されている
これがプッシュトークンだ!
Android でのプッシュ通知実装
Firebase の登録• Firebase で Sender ID と Server API Key を作る • https://console.firebase.google.com/
Firebase の登録
Firebase の登録
Firebase の登録
Firebase の登録
Firebase の登録
Firebase の登録
NuGet パッケージの導入• 以下の NuGet パッケージを導入する • Xamarin.GooglePlayServices.Base • Xamarin.Firebase.Messaging
• Firebase の設定中に作成されたgoogle-services.json をプロジェクトに追加 ビルドアクションを GoogleServicesJson に変更 • 出てこなかったら VSMac 再起動
Google Play Service の判定public bool IsPlayServicesAvailable(){ int resultCode = GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(this); if (resultCode == ConnectionResult.Success) { return true; } if (GoogleApiAvailability.Instance.IsUserResolvableError(resultCode)) { // ユーザーの操作によって解決できる問題の場合 (Google Play 開発者サービスが古いなど) return false; } else { // Google Play 開発者サービスが入っていない端末の場合 return false; }}
トークンの取得button.Click += delegate{ if (IsPlayServicesAvailable()) { Log.Debug("PushNotificationSample", $"Push Token is {FirebaseInstanceId.Instance.Token} "); }}; これがプッシュトークンだ!
• 実際は IsPlayServiceAvailable の判定でユーザーに開発者サービスの更新を促したり、アプリを強制終了したりする
Instance ID Receiver の登録• Android のプッシュトークンは突然変わる場合がある • トークンの変更を受信するサービスをアプリに登録しておかなければならない • AndroidManifest に記載<application …> <receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" /> <receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND"> <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE" /> <action android:name="com.google.android.c2dm.intent.REGISTRATION" /> <category android:name="${applicationId}" /> </intent-filter> </receiver></application>
Instance ID Service の実装
[Service][IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]public class AppInstanceIdService : FirebaseInstanceIdService{ public override void OnTokenRefresh() { var refreshedToken = FirebaseInstanceId.Instance.Token; Log.Debug(Log.Debug("PushNotificationSample", $"Push Token is {FirebaseInstanceId.Instance.Token}"); }} プッシュトークンが更新されると通知される
通知の送信
ライブラリの選定• プッシュの API ってわりとややこしい。 サーバーサイドにどんな言語を使うかで変わってくる。
• C# を使うなら PushSharp という選択肢か。 • https://github.com/Redth/PushSharp
• 言語 + APNs / GCM で検索してみよう
• Firebase べったりになるなら、Firebase の管理画面でやるというのも一案
PushSharp を使った送信https://github.com/Redth/PushSharp#sample-usage
!
BoltzEngine で送ってみよう有償製品なんでちょっと申し訳ないですが…
そもそもBoltzEngineは• 超高速プッシュ通知エンジン • 秒間 3.5 万デバイス (国内最速クラス) • 大量・高速の配信要件に適合
• パッケージタイプ • プライベートクラウド・オンプレミスなどへの展開 • セキュリティポリシー的に ASP や Azure を選択できない場合
• スマートフォン向け (APNs/GCM) ウェブブラウザ向け の送信に対応
BoltzEngine への接続• BoltzEngine 本体は送信のみを担当。配信管理は別途用意します。 • 独自開発する場合は gRPC / HTTP / CSV などで送信可能
標準の配信管理機能標準の配信管理機能・リファレンス実装として BoltzMessenger を用意
BoltzEngine への接続• BoltzEngine 本体は送信のみを担当。配信管理は別途用意します。 • 独自開発する場合は gRPC / HTTP / CSV などで送信可能
BoltzEngine への接続• BoltzEngine 本体は送信のみを担当。配信管理は別途用意します。 • 独自開発する場合は gRPC / HTTP / CSV などで送信可能
gRPC• Google が公開した Google 内でも使われている RPC フレームワーク • Protocol Buffer から各言語向けのライブラリを生成するので、様々な言語から接続できる
• HTTP/2 が使われている
• BoltzEngine では 2016 年 6 月の 2.0 から対応
• それまでの Go の net/rpc に代わり標準 API となりつつある
• BoltzEngine への接続は Go, JavaScript, Ruby, C# で実績あり
C# で gRPC を使う
• proto ファイルの変換 • NuGet パッケージのインストール • コードの記述 • 今のところ Xamarin.Mac はダメそう。.NET Framework 4.6 がいいかと。
protoファイルの変換• BoltzEngine 添付の proto ファイルをまとめる • NuGet から Grpc.Tools をダウンロードする(NuGet から nupkg 落としてきて unzip…)
$ temp_dir=packages/Grpc.Tools.1.3.6/tmp $ curl_url=https://www.nuget.org/api/v2/package/Grpc.Tools/ $ mkdir -p $temp_dir && cd $temp_dir && curl -sL $curl_url > tmp.zip; unzip tmp.zip \ && cd .. && cp -r tmp/tools . && rm -rf tmp && cd ../.. $ chmod +x packages/Grpc.Tools.1.3.6/tools/macosx_x64/protoc $ chmod +x packages/Grpc.Tools.1.3.6/tools/macosx_x64/grpc_csharp_plugin
protoファイルの変換• 以下のようなコマンドで変換 • proto ディレクトリに入っているものを BoltzEngine.Rpc フォルダに展開
$ mkdir BoltzEngine.Rpc $ packages/Grpc.Tools.1.3.6/tools/macosx_x64/protoc \ --plugin=protoc-gen-grpc=packages/Grpc.Tools.1.3.6/tools/macosx_x64/grpc_csharp_plugin --csharp_out BoltzEngine.Rpc --grpc_out BoltzEngine.Rpc \ -Iprotos protos/*.proto
NuGet パッケージのインストール
• プロジェクトに以下の NuGet パッケージをインストール • Grpc • Google.Protobuf
• proto ファイルから変換した .cs ファイルも追加する
BoltzEngine での通知送信• Channel クラスのインスタンスを生成 • BoltzGatewayClient を生成
• Message を生成 • Send で送信 • 失敗情報・トークン変更情報を Stream から取得
var channel = new Channel("boltzengine.example.com:13009", ChannelCredentials.Insecure);var client = new BoltzGateway.BoltzGatewayClient(channel);
メッセージの作成 (ヘッダ)var cert = File.ReadAllText("cert.pem");var key = File.ReadAllText("key.pem");var message = new Message{ ApnsHeader = new BoltzEngine.Rpc.Apns.Header() { Address = "gateway.push.apple.com:2195", KeyPEMBlock = Google.Protobuf.ByteString.CopyFromUtf8(key), CertPEMBlock = Google.Protobuf.ByteString.CopyFromUtf8(cert), }, GcmHeader = new BoltzEngine.Rpc.Gcm.Header() { RequestURL = "https://fcm.googleapis.com/fcm/send", ServerKey = “AAAAw…”, SenderID = "825337918540", }, Priority = Priority.High, BandWidth = 0} ;
メッセージの作成 (ヘッダ)var cert = File.ReadAllText("cert.pem");var key = File.ReadAllText("key.pem");var message = new Message{ ApnsHeader = new BoltzEngine.Rpc.Apns.Header() { Address = "gateway.push.apple.com:2195", KeyPEMBlock = Google.Protobuf.ByteString.CopyFromUtf8(key), CertPEMBlock = Google.Protobuf.ByteString.CopyFromUtf8(cert), }, GcmHeader = new BoltzEngine.Rpc.Gcm.Header() { RequestURL = "https://fcm.googleapis.com/fcm/send", ServerKey = “AAAAw…”, SenderID = "825337918540", }, Priority = Priority.High, BandWidth = 0} ;
APNs 秘密鍵と証明書を読み込み
ByteString で指定
メッセージの作成 (ヘッダ)var cert = File.ReadAllText("cert.pem");var key = File.ReadAllText("key.pem");var message = new Message{ ApnsHeader = new BoltzEngine.Rpc.Apns.Header() { Address = "gateway.push.apple.com:2195", KeyPEMBlock = Google.Protobuf.ByteString.CopyFromUtf8(key), CertPEMBlock = Google.Protobuf.ByteString.CopyFromUtf8(cert), }, GcmHeader = new BoltzEngine.Rpc.Gcm.Header() { RequestURL = "https://fcm.googleapis.com/fcm/send", ServerKey = “AAAAw…”, SenderID = "825337918540", }, Priority = Priority.High, BandWidth = 0} ;
APNs のゲートウェイサーバー Sandbox の場合は gateway.sandbox.push.apple.com
メッセージの作成 (ヘッダ)var cert = File.ReadAllText("cert.pem");var key = File.ReadAllText("key.pem");var message = new Message{ ApnsHeader = new BoltzEngine.Rpc.Apns.Header() { Address = "gateway.push.apple.com:2195", KeyPEMBlock = Google.Protobuf.ByteString.CopyFromUtf8(key), CertPEMBlock = Google.Protobuf.ByteString.CopyFromUtf8(cert), }, GcmHeader = new BoltzEngine.Rpc.Gcm.Header() { RequestURL = "https://fcm.googleapis.com/fcm/send", ServerKey = “AAAAw…”, SenderID = "825337918540", }, Priority = Priority.High, BandWidth = 0} ;
FCM の API キーと送信者 ID を指定
メッセージの作成 (内容)
message.Payload = @"{""aps"":{""alert"":""Hello Hello""}}";message.Parameters = new BoltzEngine.Rpc.Gcm.Parameters();message.Parameters.Notification.Add("title", "Hello Hello");
• BoltzEngine gRPC は 1 つのメッセージで複数のサービスに送信可能 • iOS は Payload, Android は Parameters, WebPush は Body に指定
メッセージの作成 (送り先)
• 各プラットフォームのトークンの前に、数字を結合して渡す • BoltzEngine gRPC の仕様上必要な操作で、他のライブラリを使う時は不要 • iOS は 1, Android は 2, WebPush は 4
message.Tokens.Add("1" + “126ae202b2d51…");message.Tokens.Add("2" + “cTSHJ6XaN4Y:APA91bH…");
メッセージの送信var streamingCall = client.Send(message);while (await streamingCall.ResponseStream.MoveNext(CancellationToken.None)){ var ev = streamingCall.ResponseStream.Current; switch (ev.EventCase) { case Event.EventOneofCase.Failed: Console.WriteLine($"Failed: {ev.Failed.Token} reason: {ev.Failed.Kind} "); break; case Event.EventOneofCase.Renewed: Console.WriteLine($"Renewed: {ev.Renewed.RecentToken} to {ev.Renewed.LatestToken} "); break; }}
今回お話ししなかったこと
• サーバー上でのトークンの取り扱い • 無効・更新トークンの取り扱い • コンテンツ付きプッシュ • プッシュをトリガーしたデータ更新・バックグラウンド動作 • 他
プッシュ通知の仕組みを理解するとより活用できます
プッシュ通知を有効に利用して アプリをより魅力的にしましょう
参考
OSごとのプッシュ通知の差
iOS Android Web
許可要求 トークンの取得時に許可ダイアログ
デフォルトで許可 (トークンは必ず取れる)
トークンの取得時に許可ダイアログ
サーバーの認証・登録 ADPで証明書発行 Firebaseでキー発行 自分で暗号化キーを生成
通知の内容 フォーマットに決まりあり JSON形式 Key/Value で渡す 任意のテキスト
JSONを流すのが実用的
通知をトリガーとした動作 フラグを立てると可能 発動は不安定
可能 可能 端末の通知表示もアプリ側で実装
無効端末の処理 フィードバックサービスへ 定期的に見に行く 送信時に判明する 送信時に判明する
Recommended