Firebase Cloud Messaging(FCM)のPUSH通知をトリガーに、スリープ状態から復帰する
Androidの場合、スリープ状態でPUSH通知を受けると、音だけ再生されて画面は真っ暗。
それだと気づけないので、LINEとかのPUSH通知とかは、PowerManagerで電源をONにした後にPUSH通知を作って表示したりしてます。
FCMで普通にやるとPUSH通知の受信から制御できないので、
どうやったらできるようになるのか、FCMの通知の仕組みから見ていきます。
FCMには通知が2種類存在します。
通知 | 通知の表示 | 通知の受信を制御 | 通知のタップを制御 |
---|---|---|---|
通知メッセージ | Auto | X | ◯ |
データメッセージ | Manual | ◯ | ◯ |
凄くざっくりした感じですが、簡単に通知を表示するなら「通知メッセージ」を使って、
通知の受信をハンドリングしたり、通知の表示をカスタマイズしたい場合は「データメッセージ」を使います。
今回は、受信をハンドリングして電源をONにしたいので、「通知メッセージ」ではなく「データメッセージ」を利用します。
2種類の通知はAPIを叩く際に渡すJSONの構造によって決まります。
通知メッセージの場合は、 notification
に通知内容を設定します。(dataも設定可能です)
{ "to" : "axd2Wedbd2Wed2WBed9ebVeewdbd2WedbWq1", "notification" : { "body" : "hello", "title" : "greeting" }, "data": { sound: 'ping' } }
データメッセージの場合は data
のみに通知内容を設定します。
{ "to" : "axd2Wedbd2Wed2WBed9ebVeewdbd2WedbWq1", "data" : { "body" : "hello", "title" : "greeting" } }
今回は、データメッセージを使いたいので、dataのみに値を設定してFCMのAPIを叩いてPUSH通知を送信します。
ここまでで、サーバー側の仕事は完了です。
後は、アプリ側でデータメッセージを受けて処理するだけです。
仕事上、最近、ReactNativeを使う機会が多いので、RNのコード例です。
import { AppRegistry } from "react-native"; import Wakeful from "react-native-wakeful"; import App from "./App"; AppRegistry.registerComponent("app", () => App); AppRegistry.registerHeadlessTask( "RNFirebaseBackgroundMessage", () => async message => { Wakeful.acquire(); // 電源をON return Promise.resolve(); } );
PUSH通知を作る所までコード例を書いてないですが、RNでPUSH通知を作るか、
AppRegistry.registerHeadlessTask
せずに、Android側でRNFirebaseMessagingServiceを継承して、
onMessageReceivedでPowerManagerで電源入れて、PUSH通知つくることまでやっちゃっても良い気がします。
React: Componentをreconstructする。
Componentをconstructorから再実行させたい場合、keyを変えてやれば良い。
(ReactNative)
<FlatList key={tournament.roundName} ...
FlatListの initialScrollIndex
は、リストアイテムの表示後、1回しかスクロールされない。
リストアイテムを更新した時も再度、スクロール位置を決めたい場合は、renderし直すのではなく、Component自体を作り直す必要があります。
FlatListに関しては、 scrollToIndex
っていうメソッドが生えてますが、実行タイミングによっては、
スクロールが中途半端になってしまう事と、 initialScrollIndex
の方が無駄なリストアイテムの描画が減る。
「やさしいLispの作り方」を読んで言語の作り方を学ぶ
「やさしいLispの作り方」はC言語で簡単なLispの処理系を実装して、REPLで動かすまでが書いてあります。
低レイヤーの知識がまったくない僕にとっては学びが多かった。
やさしいLispの作り方: C言語で作るミニミニLisp処理系
- 作者: 笹川 賢一
- 出版社/メーカー: 笹川 賢一
- 発売日: 2016/01/24
- メディア: Kindle版
- この商品を含むブログ (1件) を見る
REPLって何?
インタラクティブにコードを実行できる環境ぐらいの認識でしかなかった。
まあ、間違いじゃないんだけど。
「Read-eval-print-loop」って、それはそうなんですが、本当に、「標準出力から読み取って、評価して結果を標準出力に返して、それをループする」だけのプログラムだって事に、実際に書いてみてより正確に理解できた気がします。
Lispだから良い
(+ 1 2)
Lispの構文(S式)はかなり単純。
S式じゃなければ、構文解析するだけでもかなり大変です。
構文解析よりも、標準出力から読み取ったコードをどうやってメモリに乗せて評価してくのかを見たいので、S式が最適だと思いました。
Heap(ヒープ)とStack(スタック)
メモリ管理の方法として、ヒープとスタックと呼ばれる領域がある事は、知ってるぐらいの知識でした。
本書では、構文解析した結果や、ユーザーが定義した変数、関数定義などはヒープ領域に格納して、GCに必要な変数はスタック領域に格納していました。
一般的にユーザーがアクセスできる領域がヒープで、スタックはアクセスできない領域だと理解していいのかな?
ヒープとスタック | 学校では教えてくれないこと | [技術コラム集]組込みの門 | ユークエスト株式会社
そしてただのC言語の配列として表現されてるだけなので難しくないです。
標準出力から読み取ってメモリに乗せる(Read)
評価できるように、標準出力の内容をHeap領域に乗せていきます。
メモリに乗せる時は、文字列をプログラムが認識できる最小単位に分解(トークナイズ)します。
ただ、ヒープ領域に適当に乗せていくと、順序や関連がわからなくなってしまうので、
数珠繋ぎに前後のアドレス(配列の添字)を記憶できる構造体に値を入れてヒープ領域(配列)に詰めて行きます。
まず、シンボルや数値を左から順番に詰めて行き、再帰的に鎖を繋いで行く感じです。(CARが前でCDRが後の繋がり)
(図:ヒープ領域をDUMPしたもの)
S式を評価する(Eval)
Readすることで、ヒープ領域に評価できるS式がメモリに乗ったので、後は評価するだけです。
eval関数には、最後にreadしたアドレスを与えることで、鎖の初めから評価していきます。
鎖の先頭がシンボル(+)なので、C言語で書いた可変長引数を受け取って足し算する関数 int f_plus(int arglist)
を呼び出します。
(シンボルと関数の紐付けはREPL起動時にヒープ領域に初期化しています。)
あとは、CDRにアクセスして残りの引数を int f_plus(int arglist)
に渡して計算結果をヒープ領域に追加します。
0000148 FRE NUM 0000000 0000000 0000024 (null)
標準出力に表示する(Print)
PrintはReadの逆で、ヒープ領域にある結果を文字列に復元します。
Evalで構築された鎖の先頭アドレスを受け取り、終端まで文字列化していくだけです。
本記事の例では、 0000148
が Print に渡ってきて、TAGがNUMになってるので、24を取り出して標準出力に出力します。
再度受け付ける状態にする(Loop)
標準出力から再度、S式を受け取れる状態にしてREPLとしての振る舞いは完了です!
本書の通り実装すると、変数(setq)もネストしたS式も扱えるようになります。
長くなってしまうので、その部分の解説は避けますが、基本的な流れは本記事で紹介した流れに沿って、分岐していきます。
後、GCも mark-and-sweep で実装されてるので、別記事で解説できればやろうと思います。
実際に実装してみて詳細の動作を確認したい方は、¥500で買えるので是非!
grpc-gatewayでCookieをmetadataに設定しない
grpc-gatewayはデフォルトで、一般的なHeaderの値を、Metadataに入れてリクエストを中継します。
ただ、Cookieの値は使わない場合もあるし、 ユーザーに見えるkeyよりも、もっと意味持たせたkeyをMetadataに入れたいとかある。
そんな時は、mux作るときに、ServeMuxOption
を渡してあげれば良いです。
func run() error { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() mux := runtime.NewServeMux( runtime.WithIncomingHeaderMatcher(func(key string) (string, bool) { if key == "Cookie" { // CookieはMetadataに設定しない。 return "", false } return runtime.DefaultHeaderMatcher(key) }), ) opts := []grpc.DialOption{grpc.WithInsecure()} // ... return http.ListenAndServe(":8080", mux) }
grpc-gatewayにAPIを実装する
これはかなりおすすめしないですが、とりあえず検証ということでやってみました。
helthcheckぐらいならいいかも?
まずは、登録するハンドラーを用意。
package echo import ( "io" "net/http" "github.com/grpc-ecosystem/grpc-gateway/runtime" "google.golang.org/grpc/grpclog" ) func RegisterEchoHandlerFromEndpoint(mux *runtime.ServeMux) (err error) { // endpoint: /echo mux.Handle("GET", pattern_echo, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { // レスポンスを書き込む if _, err = w.Write([]byte("hello.")); err != nil { grpclog.Printf("Failed to write response: %v", err) } }) return nil } var ( pattern_echo = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{"echo"}, "")) )
あとは登録するだけで動きます。
... if err := gw_echo.RegisterEchoHandlerFromEndpoint(mux); err != nil { return err } return http.ListenAndServe(":8080", mux)
リクエストしてみる。
→ curl -i -XGET http://localhost:8080/echo HTTP/1.1 200 OK Date: Thu, 18 Jan 2018 09:40:38 GMT Content-Length: 6 Content-Type: text/plain; charset=utf-8 hello.%
1つのgrpc-gatewayに複数のAPI(gRPC)サーバーを繋ぐ
1つのgrpc-gatewayに複数サービス繋いで、認証は共通化するみたいなの作りたかったので調べてみました。
実装方法は簡単で、RegisterXXXXHandlerFromEndpoint
に渡すendpointを変えればいいだけでした。
package main import ( "flag" "net/http" "github.com/golang/glog" "github.com/grpc-ecosystem/grpc-gateway/runtime" "golang.org/x/net/context" "google.golang.org/grpc" gw_user "protocol/user" gw_cart"protocol/cart" ) var ( userEndpoint = flag.String("user_endpoint", "localhost:50051", "endpoint of YourService") cartEndpoint = flag.String("cart_endpoint", "localhost:50052", "endpoint of YourService") ) func run() error { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithInsecure()} // localhost:50051と通信する if err := gw_user.RegisterUserHandlerFromEndpoint(ctx, mux, *userEndpoint, opts); err != nil { return err } // localhost:50052と通信する if err := gw_cart.RegisterCartHandlerFromEndpoint(ctx, mux, *cartEndpoint, opts); err != nil { return err } return http.ListenAndServe(":8080", mux) } func main() { flag.Parse() defer glog.Flush() if err := run(); err != nil { glog.Fatal(err) } }
grpc-gatewayでネストされたリクエストパラメタを指定する
gRPCで、ValueObject的にサービスのリクエストメッセージを定義することはよくあるのかなーと思います。
ただ、grpc-gatewayを使う際に、型がネストするので、パラメタの指定がわかりにくかった。。
まとめられてるページもなさそうだったので、ここでまとめてみます。
クエリパラメタ
.
区切りでプリミティブな型まで指定します。
こんな感じのリクエストメッセージがあるとすると、
service User { rpc FetchByName (UserFetchByNameRequest) returns (UserFetchByNameResponse) { option (google.api.http) = { get: "/users" }; }; } message UserFetchByNameRequest { User.Name name = 1; } message User { ... message Name { string value = 1; } }
?name.value="hikouki"
でクエリパラメタを指定できます。
curl -i -XGET http://localhost:8080/users?name.value="hikouki"
パスパラメタ
クエリパラメタと同じく、.
区切りでプリミティブな型まで指定します。
こんな感じのリクエストメッセージがあるとすると、
service User { rpc Fetch (UserFetchRequest) returns (UserFetchResponse) { option (google.api.http) = { get: "/users/{id.value}" }; }; } message UserFetchRequest { User.ID id = 1; } message User { ... message ID { string value = 1; } }
/users/1
でパスパラメタを指定できます。
curl -i -XGET http://localhost:8080/users/1
リクエストボディ
オブジェクトでネストして指定します。
こんな感じのリクエストメッセージがあるとすると、
service User { rpc Create (UserCreateRequest) returns (UserCreateResponse) { option (google.api.http) = { post: "/users" body: "*" }; }; } message UserCreateRequest { User.Name name = 1; } message User { ... message Name { string value = 1; } }
JSONで、'{"name": {"value": "hikouki"}}'
をリクエストボディに指定します。
curl -i -XPOST http://localhost:8080/users -d '{"name": {"value": "hikouki"}}'