Real World Android Akka - 日本語版

46
Real World Android Akka Taisuke Oe

Transcript of Real World Android Akka - 日本語版

Page 1: Real World Android Akka - 日本語版

Real WorldAndroid Akka

Taisuke Oe

Page 2: Real World Android Akka - 日本語版

自己紹介?麻植泰輔 / TaisukeOe

GitHub: / Twitter:

お仕事

セプテーニ・オリジナル 技術アドバイザーとしてPullReqレビュー/新卒研修

座長

taisukeoe @OE_uia

BONX Android App

ScalaMatsuri

Page 3: Real World Android Akka - 日本語版

次回のScalaMatsuri2018年3月予定!現在日程調整中。お楽しみに!

Page 4: Real World Android Akka - 日本語版

今日話すことVoIPクライアント

VoIP : Voice Over Internet Protocol

Page 5: Real World Android Akka - 日本語版

VoIPサンプルtaisukeoe/VoIPAkka

Page 6: Real World Android Akka - 日本語版

Real World Example

Page 7: Real World Android Akka - 日本語版
Page 8: Real World Android Akka - 日本語版
Page 9: Real World Android Akka - 日本語版

BONXとは?アウトドア用の通話システムスノーボードスキー釣りサイクリング...

Page 10: Real World Android Akka - 日本語版

BONXアーキテクチャアプリ

VoIPクライアントAndroid : ScalaiOS : Objective C / Swift

サーバー

APIサーバー : Ruby on RailsVoiceサーバー : golang

Bluetoothヘッドセット

Page 11: Real World Android Akka - 日本語版

Android Akkaを使ったワケ

Page 12: Real World Android Akka - 日本語版

VoIPはステートフルな非同期ストリーム処理

Page 13: Real World Android Akka - 日本語版

Upstream

Recorder -> some DSP -> Encoder -> Socket

Downstream

Socket -> Decoder -> some DSP -> Player

DSP : Digital Signal Processing

全てのコンポーネントは状態が有り、更に スレッドセーフではない.

Page 14: Real World Android Akka - 日本語版

求められる障害からの回復力(Resiliency).アウトドアでは、様々なエラーに遭遇する

不安定なネットワーク圏外<->圏内間の度重なる移動ハードウェアI/OエラーBluetooth切断

アウトドアで遊んでいる最中は画面操作できないので、自動復旧が肝。

Page 15: Real World Android Akka - 日本語版

更に悪いことに:エラーは必ずしも問題のコンポーネントで捕捉されない。

下流で気づくことがあるすなわちエラーを逆方向に伝搬させたい

問題のあるコンポーネントは、壊れた音声データを生むかもしれない

そしてノイズの原因になりうるすなわちエラーの種類によっては、キューにたまった音声データを消さなければいけない(逆もまた然り)

Page 16: Real World Android Akka - 日本語版

まとめ: BONXの要求仕様ステートフルな非同期ストリーム処理障害からの回復力

Page 17: Real World Android Akka - 日本語版

そこでAkkaの出番!Akkaは 並行プログラミング、障害からの自動回復

... そして分散システムを構築するためのtoolkit.

Page 18: Real World Android Akka - 日本語版

(ステートフルな) 並行プログラミングのためにメッセージ駆動なアクターモデル

アクターは、状態を内部に閉じ込めたまま、並行プログラミングが容易

Page 19: Real World Android Akka - 日本語版

