DAY2の参加カンファレンス
1.中規模以上のアプリ開発におけるCIレシピとリリースフロー戦略
2.Guide to app architectureを踏まえた既存アプリの設計改良
3.All About Test of Flutter
4.Android Studio設定見直してみませんか?
5.今日から始める依存性の注入
6.Androidにおけるパフォーマンスチューニング実践
7.UIテスト(Espresso)の高速化をさらにすすめる
8.FlutterでのWidgetツリーへの状態伝播とアクセス制限の基本戦略
9.Android アプリ開発における、デザイナーとエンジニアのワークフロー
中規模以上のアプリ開発におけるCIレシピとリリースフロー戦略
CIで安定・定期的に運用するには、属人化しないリリース方法が求められる。
テスト・デプロイ・リリースを自動化して、開発に集中出来る。
GitFlowの導入
GitFlowとはGitの拡張。
ヴィンセントが提唱したブランチモデル。
GitFlowで用いるブランチ
-
hotfix
緊急バグ対応。
masterから切る。
releaseブランチとほぼ同じ扱い。
masterとdevelopにマージする。 -
releaseリリース準備用
developから派生してリリースブランチを作る。
開発が終わったタイミングで作る。
この時点でdevelopは次のバージョンのブランチ開発を行う。
masterとdevelop両方にマージする。
リリース時にタグを切る。 -
master
リリースしたものが並ぶ -
develop
開発用のメインブランチ -
feature
単一の機能開発
featureが巨大になった場合はそこをベースとしてこまめにブランチとプルリクを作る。
これはGitFlowには定義されていない。
CIツールとブランチの対応
- 人間のやる必要のない作業はCIツールに任せる。
- GUIで組めるツールとしてbitriseがある。
- yamlをリポジトリを含める必要がない。yamlにトークンとかを書く場合もあるので、秘匿情報を出さないことが出来る。
- アプリに特化している。
- エディタがよく出来ていて直感的に出来る。
CIツールとGitFlowの連携
- 本番環境とステージング環境。
- GitFlowとかみあうようにワークフローを作る。
- プルリクエストを作った時にテストが走るようにする。
- developにプッシュした時は機能開発の区切りなのでapkを作成する。
- リリース時にはタグをつけてdevelopとmasterに反映する。タグ付けたをトリガーとして実行する。
- hotfixは修正した時にreleaseとdevelopへ反映する。そうするとステージングと開発環境でも確認出来る。
2.Guide to app architectureを踏まえた既存アプリの設計改良
今回のターゲットは既存プロダクトのマイグレート
設計面での課題と解決方法
- ViewにView以外のソースが詰め込まれている
設計パターンにMVVMを用いてView(Activity,Flagment)を
ViewとViewModelに分ける。
ViewModelはActivityより生存期間が長い。
ViewとViewModelの間のインターフェースを明確にする。
ViewModelのデータには揮発性の「イベント」と
不揮発性の「状態」がある。
・イベント
- SingleLiveEvent(LiveDataを継承したもの)
- observeしても〜されない
- onCreateでobserveで監視する処理並べていく
・状態(読み込み中のステータスなど画面に反映出来るもの)
- レイアウトの更新はDataBinding
- UIイベントもDataBindingでViewからViewModelに通知
- onCreateでDataBindingの初期化を行う
ViewとViewModelの間はSingleLiveEventとDataBindingだけ。
-
Activityに組み込んだビジネスロジックのテストが出来ない
MVVMを用いることによってビジネスロジックを切り出せるので
テストが出来るようになる。 -
画面回転などActivityの再生成への弱さ
savedInstanceStateをViewModelに持たせることでActivityやプロセスが破棄されても
データを保持し続けられる。
まとめ
画面で大量の入力項目があるとView(Activity)が膨らんでいくが、MVVMならかなり改善出来る。
3.All About Test of Flutter
Flutterで出来るテスト
1.ユニットテスト
単一の関数やモジュールをテストする。
ホスト Dart VMでテスト。
test() 1テストケース
group()でtest()をグループ化出来る
skip:でgroup・test・expectをスキップすることが出来る。未実装のところに入れる。
テスト実行時はok扱いになる。
特定のtest()やgroup()だけテストできる
CIからの実行は
flutter test
特定のソースのみ実行は
flutter test(パス)
指定したテストケース
flutter test --name 正規表現の記述
setupAllでテストスイート(≒ファイル)の一番始めに一度だけ実行される
setupで各テストケースの前に実行される
teardownで各テストケースの後に実行される
tearDownallでテストスイートの一番最後に一度だけ実行される
addTearDownで特定のテストケースのあとだけの記述が出来る
2.ウィジェットテスト
単一のウィジェットをテストする。
ホスト Dart VMでテスト。
単一Widgetのテストをする。
複数Widgetの相互作用確認も出来る。
test()ではなくtestWidget()でテストを登録する。
ラムダで、第2引数はWidgetTesterを指定する。仮想画面。
pumpWidgetはawaitする。
pump()でフレームを進める。
WidgetTestのTips
MaterialAppの中にDirectionalityでラップしてやらないと
Textをテスト出来ない
デフォルトの仮想画面サイズは800*600
現在はAndroidのInstrumentedTest(Roborectric)のAPIほど充実していない
3.統合テスト
アプリそのものの挙動と複数のモジュールの相互作用をテスト。
実機でテストする。
AndroidのInstrumentedTestとほぼ同等。
ドライバを有効にしたアプリをインストール。
FlutterDriver経由で操作しつつテストケースを実行。
テスト結果を通知する。
依存設定をflutter_driverとtest_coreをyamlに書く必要がある。
testフォルダではなく。
test_driverフォルダにテストをかく
flutter_driver/driver_extention.dartをimport
mainのdartもimport。
FlutterDriverというクラスがある
テストスイート起動時にFlutterDriver.connct()で接続する
終わったらcloseする。
Textにkeyというものを指定する、それはテストでウィジェットを探すために使う。
ButtonなどにもKey属性がある。
find.byValueKeyで探す。
これで統合テスト出来る。
コマンドラインからの実施はflutter drive --target=test_driver/main.dart
IntelliJでは現状コマンドラインから実行できない。
flutter driveはデフォルトではlib/main.dartをインストールしてテストをしようとする。
当然そこにはテスト用コードは置けないのでtest_driverフォルダを作っている。
依存性注入
dependeeは本番ではサーバに通信するクラスだが
テストではモック化して失敗したときの動作を確認したい。
本物とモックをとりかえたい。
DepenDerがDepenDeeに依存している。
コンストラクタで外部から依存対象を渡すことで解決する。
テストのコスト
コストを感じるのは難しい時。
確認の簡単なテストの割合を増やす(ユニットテスト70%,ウィジェットテスト20%,統合テスト10%
MethodChannelのテストをたんたいだけでカバーすることはできない。
自動テストと人力テストで損益分岐点を超えるまで自動テストをつづけられるかどうか考える。
自動テストは初期コストが大きく、継続コストが小さい。
長期的に運用して、人力テストのコストをした回れるかが重要。
変わりづらいDomain層のテストを増やすとかでコストを減らす。
4.Android Studio設定見直してみませんか?
5.今日から始める依存性の注入
Dependency Injection(DI)とは? Androidアプリ開発を例に紹介
コンポーネント間が疎結合になりテストを書きやすくなる
Androidアプリ開発でよくあるパターン
MVP(Model、View、Presenter)
DAOとクライアントをコンストラクタで受け取るようにすることで
インターフェースだけを知っている状態になり密結合が解消されている。
しかし中でnewしている。
グローバルに一つだけ(シングルトン)存在するコンテナを作ることによって解消出来る。
これがDI。
ActivityはPresenterに依存している。
ActivityやFragmentはコンストラクタを自分では追加出来ない。
Presenterが密結合している状態。
onCreateの中でインスタンスを作っているがこれも密結合。
→これもContainer経由で取得するようにして解決する。
DIにはスコープがあり、SingletonやActivityと同じ生存期間など
ライフサイクルを指定出来る。PresenterはActivityスコープ。
スコープは生存期間をあらわす。
Activityではそれに対応するインスタンスを取り出してそこに注入する。
DIコンテナの基本的な使い方
DIコンテナはDIを簡単に実現するためのライブラリ。
さきほどのは自分で作ったもの。
ライブラリにはDagger2やKoinがある。
-
Dagger2
Dagger2はSquareが開発。現在はGoogleがfork。
AnnotationProcessorをつかっているのでビルドが遅い。
Dagger2はドキュメントが難しい。
Dagger2はかなり多機能。
普通のJavaでも使えるし、DaggerAndroidもある。
おまじないコードがたくさんあるが理解する必要はない。
誰がみてもすぐには構造を理解できないので、実装する時は、
カンファレンスの資料や色々なサイトの実装を見ながら行う。 -
Koin
kotlinで書かれたDIコンテナ。
Annotation processorを使っていないのでビルド速度に悪影響はない。
ドキュメントがわかりやすい。公式のドキュメントだけで実装出来る。
特に良い点として、AAC(Android Architecture Components)=ViewModelに対応している。
Dagger2 & Koin DI導入後のテストの書き方
-
単体テスト
テスト対象でないApiClientまでテストすることによって
そっちに不具合があったらRepositoryの方も通らなくなる。
テストしたいのはRepositoryのみ。
MockkやMockitoでApiClientをモック化してテストする。
APIクライアントのgetUser()でなにがわたされても同じ振る舞いをする。 -
結合テスト
EspressoでUIテストを書く。
APIクライアントをモックに差し替えて実行できるようになる。
常に特定のユーザが返るようになる。
テスト用の依存ツリーを作る。
Instrumented testのランナーをapp/build.gradleで指定することによって
モックを差し替えてテスト出来るようになる。
6.Androidにおけるパフォーマンスチューニング実践
パフォーマンスチューニングの対象となるもの
- UI表示が遅い
- データ通信量
- バッテリー消費量
→これらはアプリパフォーマンスの中で再現しづらい部類に入る。
これらはテストリリース後に発覚することが多い。
なぜリリースごに発生しやすいのか
環境の際がある、時間的制約で機能テストが困難
→数日使っていると出てくる不具合。
UIの改善
UIのかくつき。フレームレートが低い。
人間は60FPSが最適に感じる。
1000/60=16フレーム
アプリの起動=Linuxプロセスの起動
全てのタスクはメインスレッド(UIスレッド)で実行。
これらが一番大事。
16msをこえるとFrameがDropされる
解決方法
・タスクを16ms以下におさえる
・タスクを分散する(メインスレッド以外で処理させる)
ネットワーク処理とI/O処理
この2つをメインスレッド以外でやらせる(ワーカースレッド)。
結果をメインスレッドで受け取る。
しかし以下が起こる
・メモリ管理・リーク
スレッドをいつ生成し・いつ破棄するかは必ず決める。
アクティビティ破棄時に破棄していないとリークする。
解決するには
・ActivityにはLeakCanaryの利用
・MemoryProfilerツールを使う
ヒープ・メモリ量のタイムライン
特定のタイミングでアロケーとされたオブジェクト。これは結構重要。
ここでGCがやたら呼ばれているとそれは欠陥なので解消する必要がある。
・スレッド生成のオーバーヘッド
一度作られたスレッドを再利用する(RxJava)
スレッド自体を減らす(最大個数はCPUに依存。出来るだけ最大以上に作らないようにする)
CPU Profileツールの利用。
・LayoutInspectorというツールでCPU・GPUに負担を与える処理をアプリ画面を動かしながら
確認出来る。
自分のリソースは限られるので
利用頻度が高い画面で測定してそれを改善していく。
パフォーマンス改善の基準を設置する。
無駄な改善をしない。
・定量的な改善が難しいなら、基準を作る。
Instagramなど高品質アプリとの比較。
データ通信量
画像データが大きいのでそれを減らす。
シリアライゼーションのコスト。
直列化。特定の環境に依存したオブジェクトをバイト配列に変換すること。
・テキスト(json、XML)は可読性が高い。
しかし、無駄なデータが多い(リターンとか空白とか)。データ量が多い。
・バイナリ。可読性が低い。データ量が少ない。
Protocol BuffersはDSLのようなもので、バイナリ形式でデータ量が少ない
シリアライズを行う。
・画像のフォーマットに関して
基本WebPを使う。パフォーマンスが高い。
透過が必要ならPNG。
それ以外はJPEG。
・キャッシュ
Picassoを用いて
メモリキャッシュとディスクキャッシュを行い
ネットワークの通信処理が減るのでパフォーマンスが高くなる。
バッテリ消費
次の処理に備えるKeepAwakeのコスト
バッチ処理でAPI数回の実行をまとめて行う。
→サーバとのデータの同期性が厳密でないものを対象とする。
最悪、データが失われても問題ないもの。
WorkManagerクラスを使う。
Battery Historianでどこでバッテリ消費しているか分かる。
7.UIテスト(Espresso)の高速化をさらにすすめる
EspressoによるUIテストのCI/CDサーバでの実行時間を減らすには
-
テストケースの並列化(シャーディング)を行う。
AndroidのadbコマンドでnumShardsとsharedIndexを指定する。
並列処理する分の端末が必要。 -
テスト実行時間以外にかかっている時間を減らす
テスト内でapkをビルドするのではなく、事前に用意しておく。 -
並列環境を作るコストを減らす
デバイスファームの利用。OpenSTFで自前で端末を複数台揃えると
お金がかかるので、Firebase Test LabやAWS Device Farm
Visual Studio App Centerなどの機能を利用する。
Firebase Test LabではVirtualDeviceTests($1/deveice/hour)と
PhiscialDeviceTests($5/device/hour)が用意されている。 -
手動でテストケースを分割するコストを減らす
Flankというツールを利用することにより指定したシャード数に分けてテストを並列実行してくれる -
並列一つあたりの実行時間のばらつきをなくす
テストケースの時間が長いものが集まる場合があり、結果として並列実行が活かしきれない
その時の解決方法として、Flankで再度並列実行する時に、前回の並列実行内での各テストケースの
実行時間から、最適な実行時間となるようにテストケースの組み合わせをして、シャーディングを行う。
8.FlutterでのWidgetツリーへの状態伝播とアクセス制限の基本戦略
UIのネスト記述の解消をする
FlutterのWidgetのネスト構造は、単一のWidgetを組み合わせて画面を作るというのが
Flutterの思想思想なので解消するのは無理。
対応としては、Widgetを意味のある部品としてコンポーネント化したり、
単に入れ子になっている部分をメソッド化する。
ページ間の値受け渡しを出来るようにする
StatelessWidgetやStatefullWidgetなどのWidgetは
ツリー構造の下のWidgetが先祖の変更を感知して、自身をリビルドするしない
の判断は出来ない。
InheritedWidgetを使えば、それが出来るようになる。
この動作を使えば、通常は出来ないページ間の値やメソッドの共有が出来るようになる。
ScopedModelやBLoCの設計パターンはInheritedWidgetを利用している。
9.Android アプリ開発における、デザイナーとエンジニアのワークフロー
マテリアルデザインだと誰がやっても同じようなデザインになってしまう。
マテリアルデザインを利用してエンジニアでもある程度デザインが出来てしまう。
・現場では以下の問題が起こっている
デザイナが作ったデザインをエンジニアが実現できない
iOSのコンポーネントとまざっている
マテリアルデザインに準拠しすぎている
→デザイナとエンジニアがより手を取り合ってやっていく
Material Design Component
マテリアルデザインを各プラットフォームで簡単に実装する為のフレームワーク。
Android、iOS、Flutter、Web版がある。
Gallery
Webサイト。
デザイナとエンジニアのブリッジとなるツール。
Sketchのプロジェクトをアップロードすることで
デザイナがコメントをつけたり出来る。コラボツールとしても使える。
一つ一つどのマテリアルコンポーネントが使われているか分かる。
どのようなサイズが指定されていることも分かる。
AndroidとiOSの見え方を確認することが出来る。
Material Theming(スイミング)
エンジニアが使う。
マテリアルデザインを拡張したもの。
マテリアルデザインに会社のテーマを加える。
色とか以外にテーマを加えることが出来る。
→アプリが表現したい世界観に近づけられる。
各コンポーネントに色を加える
ボタンを四角くする
ダイヤモンドカットにするとか
これを使っているとGalleryを使いやすくなる
Material ThemingはSketchのプラグインとして用意されている。
Sketch以外にも展開予定。
全て16種のマテリアルコンポーネントが入った状態で提供される。
プライマリカラーやフォントの修正など、個別に修正しないで一括して変わるので作業効率があがる。
何か修正したらGalleryにプロジェクトとしてアップロードすることが出来る。
Googleの色々なアプリに使われている。
色やフォントが会社色を出しやすい。フォントが重要になってくる。
カットやコーナーをラウンドすることもできる。
トライアングル(1箇所だけかっとする)
まとめ
デザイナとエンジニアでお互いにフイードバックしていく。
お互いどのようなツールやライブラリを使っているかを確認する。
いきなりプロダクトにいくのではなくて実践してみるのが良い。
Q&A
Q.AndroidとiOSはiOSの方がユーザが多い。どっちともバランスよく作っていくにはどうすれば良いか?
A.OSネイティブはお互いのPFを尊重する。
→システムメニュー(設定メニューとかは)そのOSのものを使う、アイコンとか戻るボタンのデザインとか。