- 投稿日:2020-03-21T19:11:45+09:00
Lapack(clapack)を Android arm64 + cmake + NDK r20 or later でビルドする
背景
- Lapack(+ BLAS) を使うアプリを Android でビルドせねばならない.
- CMake + NDK r20 or later(clang)でビルドしたい
ビルド
https://github.com/syoyo/clapack-cmake-android-arm64
に上げました.
clapack-3.2.1-cmake をベースにしています.
いくらか warning 出ますが, build できます.
aarch64 linux(Jetson AGX)で unit test を走らせたら,
xeigtstz_***
のテストは実行がコケました.変更したところ
uninit.c
FPU の設定をします.
使っていないようなので取り除きました.
(Android では FPU control できるかな?)
__off64_t
sysdep1.h で, OFF_T を int64_t にしました.
arith.h
f2clib で, 実行時にシステムの情報を取得して arith.h を作っています.
cross compile だとコケますので, aarch64 linux(Jetson AGX)ででっち上げました.TODO
- Test を build & 走らせて, 実機で動くか確認する.
- OpenBLAS とリンクする.
- OpenBLAS を Android aarch64 + NDK r20 or later でビルドするメモ https://qiita.com/syoyo/items/6f15ddc8eeb260bda433
- 投稿日:2020-03-21T18:01:21+09:00
Fuelを使ってパラメータ付きPOSTリクエストを送信した
なんとかHTTPリクエストをAndroidのエミュレーターからローカルホストのLaravelサーバーに送信することができたので投稿します。
ライブラリのインストール
app/build.gradle
にインストールするライブラリを書いていきます。app/build.gradledependencies { // Fuel for HTTP Connections implementation 'com.github.kittinunf.fuel:fuel:2.2.0' implementation 'com.github.kittinunf.fuel:fuel-gson:2.2.0' implementation "com.squareup.moshi:moshi:1.5.0" implementation "com.squareup.moshi:moshi-kotlin:1.5.0" }HTTPリクエストを送信するための
Fuel
と,JSONのパラメータを作成するためのmoshi
をインストールします。
(僕は最初,app/build.gradle
じゃなくてルートのgradleにimplementation
を書いちゃって動きませんでした...。)パラメータのフォーマットを決める
JSONのパラメータのフォーマットを決めるためのクラス
SampleRequestFormat
クラスを作成します。SampleRequestFormat.ktclass SampleRequestFormat(val kekey: String)
kekey
という名前のキーを定義しました。Androidの画面作成
画面にボタンが一つ配置されているだけのものです。
activity_debug.xml<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/post_debug_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Button" /> </LinearLayout>リクエスト送信用のアクティビティを作成
POSTでLaravelのサーバーへリクエストをパラメータ付きで送るためのプログラムを作成します。
DebugActivity.ktimport android.os.Bundle import android.widget.Button import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import com.example.live_barcode_reader.request_format.SampleRequestFormat import com.github.kittinunf.fuel.Fuel import com.squareup.moshi.KotlinJsonAdapterFactory import com.squareup.moshi.Moshi class DebugActivity : AppCompatActivity() { private var button: Button? = null private val URL: String = "http://10.0.2.2:8000" private val domain: String = "/sample9999" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_debug) val targetURL: String = URL + domain val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() val requestAdaper = moshi.adapter(SampleRequestFormat::class.java) button = findViewById(R.id.post_debug_button) button?.setOnClickListener { val header: HashMap<String, String> = hashMapOf("Content-Type" to "application/json") val sampleRequest = SampleRequestFormat( kekey = "from Android" ) Fuel.post(targetURL) .header(header) .body(requestAdaper.toJson(sampleRequest)) .response { _, response, result -> println(response) println(result) } //Toast.makeText(this, "PUSHED", Toast.LENGTH_SHORT).show() } } }このとき,
URL
の中身がhttp://10.0.2.2:8000
であることを覚えておいてください!
最後に,HTTP通信の設定についてAndroidManifest.xml
に記述します。HTTP通信の設定
network_security_config.xml
というファイルを作成し,HTTP通信できるドメインを指定して許可します。network_security_config.xml<?xml version="1.0" encoding="utf-8"?> <network-security-config xmlns:android="http://schemas.android.com/apk/res/android"> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">10.0.2.2</domain> </domain-config> </network-security-config>その後,
AndroidManifest.xml
にも変更を加えます。AndroidManifest.xml<application ... android:networkSecurityConfig="@xml/network_security_config">次はLaravelのサーバーです。
サーバーサイド
web.phpRoute::post('/sample9999', 'TestController@sample9999');
sample9999
というPOSTリクエストに反応してTestController
の中のsample9999()
という関数が反応するようにします。9999という数字は少し理由があって付けました。TestController.php<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; use Illuminate\Http\Request; class TestController extends Controller { public function sample9999(Request $request) { $json_string = file_get_contents('php://input'); logger($json_string); $json_object = json_decode($json_string); logger(var_export($json_object, true)); logger(gettype($json_object)); logger($json_object->kekey); return 'got request'; } }とりあえずこのコードで動作確認だけしてみました。
動作確認
Androidとサーバーを起動して,エミュレータのボタンを押すと,
laravel.log[2020-03-21 08:59:49] local.DEBUG: {"kekey":"from Android"} [2020-03-21 08:59:49] local.DEBUG: (object) array( 'kekey' => 'from Android', ) [2020-03-21 08:59:49] local.DEBUG: object [2020-03-21 08:59:49] local.DEBUG: from AndroidAndroidのログ↓
I/System.out: <-- 200 http://10.0.2.2:8000/sample9999 Response : OK Length : -1 Body : got request Headers : (11) Cache-Control : no-cache, private Connection : close Set-Cookie : laravel_session=eyJpdiI6IkVrR0RxK241U0xkWkRUbFhWWmhMSXc9PSIsInZhbHVlIjoicEF2d2h5dXVDVFdmVElDc09hLzNvMFJmNmM2bitBbTRrSFBBQjVucEN1elY5eGNUQy9ZZUFqVkExdkplTGEwWSIsIm1hYyI6IjAzMTBiZWEwN2FlYmYxODhlZWQ3OWY2MmU1YTBhMWQ2MWNiYzIzMGI3ZWY1NWM0NjlhYTNkMzkxMzJiZTgyYzIifQ%3D%3D; expires=Sat, 21-Mar-2020 10:59:49 GMT; Max-Age=7200; path=/; httponly; samesite=lax Date : Sat, 21 Mar 2020 08:59:49 GMT, Sat, 21 Mar 2020 08:59:49 GMT X-Android-Selected-Protocol : http/1.1 X-Powered-By : PHP/7.3.11 Content-Type : text/html; charset=UTF-8 Host : 10.0.2.2:8000 X-Android-Received-Millis : 1584781188564 X-Android-Response-Source : NETWORK 200 X-Android-Sent-Millis : 1584781188475 [Success: [B@615227d]HTTPリクエストが送られています!
- 投稿日:2020-03-21T17:33:30+09:00
OpenBLAS を Android aarch64 + NDK r20 or later でビルドするメモ
背景
OpenBLAS(+ Lapack)を Android arm64 で動かしたい.
Lapack(clapack)はこちら
Lapack(clapack)を Android arm64 + cmake + NDK r20 or later でビルドする
https://qiita.com/syoyo/items/09009ca77393cc0bd986ビルド方法
How to build OpenBLAS for Android
https://github.com/xianyi/OpenBLAS/wiki/How-to-build-OpenBLAS-for-Androidは情報が古いです.
NDK r20 などでは, gcc 関連がそれなりに消えて clang only になったので, wiki の情報だとビルドがうまくいきません.
Compiling OpenBLAS for Android #2005
https://github.com/xianyi/OpenBLAS/issues/2005にありました.
cd $OPENBLAS_SOURCE export NDK=${HOME}/Android/sdk/ndk-bundle export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64 make \ TARGET=CORTEXA57 \ ONLY_CBLAS=1 \ CC=$TOOLCHAIN/bin/aarch64-linux-android21-clang \ AR=$TOOLCHAIN/bin/aarch64-linux-android-ar \ HOSTCC=gcc \ -j4 make PREFIX=./android-openblas/arm64 installでビルドできます! sysroot 指定など不要でビルド楽です.
CORTEX architecture
現時点(2020/03/21)では, CORTEXA53, 57, 72, 73 がありました.
Cmake build
ソースコードのレイアウト上, out-of-tree build はちょっと気持ち悪いことがわかりました
(生成された congig.h ではなく, 既存の config.h(x86 用のデフォルト設定) をインクルードしてしまうため, config.h を消さないといけない)in-source ビルドではこんな感じでいけました.
https://github.com/syoyo/OpenBLAS/blob/develop/scripts/bootstrap-android-cmake.sh
## Please edit android sdk/ndk/cmake path ANDROID_SDK_ROOT=$HOME/Android/Sdk/ ANDROID_NDK_ROOT=$HOME/Android/Sdk/ndk-bundle #ANDROID_NDK_ROOT=$HOME/Android/Sdk/ndk/android-ndk-r21/ # CMake 3.10 or later required CMAKE_BIN=$ANDROID_SDK_ROOT/cmake/3.10.2.4988404/bin/cmake #CMAKE_BIN=cmake # For ninja build # -DCMAKE_MAKE_PROGRAM=$ANDROID_SDK_ROOT/cmake/3.6.4111459/bin/ninja \ # Due to the source code layout of OpenBLAS, we need to use in-source build # Otherwise compilation fails due to the conflict of existing `config.h` and generated `config.h` # (Using in-source build correctly overrides `config.h`) $CMAKE_BIN \ -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake \ -DANDROID_ARM_MODE=arm \ -DANDROID_ARM_NEON=On \ -DANDROID_ABI=arm64-v8a \ -DANDROID_NATIVE_API_LEVEL=28 \ -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" \ -DANDROID_STL=c++_shared \ -DTARGET=CORTEXA73 \ -DNOFORTRAN=1 \ -DDYNAMIC_ARCH=0 \ -DBUILD_WITHOUT_LAPACK=On \ -DBUILD_RELAPACK=Off \ .参考情報
clapack の Android ビルド(ndk-build であるが)
Compile Kaldi for 64-bit Android on Ubuntu 18
https://medium.com/swlh/compile-kaldi-for-64-bit-android-on-ubuntu-18-70967eb3a308TODO
- LAPACK をビルドする(clapack を使う)
- cmake で cross compile を試す.
- in-source ビルドでなんとかビルド. out-of-tree はちょっとめんどい(既存
config.h
を消せばいける)- CORTEXA73 での性能を試す(Snapdragon 835 などの A73 アーキでいくらか速くなるはず?)
- 投稿日:2020-03-21T14:14:25+09:00
Android/Kotlin + Go で gRPC Bidirectional Streaming するサンプルをゼロから構築
これは何
サーバが Go 、クライアントが Android/Kotlin という構成で 双方からリアムタイムにメッセージを送り合う仕組みとして、gRPC Bidirectional Streaming を使ったサンプルを作るのに必要な設定やコードをまとめたものです。
いろいろ調べながらやっていると新旧どれが正しい情報かわからず結構手間がかかったり、基本的な所でつまづいていることに気づきにくかったりしたため、この記事もしばらくの間しか使えないと思いつつ、記事にまとめておくことにしました。
Go も Android/Kotlin も普段使いしているわけではないため、色々と内容に不備があると思います。何かあれば指摘いただけるとありがたいです!
仕様
- 文字列ひとつだけのメッセージ
Greeting
をサーバとクライアント間で双方向で送り合う- サーバは数秒ごとに連番付きのメッセージをクライアントに送り続ける
- サーバはクライアントからメッセージを受信したら標準出力に表示
- クライアントはボタンをクリックすると TextEdit の内容をサーバに送信する
- クライアントはサーバからメッセージを受信したら TextView に表示
proto ファイルはこういう内容にしました
proto/sample.protosyntax = "proto3"; package main; service Greet { rpc Say(stream Greeting) returns (stream Greeting) {} } message Greeting { string body = 1; }サーバ(Golang)
MacOSX Mojave で開発しました。
まずは最新版の protoc バイナリをインストールします
$ brew update $ brew upgrade $ brew upgrade protobuf $ protoc --version libprotoc 3.6.0Go 用のプラグインを追加して
PATH
に追加$ go get -u github.com/golang/protobuf/proto $ go get -u github.com/golang/protobuf/protoc-gen-go PATH=$PATH:$HOME/go/bin前述の proto ファイル
proto/sample.proto
をコンパイルしてsrc/sample.pb.go
を生成します。今回はサンプルなので main package 内に作ってしまいます$ protoc --go_out=plugins=grpc:src -Iproto sample.protoサーバの実装は以下のようにしました。
gRPC のサーバに必要なインターフェースを実装した
func (s *myGreetService) Say(stream Greet_SayServer) error
が本体です。そこから 2つの goroutine を起動して無限ループします。それぞれで送信と受信を行いつつ、どちらかでエラーが出たらその接続を終了しています。
src/main.gopackage main import ( "fmt" "io" "log" "net" "time" "google.golang.org/grpc" ) type myGreetService struct{} func send(stream Greet_SayServer, ch chan error) { i := 0 for { message := "message #" + fmt.Sprintf("%d", i) log.Println("sending: " + message) err := stream.Send(&Greeting{Body: message}) if err != nil { log.Println(err) ch <- err return } time.Sleep(1 * time.Second) i++ } } func receive(stream Greet_SayServer, ch chan error) { for { in, err := stream.Recv() if err == io.EOF || err != nil { log.Println(err) ch <- err return } log.Println("received: " + in.GetBody()) } } func (s *myGreetService) Say(stream Greet_SayServer) error { log.Println("Stream started.") ch := make(chan error) go send(stream, ch) go receive(stream, ch) err := <-ch log.Println("Stream closed.") return err } func main() { listenPort, err := net.Listen("tcp", ":5000") if err != nil { log.Fatalln(err) } server := grpc.NewServer() sampleService := &myGreetService{} RegisterGreetServer(server, sampleService) server.Serve(listenPort) }クライアント(Android/Kotlin)
環境設定
Android Studio 3.5 で環境を作るのに少し苦労しました
まずは Empty Activity のテンプレートから新規プロジェクトを作成。
最初に Gradle のバージョンを最新に変更します。これがないと 後のプラグインが
java.lang.NoClassDefFoundError: org/gradle/api/attributes/LibraryElemen
を出してしまいハマります/gradle-wrapper.propertiesdistributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-all.zip続いて build.gradle と app/build.gradle に protoc 関連の依存関係設定を追加します。
protobuf-gradle-plugin のドキュメントが一番頼りになりました。 1
なお、各種ライブラリのバージョンは 全て、常に何が最新版なのかを調べてそれを設定 したほうが良さそうです。
/build.gradlebuildscript { ext.kotlin_version = '1.3.50' repositories { ... mavenCentral() } dependencies { ... classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.12' } }/app/build.gradleapply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'com.google.protobuf' // <- これは 順番としてここにないといけない protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.11.0' } plugins { grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.28.0' } } generateProtoTasks { all().each { it.builtins { java { option 'lite' } } it.plugins { grpc { option 'lite' } } } } } dependencies { ... implementation 'javax.annotation:javax.annotation-api:1.3.2' // 生成されるスタブのためのにこれも必要 implementation "io.grpc:grpc-okhttp:1.28.0" implementation "io.grpc:grpc-protobuf-lite:1.28.0" implementation "io.grpc:grpc-stub:1.28.0" }クライアントアプリ
proto ファイルは
src/java/main/proto/sapmle.proto
に配置しました。Java の パッケージ名を追加しています
... option java_package = "com.github.reki2000.grpcclient"; ...Activity の具体的な実装内容は以下のようになりました。
MainActivity.ktpackage com.github.reki2000.grpcclient import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.os.Handler import android.util.Log import io.grpc.ManagedChannelBuilder import io.grpc.stub.StreamObserver import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { lateinit private var stream : StreamObserver<Sample.Greeting> lateinit private var handler : Handler override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) handler = Handler() Log.i("app","started") initGRPC() button.setOnClickListener({ onButtonClick() }) } fun initGRPC() { try { val channel = ManagedChannelBuilder.forAddress("10.0.2.2", 5000).usePlaintext().build() val stub = GreetGrpc.newStub(channel) val responseObserver = object : StreamObserver<Sample.Greeting> { override fun onNext(greet: Sample.Greeting) { onServerMessageReceived(greet) } override fun onError(t: Throwable) { Log.e("app", "error", t) } override fun onCompleted() { Log.i("app", "connection closed") } } stream = stub.say(responseObserver) } catch (e: Exception) { Log.e("app", "grpc connection error",e) } } // サーバからのメッセージを受信したら 画面表示用スレッド経由で textView に表示する fun onServerMessageReceived(greet: Sample.Greeting) { handler.post({showMessage("Received: ${greet.body}")}) } // ボタンがクリックされたら textView に表示するとともに サーバにメッセージを送信する fun onButtonClick() { val text = editText.text.toString() try { val request = Sample.Greeting.newBuilder().setBody(text).build() stream.onNext(request) } catch (e: Exception) { Log.e("app", "grpc error",e) } showMessage("Sent: ${text}") } fun showMessage(text: String) { textView.text = "${textView.text.toString()}${System.lineSeparator()}${text}" Log.i("app", text) } }なお、アプリの manifest で HTTP を有効にしていないといけません
AndroidManifest.xml<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.github.reki2000.grpcclient" > ... <uses-permission android:name="android.permission.INTERNET"></uses-permission> </manifest>実行
サーバのログ
2020/03/21 13:51:20 Stream started. 2020/03/21 13:51:20 sending: message #0 2020/03/21 13:51:21 sending: message #1 2020/03/21 13:51:22 sending: message #2 2020/03/21 13:51:23 sending: message #3 2020/03/21 13:51:24 sending: message #4 2020/03/21 13:51:25 sending: message #5 2020/03/21 13:51:26 sending: message #6 2020/03/21 13:51:26 received: message 2020/03/21 13:51:27 sending: message #7だいぶ苦労しましたが、うまく双方向で通信できています!
その他気になっていること
- エンドポイントが一つだけなので いろいろなメッセージをやり取りする場合も一つのオブジェクトがいろいろな処理のための情報を含むような形にしなければならない
- TCPが張りっぱなしになるが、通信が切れた・黙ってしまったときの復旧を含めた構成を検討したい
- この構成でサーバがどれくらいの同時接続に耐えられるか
注意点として README が長く いろいろな記述サンプルがありますが、Androidの場合は最後の方の "Starting from Protobuf 3.8.0, lite code generation is built into protoc's "java" output. Example:" のところを見なければならないということを理解するまでにだいぶつまづきました ↩
- 投稿日:2020-03-21T14:14:25+09:00
Android/Kotlin + golang で gRPC Bidirectional Streaming するサンプルをゼロから構築
これは何
サーバが golang 、クライアントが Android/Kotlin という構成で 双方からリアムタイムにメッセージを送り合う仕組みとして、gRPC Bidirectional Stream を使ったサンプルを作るのに必要な設定やコードをまとめたものです。
いろいろ調べながらやっていると新旧どれが正しい情報かわからず結構手間がかかったり、基本的な所でつまづくいていることに気づきにくかったりしたため、この記事もしばらくの間しか使えないと思いつつ、記事にまとめておくことにしました。
golang も Android/Kotlin も普段使いしているわけではないため、色々と内容に不備があると思います。何かあれば指摘いただけるとありがたいです!
仕様
- 文字列ひとつだけのメッセージ
Greeting
をサーバとクライアント間で双方向で送り合う- サーバは数秒ごとに連番付きのメッセージをクライアントに送り続ける
- サーバはクライアントからメッセージを受信したら標準出力に表示
- クライアントはボタンをクリックすると TextEdit の内容をサーバに送信する
- クライアントはサーバからメッセージを受信したら TextView に表示
proto ファイルはこういう内容にしました
proto/sample.protosyntax = "proto3"; package main; service Greet { rpc Say(stream Greeting) returns (stream Greeting) {} } message Greeting { string body = 1; }サーバ(Golang)
MacOSX Mohave で開発しました。
まずは最新版の protoc バイナリをインストールします
$ brew update $ brew upgrade $ brew upgrade protobuf $ protoc --version libprotoc 3.6.0golang 用のプラグインを追加して
PATH
に追加$ go get -u github.com/golang/protobuf/proto $ go get -u github.com/golang/protobuf/protoc-gen-go PATH=$PATH:$HOME/go/bin前述の proto ファイル
proto/sample.proto
をコンパイルしてsrc/sample.pb.go
を生成します。今回はサンプルなので main package 内に作ってしまいます$ protoc --go_out=plugins=grpc:src -Iproto sample.protoサーバの実装は以下のようにしました。
gRPC のサーバに必要なインターフェースを実装した
func (s *myGreetService) Say(stream Greet_SayServer) error
が本体です。そこから 2つの goroutine を起動して無限ループします。それぞれで送信と受信を行いつつ、どちらかでエラーが出たらその接続を終了しています。
src/main.gopackage main import ( "fmt" "io" "log" "net" "time" "google.golang.org/grpc" ) type myGreetService struct{} func send(stream Greet_SayServer, ch chan error) { i := 0 for { message := "message #" + fmt.Sprintf("%d", i) log.Println("sending: " + message) err := stream.Send(&Greeting{Body: message}) if err != nil { log.Println(err) ch <- err return } time.Sleep(1 * time.Second) i++ } } func receive(stream Greet_SayServer, ch chan error) { for { in, err := stream.Recv() if err == io.EOF || err != nil { log.Println(err) ch <- err return } log.Println("received: " + in.GetBody()) } } func (s *myGreetService) Say(stream Greet_SayServer) error { log.Println("Stream started.") ch := make(chan error) go send(stream, ch) go receive(stream, ch) err := <-ch log.Println("Stream closed.") return err } func main() { listenPort, err := net.Listen("tcp", ":5000") if err != nil { log.Fatalln(err) } server := grpc.NewServer() sampleService := &myGreetService{} RegisterGreetServer(server, sampleService) server.Serve(listenPort) }クライアント(Android/Kotlin)
環境設定
Android Studio 3.5 で環境を作るのに少し苦労しました
まずは Empty Activity のテンプレートから新規プロジェクトを作成。
最初に Gradle のバージョンを最新に変更します。これがないと 後のプラグインが
java.lang.NoClassDefFoundError: org/gradle/api/attributes/LibraryElemen
を出してしまいハマります/gradle-wrapper.propertiesdistributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-all.zip続いて build.gradle と app/build.gradle に protoc 関連の依存関係設定を追加します。
protobuf-gradle-plugin のドキュメントが一番頼りになりました。 1
なお、各種ライブラリのバージョンは 全て、常に何が最新版なのかを調べてそれを設定 したほうが良さそうです。
/build.gradlebuildscript { ext.kotlin_version = '1.3.50' repositories { ... mavenCentral() } dependencies { ... classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.12' } }/app/build.gradleapply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'com.google.protobuf' // <- これは 順番としてここにないといけない protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.11.0' } plugins { grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.28.0' } } generateProtoTasks { all().each { it.builtins { java { option 'lite' } } it.plugins { grpc { option 'lite' } } } } } dependencies { ... implementation 'javax.annotation:javax.annotation-api:1.3.2' // 生成されるスタブのためのにこれも必要 implementation "io.grpc:grpc-okhttp:1.28.0" implementation "io.grpc:grpc-protobuf-lite:1.28.0" implementation "io.grpc:grpc-stub:1.28.0" }クライアントアプリ
proto ファイルは
src/java/main/proto/sapmle.proto
に配置しました。Java の パッケージ名を追加しています
... option java_package = "com.github.reki2000.grpcclient"; ...Activity の具体的な実装内容は以下のようになりました。
MainActivity.ktpackage com.github.reki2000.grpcclient import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.os.Handler import android.util.Log import io.grpc.ManagedChannelBuilder import io.grpc.stub.StreamObserver import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { lateinit private var stream : StreamObserver<Sample.Greeting> lateinit private var handler : Handler override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) handler = Handler() Log.i("app","started") initGRPC() button.setOnClickListener({ onButtonClick() }) } fun initGRPC() { try { val channel = ManagedChannelBuilder.forAddress("10.0.2.2", 5000).usePlaintext().build() val stub = GreetGrpc.newStub(channel) val responseObserver = object : StreamObserver<Sample.Greeting> { override fun onNext(greet: Sample.Greeting) { onServerMessageReceived(greet) } override fun onError(t: Throwable) { Log.e("app", "error", t) } override fun onCompleted() { Log.i("app", "connection closed") } } stream = stub.say(responseObserver) } catch (e: Exception) { Log.e("app", "grpc connection error",e) } } // サーバからのメッセージを受信したら 画面表示用スレッド経由で textView に表示する fun onServerMessageReceived(greet: Sample.Greeting) { handler.post({showMessage("Received: ${greet.body}")}) } // ボタンがクリックされたら textView に表示するとともに サーバにメッセージを送信する fun onButtonClick() { val text = editText.text.toString() try { val request = Sample.Greeting.newBuilder().setBody(text).build() stream.onNext(request) } catch (e: Exception) { Log.e("app", "grpc error",e) } showMessage("Sent: ${text}") } fun showMessage(text: String) { textView.text = "${textView.text.toString()}${System.lineSeparator()}${text}" Log.i("app", text) } }なお、アプリの manifest で HTTP を有効にしていないといけません
AndroidManifest.xml<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.github.reki2000.grpcclient" > ... <uses-permission android:name="android.permission.INTERNET"></uses-permission> </manifest>実行
サーバのログ
2020/03/21 13:51:20 Stream started. 2020/03/21 13:51:20 sending: message #0 2020/03/21 13:51:21 sending: message #1 2020/03/21 13:51:22 sending: message #2 2020/03/21 13:51:23 sending: message #3 2020/03/21 13:51:24 sending: message #4 2020/03/21 13:51:25 sending: message #5 2020/03/21 13:51:26 sending: message #6 2020/03/21 13:51:26 received: message 2020/03/21 13:51:27 sending: message #7だいぶ苦労しましたが、うまく双方向で通信できています!
その他気になっていること
- エンドポイントが一つだけなので いろいろなメッセージをやり取りする場合も一つのオブジェクトがいろいろな処理のための情報を含むような形にしなければならない
- TCPが張りっぱなしになるが、通信が切れた・黙ってしまったときの復旧を含めた構成を検討したい
- この構成でサーバがどれくらいの同時接続に耐えられるか
注意点として README が長く いろいろな記述サンプルがありますが、Androidの場合は最後の方の "Starting from Protobuf 3.8.0, lite code generation is built into protoc's "java" output. Example:" のところを見なければならないということを理解するまでにだいぶつまづきました ↩
- 投稿日:2020-03-21T11:23:30+09:00
簡単なゲームを作ってみよう(Win or Lose)
簡単なゲームを作ってみよう
ドットインストールのはじめてのJavaScriptで「#11 簡単なゲームを作ってみよう」をやっあと、
ふと思い立ったのでFlutterで作ってみました。仕様
- 5つのマスがあり、5つのうちランダムで1つが「Win」、4つが「Lose」になる。
- 「Lose」が選択された場合、マスに"Lose!"が表示される。
- "Lose!"が表示されるのと同時にマスが小さくなる。
- 「Win」が選択された場合、マスに"Win!"が表示される。
- "Win!"が表示されるのと同時にマスが青からピンクに変わり、回転しながら四角形から円に変わる。
作ってみる
とりあえず、以下がマスを操作するソースコードです。
もっと効率の良い書き方や操作の仕方、お行儀の良い書き方など教えていただければありがたいです。
- winnerはランダムで「Win」を設定しています。
- for文でマスをつくり、リストのindexとwinnerとが一致したら「Win」の操作、それ以外は「Lose」の操作をします。
- RotationTransitionでマスの回転、AnimatedPaddingとAnimatedContainerでマスのサイズ、色の変更、文字の挿入をしています。
class MyCard extends StatefulWidget{ MyCard({Key key, this.index}) : super(key: key); final int win = winner; final int index; @override _MyCardState createState() => _MyCardState(); } class _MyCardState extends State<MyCard> with SingleTickerProviderStateMixin { Color _mycolor = Colors.blue[500]; String _mytxt = ''; double _width = 100, _heigth = 100, _padding = 10, _radius = 0; final Tween<double> turnsTween = Tween<double>( begin: 1, end: 2, ); AnimationController _controller; initState() { _controller = AnimationController( vsync: this, duration: const Duration(seconds: 1), ); super.initState(); } @override Widget build(BuildContext context) { return card(); } void _winorlose(){ setState(() { if (widget.index == widget.win) { _mycolor = Colors.pink[100]; _mytxt = 'Win!'; _radius = 100; _controller.forward(); } else { _mytxt = 'Lose!'; _width = 80; _heigth = 80; _padding = 20; } }); } Widget card() { return GestureDetector( onTap: () { _winorlose(); }, child: RotationTransition( turns: turnsTween.animate(_controller), child: AnimatedPadding( duration: const Duration(milliseconds: 500), padding: EdgeInsets.all(_padding), child: AnimatedContainer( duration: const Duration(milliseconds: 500), width: _width, height: _heigth, child: Center( child: Text(_mytxt), ), decoration: BoxDecoration( color: _mycolor, borderRadius: BorderRadius.circular(_radius), ), ) ), ), ); } }こんな感じになりました
終わりに
読んでいただきありがとうございました。
そういえば、Google Mapsの操作も放置したままだったな。。。