状態を内部に閉じ込めたActorの例class PlayActor() extends Actor { private val track: AudioTrack = createNewAudioTrack //state. Thread UN-safe

override def preStart(): Unit = track.play()

override def receive: Receive = { //An actor retrieves and process a message from its mailbox one-at-a-time. case AudioPacketMessage(content:ByteString) => track.write(content.toArray[Byte], 0, content.length) }

override def postStop(): Unit = { track.stop() track.flush() track.release() } }

Page 20: Real World Android Akka - 日本語版

Akkaによる障害からの回復力(Resiliency)例外のハンドリング

Actorヒエラルキー

子アクターをsupervisorStrategyにもとづいてRestart, Stop, ResumeこれによるRestartは メッセージボックスにメッセージを貯める.

モニタリング (Death watch)

監視対象のアクターがstopすると、監視側のアクターはTerminated(actorRef) メッセージを受け取るので、そこで再生成するこれによるアクター再生性は メッセージボックス内のメッセージを破棄する.

Page 21: Real World Android Akka - 日本語版

アクターによる例外のハンドリングのサンプルclass ExceptionHandlingActor() extends Actor{ private var childActor:ActorRef = createChildActor

override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy(){ //Want to reset all mailboxes case _:BrokenAudioDataException => Stop //At default, child actors will be restarted with keeping their mailbox. case t => super.supervisorStrategy.decider.applyOrElse(t, (_:Any) => Escalate) }

def receive = { //Stopped childActor will be recreated. case Terminated(ref) => if(childActor == ref) { context.unwatch(childActor) childActor = createChildActor } }

private def createChildActor = { val c = context.child(ChildActor.props) context watch c c } }

Page 22: Real World Android Akka - 日本語版

Recaps: なぜAkkaがうまく働いたのかステートフルな並行プログラミング

アクターの状態のカプセル化 / 一度に一つだけメッセージを処理するのでロック不要

障害からの回復力

アクターのヒエラルキーないしはモニタリングで管理

AkkaによってVoIP問題が「だいたい」解消した!

Page 23: Real World Android Akka - 日本語版

なぜ「だいたい」なのか ?ものによっては例外として検知しにくい問題がある

オーディオドライバが場合によっては(ブロックしたまま)沈黙し、何も返さないことがある

銀の弾丸はない

Askパターンで、Ack が Timeout 内に返ってくるかチェック。

もし Ask がタイムアウトしたら、stopし再生成。

とはいえ、まずは「例外」として問題を検知する方法を探るべき

あまりオススメはしない

Page 24: Real World Android Akka - 日本語版

どうしてScalaがAndroidで使えるのか?

Page 25: Real World Android Akka - 日本語版

ビルドツールチェインscalac がScalaのソースをJava bytecodeにコンパイル

Android SDK の dex が Java bytecode を Dalvik Executable bytecodes に翻訳

Android標準のビルドシステムの主要機能は抑えているsbt pluginと組み合わせ等が柔軟

scala-android/sbt-android

Page 26: Real World Android Akka - 日本語版

Android端末のスペックも上がってきたここ数年の�agship級端末だと、最低でも:

4 to 10 cores CPU3 to 4 GB memorieslargeHeap=true で512MB程度使える

Page 27: Real World Android Akka - 日本語版

Q. じゃあScala Android使うべき?万人にオススメはしない 技術にはトレードオフがつきもの

Page 28: Real World Android Akka - 日本語版

Pros & Cons

Page 29: Real World Android Akka - 日本語版

Pros豊富な言語機能とエコシステム ecosystem.

並行プログラミングコレクションAPI代数的データ型とパターンマッチトレイト

生産的

0.5 ~ 1.2 人で2年間新機能開発と運用まわしてました

サーバーサイドもScalaだと頭の切り替え不要で楽

Page 30: Real World Android Akka - 日本語版

他にScalaで嬉しい話(View以外の)ステートレスな並行プログラミング

Viewの修正はUIThreadで行うため、Viewについては排他制御を考える必要性が薄い

Page 31: Real World Android Akka - 日本語版

Scalaの並行プログラミングAPIは優秀Scala標準のFuture悪くないですよ.UIThreadで実行するためのExecutionContextを定義して onComplete, andThen 等に明示的に渡せる

Page 32: Real World Android Akka - 日本語版

UIExecutionContext Patternclass UIExecutionContext(activity:Activity, reporter: Throwable => Unit) extends ExecutionContext{ def execute(runnable: Runnable): Unit = activity.runOnUiThread(runnable) def reportFailure(t: Throwable): Unit = reporter(t) }

Future{ //some heavy calculation }.onComplete{ case Success(result) => //reflect your result on views case Failure(exp) => //show error message }(uiExecutionContext) //Explicitly!

Page 33: Real World Android Akka - 日本語版

その他のScala Androidプロジェクトたちセプテーニ・オリジナル

hits 6 million DLs!

47 Degrees

Akkaモジュール有り.

GANMA!

47deg/macroid47deg/nine-cards-v247deg/scala-days-android

pocorall/scaloid

Page 34: Real World Android Akka - 日本語版

Consdexファイルあたり、メソッド数 64k の上限.

Scala標準ライブラリのjarはちょっと大きすぎる.ProguardかMultiDexが必要.

Java8 supportなし

Android SDK toolchain はいまだ Java6+ 環境を対象.Akkaのバージョンは2.3.16まで。ただしEnd-of-Lifeのマーク付き.

メモリ管理もある程度気にしたい

GoogleもLightbend公式サポート無し

Page 35: Real World Android Akka - 日本語版

ProguardProguardは使用されていないクラス、フィールド、メソッドを除くポストプロセシングツール.

使用されていない とは 参照されていない.Proguard はリフレクションを解しない.

AkkaはリフレクションをPropsや.confで多用している.

もしProguardが正しく設定されていなければ, NoClassDefFoundErrorないしはNoSuchMethodErrorが ランタイムに投げられる.

Android Scalaをやる上でとても苦痛な作業.

Page 36: Real World Android Akka - 日本語版

とはいえ、Proguardの設定って最初から全部自分でやらなきゃいけないのか?

そんなことはない

Scala標準のライブラリ用のProguard設定は組み込み済

akka.actor と akka.io パッケージについては作りました

scala-android/sbt-android

proguard-con�g.txt for akka-actor v2.3.16

Page 37: Real World Android Akka - 日本語版

新しいライブラリについてProguardの設定を簡単にやる方法(手抜き編)

Page 38: Real World Android Akka - 日本語版
Page 39: Real World Android Akka - 日本語版

実際とりあえず試すにはこれで十分ライブラリが十分小さい限りにおいては.例えば、Akkaについて同じ方法で設定するなら、proguard-con�g.txtに以下の一行を足す:-keep class akka.** { *; }

後でちゃんと設定しましょうメソッド数が64kを超えたタイミング.

MultiDexで乗り切るという手もあるアプリをリリースする直前にapkサイズを削りたいとき

Page 40: Real World Android Akka - 日本語版

Proguardの設定方法(推奨)リフレクションで参照されているクラスを調べよう

grep -r "classOf" . が最も簡単

FQCN(フル修飾名)が.confファイルなどの設定ファイルに記述されているクラスを調べよう.

ラインタイムにNoClassDefFoundErrorや NoSuchMethodErrorを起こしたクラスを調べよう.

Page 41: Real World Android Akka - 日本語版

Proguard-Con�gは最終的にこんな感じ:# Akka configuration # Akka 2.3.16 ## for akka.actor ### Classes used in reference.conf #### akka.actor.provider -keep class akka.actor.LocalActorRefProvider { *; }

#### akka.actor.guardian-supervisor-strategy -keep class akka.actor.DefaultSupervisorStrategy { *; }

#### akka.actor.mailbox -keep class akka.dispatch.UnboundedMailbox { *; } -keep class akka.dispatch.BoundedMailbox { *; } -keep class akka.dispatch.UnboundedDequeBasedMailbox { *; } -keep class akka.dispatch.BoundedDequeBasedMailbox { *; }

#### akka.actor.mailbox.requirements -keep class akka.dispatch.BoundedDequeBasedMessageQueueSemantics { *; } -keep class akka.dispatch.UnboundedMessageQueueSemantics { *; } -keep class akka.dispatch.UnboundedDequeBasedMessageQueueSemantics { *; } -keep class akka.dispatch.DequeBasedMessageQueueSemantics { *; } -keep class akka.dispatch.MultipleConsumerSemantics { *; }

#### akka.scheduler.implementation -keep class akka.actor.LightArrayRevolverScheduler { *; }

Page 42: Real World Android Akka - 日本語版

Proguardの設定でapkファイルのAPIメソッド数の比較In akka.actor and akka.io 2.3.16 (and Scala 2.11.11)

Proguard setting # of methods index

APKsize

Akkaの全クラスを残す 35033 1132KB

15784 590KBAkkaのクラスのうち、リフレクション、confファイルからの参照のみ残す

注意: レポジトリで比較した結果で、もちろんどの程度Akka, Scalaのクラスを利用するかに寄って最終的な差は変わります

サンプル

Page 43: Real World Android Akka - 日本語版

AndroidはJava8をサポートするのか?GoogleはAndroidのdex toolchainがJava8をサポートする旨アナウンスしている.

Java 8 Language Features Support Update - Android Developers Blog

とはいえ、Scalaと組み合わせてちゃんと動くのかはまだ分からない。

Page 44: Real World Android Akka - 日本語版

もしAndroidがJava8サポートしたらScala 2.12以上

Scala標準ライブラリjarが小さく。Dotty(Scala 3.0)が 由来のcallgraph analysisによって、dead codeをなくせるかも. (Proguardが不要に!)

Akka Streams (Akka 2.4)

�ow graphの抽象化と, 扱いやすいback pressure管理。

Akka Typed (2.5)

Backo�Supervisor (2.4以上)

Backo�SupervisorとThreadLocalRandomをbackportすることもさほど難しくない

Dotty-Linker

Page 45: Real World Android Akka - 日本語版

メモリ管理Android GCはConcurrent Mark & Sweep.

WrappedArray#mapやその他の高階関数はprimitive型をboxingすることがある

特にコピーが走りやすい場所ではring bu�erが良い

ByteStringはconcatenationとslicingに効率が良い

狭いスコープでmutableなオブジェクト/コレクションを使うのは悪くない選択肢.

xuwei_k/nobox

Page 46: Real World Android Akka - 日本語版

結論ActorモデルはAndroidでもうまく働く。特に ステートフル な並行プログラミングにおいて。

Akkaの障害からの復旧力は(アウトドアのような)エラーの起きやすい環境では役立つ

BONX AndroidアプリはAkkaの特長を活用してVoIPシステムを構築している.