Building an Android project in a docker container

When developing a project for the Android platform, even the smallest one, sooner or later, has to deal with the development environment. In addition to the Android SDK, you need the latest version of Kotlin, Gradle, platform-tools, build-tools. And if on the developer's machine all these dependencies are solved to a greater extent using the Android Studio IDE, then on the CI / CD server each update can turn into a headache. And if in web development, Docker became the standard solution to the environment problem, then why not try to solve a similar problem with it in Android development ...



For those who do not know what Docker is - if it’s quite simple, then this is a tool for creating the so-called “Containers” where the minimum OS kernel and the necessary set of software are contained, which we can deploy wherever we want, while preserving the environment. What exactly will be in our container is defined in the Dockerfile, which is then assembled into an image launched anywhere and possessing idempotency properties.



The installation process and the basics of Docker are perfectly described on its official website . Therefore, looking a little ahead, here we have such a Dockerfile



# ..     Android-  Gradle, #        Docker- #            Gradle FROM gradle:5.4.1-jdk8 #       Android SDK  #     ENV SDK_URL="https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip" \ ANDROID_HOME="/usr/local/android-sdk" \ ANDROID_VERSION=28 \ ANDROID_BUILD_TOOLS_VERSION=28.0.3 #  ,   SDK   , #     RUN mkdir "$ANDROID_HOME" .android \ && cd "$ANDROID_HOME" \ && curl -o sdk.zip $SDK_URL \ && unzip sdk.zip \ && rm sdk.zip \ #          #  .  .  Android    #          #      #    && mkdir "$ANDROID_HOME/licenses" || true \ && echo "24333f8a63b6825ea9c5514f83c2829b004d1" > "$ANDROID_HOME/licenses/android-sdk-license" \ && echo "84831b9409646a918e30573bab4c9c91346d8" > "$ANDROID_HOME/licenses/android-sdk-preview-license" #   SDK   build-tools, platform-tools RUN $ANDROID_HOME/tools/bin/sdkmanager --update RUN $ANDROID_HOME/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \ "platforms;android-${ANDROID_VERSION}" \ "platform-tools"
      
      





We save it in the folder with our Android project and start the assembly of the container with the command



 docker build -t android-build:5.4-28-27 .
      
      





The -t parameter specifies the tag or name of our container, which usually consists of its name and version. In our case, we called it android-build, and in the version we specified a set of versions of gradle, android-sdk and platform-tools. In the future, it will be easier for us to search for the image we need by name using such a “version”.



After the assembly has passed we can use our image locally, we can upload it with the docker push command to a public or private image repository to download it to other machines.



As an example, we collect the project locally. To do this, execute the command in the project folder



 docker run --rm -v "$PWD":/home/gradle/ -w /home/gradle android-build:5.4.1-28-27 gradle assembleDebug
      
      





Let's see what it means:



docker run - the image launch command itself

-rm - means that after the container stops, it removes behind itself everything that was created in the course of its life

-v "$ PWD": / home / gradle / - mounts the current folder with our Android project into the internal folder of the container / home / gradle /

-w / home / gradle - sets the working directory of the container

android-build: 5.4.1-28-27 is the name of our container that we built

gradle assembleDebug - actually the build team that builds our project



If everything goes well, then in a couple of seconds / minutes you will see on your screen something like BUILD SUCCESSFUL in 8m 3s ! And in the folder app / build / output / apk will be the assembled application.



In the same way, you can perform other gradle tasks - check the project, run tests, etc. The main advantage is that if you need to build the project on any other machine, we do not need to worry about installing the entire environment and it will be enough to download the necessary image and run the assembly in it.



The container does not store any changes, and each assembly starts from scratch, which on the one hand guarantees the identity of the assembly regardless of where it was launched, on the other hand, every time you have to download all the dependencies and compile all the code again, and this can sometimes take considerable time. Therefore, in addition to the usual "cold" start, we have the option of starting the assembly while maintaining the so-called. "Cache", where we save the ~ / .gradle folder simply by copying it to the working folder of the project, and at the beginning of the next build we return it back. We put all the copying procedures into separate scripts and the launch command itself began to look like this



 docker run --rm -v "$PWD":/home/gradle/ -w /home/gradle android-build:5.4.1-28-27 /bin/bash -c "./pre.sh; gradle assembleDebug; ./post.sh"
      
      





As a result, the average project assembly time for us was reduced several times (depending on the number of dependencies on the project, but the average project thus began to assemble in 1 minute instead of 5 minutes).



All this by itself makes sense only if you have your own internal CI / CD server, the support of which you yourself are engaged in. But now there are many cloud services in which all these problems have been resolved and you don’t have to worry about it and the necessary assembly properties can also be specified in the project settings.



All Articles