Upload
taisuke-oe
View
2.524
Download
0
Embed Size (px)
Citation preview
Real WorldAndroid Akka
Taisuke Oe
自己紹介?麻植泰輔 / TaisukeOe
GitHub: / Twitter:
お仕事
セプテーニ・オリジナル 技術アドバイザーとしてPullReqレビュー/新卒研修
座長
taisukeoe @OE_uia
BONX Android App
ScalaMatsuri
次回のScalaMatsuri2018年3月予定!現在日程調整中。お楽しみに!
今日話すことVoIPクライアント
VoIP : Voice Over Internet Protocol
VoIPサンプルtaisukeoe/VoIPAkka
Real World Example
BONXとは?アウトドア用の通話システムスノーボードスキー釣りサイクリング...
BONXアーキテクチャアプリ
VoIPクライアントAndroid : ScalaiOS : Objective C / Swift
サーバー
APIサーバー : Ruby on RailsVoiceサーバー : golang
Bluetoothヘッドセット
Android Akkaを使ったワケ
VoIPはステートフルな非同期ストリーム処理
Upstream
Recorder -> some DSP -> Encoder -> Socket
Downstream
Socket -> Decoder -> some DSP -> Player
DSP : Digital Signal Processing
全てのコンポーネントは状態が有り、更に スレッドセーフではない.
求められる障害からの回復力(Resiliency).アウトドアでは、様々なエラーに遭遇する
不安定なネットワーク圏外<->圏内間の度重なる移動ハードウェアI/OエラーBluetooth切断
アウトドアで遊んでいる最中は画面操作できないので、自動復旧が肝。
更に悪いことに:エラーは必ずしも問題のコンポーネントで捕捉されない。
下流で気づくことがあるすなわちエラーを逆方向に伝搬させたい
問題のあるコンポーネントは、壊れた音声データを生むかもしれない
そしてノイズの原因になりうるすなわちエラーの種類によっては、キューにたまった音声データを消さなければいけない(逆もまた然り)
まとめ: BONXの要求仕様ステートフルな非同期ストリーム処理障害からの回復力
そこでAkkaの出番!Akkaは 並行プログラミング、障害からの自動回復
... そして分散システムを構築するためのtoolkit.
(ステートフルな) 並行プログラミングのためにメッセージ駆動なアクターモデル
アクターは、状態を内部に閉じ込めたまま、並行プログラミングが容易
状態を内部に閉じ込めた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() } }
Akkaによる障害からの回復力(Resiliency)例外のハンドリング
Actorヒエラルキー
子アクターをsupervisorStrategyにもとづいてRestart, Stop, ResumeこれによるRestartは メッセージボックスにメッセージを貯める.
モニタリング (Death watch)
監視対象のアクターがstopすると、監視側のアクターはTerminated(actorRef) メッセージを受け取るので、そこで再生成するこれによるアクター再生性は メッセージボックス内のメッセージを破棄する.
アクターによる例外のハンドリングのサンプル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 } }
Recaps: なぜAkkaがうまく働いたのかステートフルな並行プログラミング
アクターの状態のカプセル化 / 一度に一つだけメッセージを処理するのでロック不要
障害からの回復力
アクターのヒエラルキーないしはモニタリングで管理
AkkaによってVoIP問題が「だいたい」解消した!
なぜ「だいたい」なのか ?ものによっては例外として検知しにくい問題がある
オーディオドライバが場合によっては(ブロックしたまま)沈黙し、何も返さないことがある
銀の弾丸はない
Askパターンで、Ack が Timeout 内に返ってくるかチェック。
もし Ask がタイムアウトしたら、stopし再生成。
とはいえ、まずは「例外」として問題を検知する方法を探るべき
あまりオススメはしない
どうしてScalaがAndroidで使えるのか?
ビルドツールチェインscalac がScalaのソースをJava bytecodeにコンパイル
Android SDK の dex が Java bytecode を Dalvik Executable bytecodes に翻訳
Android標準のビルドシステムの主要機能は抑えているsbt pluginと組み合わせ等が柔軟
scala-android/sbt-android
Android端末のスペックも上がってきたここ数年の�agship級端末だと、最低でも:
4 to 10 cores CPU3 to 4 GB memorieslargeHeap=true で512MB程度使える
Q. じゃあScala Android使うべき?万人にオススメはしない 技術にはトレードオフがつきもの
Pros & Cons
Pros豊富な言語機能とエコシステム ecosystem.
並行プログラミングコレクションAPI代数的データ型とパターンマッチトレイト
生産的
0.5 ~ 1.2 人で2年間新機能開発と運用まわしてました
サーバーサイドもScalaだと頭の切り替え不要で楽
他にScalaで嬉しい話(View以外の)ステートレスな並行プログラミング
Viewの修正はUIThreadで行うため、Viewについては排他制御を考える必要性が薄い
Scalaの並行プログラミングAPIは優秀Scala標準のFuture悪くないですよ.UIThreadで実行するためのExecutionContextを定義して onComplete, andThen 等に明示的に渡せる
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!
その他のScala Androidプロジェクトたちセプテーニ・オリジナル
hits 6 million DLs!
47 Degrees
Akkaモジュール有り.
GANMA!
47deg/macroid47deg/nine-cards-v247deg/scala-days-android
pocorall/scaloid
Consdexファイルあたり、メソッド数 64k の上限.
Scala標準ライブラリのjarはちょっと大きすぎる.ProguardかMultiDexが必要.
Java8 supportなし
Android SDK toolchain はいまだ Java6+ 環境を対象.Akkaのバージョンは2.3.16まで。ただしEnd-of-Lifeのマーク付き.
メモリ管理もある程度気にしたい
GoogleもLightbend公式サポート無し
ProguardProguardは使用されていないクラス、フィールド、メソッドを除くポストプロセシングツール.
使用されていない とは 参照されていない.Proguard はリフレクションを解しない.
AkkaはリフレクションをPropsや.confで多用している.
もしProguardが正しく設定されていなければ, NoClassDefFoundErrorないしはNoSuchMethodErrorが ランタイムに投げられる.
Android Scalaをやる上でとても苦痛な作業.
とはいえ、Proguardの設定って最初から全部自分でやらなきゃいけないのか?
そんなことはない
Scala標準のライブラリ用のProguard設定は組み込み済
akka.actor と akka.io パッケージについては作りました
scala-android/sbt-android
proguard-con�g.txt for akka-actor v2.3.16
新しいライブラリについてProguardの設定を簡単にやる方法(手抜き編)
実際とりあえず試すにはこれで十分ライブラリが十分小さい限りにおいては.例えば、Akkaについて同じ方法で設定するなら、proguard-con�g.txtに以下の一行を足す:-keep class akka.** { *; }
後でちゃんと設定しましょうメソッド数が64kを超えたタイミング.
MultiDexで乗り切るという手もあるアプリをリリースする直前にapkサイズを削りたいとき
Proguardの設定方法(推奨)リフレクションで参照されているクラスを調べよう
grep -r "classOf" . が最も簡単
FQCN(フル修飾名)が.confファイルなどの設定ファイルに記述されているクラスを調べよう.
ラインタイムにNoClassDefFoundErrorや NoSuchMethodErrorを起こしたクラスを調べよう.
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 { *; }
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のクラスを利用するかに寄って最終的な差は変わります
サンプル
AndroidはJava8をサポートするのか?GoogleはAndroidのdex toolchainがJava8をサポートする旨アナウンスしている.
Java 8 Language Features Support Update - Android Developers Blog
とはいえ、Scalaと組み合わせてちゃんと動くのかはまだ分からない。
もし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
メモリ管理Android GCはConcurrent Mark & Sweep.
WrappedArray#mapやその他の高階関数はprimitive型をboxingすることがある
特にコピーが走りやすい場所ではring bu�erが良い
ByteStringはconcatenationとslicingに効率が良い
狭いスコープでmutableなオブジェクト/コレクションを使うのは悪くない選択肢.
xuwei_k/nobox
結論ActorモデルはAndroidでもうまく働く。特に ステートフル な並行プログラミングにおいて。
Akkaの障害からの復旧力は(アウトドアのような)エラーの起きやすい環境では役立つ
BONX AndroidアプリはAkkaの特長を活用してVoIPシステムを構築している.