Celeste Engineer

Androidとか自転車とか

ビーナスライン

最近は毎日30度超えの真夏日で、自宅から日帰りで行って帰ってこれる場所では暑すぎて熱中症待ったなしだと思ったので、電車輪行の練習も兼ねて長野県茅野市から松本市に至るビーナスラインを走ってきました。 電車での移動距離もそこそこあるので、特急に乗ってシュッと行ってシュッと帰ってきました。調べてみるとスーパーあずさは電車輪行しやすそうだったので、行き帰りともにスーパーあずさのチケットを取りました。

朝いちのスーパーあずさで立川から茅野まで行き、そこからまっすぐビーナスラインを走りました。

まずは茅野から早速ずいずい坂道を登って蓼科湖に。

f:id:KeithYokoma:20170716102041j:plain

蓼科湖で少し休憩と軽食を取って本格的な山登り。

f:id:KeithYokoma:20170716111704j:plain

途中の見晴らしのいい駐車場でハリボーをムシャムシャ。

f:id:KeithYokoma:20170716112240j:plain

白樺湖を超え、車山高原へ向かうところの駐車場で白樺湖をのぞみ休憩。

f:id:KeithYokoma:20170716115907j:plain

逆側も。

f:id:KeithYokoma:20170716120145j:plain

車山高原では鹿肉のジビエバーガーとソフトクリーム、コーヒーでお昼。

f:id:KeithYokoma:20170716122020j:plain

スッと登って途中の駐車場。

f:id:KeithYokoma:20170716130802j:plain

更に奥地へ。

f:id:KeithYokoma:20170716140949j:plain

360度大自然がのぞめるパノラマ!!

f:id:KeithYokoma:20170716141602j:plain

f:id:KeithYokoma:20170716141615j:plain

本当はさらに美ヶ原の北をぐるりとまわりたかったのですが、天候と体力と時間の関係で手前で松本に降りることに。 降りてきてからは松本城を見たり旧開智学校を見たり、大道芸を見たりしました。

f:id:KeithYokoma:20170716153310j:plain

f:id:KeithYokoma:20170716154034j:plain

無事に松本駅に到着。一瞬だけ市内で雨に振られましたが、それ以外は概ね涼しく心地よい天気でした。

f:id:KeithYokoma:20170716163402j:plain

初めての電車輪行でしたが、良い感じに一日を過ごすことが出来ました。そして一度走ってみたかったビーナスラインを自転車で走ることができてとても楽しかったです!また行きたい!!(次はもう少し身軽にして…)

<merge> タグをつかったレイアウトのプレビューを期待通りに表示する

Issue

Fragment を使わず View をベースにしてレイアウトを組むと、Fragment と同じような役割をもった CustomView を作ってレイアウトを組みます。 このとき CustomView は何かしらの ViewGroup を継承することになりますが、そのレイアウトファイルのルートを<merge>にしないと無駄な ViewGroup がひとつ挟まってしまいます。一方で、<merge>をそのまま使うと、レイアウトのプレビューでは親が何になるのかわからないため、期待通りの表示ができません。

Solution

tools 属性に<merge>の親が何になるかを指定するtools:parentTagが AndroidStudio 2.2 から増えています。

<merge
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:parentTag="android.widget.LinearLayout">
    <!-- views -->
</merge>

tools:parentTagには親となる ViewGroup の名前を指定しますが、Android フレームワークの ViewGroup であっても FQCN をつかいます。またandroid:layout_widthandroid:layout_heightを設定しないとプレビューがうまく表示できないことに注意しましょう。

See Also

stackoverflow.com

Kotlin でかいた interface を Retrofit に食わせたときのエラーに対処する

TL; DR

Kotlin & @Body · Issue #1805 · square/retrofit · GitHub

問題

Retrofit で REST API のクライアント実装を生成する時、Kotlin で書いた interface を渡すとき、メソッドにジェネリクスを使った引数がいるとまれに次のようなエラーメッセージを吐き出してクラッシュします。

