Androidのリモートビルドを行う

概要・結論

sys1yagiさんのブログ1などで興味を持って、Androidのbuildはマシンスペックで殴ろうというお話。結果として、開発で使っているMacbook Airでビルドするよりとても早い。

最新のGradle Pluginからは爆速っぽい2ですが、試せてないのと試そうとするとなんか罠に嵌りそうなので。

マシンスペック ビルド時間
CPU: 1.3GHz Intel(R) Core(TM) i5, Mem: 4GB 4min程度
CPU: 3.4GHz Intel(R) Core(TM) i7-2600 Mem:24GB 40sec程度(ファイルの同期時間:10sec, ビルド:30sec)

こんなひと向け

  • 開発で使っているPCのスペックが低くAndroidのbuildに1min以上かかっている
  • AWS, GCPなどで十分なマシンスペックのインスタンスを用いてでもビルド時間を短くしたい

リモートビルドの仕組み

ただ単純に高スペックなマシンを用意して、

  • そのマシンにビルドに必要なファイルを同期する
  • そのマシン上でbuild
  • その成果物をローカルマシンに同期する

ってことをしている。

Androidのビルドができる高スペックなマシン

AWSとかにインスタンスを立てることもなく、5年前に買ったPCのスペックで十分だったのでAWSとかには用意していない。
そのマシンにはDockerを起動していて、その上に今回のビルド環境のコンテナを作成した。
Dockerfileは下記のようなもの

FROM ubuntu:16.04

EXPOSE 22

ENV DEBIAN_FRONTEND noninteractive

ENV ANDROID_SDK_URL http://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
ENV ANDROID_HOME /usr/local/android-sdk-linux
ENV ANDROID_SDK /usr/local/android-sdk-linux
ENV PATH ${ANDROID_HOME}/tools:$ANDROID_HOME/platform-tools:$PATH

# Install Java.
RUN \
  apt-get update && \
  apt-get install -yq curl software-properties-common python-software-properties && \
  echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | debconf-set-selections && \
  add-apt-repository -y ppa:webupd8team/java && \
  apt-get update && \
  apt-get install -y oracle-java8-installer && \
  rm -rf /var/lib/apt/lists/* && \
  rm -rf /var/cache/oracle-jdk8-installer

# Define commonly used JAVA_HOME variable
ENV JAVA_HOME /usr/lib/jvm/java-8-oracle

# Install for Android
RUN dpkg --add-architecture i386 && \
  apt-get update && \
  apt-get install -yq openssh-server libc6:i386 libstdc++6:i386 zlib1g:i386 libncurses5:i386 && \
  apt-get clean

# Download and untar SDK
RUN curl -L "${ANDROID_SDK_URL}" | tar --no-same-owner -xz -C /usr/local

# Install Android SDK components
# License Id: android-sdk-license-ed0d0a5b
ENV ANDROID_COMPONENTS platform-tools,build-tools-25,android-25
# License Id: android-sdk-license-5be876d5
ENV GOOGLE_COMPONENTS extra-android-m2repository,extra-google-m2repository

RUN echo y | android update sdk --no-ui --all --filter "${ANDROID_COMPONENTS}"
RUN echo y | android update sdk --no-ui --all --filter "${GOOGLE_COMPONENTS}"

リモートマシンへのファイル同期、リモートマシン上でのコマンド実行

参考にしている方たちのブログにあるpythonのスクリプトを用いている3。pythonとか普段使わないので、中身をすこし見てみると他のことにも転用できそうな感じでとても良いものだった。
pythonにはFabric4というssh経由でリモートマシン上でのコマンド実行を行うためのライブラリがあり、このライブラリを使ってファイル同期、リモートマシン上でのbuild実行を行う。
git push時のhookでテストだけリモートでやるなどってこともスクリプトに追加して行えるようにしてみたりしている。


  1. http://sys1yagi.hatenablog.com/entry/2016/11/13/162400, http://qiita.com/shikajiro/items/6bc52c3f4c0b6dcbbfc3 
  2. http://tools.android.com/tech-docs/new-build-system/2-5-alpha-gradle-plugin 
  3. https://gist.github.com/sys1yagi/1f395190061a8d48db5e30ae160b4b99 
  4. http://www.fabfile.org/ 

CoordinatorLayoutのBehaviorを自作する

この記事の概要

上記のBehaviorでは達成できない動きでも、自身でBehaviorを実装するとこんなこともできるようになるよっていうことをBehaviorの種類に合わせて紹介したいと思います。

Behaviorの種別

Behaviorを実装する上での方針です。下記のどの表現を行うかでBehaviorの実装の仕方を変えると良いです。
この記事での成果物は、GitHub[https://github.com/ttymsd/coordinator-behaviors](https://github.com/ttymsd/coordinator-behaviors)にまとめてあります。

1. CoordinatorLayout直下のViewとの相対位置を保つ

実装するもの

  • Behavior#onLayoutChild
    • CoordinatorLayout上での初期位置を指定します。
  • Behavior#layoutDependsOn
    • View(child)がどのView(dependency)との相対位置とするか決定する。
  • onDependentViewChanged
    • layoutDependsOnで対象としたViewがCoordinatorLayout上で位置が変わったときにコールバックされ、Behaviorを持つViewの位置の再レイアウトができる。

2. NestedScrollChildが実装されているViewのスクロール量に合わせて動かす

実装するもの

  • Behavior#onLayoutChild
    • CoordinatorLayout上での初期位置を指定します。
  • Behavior#onStartNestedScroll
  • Behavior#onStopNestedScroll
  • Behavior#onNestedPreFling
  • Behavior#onNestedFling
  • Behavior#onNestedPreScroll
  • Behavior#onNestedScroll
  • Behavior#onNestedScrollAccepted
    • CoordinatorLayout直下のNestedScollViewで発生したScrollEventが通知されます。

上記のメソッドを実現したい表現に合わせてOverrideしてBehaviorが設定されているViewを動かします。
Ex.BottomNavigationBehavior

3. TouchEventを処理する(Ex.CoordinatorLayout内でDragなどで移動させる)

  • Behavior#onLayoutChild
    • CoordinatorLayout上での初期位置を指定します。
  • Behavior#onInterceptTouchEvent
    • CoordinatorLayoutで発生したonInterceptTouchEventの処理が委譲されます。trueを返すと引き続きTouchEventの処理をこのBehaviorでできます。
  • Behavior#onInterceptTouchEvent
    • CoordinatorLayoutで発生しているTouchEventが委譲されているので、TouchEventを処理してViewを動かしたりします。

まとめ

いままで、CustomViewを作ってchildView等を把握してDrag処理等に合わせてLayoutしていたものが、Layout処理だけがBehaviorに切り出されることで
使い回しがしやすくなった感じを受けるのと、動かしたいView毎にBehaviorを用意することで1画面上でいろいろな動きをさせられるようになったと思う。