java.lang.IllegalArgumentException: Parameter type must not include a type variable or wildcard: java.util.List<? extends Something> (parameter #4)

たとえ Kotlin のコード上ではワイルドカードを使っていない場合でも、Java に変換する過程でワイルドカードに翻訳*1されることがあり、このような問題が発生します。

対処

@JvmSuppressWildcardsというアノテーションを使います。メソッドに対してこのアノテーションを付けることで、Java への変換時にワイルドカードを使わないようになります。

Robolectric 3.4 RC3 で dependencies の ArtifactId が変わります

Robolectric にはいくつかのサブプロジェクトがあり、それぞれに dependencies を追加する必要があります。Robolectric ではサブプロジェクト名が ArtifactId になっています。 そして 3.4 RC3 から ArtifactId が変更になったものがあります。主には、robolectric-というプレフィクスが取れたり、shadows-というプレフィクスが取れるなどの変更ですが、shadows-coreframeworkというようにガラリと変わったものもあります。

3.3.2 まで

dependencies {
  testCompile "org.robolectric:robolectric-annotations:3.3.2"
  testCompile "org.robolectric:robolectric-junit:3.3.2"
  testCompile "org.robolectric:robolectric-processor:3.3.2"
  testCompile "org.robolectric:robolectric-resources:3.3.2"
  testCompile "org.robolectric:robolectric-sandbox:3.3.2"
  testCompile "org.robolectric:robolectric-utils:3.3.2"
  testCompile "org.robolectric:shadows-core:3.3.2"
  testCompile "org.robolectric:shadows-httpclient:3.3.2"
  testCompile "org.robolectric:shadows-maps:3.3.2"
  testCompile "org.robolectric:shadows-play-services:3.3.2"
  testCompile "org.robolectric:shadows-multidex:3.3.2"
  testCompile "org.robolectric:shadows-support-v4:3.3.2"
}

3.4 RC3 から

dependencies {
  testCompile "org.robolectric:annotations:3.4-rc3"
  testCompile "org.robolectric:junit:3.4-rc3"
  testCompile "org.robolectric:processor:3.4-rc3"
  testCompile "org.robolectric:resources:3.4-rc3"
  testCompile "org.robolectric:sandbox:3.4-rc3"
  testCompile "org.robolectric:utils:3.4-rc3"
  testCompile "org.robolectric:framework:3.4-rc3"
  testCompile "org.robolectric:httpclient:3.4-rc3"
  testCompile "org.robolectric:maps:3.4-rc3"
  testCompile "org.robolectric:playservices:3.4-rc3"
  testCompile "org.robolectric:multidex:3.4-rc3"
  testCompile "org.robolectric:supportv4:3.4-rc3"
}

Wercker での Android アプリの CI を速くするポイント

CI の速さは開発のプロセスを高速化する上で重要です。Android アプリのビルドはどうしても時間のかかる部分が多く数分から十数分の時間を要してしまいますが、工夫次第では数十秒から数分の短縮が可能です。 プロジェクトの規模や構成にもよるので一概にすべて効果があるとは言いにくい部分もありますが、この記事で取り上げる幾つかのポイントを抑えておくと、CI の高速化に役立つと思います。今回は Wercker を使用した場合の高速化の方法を書きます。

1.ビルドキャッシュ

Wercker の場合、WERCKER_CACHE_DIRという環境変数にキャッシュのためのディレクトリ情報が入っています。ここにビルドキャッシュを放り込むことで、複数ステップで gradlew コマンドを叩いたときにあとで実行するコマンドが速くなります。また、dependencies の artifact をキャッシュすることで、次回以降のビルドで依存解決が高速になります。

ビルドキャッシュをWERCKER_CACHE_DIRに放り込む場合は、gradlew コマンドのオプションに--project-cache-dir=$WERCKER_CACHE_DIRを指定します。

dependencies の artifact をWERCKER_CACHE_DIRに放り込む場合は、ビルド後のステップで~/.m2~/.gradleWERCKER_CACHE_DIRに cp するか、ビルド前に~/.m2~/.gradleWERCKER_CACHE_DIR配下のディレクトリを指すようシンボリックリンクを作るかのいずれかの方法があります。

依存するライブラリが多くなればなるほど効果が出ます。最近は JCenter が不安定なのか dependencies がダウンロードできなくてビルドがコケるという事象が日に何度か起こっていて、その失敗を解消することにも役立ちます。

2.マルチモジュール化

巨大なモジュールをモリモリビルドするのではなく、小さなモジュールを並列でビルドするようにします。最近の Gradle プラグインはマルチモジュールのビルドについて改善が入っているので、並列ビルドで起こりがちな問題もある程度はうまくやってくれます。キャッシュが効くとなお速いので、ローカルマシンでのビルド時間のほうが改善するかもしれません。

3.メモリ割り当て

Androidアプリのビルドはとにかくメモリが重要です。何はなくともメモリだけは広く確保する必要があります。MacBook Pro など多くのラップトップマシンでは 16GB が上限となりますが、Wercker では(2017年6月現在のところ)コンテナごとにメモリの上限は設定していないようですので、思い切って 32GB 割り当てるなどという富豪的な使い方ができます。特に build.gradle で指定するdexOptionsdexInProcessを有効にしたとき、javaMaxHeapSizeが大きくないと時間がかかってしまいます。CIという環境変数trueのときは32gなど大きな数字を割り当て、そうでないローカルマシン等では8gなどになるような柔軟性があるとよいです。

dexOptions {
  dexInProcess true
  javaMaxHeapSize "true".equals(System.getenv("CI")) ? "32g" : "8g"
}

大抵の CI as a Service ではCI環境変数が用意されているはずです。サービスによっては環境変数の値が真偽値の場合とCIサービス名の場合とがあるので注意が必要です。 また、dexOptionsjavaMaxHeapSizeを指定しても、gradle.propertiesorg.gradle.jvmArgsに何も指定しないと「もっと大きなヒープを使わないと意味が無いぞ」という警告が出ます。併せて、次のようにJVMが使える領域を大きくします。

org.gradle.jvmargs=-Xmx33280M

ただし、この記述をそのままリポジトリに放り込むと、そんなにたくさんメモリのないマシンで困ることになります。できれば CI でだけ大きな領域を確保したいので、リポジトリに入れる gradle.propertis にはローカルマシンで確保可能な数字にしておき、CI ではビルドステップの前に次のようなコマンドを実行するようにして大きな領域を確保するようにします。

- script:
  name: set up environment
  code: |
    echo -e "org.gradle.jvmargs=-Xmx33280M\nandroid.enableBuildCache=true\norg.gradle.parallel=true\norg.gradle.caching=true\norg.gradle.configureondemand=true\n" > gradle.properties

4.テストの並列実行

アプリの機能が増えればその分テストも増え、テストを実行する時間も長くなります。testOptionsでテストを並列実行するための設定項目があるのでこれを使います。ただし、並列実行すると壊れるテストも中にはあるかもしれませんので注意してください。

testOptions {
  unitTests.all {
    maxParallelForks = 2
    forkEvery = 150
  }
}

maxParallelForksが何並列で動かすかを決めるパラメータで、forkEveryJVM を再起動するタイミングをいつにするかを決めるパラメータです。この設定の場合、2並列で150個のテストケースを実行するごとに JVM を再起動します。

併せて、テスト実行時のメモリについても設定しましょう。次の例では Java 8 のランタイムでテストを動かすことを想定しています。

testOptions {
  unitTests.all {
    maxHeapSize = '8192m'
    jvmArgs '-XX:MaxMetaspaceSize=8192M', '-noverify', '-Xmx8192M'
  }
}

5.ビルドステップの整理

大抵の CI as a Service では、ビルドそのものや個々のビルドステップごとにタイムアウトが設定されていて、一定時間を過ぎてもコマンドが終わらないと失敗とみなされます。このため、時間のかかるステップを小分けにしてタイムアウトを回避するような対策をとることがあります。一方で、ステップを小分けにすると、毎回 gradlew を叩いてプロセスを起こすので、少し無駄な時間がどうしてもできてしまいます。先述のビルドキャッシュである程度は改善できますが、これまでの高速化ポイントで速くなったステップは、タイムアウト以内に納まるのであればマージしましょう。自分の場合、assemble と test でそれぞれ別のステップを作っていましたが、それぞれタイムアウト以内に十分納まる時間で終わるようになったのでマージしました。

ツールド美ヶ原

先月たまたま申し込み期限ギリギリに申し込んだツールド美ヶ原に行ってきました。 激坂があり、それを乗り越えた先には雄大な自然!気持ちよさそう!!楽しそう!!!という勢いで申込んだわけですが、梅雨真っ盛りとあって天気は週間予報からあまり良くなく、実際当日の早朝は雨が降っていました。 開会式が始まる頃には雨もやんでいて、頂上付近は曇りと言われていました。ただ、スタートしてしばらくは雨に濡れたウェットな路面でしたので、注意深くいくことに。

スタートが切られて、いざっと思ったらいきなりすごい激坂でした。16%とか18%とかいう鬼のような坂をいきなりぶっこんでくるのはすごいですね。しかもグレーチングがところどころにあるので油断ならない…とおもっていたら、シッティングでペダルを踏んだときのトルクがかかる瞬間にグレーチングを踏んでしまったようで、ズルッと後輪がすべりました。幸い転ぶことはなく、最初の難関はなんとかクリアすることが出来ました。ただ、10%後半だった坂が10%前半に落ち着いたというだけで、キツイことには変わりないのですが😭美鈴湖手前でスタッフのおじさんが「もうすぐ激坂おわるよ!」と声をかけていましたが、踏めども踏めども10%とか13%とかの坂が続いて泣きそうでした😂

途中、クリートのネジが緩んでいることに気がついて、第二給水ポイントで締めなおすことに。ロングライド装備をほぼそのままヒルクライムにもっていったのが役に立ちました。おかげで重たかったですが😒 ツールド美ヶ原の給水ポイントは3つありますが、全部走りながら水のはいった紙コップを受け取れて、回収もバッチリスタッフさんが面倒を見てくれる親切設計でした。

そういえば、悪魔おじさんに扮した悪魔お兄さんが走っていました。途中で追い越したのですが、周りの人全てに「キャノンデール!アレ!アレ!」とか「ビアンキ頑張れ!」とかと声をかけていました。下山時にはゴール地点であとから来る人達も応援していて、悪魔お兄さんの本気度が伝わってきました。

最後の給水ポイントからはガンガン踏んでいきました。平坦路と若干のくだりもあってスピードに乗りつつ、無事にゴールしました。タイムは1時間46分だったようです。 ゴール地点では冷やしトマトの差し入れをいただき休憩後下山しました。

序盤からきっつい坂が続く厳しいイベントでしたが、また来年もきたいですね!

Again: AsyncLayoutInflater vs Litho #potatotips

前回 は実装が悪くて完全に Litho が負けていましたが、ある程度動かせるものができたので、potatotips で発表してきました。

speakerdeck.com

結論から言えば、これでもやはり AsyncLayoutInflater のほうが速いです。「Litho は既存の XML によるレイアウトを完全に置き換えるものではない」との言葉通り、適用箇所をきちんと見極めないと意味がないよね、ということを身にしみて感じました。

前回の計測で抱えた問題

Litho は RecyclerView を念頭に置いているため、今のところ単なる ScrollView に相当するコンポーネントを持っていません。LinearLayout 相当のコンポーネントに子 View を足しても、与えられた領域のなかに子 View がおさまるよう計算するだけで、これが前回の計測で問題となった点でした。子 View が多くなると、子同士がオーバーラップし始め、その分無駄に計算しまくるためです(ログにもそのようなメッセージが表示されます)。

どう改善したか

ScrollView を親にその下で Column を設定しました。これで画面内にびっしり Text が敷き詰められるようなことがなくなり、計算がシンプルになって高速化しました。

ただしまだ問題はある

スクロールすると、初回描画領域外にあった Text が見えません😂。